web-dev-qa-db-fra.com

C # Entity-Framework: Comment combiner un .Find et .Include sur un objet de modèle?

Je fais le tutoriel pratique mvcmusicstore. J'ai remarqué quelque chose lors de la création de l'échafaudage du gestionnaire d'albums (ajout, suppression, édition).

Je veux écrire du code avec élégance, alors je cherche la façon propre d’écrire cela.

Pour info, je rends le magasin plus générique:

Albums = Articles

Genres = Catégories

Artiste = Marque

Voici comment récupérer l'index (généré par MVC):

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

Voici comment l’élément à supprimer est récupéré:

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

Le premier ramène tous les articles et renseigne les modèles de catégorie et de marque à l'intérieur du modèle d'article. La seconde ne renseigne pas la catégorie et la marque.

Comment puis-je écrire le deuxième pour faire la recherche ET peupler ce qui est à l'intérieur (de préférence en 1 ligne) ... en théorie - quelque chose comme:

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

Vous devez d'abord utiliser Include(), puis récupérer un seul objet à partir de la requête résultante:

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

La réponse de Dennis utilise Include et SingleOrDefault. Ce dernier fait le tour de la base de données.

Une alternative consiste à utiliser Find, en combinaison avec Load, pour le chargement explicite d'entités associées ...

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

Bien entendu, Find retourne immédiatement sans faire de demande au magasin, si cette entité est déjà chargée par le contexte.

61
Learner

Cela n'a pas fonctionné pour moi. Mais je l'ai résolu en faisant comme ça.

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

Je ne sais pas si c'est une solution acceptable. Mais l'autre que Dennis m'a donné m'a donné une erreur bool dans .SingleOrDefault(x => x.ItemId = id);

0
Johan

Vous devez lancer IQueryable sur DbSet

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

return dbSet.Find(id);

0
Rafael R. Souza

Il n'y a pas vraiment de moyen facile de filtrer avec une recherche. Mais je suis venu avec un moyen étroit de reproduire la fonctionnalité, mais s'il vous plaît prendre note de quelques choses pour ma solution.

Cette solution vous permet de filtrer de manière générique sans connaître la clé primaire dans .net-core

  1. Find est fondamentalement différent car il obtient l'entité si elle est présente dans le suivi avant d'interroger la base de données.

  2. De plus, il peut filtrer par objet, évitant ainsi à l'utilisateur de connaître la clé primaire.

  3. Cette solution est destinée à EntityFramework Core.

  4. Cela nécessite un accès au contexte

Voici quelques méthodes d'extension à ajouter qui vous aideront à filtrer par clé primaire afin

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

Une fois que vous avez ces méthodes d'extension, vous pouvez filtrer comme suit:

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