web-dev-qa-db-fra.com

DbSet n'a pas de méthode Find dans EF7

J'essaie de créer un référentiel générique pour accéder à ma base de données. Dans EF6, je pouvais le faire pour obtenir une entité spécifique:

protected IDbSet<T> dbset;

public T Get(object id)
{
    return this.dbset.Find(id);
}

DbSet dans EF7 manque une méthode Find. Est-il possible d'implémenter le code ci-dessus?

46
Lyudmil Dimitrov

Voici une implémentation très grossière, incomplète et non testée de .Find() en tant que méthode d'extension. Si rien d'autre, cela devrait vous faire pointer dans la bonne direction.

La mise en œuvre réelle est suivie par # 797 .

static TEntity Find<TEntity>(this DbSet<TEntity> set, params object[] keyValues)
    where TEntity : class
{
    var context = ((IAccessor<IServiceProvider>)set).Service.GetService<DbContext>();

    var entityType = context.Model.GetEntityType(typeof(TEntity));
    var key = entityType.GetPrimaryKey();

    var entries = context.ChangeTracker.Entries<TEntity>();

    var i = 0;
    foreach (var property in key.Properties)
    {
        var keyValue = keyValues[i];
        entries = entries.Where(e => e.Property(property.Name).CurrentValue == keyValue);
        i++;
    }

    var entry = entries.FirstOrDefault();
    if (entry != null)
    {
        // Return the local object if it exists.
        return entry.Entity;
    }

    // TODO: Build the real LINQ Expression
    // set.Where(x => x.Id == keyValues[0]);
    var parameter = Expression.Parameter(typeof(TEntity), "x");
    var query = set.Where((Expression<Func<TEntity, bool>>)
        Expression.Lambda(
            Expression.Equal(
                Expression.Property(parameter, "Id"),
                Expression.Constant(keyValues[0])),
            parameter));

    // Look in the database
    return query.FirstOrDefault();
}
23
bricelam

Si vous utilisez EF 7.0.0-rc1-final , vous trouverez ci-dessous une petite mise à jour du code présenté par @bricelam dans la réponse précédente. En passant, merci beaucoup @bricelam - votre code m'a été extrêmement utile.

Voici mes dependencies sous "project.config":

"dependencies": {
    "EntityFramework.Commands": "7.0.0-rc1-final",
    "EntityFramework.MicrosoftSqlServer": "7.0.0-rc1-final",
    "Microsoft.Framework.Configuration.Json": "1.0.0-beta8",
    "Microsoft.Framework.ConfigurationModel": "1.0.0-beta4",
    "Microsoft.Framework.ConfigurationModel.Json": "1.0.0-beta4",
    "Microsoft.Framework.DependencyInjection": "1.0.0-beta8"
}

Et voici la méthode d'extension pour DbSet.Find (TEntity) :

using Microsoft.Data.Entity;
using Microsoft.Data.Entity.Infrastructure;
using System;
using System.Linq;
using System.Linq.Expressions;

namespace Microsoft.Data.Entity.Extensions
{
    public static class Extensions
    {
        public static TEntity Find<TEntity>(this DbSet<TEntity> set, params object[] keyValues) where TEntity : class
        {
            var context = ((IInfrastructure<IServiceProvider>)set).GetService<DbContext>();

            var entityType = context.Model.FindEntityType(typeof(TEntity));
            var key = entityType.FindPrimaryKey();

            var entries = context.ChangeTracker.Entries<TEntity>();

            var i = 0;
            foreach (var property in key.Properties)
            {
                entries = entries.Where(e => e.Property(property.Name).CurrentValue == keyValues[i]);
                i++;
            }

            var entry = entries.FirstOrDefault();
            if (entry != null)
            {
                // Return the local object if it exists.
                return entry.Entity;
            }

            // TODO: Build the real LINQ Expression
            // set.Where(x => x.Id == keyValues[0]);
            var parameter = Expression.Parameter(typeof(TEntity), "x");
            var query = set.Where((Expression<Func<TEntity, bool>>)
                Expression.Lambda(
                    Expression.Equal(
                        Expression.Property(parameter, "Id"),
                        Expression.Constant(keyValues[0])),
                    parameter));

            // Look in the database
            return query.FirstOrDefault();
        }
    }
}
19
Roger Santana

Vous ne pouvez pas commenter pour des raisons de réputation, mais si vous utilisez RC2 (ou une version ultérieure?), Vous devez utiliser

var context = set.GetService<ICurrentDbContext>().Context;

au lieu de

var context = set.GetService<DbContext>();
9
Ilpo Toni

J'ai pris certaines des réponses fournies précédemment et les ai peaufinées pour résoudre quelques problèmes:

  • Fermeture implicite
  • La clé ne devrait pas être codée "Id"

    public static TEntity Find<TEntity>(this DbSet<TEntity> set, params object[] keyValues) where TEntity : class
    {
        var context = set.GetService<DbContext>();
    
        var entityType = context.Model.FindEntityType(typeof(TEntity));
        var key = entityType.FindPrimaryKey();
    
        var entries = context.ChangeTracker.Entries<TEntity>();
    
        var i = 0;
        foreach (var property in key.Properties)
        {
            var i1 = i;
            entries = entries.Where(e => e.Property(property.Name).CurrentValue == keyValues[i1]);
            i++;
        }
    
        var entry = entries.FirstOrDefault();
        if (entry != null)
        {
            // Return the local object if it exists.
            return entry.Entity;
        }
    
        var parameter = Expression.Parameter(typeof(TEntity), "x");
        var query = set.AsQueryable();
        i = 0;
        foreach (var property in key.Properties)
        {
            var i1 = i;
            query = query.Where((Expression<Func<TEntity, bool>>)
             Expression.Lambda(
                 Expression.Equal(
                     Expression.Property(parameter, property.Name),
                     Expression.Constant(keyValues[i1])),
                 parameter));
            i++;
        }
    
        // Look in the database
        return query.FirstOrDefault();
    }
    
8
Shaul Behr

Find arrive enfin dans le noyau Entity Framework.

6
Sebastian Busek

Alors ... les méthodes de recherche ci-dessus ont fonctionné à merveille, mais si vous n'avez pas de colonne nommée "Id" dans votre modèle, tout échouera sur la ligne suivante. Je ne sais pas pourquoi le PO aurait mis une valeur codée en dur à cet endroit

  Expression.Property(parameter, "Id"),

Voici une révision qui résoudra le problème pour ceux qui nomment nos colonnes Id de manière appropriée. :)

var keyCompare = key.Properties[0].Name;

        // TODO: Build the real LINQ Expression
        // set.Where(x => x.Id == keyValues[0]);
        var parameter = Expression.Parameter(typeof(TEntity), "x");
        var query = set.Where((Expression<Func<TEntity, bool>>)
            Expression.Lambda(
                Expression.Equal(
                    Expression.Property(parameter, keyCompare),
                    //Expression.Property(parameter, "Id"),
                    Expression.Constant(keyValues[0])),
                parameter));

        // Look in the database
        return query.FirstOrDefault();
    }

Cette STILL pourrait très bien échouer si vous avez plus d’une configuration de clé sur l’objet entité et si la clé par laquelle vous recherchez n’est pas la première, mais elle devrait être un peu plus complexe de cette façon.

5
Bryan - S

Pas assez de réputation pour commenter, mais il y a un bug dans la réponse de @ Roger-Santana lors de son utilisation dans une application console/une assemblée séparée:

var i = 0;
foreach (var property in key.Properties)
{
    entries = entries.Where(e => e.Property(property.Name).CurrentValue == keyValues[i]);
    i++;
}
var entry = entries.FirstOrDefault();

La valeur de 'i' est capturée dans le foreach de sorte que, lors de l'appel de entries.FirstOrDefault (), keyValues ​​[i] ait la valeur (au moins) keyValues ​​[i ++], qui dans mon cas s'est écrasé avec une erreur d'index Un correctif serait de copier la valeur de 'i' dans la boucle:

var i = 0;
foreach (var property in key.Properties)
{
   var idx =i;
    entries = entries.Where(e =>  e.Property(property.Name).CurrentValue == keyValues[idx]);
    i++;
}
var entry = entries.FirstOrDefault();
5
Paul Lawrence

J'utilise linq; Au lieu de la méthode Find, vous pouvez utiliser:

var record = dbSet.SingleOrDefault(m => m.Id == id)
4
A-Sharabiani

Permettez-moi de contribuer à une révision qui inclut la construction de l'expression. Je vous avouerai que je n’ai pas testé cela ;-)

    public static TEntity Find<TEntity>(this DbSet<TEntity> dbSet, params object[] keyValues) where TEntity : class
    {
        // Find DbContext, entity type, and primary key.
        var context = ((IInfrastructure<IServiceProvider>)dbSet).GetService<DbContext>();
        var entityType = context.Model.FindEntityType(typeof(TEntity));
        var key = entityType.FindPrimaryKey();

        // Build the lambda expression for the query: (TEntity entity) => AND( entity.keyProperty[i] == keyValues[i])
        var entityParameter = Expression.Parameter(typeof(TEntity), "entity");
        Expression whereClause = Expression.Constant(true, typeof(bool));
        uint i = 0;

        foreach (var keyProperty in key.Properties) {
            var keyMatch = Expression.Equal(
                Expression.Property(entityParameter, keyProperty.Name),
                Expression.Constant(keyValues[i++])
            );

            whereClause = Expression.And(whereClause, keyMatch);
        }

        var lambdaExpression = (Expression<Func<TEntity,bool>>)Expression.Lambda(whereClause, entityParameter);

        // Execute against the in-memory entities, which we get from ChangeTracker (but not filtering the state of the entities).
        var entries = context.ChangeTracker.Entries<TEntity>().Select((EntityEntry e) => (TEntity)e.Entity);
        TEntity entity = entries.AsQueryable().Where(lambdaExpression).First(); // First is what triggers the query execution.

        // If found in memory then we're done.
        if (entity != null) { return entity; }

        // Otherwise execute the query against the database.
        return dbSet.Where(lambdaExpression).First();
    }
1
sjb-sjb

Une modification a été proposée pour changer ".First ()" en ".FirstOrDefault ()" dans la dernière ligne de mon post précédent. Le montage a été rejeté, mais je l’accepte. Je m'attendrais à ce que la fonction renvoie la valeur null si la clé n'a pas été trouvée. Je ne voudrais pas qu'il jette une exception. Dans la plupart des cas, j'aimerais savoir si la clé existait dans le jeu, et la gestion d'une exception est un moyen très lent de le savoir. 

0
sjb-sjb

voici ce que j'utilise . Pas une méthode de recherche, mais fonctionne comme un charme

var professionalf = from m in _context.Professionals select m;
professionalf = professionalf.Where(s => s.ProfessionalId == id);
Professional professional = professionalf.First();
0
ditsikts