C # Entity-Framework: Nasıl bir .Find ve .Include model nesnede birleştirebilirim?


146

Ben mvcmusicstore uygulama öğretici yapıyorum. Albüm yöneticisi için iskele oluştururken bir şey fark ettim (silme düzenleme ekle).

Zarif bir şekilde kod yazmak istiyorum, bu yüzden bunu yazmak için temiz bir yol arıyorum.

Mağazayı daha genel hale getiriyorum:

Albümler = Öğeler

Türler = Kategoriler

Sanatçı = Marka

Dizin nasıl alınır (MVC tarafından oluşturulur):

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

Silinecek öğe şu şekilde alınır:

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

Birincisi tüm eşyaları geri getirir ve ürün modeli içindeki kategori ve marka modellerini doldurur. İkincisi, kategoriyi ve markayı doldurmaz.

Nasıl bulmak ve içinde (tercihen 1 satırda) doldurmak için ikincisini nasıl yazabilirim ... teorik olarak - gibi bir şey:

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

Herkes bunu genel olarak in.net-core yapmak gerekirse cevabımı görmek
johnny 5

Yanıtlar:


162

Önce kullanmanız Include(), ardından ortaya çıkan sorgudan tek bir nesne almanız gerekir :

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

24
Gerçekten ikincisini (SingleOrDefault) kullanmanızı tavsiye ederim, ToList önce tüm girişleri alır ve daha sonra birini seçer
Sander Rijken

5
Bileşik bir birincil anahtarımız varsa ve ilgili bulma aşırı yükünü kullanırsak bu bozulur.
jhappoldt

78
Bu işe yarayabilir, ancak "Bul" ile "SingleOrDefault" arasında bir fark vardır. "Bul" yöntemi, nesneyi yerel izlenen depodan döndürür ve veritabanına gidiş dönüşten kaçınır; burada "SingleOrDefault" kullanıldığında bir sorgu yine de veritabanına zorlanır.
Iravanchi

3
@Iravanchi doğru. Bu kullanıcı için işe yaramış olabilir, ancak operasyon ve yan etkileri bildiğim kadarıyla Find ile eşdeğer değil.
mwilson

3
Ops sorusunu aslında kullanmadığı için cevaplamıyor.
Bul

73

Dennis'in cevabı Includeve kullanıyor SingleOrDefault. İkincisi veritabanına yuvarlak açma yapar.

Alternatif olarak, ilgili varlıkların açıkça yüklenmesi için Findbirlikte kullanılmasıdır Load...

Aşağıda bir MSDN örnek :

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

Tabii ki, Findeğer varlık zaten içerik tarafından yüklenmişse, mağazaya bir istekte bulunmadan hemen geri döner.


30
Bu yöntem Find, varlık mevcutsa, varlığın kendisi için DB'ye gidiş-dönüş yok demektir. ANCAK, size olduğunuz her ilişki için ring seferi olacak Loadoysa ing SingleOrDefaultbirlikte Includetek seferde yükler her şeyi.
Iravanchi

SQL profilerdeki 2'yi karşılaştırdığımda, Bul / Yükle durumum için daha iyiydi (1: 1 ilişkim vardı). @Iravanchi: 1: m'lik bir ilişkim olsaydı, mağazaya m çarpı derdi mi demek istediniz? ... çünkü pek mantıklı değil.
Öğrenci

3
1 değil: m ilişkisi, ancak çoklu ilişkiler. Loadİşlevi her aradığınızda , çağrı geri döndüğünde ilişki doldurulmalıdır. Bu nedenle, Loadbirden çok ilişki için birden çok kez çağırırsanız , her seferinde bir gidiş dönüş olacaktır. Tek bir ilişki için bile, eğer Findyöntem hafızadaki varlığı bulamazsa, iki gidiş-dönüş yapar: biri için Findve diğeri için Load. Ama Include. SingleOrDefaultyaklaşımı, bildiğim kadarıyla bir seferde varlık ve ilişkiyi getirir (ama emin değilim)
Iravanchi

1
Koleksiyonları ve referansları farklı şekilde ele almak yerine Include tasarımını bir şekilde izlemiş olsaydı güzel olurdu. Bu, isteğe bağlı olarak İfade <Tunc <T, nesne >> koleksiyonunu (örneğin, _repo.GetById (id, x => x.MyCollection)) alan bir GetById () cephesi oluşturmayı zorlaştırır
Derek Greer

4
Yayınınızın referansından bahsetmeye dikkat edin: msdn.microsoft.com/en-us/data/jj574232.aspx#explicit
Hossein

1

IQueryable'ı DbSet'e yayınlamanız gerekiyor

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

return dbSet.Find(id);


DbSet içinde .Find veya .FindAsync yoktur. Bu EF Çekirdeği mi?
Thierry

ef çekirde de ef 6 var
Rafael R. Souza

Ben umutlu ve sonra "InvalidCastException"
ZX9 20

0

Benim için çalışmadı. Ama ben böyle yaparak çözdüm.

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

Bu iyi bir çözüm olup olmadığını bilmiyorum. Ama Dennis'in verdiği diğeri bana bir bool hatası verdi .SingleOrDefault(x => x.ItemId = id);


4
Dennis'in çözümü de çalışmalı. Belki de bu hata SingleOrDefault(x => x.ItemId = id)sadece =çift ​​yerine yanlış single nedeniyle var ==mı?
Slauma

6
evet, kullandığınız gibi görünüyor = değil ==. Sözdizimi hatası;)
Ralph N

Her ikisini de denedim == ve = hala bana bir hata verdi .SingleOrDefault (x => x.ItemId = id); = / Kodumda yanlış olan başka bir şey olmalı. Ama yaptığım yol kötü bir yol mu? Belki Dennis'in ne demek istediğini anlamıyorum.
Johan

0

Bir buluntu ile filtrelemenin gerçek kolay bir yolu yoktur. Ancak işlevselliği çoğaltmak için yakın bir yol buldum, ancak lütfen çözümüm için birkaç şeye dikkat edin.

Bu Çözümler, .net-core içindeki birincil anahtarı bilmeden genel olarak filtrelemenize olanak tanır

  1. Bul temel olarak farklıdır, çünkü veritabanını sorgulamadan önce varlıkta varsa varlığı elde eder.

  2. Ek olarak, kullanıcının birincil anahtarı bilmek zorunda kalmaması için bir Object tarafından filtrelenebilir.

  3. Bu çözüm EntityFramework Core içindir.

  4. Bu, içeriğe erişim gerektirir

Burada, birincil anahtarla filtrelemenize yardımcı olacak bazı uzantı yöntemleri verilmiştir.

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

Bu uzantı yöntemlerine sahip olduktan sonra şöyle filtreleyebilirsiniz:

query.FilterByPrimaryKey(this._context, id);
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.