C#エンティティフレームワーク:モデルオブジェクトで.Findと.Includeを組み合わせるにはどうすればよいですか?


145

私はmvcmusicstoreの練習チュートリアルをやっています。アルバムマネージャーの足場を作成するときに何かに気づきました(削除編集を追加)。

エレガントにコードを書きたいので、これを書くきれいな方法を探しています。

ちなみに私は店をより一般的にしています:

アルバム=アイテム

ジャンル=カテゴリ

アーティスト=ブランド

インデックスを取得する方法を次に示します(MVCによって生成されます)。

var items = db.Items.Include(i => i.Category).Include(i => i.Brand);

削除するアイテムを取得する方法は次のとおりです。

Item item = db.Items.Find(id);

1つ目は、すべてのアイテムを元に戻し、アイテムモデル内のカテゴリモデルとブランドモデルを設定します。2つ目は、カテゴリとブランドを入力しません。

検索を実行して内容を(好ましくは1行で)入力するために2つ目の方法を作成するにはどうすればよいですか...理論的には-次のようなものです。

Item item = db.Items.Find(id).Include(i => i.Category).Include(i => i.Brand);

誰かがこれを一般的にin.net-coreで行う必要がある場合は、私の答えを見てください
ジョニー5

回答:


162

Include()最初に使用し、次に結果のクエリから単一のオブジェクトを取得する必要があります。

Item item = db.Items
              .Include(i => i.Category)
              .Include(i => i.Brand)
              .SingleOrDefault(x => x.ItemId == id);

24
後者(SingleOrDefault)を使用することをお勧めします。ToListは最初にすべてのエントリを取得してから1つを選択します
Sander Rijken

5
これは、複合主キーがあり、関連する検索オーバーロードを使用している場合に機能しなくなります。
jhappoldt

78
これは機能しますが、「検索」と「SingleOrDefault」の使用には違いがあります。「Find」メソッドは、存在する場合はローカルの追跡ストアからオブジェクトを返し、データベースへの往復を回避します。「SingleOrDefault」を使用すると、データベースへのクエリが強制されます。
Iravanchi 2012年

3
@Iravanchiは正しいです。これはユーザーにとってはうまくいったかもしれませんが、私の知る限り、操作とその副作用はFindと同等ではありません。
mwilson 2013

3
それは.Find使用されていないとして、実際にOPSの質問に答えていない
ポールSwetz

73

デニスの答えはIncludeand を使用していSingleOrDefaultます。後者はデータベースにラウンドトリップします。

別の方法として、をとFind組み合わせて使用して、Load関連するエンティティを明示的に読み込む...

MSDNの例の下:

using (var context = new BloggingContext()) 
{ 
  var post = context.Posts.Find(2); 

  // Load the blog related to a given post 
  context.Entry(post).Reference(p => p.Blog).Load(); 

  // Load the blog related to a given post using a string  
  context.Entry(post).Reference("Blog").Load(); 

  var blog = context.Blogs.Find(1); 

  // Load the posts related to a given blog 
  context.Entry(blog).Collection(p => p.Posts).Load(); 

  // Load the posts related to a given blog  
  // using a string to specify the relationship 
  context.Entry(blog).Collection("Posts").Load(); 
}

もちろん、Findエンティティがコンテキストによって既にロードされている場合は、ストアへのリクエストを行わずにすぐに戻ります。


30
この方法ではFind、エンティティが存在する場合、エンティティ自体のDBへのラウンドトリップはありません。しかし、あなたはあなたがしているそれぞれの関係のための往復がありますがLoad、とのSingleOrDefault組み合わせIncludeは一度にすべてをロードします。
Iravanchi 2014年

SQLプロファイラーで2つを比較したところ、私の場合はFind / Loadの方が優れていました(1:1の関係がありました)。@Iravanchi:私が1:mの関係を持っていたとしたら、店舗のm倍と呼ばれることになると思いますか?...あまり意味がありません。
学習者2014年

3
1:mの関係ではなく、複数の関係。Load関数を呼び出すたびに、呼び出しが戻ったときにリレーションが生成されます。したがってLoad、複数の関係に対して複数回呼び出すと、毎回ラウンドトリップが発生します。単一の関係の場合でも、Findメソッドがメモリ内でエンティティを見つけられない場合、2つのラウンドトリップが発生します。1つはFind、もう1つはですLoad。しかしIncludeSingleOrDefaultアプローチは、私が知る限り、エンティティと関係を一度にフェッチします(しかし、私にはわかりません)
Iravanchi 14年

1
コレクションと参照を別の方法で処理する必要がなく、インクルードデザインに何らかの形で従うことができれば良かったでしょう。そのため、Expression <Func <T、object >>のオプションのコレクション(例:_repo.GetById(id、x => x.MyCollection))を取得するだけのGetById()ファサードを作成するのが困難になります
Derek Greer

4
あなたの投稿のリファレンスについて言及することに注意してください
Hossein


0

私のために働いていませんでした。でもこうやって解決しました。

var item = db.Items
             .Include(i => i.Category)
             .Include(i => i.Brand)
             .Where(x => x.ItemId == id)
             .First();

それが大丈夫な解決策かどうかわからない。しかし、デニスが与えたもう1つは私にブールエラーを与えました .SingleOrDefault(x => x.ItemId = id);


4
デニスのソリューションも機能する必要があります。おそらく、ダブルではなくSingleOrDefault(x => x.ItemId = id)シングルが間違っているためだけにこのエラーが発生しますか?===
Slauma、2011

6
ええ、あなたは==ではなく=を使用したように見えます。構文の誤り;)
Ralph N

==と=の両方を試しましたが、.SingleOrDefault(x => x.ItemId = id);でエラーが発生しました。= /私のコードでは間違っている何かでなければなりません。しかし、私がした方法は悪い方法ですか?多分私はあなたがデニスが同様にsingel =を彼のコードに持っているという意味が理解できません。
ヨハン

0

検索でフィルタリングする実際の簡単な方法はありません。しかし、私は機能を再現するための近道を考え出しましたが、私の解決策についていくつか注意してください。

このソリューションでは、.net-coreの主キーを知らなくても、一般的にフィルタリングできます

  1. Findは、データベースにクエリを実行する前の追跡に存在するエンティティを取得するため、根本的に異なります。

  2. さらに、オブジェクトでフィルタリングできるため、ユーザーは主キーを知る必要がありません。

  3. このソリューションはEntityFramework Core向けです。

  4. これにはコンテキストへのアクセスが必要です

追加するいくつかの拡張メソッドは、主キーでフィルタリングするのに役立ちます。

    public static IReadOnlyList<IProperty> GetPrimaryKeyProperties<T>(this DbContext dbContext)
    {
        return dbContext.Model.FindEntityType(typeof(T)).FindPrimaryKey().Properties;
    }

    //TODO Precompile expression so this doesn't happen everytime
    public static Expression<Func<T, bool>> FilterByPrimaryKeyPredicate<T>(this DbContext dbContext, object[] id)
    {
        var keyProperties = dbContext.GetPrimaryKeyProperties<T>();
        var parameter = Expression.Parameter(typeof(T), "e");
        var body = keyProperties
            // e => e.PK[i] == id[i]
            .Select((p, i) => Expression.Equal(
                Expression.Property(parameter, p.Name),
                Expression.Convert(
                    Expression.PropertyOrField(Expression.Constant(new { id = id[i] }), "id"),
                    p.ClrType)))
            .Aggregate(Expression.AndAlso);
        return Expression.Lambda<Func<T, bool>>(body, parameter);
    }

    public static Expression<Func<T, object[]>> GetPrimaryKeyExpression<T>(this DbContext context)
    {
        var keyProperties = context.GetPrimaryKeyProperties<T>();
        var parameter = Expression.Parameter(typeof(T), "e");
        var keyPropertyAccessExpression = keyProperties.Select((p, i) => Expression.Convert(Expression.Property(parameter, p.Name), typeof(object))).ToArray();
        var selectPrimaryKeyExpressionBody = Expression.NewArrayInit(typeof(object), keyPropertyAccessExpression);

        return Expression.Lambda<Func<T, object[]>>(selectPrimaryKeyExpressionBody, parameter);
    }

    public static IQueryable<TEntity> FilterByPrimaryKey<TEntity>(this DbSet<TEntity> dbSet, DbContext context, object[] id)
        where TEntity : class
    {
        return FilterByPrimaryKey(dbSet.AsQueryable(), context, id);
    }

    public static IQueryable<TEntity> FilterByPrimaryKey<TEntity>(this IQueryable<TEntity> queryable, DbContext context, object[] id)
        where TEntity : class
    {
        return queryable.Where(context.FilterByPrimaryKeyPredicate<TEntity>(id));
    }

これらの拡張メソッドを取得したら、次のようにフィルタリングできます。

query.FilterByPrimaryKey(this._context, id);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.