🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Entity Framework

Entity Frameworkは、.NET Framework 3.5より追加されたデータアクセス技術。正式名称は「ADO.NET Entity Framework」です。データベースエンジンに依存しておらず、データプロバイダの変更のみで様々なデータベースに対応できます。

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Q&A

解決済

3回答

2363閲覧

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

loop-dog

総合スコア6

Entity Framework

Entity Frameworkは、.NET Framework 3.5より追加されたデータアクセス技術。正式名称は「ADO.NET Entity Framework」です。データベースエンジンに依存しておらず、データプロバイダの変更のみで様々なデータベースに対応できます。

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

0グッド

1クリップ

投稿2019/11/02 18:38

編集2019/11/07 05:56

前提・実現したいこと

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

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

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

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

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

該当のソースコード

C#

1 2class Owner 3{ 4 public string Id { get; set; } 5 public string FirstName { get; set; } 6 public string LastName { get; set; } 7 public IEnumerable<Pet> Pets{ get; set; } 8} 9 10class Pet 11{ 12 public string Id { get; set; } 13 public string OwnerId { get; set; } 14 public string Name { get; set; } 15} 16 17 18var entity = (from o in context.Owners 19 join p in context.Pets 20 on o.Id equals d.OwnerId into gj 21 from gjp in gj.DefaultIfEmpty() 22 select o).FirstOrDefault(); 23 24

試したこと

C#

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

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

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

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

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

Visual Studio 2019
EF Core 2.2
.NET Standard 2.0.3

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Zuishin

2019/11/02 21:30

リレーションシップを考えたらいいと思います。 使いもしない Owners クラスと Pets クラスが無駄です。
Zuishin

2019/11/02 21:39

もし使うのであれば、Pets クラスに Owners クラスを保持するプロパティを持たせれば解決です。Owners クラスに Pets クラスのリストを保持するプロパティを持たせると更に利便性が上がります。 匿名型には色々制限があるので、メインに使うものではありません。
退会済みユーザー

退会済みユーザー

2019/11/02 21:50

外部結合した結果を取得したいのだと理解してますが、具体的にどういう項目なのですか? それを書いてもらえないと・・・
loop-dog

2019/11/03 09:29

> Zuishinさん > Owners クラスに Pets クラスのリストを保持するプロパティを持たせると更に利便性が上がります。 これについては、 public IEnumerable<Pet> Pets{ get; set; } という形で、用意してます。 また、逆参照は必要がないと考えていますので、PetsクラスにはOwnersクラスの情報を持たせるプロパティーは定義しておりません。
退会済みユーザー

退会済みユーザー

2019/11/04 03:38 編集

> public IEnumerable<Pet> Pets{ get; set; } それ、そもそも文法的に間違ってませんか? 文法的に合っていても、何の役にも立たないかと思いますけど。
loop-dog

2019/11/03 09:52

> SurferOnWwwさん いつも(勝手にお世話になっていまして、)ありがとうございます。 はい、外部結合の結果として、Ownersクラスを返す関数になっています。 その詳細データである、Petsクラスの配列( public IEnumerable<Pet> Pets{ get; set; })をプロパティーとして用意していますので、そこに外部結合のデータを代入したいと思っています。 LINQでJoinしているので、データはSelect newなどしなくても、(from o in context.Ownersのoに)入ってくるのかなと思っていたのですが、ウオッチ式で見ても入っておらず。。。 今書いてておもったのですが、結局、oはcontext.Ownersなので、JOINする前のデータしか入らないのかなと。 であれば、joinした後である、on o.Id equals d.OwnerId into gjのgjだったらいいのかと試しましたが、PetsのListが、取得されるだけで、Ownersクラスの情報は付いていませんでした。 なので、select のところで、from o in context.OwnersのoのPetsプロパティーにgjをそのまま代入できればなと思った次第なのですが、、、
Zuishin

2019/11/03 10:05

用意しているなら、そこに追加すれば匿名クラスを使う必要など全く無いと思います。
Zuishin

2019/11/03 10:09

普通は join を使う必要すらなく、それぞれのエンティティーを Load するだけで使えます。
loop-dog

2019/11/03 10:21

> SurferOnWwwさん >> public IEnumerable<Pet> Pets{ get; set; } >それ、そもそも文法的に間違ってませんか? 文法的に合っていても、何の欲にも立たないかと思いますけど。 ビルドは通っていますので、文法的には間違っていないと思うのですが。。。 それとも、何か考え間違いしているのでしょうか。。。 > Zuishinさん (用意しているので、)その追加する方法がわからないという状況に陥っています。
Zuishin

2019/11/03 10:23

このコードではプロパティを用意しているだけで実装が無いので、追加できません。 リレーションシップを張れば使えるようになります。 張らないのなら自分で実装する必要があります。
Zuishin

2019/11/03 10:26

SurferOnWww さん、そこは EntityFramework が依存性注入してくれること前提のコードです。リレーションシップを張ってないらしいので単なる無駄プロパティになっているようですが。
退会済みユーザー

退会済みユーザー

2019/11/03 10:57 編集

> ビルドは通っていますので、文法的には間違っていないと思うのですが。。。 IEnumerable<Pet> の Pet はアップされてるコードを見る限りどこにも定義されてないようですが・・・
退会済みユーザー

退会済みユーザー

2019/11/03 11:11 編集

Zuishin さん、書かれているプロパティは、上に書きましたが Pet が定義不明ですし(class Pets が class Pet の間違い?)、ましてやナビゲーションプロパティでもなさそうですし(virtual がないので)、意味不明&役に立たないと思うのですが。ビルドは通るとのことなので、質問に書いてないところに何かがあるのかもしれませんけど。
退会済みユーザー

退会済みユーザー

2019/11/03 11:08

質問者さん、上にも書きましたが外部結合の結果、何を取得できればいいのですか? 詳しく書いてください。
Zuishin

2019/11/03 12:12

SurferOnWww さん、おっしゃる通りです。脳内補完しながら読んでしまいました。
退会済みユーザー

退会済みユーザー

2019/11/04 02:47

質問者さんはいなくなってしまったようですが、 > ※DBでのリレーションシップは考えておりません。 では何ともならないようですので、そこは考え直してもらうとして(リレーションを張ってナビゲーションプロパティを使うようにして)、案を回答欄に書いておきます。
loop-dog

2019/11/07 05:51

SurferOnWwwさん、Zuishinさん 脳内変換申し訳ないです。 public IEnumerable<Pet> Pets{ get; set; } DBに保存されているPetのデータをOwnerと一緒に読み込めればいいという話なのですが、そういうことでは足りないでしょうか。
loop-dog

2019/11/07 05:53

SurferOnWwwさん すみません。山籠もりしていました。(嘘ですが、貝になって考えていました。) やはり、リレーションシップはしないで、という条件でどうにかならないか、という事が知りたいので、EF coreの仕様上どうにもならないという、話でしたら、致し方なしという事になるのですが。。。。
Zuishin

2019/11/07 05:58

そうですね。DB を適切に設定すれば Pet と Owner の関連付けを自動的にやってくれるので、遅延読み込みを使用してもしなくても始めから使用可能になっています。 Load をお勧めしたのは N+1 問題対策ですが、問題が起こっていなければ遅延読み込みでも構いません。
loop-dog

2019/11/07 05:59

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

2019/11/07 06:01

> 始めから使用可能になっています。 これは語弊があるので補足します。遅延読み込み設定された時は必要となった時点でデータが読み込まれます。そうでない時は Load などで最初にデータを読み込んでおく必要があります。
loop-dog

2019/11/07 06:14

Zuishinさん ありがとうございます。 > それぞれのエンティティーを Load するだけで使えます。 は、自分の回答が出て、先ほど再度読み直して、理解できました。(汗 (どういう意味のLoadかと) さらに、補足分(2019/11/07 15:01)に関しても、今なら理解できます。 o(゚▽゚o)(o゚▽゚)oニパ
guest

回答3

0

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

※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/04 03:19

退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

loop-dog

2019/11/07 06:09

ご回答、ありがとうございます。 質問した時点ではナビゲーションプロパティーというのをはっきりとわかっておらず、ですが、クラスには入っていました。 そこから、自分の回答につながるわけですが、解答してくださったものは、まさに私が個人的趣味でやらなかった、【一括読み込み】ですね。 確かにナビゲーションプロパティーを正しく利用できれば、そもそも、私の質問は無用でした。 ありがとうございます。
退会済みユーザー

退会済みユーザー

2019/11/07 14:50 編集

> まさに私が個人的趣味でやらなかった、【一括読み込み】ですね。 こういう話に個人的趣味が入る余地はなくて、一括読み込みと遅延読み込みにどちらが目的に合っているか、どちらがメリットがあるかで考えるべきですよ。遅延読み込みでは N+1 問題が避けられないということ分かってないでしょ? N+1問題を回避せよ! LINQから出力されるSQLを見てみよう&遅延ローディングの光と闇 https://codezine.jp/article/detail/8415 今回の質問の目的では N+1 問題は避ける ⇒ 一括読み込みを採用すべきではないのですか。
guest

0

自己解決

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

遅延読み込み

書いてある通り、

  • Microsoft.EntityFrameworkCore.Proxies パッケージをインストール
  • OnConfiguringメソッドでのUseLazyLoadingProxiesの使用

C#

1protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 2 => optionsBuilder 3 .UseLazyLoadingProxies() 4 .UseSqlServer(myConnectionString);

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

C#

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

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

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

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

以上

投稿2019/11/07 05:47

loop-dog

総合スコア6

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2019/11/07 06:54 編集

> 「一括読み込み」はIncludeを使用するのに別の名前空間をusingしないといけなかったので、やめました。(色々余計なものusingするのが嫌いという個人的な趣味です。) 私の回答のコメント欄にも書きましたが「個人的な趣味」で、N+1 問題が避けられない遅延ローディングを選ぶというのは、はっきり言って間違ってますよ。「色々余計なものusingするのが嫌い」というのは理由にならないと思いますけど。 それから、もし、その「色々余計なものusingするのが嫌い」というのが using System.Data.Entity; だとすると(.Include(blog => blog.Posts) を使うのに必要)、私の回答に書いたコードでは使ってないですよ。
loop-dog

2019/11/08 19:04

(N+1や遅延読み込みについては、Read中です。) 上記の個人的趣味について、”一応”理由があります。 (パフォーマンス以上に優先度の高いものはないという突込みは、置いておいてください。) Xmarinのクロスプラットフォーム開発で、かつ、.net standerdのプロジェクト(.cspro)は「Model部分」と「それ以外」(viewなど)の2つに分けています。 そういう前提がある上で、includeを使用すると、どうしても、「それ以外」のプロジェクトの方で、Microsoft.EntityFrameworkCoreをusingしないといけませんでした。 私の考えでは「model」のプロジェクトの中だけで、(EFも含め)DBに関わる事柄は完結したいので、それを回避するために、(意味合いを理解していませんでしたが)遅延読み込みを採用しようとしました。 また、これは趣味になってしまうかもしれませんが、includeを使うより、JOINを使用した方がSQL然としており、さらにDBContextで設定する(UseLazyLoadingProxiesメソッド)方が一番わかりやすい形かと思い、遅延読み込みにしました。
guest

0

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

C#

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

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

C#

1using System.Reflection; 2 3class Owner2 : Owner { 4 public Owner2(Owner owner, IEnumerable<Pet> pets) { 5 //owner が Owner クラスのサブクラスの場やフィールドのことは考慮してません。 6 var t = owner.GetType(); 7 var properties = t.GetProperties( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ); 8 foreach( var p in properties ) { 9 p.SetValue( this, p.GetValue( owner ) ); 10 } 11 this.Pets = pets; 12 } 13 14 public IEnumerable<Pet> Pets { get; set; } 15} 16 17... 18 19var entity = (from o in context.Owners 20 join p in context.Pets 21 on o.Id equals d.OwnerId into gj 22 from gjp in gj.DefaultIfEmpty() 23 select new Owner2(o, Pets)).FirstOrDefault();

投稿2019/11/02 20:25

draq

総合スコア2577

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

loop-dog

2019/11/03 09:39

> draqさん 回答ありがとうございます。 私も一瞬コンストラクタで対応するのは、思いついたのですが、クラスのつくりは変えずに、LINQの文法だけで、対応が可能であるかを知りたかったもんですから。。。(LINQの書きまわしなど) それとも、関数内で匿名型で取りあえず、取得し、その関数の戻り値の型であるOwnersクラスに代入するとか思い浮かばず。。。 しかし、この関数はひとつのOwnersクラスを返すだけなのでいいのですが、他の場合(関数など)だと、Listのジェネリックで返す場合、全てのデータに対して代入をすると、件数が多いと(20件で区切るとかあるかもしれませんが)、処理が重くなるのかなと思ったりもしています。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問