質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.01%

LINQで取得するマスターのデータに詳細データを追加したい。

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 212

loop-dog

score -2

前提・実現したいこと

LINQで取得するマスターのデータに詳細データを追加したい。

(下記のソースコードの)OwnersクラスのPetsプロパティーにJOINしたPetsテーブルのデータを付けて、LINQでSelectしたいと思っています。

※DBでのリレーションシップは考えておりません。

発生している問題・エラーメッセージ

マスターのデータだけ取得される。

該当のソースコード

class Owner
{
    public string Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public IEnumerable<Pet> Pets{ get; set; }
}

class Pet
{
    public string Id { get; set; }
    public string OwnerId { get; set; }
    public string Name { get; set; }
}


var entity = (from o in context.Owners
              join p in context.Pets
              on o.Id equals d.OwnerId into gj
              from gjp in gj.DefaultIfEmpty()
              select o).FirstOrDefault();

試したこと

var entity = (from o in context.Owners
              join p in context.Pets
              on o.Id equals d.OwnerId into gj
              from gjp in gj.DefaultIfEmpty()
              select new Owner{o,Pets=gj}).FirstOrDefault();


みたいな感じが出来ればいいのですが。。。

ぐぐって、色々サンプルなどを見ると、select new がほとんどでした。
そして、匿名型上でプロパティーを作り、各項目を代入していました。

ここに載せているソースコードではOwnerのプロパティ(カラム)は少ないですが、
多い場合、全ての列を書くのはあほくさい、というか、バグの温床やメンテ地獄になるかと思いますので、今回の質問をさせていただきました。

補足情報(FW/ツールのバージョンなど)

Visual Studio 2019
EF Core 2.2
.NET Standard 2.0.3

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • loop-dog

    2019/11/07 14:59

    SurferOnWwwさん

    SurferOnWwwさんの2019/11/03 19:56の投稿でご指摘のあるクラス名の修正を先ほど修正しました。
    混乱させて申し訳ありません。

    キャンセル

  • Zuishin

    2019/11/07 15:01

    > 始めから使用可能になっています。

    これは語弊があるので補足します。遅延読み込み設定された時は必要となった時点でデータが読み込まれます。そうでない時は Load などで最初にデータを読み込んでおく必要があります。

    キャンセル

  • loop-dog

    2019/11/07 15:14

    Zuishinさん

    ありがとうございます。
    > それぞれのエンティティーを Load するだけで使えます。
    は、自分の回答が出て、先ほど再度読み直して、理解できました。(汗
    (どういう意味のLoadかと)

    さらに、補足分(2019/11/07 15:01)に関しても、今なら理解できます。 o(゚▽゚o)(o゚▽゚)oニパ

    キャンセル

回答 3

+2

質問者さんはいなくなってしまったようですが、

※DBでのリレーションシップは考えておりません。

では何ともならないようですので、そこは考え直してもらうとして(リレーションを張ってナビゲーションプロパティを使うようにして)、表題の「LINQで取得するマスターのデータに詳細データを追加」を「select new がほとんどでした。そして、匿名型上でプロパティーを作り、各項目を代入していました」ということをしなくて済む案を以下に書いておきます。

サンプルモデルは以下の記事のコードを使います。Code First ですが、DB First でもナビゲーションプロパティを使う点は同じです。また、CORE ではなく.NET ベースですが今回のスレッドの話は共通なはずです。

新しいデータベースへの Code First
https://docs.microsoft.com/ja-jp/ef/ef6/modeling/code-first/workflows/new-database

質問者さんのコードの Owners / Pets は、上のサンプルでは Blog / Post になっていると思ってください。ナビゲーションプロパティは上のサンプルコードを見てもらうと分かると思いますが、正しく設定されてます。デザイナで見ると以下のようになっています。

イメージ説明

上の記事の通りに作って、Code First の機能を利用して SQL Server にデータベースを生成して、それにいくらかのデータを追加したのが以下の SSMS の画像です。Posts テーブルに BlogId というフィールドがあって Blogs テーブルの BlogId に FK 制約が張られている点に注目してください。

イメージ説明

Blogs テーブルを読み込む際、関連する Posts テーブルのデータを読み込むには、以下の記事にある Include メソッドを使うのがよさそうです。

関連データの読み込み
https://docs.microsoft.com/ja-jp/ef/core/querying/related-data

上の記事にある「型 string のパラメーターを受け取る Include のオーバーロードを使用する」を利用して、以下のようにできます。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleAppCodeFirstBlog
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var context = new BloggingContext())
            {
                var blogs = context.Blogs.Include("Posts");

                foreach(var blog in blogs)
                {
                    Console.WriteLine($"BlogID: {blog.BlogId}, Name: {blog.Name}");
                    if (blog.Posts.Count > 0)
                    {
                        foreach(var post in blog.Posts)
                        {
                            Console.WriteLine($"     PostID: {post.PostId}, Title: {post.Title}, Content: {post.Content}");
                        }                        
                    }
                }
            }
        }
    }
}

//結果:
//BlogID: 1, Name: ADO.NET Blog
//     PostID: 4, Title: 概要, Content: このブログの概要説明
//     PostID: 5, Title: Code First, Content: NULL新しいデータベース
//BlogID: 2, Name: Web Forms
//     PostID: 6, Title: DataGridView, Content: 作り方の概要説明
//BlogID: 3, Name: MVC
//     PostID: 1, Title: Controller, Content: コントローラーの作り方
//     PostID: 2, Title: View, Content: ビューデザイナ
//     PostID: 3, Title: Model, Content: モデルバインディング
//BlogID: 4, Name: XML
//BlogID: 5, Name: Windows 10

表題の「LINQで取得するマスターのデータに詳細データを追加」を「select new がほとんどでした。そして、匿名型上でプロパティーを作り、各項目を代入していました」ということをしなくて済んでいるのが分かりますか?

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/11/07 15:09

    ご回答、ありがとうございます。
    質問した時点ではナビゲーションプロパティーというのをはっきりとわかっておらず、ですが、クラスには入っていました。

    そこから、自分の回答につながるわけですが、解答してくださったものは、まさに私が個人的趣味でやらなかった、【一括読み込み】ですね。

    確かにナビゲーションプロパティーを正しく利用できれば、そもそも、私の質問は無用でした。

    ありがとうございます。

    キャンセル

  • 2019/11/07 15:23 編集

    > まさに私が個人的趣味でやらなかった、【一括読み込み】ですね。

    こういう話に個人的趣味が入る余地はなくて、一括読み込みと遅延読み込みにどちらが目的に合っているか、どちらがメリットがあるかで考えるべきですよ。遅延読み込みでは N+1 問題が避けられないということ分かってないでしょ?

    N+1問題を回避せよ! LINQから出力されるSQLを見てみよう&遅延ローディングの光と闇
    https://codezine.jp/article/detail/8415

    今回の質問の目的では N+1 問題は避ける ⇒ 一括読み込みを採用すべきではないのですか。

    キャンセル

0

Owners と Pets をメンバーとする匿名型を返すのはどうでしょうか。

var entity = (from o in context.Owners
              join p in context.Pets
              on o.Id equals d.OwnerId into gj
              from gjp in gj.DefaultIfEmpty()
              select new {Owner = o, Pets = Pets}).FirstOrDefault();

または、Owner を継承したクエリ結果を受け取るクラスを定義しておいて、コンストラクタでリフレクションを利用して、Ownerのプロパティをコピーするとか。

using System.Reflection;

class Owner2 : Owner {
    public Owner2(Owner owner, IEnumerable<Pet> pets) {
        //owner が Owner クラスのサブクラスの場やフィールドのことは考慮してません。
        var t = owner.GetType();
        var properties = t.GetProperties( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance );
        foreach( var p in properties ) {
            p.SetValue( this, p.GetValue( owner ) );
        }
        this.Pets = pets;
    }

    public IEnumerable<Pet> Pets { get; set; }
}

...

var entity = (from o in context.Owners
              join p in context.Pets
              on o.Id equals d.OwnerId into gj
              from gjp in gj.DefaultIfEmpty()
              select new Owner2(o, Pets)).FirstOrDefault();

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/11/03 18:39

    > draqさん
    回答ありがとうございます。
    私も一瞬コンストラクタで対応するのは、思いついたのですが、クラスのつくりは変えずに、LINQの文法だけで、対応が可能であるかを知りたかったもんですから。。。(LINQの書きまわしなど)

    それとも、関数内で匿名型で取りあえず、取得し、その関数の戻り値の型であるOwnersクラスに代入するとか思い浮かばず。。。

    しかし、この関数はひとつのOwnersクラスを返すだけなのでいいのですが、他の場合(関数など)だと、Listのジェネリックで返す場合、全てのデータに対して代入をすると、件数が多いと(20件で区切るとかあるかもしれませんが)、処理が重くなるのかなと思ったりもしています。

    キャンセル

check解決した方法

-1

(ちょっと、時間があいてしまいましたが)
ご指摘のナビゲーションプロパティーの部分を調べ(勉強し)ました。
結果としては、遅延読み込みで対応しました。

遅延読み込み

書いてある通り、

  • Microsoft.EntityFrameworkCore.Proxies パッケージをインストール
  • OnConfiguringメソッドでのUseLazyLoadingProxiesの使用
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseLazyLoadingProxies()
        .UseSqlServer(myConnectionString);

また、
【明示的読み込み】は

context.Entry(blog)
        .Collection(b => b.Posts)
        .Load();

のように必要なテーブルをロードすることで、ナビゲーションプロパティーにデータをロードすることが出来ました。

「一括読み込み」はIncludeを使用するのに別の名前空間をusingしないといけなかったので、やめました。(色々余計なものusingするのが嫌いという個人的な趣味です。)

結果としては自己解決ですが、質問することで、問題が何か理解できました。
回答者の皆様、ありがとうございました。

以上

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/11/07 15:41 編集

    > 「一括読み込み」はIncludeを使用するのに別の名前空間をusingしないといけなかったので、やめました。(色々余計なものusingするのが嫌いという個人的な趣味です。)

    私の回答のコメント欄にも書きましたが「個人的な趣味」で、N+1 問題が避けられない遅延ローディングを選ぶというのは、はっきり言って間違ってますよ。「色々余計なものusingするのが嫌い」というのは理由にならないと思いますけど。

    それから、もし、その「色々余計なものusingするのが嫌い」というのが using System.Data.Entity; だとすると(.Include(blog => blog.Posts) を使うのに必要)、私の回答に書いたコードでは使ってないですよ。

    キャンセル

  • 2019/11/07 15:53

    違いとメリット・デメリットは以下の記事が参考になると思います。その下の「パフォーマンスに関する考慮事項」も読んでください。

    関連データを読み込む方法を学習する
    https://docs.microsoft.com/ja-jp/aspnet/mvc/overview/getting-started/getting-started-with-ef-using-mvc/reading-related-data-with-the-entity-framework-in-an-asp-net-mvc-application#learn-how-to-load-related-data

    キャンセル

  • 2019/11/09 04:04

    (N+1や遅延読み込みについては、Read中です。)


    上記の個人的趣味について、”一応”理由があります。
    (パフォーマンス以上に優先度の高いものはないという突込みは、置いておいてください。)



    Xmarinのクロスプラットフォーム開発で、かつ、.net standerdのプロジェクト(.cspro)は「Model部分」と「それ以外」(viewなど)の2つに分けています。
    そういう前提がある上で、includeを使用すると、どうしても、「それ以外」のプロジェクトの方で、Microsoft.EntityFrameworkCoreをusingしないといけませんでした。

    私の考えでは「model」のプロジェクトの中だけで、(EFも含め)DBに関わる事柄は完結したいので、それを回避するために、(意味合いを理解していませんでしたが)遅延読み込みを採用しようとしました。



    また、これは趣味になってしまうかもしれませんが、includeを使うより、JOINを使用した方がSQL然としており、さらにDBContextで設定する(UseLazyLoadingProxiesメソッド)方が一番わかりやすい形かと思い、遅延読み込みにしました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.01%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる