wake-up-neo.net

C # Entity-Framework: Wie kann ich ein .Find und .Include für ein Modellobjekt kombinieren?

Ich mache das Übungs-Tutorial für mvcmusicstore. Beim Erstellen des Gerüsts für den Album-Manager ist mir etwas aufgefallen (Hinzufügen, Löschen, Bearbeiten).

Ich möchte Code elegant schreiben, also suche ich nach einer sauberen Möglichkeit, dies zu schreiben.

Zu Ihrer Information, ich mache den Laden allgemeiner:

Albums = Items

Genres = Kategorien

Künstler = Marke

So wird der Index abgerufen (von MVC generiert):

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

So wird das zu löschende Element abgerufen:

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

Der erste Befehl bringt alle Artikel zurück und füllt die Kategorie- und Markenmodelle innerhalb des Artikelmodells. Die zweite Option füllt die Kategorie und die Marke nicht aus.

Wie kann ich den zweiten schreiben, um das Finden UND Auffüllen des Inhalts (vorzugsweise in einer Zeile) durchzuführen? Theoretisch - so etwas wie:

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

Sie müssen zuerst Include() verwenden und dann ein einzelnes Objekt aus der resultierenden Abfrage abrufen:

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

Dennis 'Antwort verwendet Include und SingleOrDefault. Letzteres führt einen Roundtrip zur Datenbank durch.

Eine Alternative ist die Verwendung von Find in Kombination mit Load zum expliziten Laden verwandter Entitäten ...

Unten ein MSDN-Beispiel :

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

Natürlich kehrt Find sofort zurück, ohne eine Anforderung an den Speicher zu richten, wenn diese Entität bereits durch den Kontext geladen ist.

61
Learner

Hat bei mir nicht funktioniert. Aber ich habe es so gelöst.

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

Weiß nicht, ob das eine gute Lösung ist. Aber der andere, den Dennis mir gegeben hat, hat mir einen boolen Fehler in .SingleOrDefault(x => x.ItemId = id); gegeben.

0
Johan

Sie müssen IQueryable in DbSet umwandeln

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

return dbSet.Find(id);

0
Rafael R. Souza

Es gibt keine wirklich einfache Möglichkeit, mit einem Fund zu filtern. Ich habe mir jedoch einen genauen Weg ausgedacht, um die Funktionalität zu replizieren. Beachten Sie jedoch einige Punkte für meine Lösung.

Mit dieser Lösung können Sie generisch filtern, ohne den Primärschlüssel in .net-core zu kennen.

  1. Find unterscheidet sich grundlegend, da es die Entität abruft, wenn sie in der Verfolgung vorhanden ist, bevor die Datenbank abgefragt wird.

  2. Zusätzlich kann nach einem Objekt gefiltert werden, sodass der Benutzer den Primärschlüssel nicht kennen muss.

  3. Diese Lösung ist für EntityFramework Core.

  4. Dies erfordert den Zugriff auf den Kontext

Hier sind einige Erweiterungsmethoden zum Hinzufügen, die Ihnen helfen, nach Primärschlüssel zu filtern

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

Sobald Sie diese Erweiterungsmethoden haben, können Sie wie folgt filtern:

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