C # Entity-Framework: Bagaimana saya bisa menggabungkan .Find dan .Include pada Model Object?

145

Saya sedang melakukan tutorial praktek mvcmusicstore. Saya perhatikan sesuatu saat membuat perancah untuk pengelola album (tambahkan hapus edit).

Saya ingin menulis kode dengan elegan, jadi saya mencari cara yang bersih untuk menulis ini.

FYI saya membuat toko ini lebih umum:

Album = Item

Genre = Kategori

Artis = Merek

Inilah cara indeks diambil (dihasilkan oleh MVC):

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

Berikut cara pengambilan item untuk dihapus:

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

Yang pertama membawa kembali semua barang dan mengisi kategori dan model merek di dalam model barang. Yang kedua, tidak mengisi kategori dan merek.

Bagaimana saya bisa menulis yang kedua untuk menemukan DAN mengisi apa yang ada di dalamnya (sebaiknya dalam 1 baris) ... secara teoritis - sesuatu seperti:

Item item = db.Items.Find(id).Include(i => i.Category).Include(i => i.Brand);
Ralph N
sumber
Jika ada yang perlu melakukan ini secara umum in.net-core lihat jawaban saya
johnny 5

Jawaban:

162

Anda harus menggunakan Include()dulu, lalu mengambil satu objek dari kueri yang dihasilkan:

Item item = db.Items
              .Include(i => i.Category)
              .Include(i => i.Brand)
              .SingleOrDefault(x => x.ItemId == id);
Dennis Traub
sumber
24
Saya benar-benar akan merekomendasikan menggunakan yang terakhir (SingleOrDefault), ToList akan mengambil semua entri terlebih dahulu dan kemudian pilih satu
Sander Rijken
5
Ini rusak jika kita memiliki kunci primer komposit dan menggunakan menemukan kelebihan yang relevan.
jhappoldt
78
Ini akan berhasil, tetapi ada perbedaan antara menggunakan "Find" dan "SingleOrDefault". Metode "Temukan" mengembalikan objek dari toko terlacak lokal jika ada, menghindari bolak-balik ke basis data, di mana menggunakan "SingleOrDefault" akan tetap memaksa kueri ke basis data.
Iravanchi
3
@ Iravanchi benar. Ini mungkin bekerja untuk pengguna, tetapi operasi dan efek sampingnya tidak setara dengan Find, sejauh yang saya tahu.
mwilson
3
Tidak benar-benar menjawab pertanyaan ops karena tidak menggunakan .Cari
Paul Swetz
73

Jawaban Dennis menggunakan Includedan SingleOrDefault. Yang terakhir berjalan-jalan ke database.

Alternatif, adalah menggunakan Find, dalam kombinasi dengan Load, untuk pemuatan eksplisit entitas terkait ...

Di bawah ini contoh 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(); 
}

Tentu saja, Findsegera kembali tanpa membuat permintaan ke toko, jika entitas itu sudah dimuat oleh konteks.

Pelajar
sumber
30
Metode ini menggunakan Findsehingga jika entitas ada, tidak ada perjalanan pulang pergi ke DB untuk entitas itu sendiri. NAMUN, Anda akan memiliki perjalanan bolak- Loadbalik untuk setiap hubungan yang sedang Anda jalani , sedangkan SingleOrDefaultkombinasi dengan Includememuat semuanya sekaligus.
Iravanchi
Ketika saya membandingkan 2 di profiler SQL, Find / Load lebih baik untuk kasus saya (saya memiliki hubungan 1: 1). @ Iravanchi: apakah Anda bermaksud mengatakan jika saya memiliki hubungan 1: m akan disebut m kali toko? ... karena tidak masuk akal.
Learner
3
Bukan 1: m relasi, tetapi beberapa relasi. Setiap kali Anda memanggil Loadfungsi, relasi harus diisi ketika panggilan kembali. Jadi, jika Anda menelepon Loadbeberapa kali untuk beberapa hubungan, akan ada perjalanan pulang pergi setiap kali. Bahkan untuk relasi tunggal, jika Findmetode ini tidak menemukan entitas dalam memori, ia membuat dua perjalanan bolak- Findbalik : satu untuk dan yang kedua untuk Load. Tapi itu Include. SingleOrDefaultPendekatan mengambil entitas dan hubungan dalam satu jalan sejauh yang saya tahu (tapi saya tidak yakin)
Iravanchi
1
Akan lebih baik jika bisa mengikuti desain Sertakan entah bagaimana daripada harus memperlakukan koleksi dan referensi secara berbeda. Itu membuatnya lebih sulit untuk membuat fasad GetById () yang hanya mengambil koleksi opsional Ekspresi <Func <T, objek >> (mis. _Repo.GetById (id, x => x.MyCollection))
Derek Greer
4
Jangan lupa menyebutkan referensi posting Anda: msdn.microsoft.com/en-us/data/jj574232.aspx#explicit
Hossein
1

Anda harus menggunakan IQueryable ke DbSet

var dbSet = (DbSet<Item>) db.Set<Item>().Include("");

return dbSet.Find(id);

Rafael R. Souza
sumber
Tidak ada .Find atau .FindAsync di dbSet. Apakah EF Core ini?
Thierry
ada ef 6 juga di ef core
Rafael R. Souza
Saya berharap dan kemudian "InvalidCastException"
ZX9
0

Tidak bekerja untuk saya. Tapi saya menyelesaikannya dengan melakukan seperti ini.

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

Jangan tahu kalau itu solusi yang ok. Tapi yang lain memberi Dennis memberi saya kesalahan bool dalam .SingleOrDefault(x => x.ItemId = id);

Johan
sumber
4
Solusi Dennis juga harus berhasil. Mungkin Anda memiliki kesalahan ini SingleOrDefault(x => x.ItemId = id)hanya karena single yang salah, =bukan double ==?
Slauma
6
ya, sepertinya Anda menggunakan = tidak ==. Kesalahan sintaks;)
Ralph N
Saya mencoba keduanya == dan = masih memberi saya kesalahan di .SingleOrDefault (x => x.ItemId = id); = / Pasti ada sesuatu yang lain dalam kode saya yang salah. Tetapi cara saya melakukannya adalah cara yang buruk? Mungkin saya tidak mengerti apa yang Anda maksud, Dennis juga memiliki singel = dalam kodenya.
Johan
0

Tidak ada cara nyata yang mudah untuk menyaring dengan menemukan. Tetapi saya telah menemukan cara yang dekat untuk mereplikasi fungsi ini, tetapi harap perhatikan beberapa hal untuk solusi saya.

Solusi ini memungkinkan Anda untuk memfilter secara umum tanpa mengetahui kunci utama dalam .net-core

  1. Temukan secara mendasar berbeda karena ia memperoleh entitas jika ada dalam pelacakan sebelum Meminta basis data.

  2. Selain itu dapat memfilter berdasarkan Obyek sehingga pengguna tidak harus tahu kunci utama.

  3. Solusi ini untuk EntityFramework Core.

  4. Ini membutuhkan akses ke konteksnya

Berikut adalah beberapa metode ekstensi untuk ditambahkan yang akan membantu Anda memfilter dengan kunci primer

    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));
    }

Setelah Anda memiliki metode ekstensi ini, Anda dapat memfilter seperti:

query.FilterByPrimaryKey(this._context, id);
johnny 5
sumber