EF Core : récupérer une entité par sa cléf primaire via une expression générée dynamiquement

EF Core : récupérer une entité par sa cléf primaire via une expression générée dynamiquement

EntityFramework Core est un outil très extensible, mais parfois on peut se retrouver bloquer à écrire une “expression” alors qu’en SQL ce serait simple. 😀

Dans mon cas je devais écrire une méthode générique permettant de récupérer une entité par clef primaire.
Or, dans ce projet, le nom des clefs primaire de mes entités ne sont pas normé.

Ce qui complique la tâche, en effet sans interface commune à toutes mes entités je ne peux pas écrire ma requête LINQ sous la forme :

DbContext.Users.Where(e => e.Id == id)

Pour résoudre mon problème j’ai procédé en deux étapes :
– j’ai récupéré dynamiquement le nom de la PrimaryKey
– puis j’ai généré une expression équivalente à Where(e=> e.MaPrimaryKey == unIdentifiant).

Ci-dessous la méthode d’extension permettant de récupérer le nom de la propriété étant déclaré comme PrimaryKey :

        public static string GetPrimaryKeyPropertyName(this DbContext dbContext, Type type)
        {
            var entityType = dbContext.Model.FindEntityType(type);
            var primaryKeys = entityType.GetProperties().Where(p => p.IsPrimaryKey());

            if (primaryKeys.Any())
            {
                if (primaryKeys.Count() == 1)
                {
                    return primaryKeys.ElementAt(0).Name;
                }
                else
                {
                    throw new TooManyPrimaryKeysException($"Too many primary keys for {type.Name}");
                }
            }
            else
            {
                throw new PrimaryKeyNotFoundException($"Primary key not found for {type.Name}");
            }

Une fois le nom de celle-ci récupérer je peux créer dynamiquement mon expression de la façon suivante :

        public static Expression<Func<TEntity, bool>> GetPrimaryKeyExpression<TEntity>(this DbContext dbContext, long id)
        {
                var type = typeof(TEntity);
                var parameter = Expression.Parameter(type, "x");
                var member = Expression.Property(parameter, dbContext.GetPrimaryKeyPropertyName(type)); //x.PrimaryKey
                var constant = Expression.Constant(id);
                var body = Expression.Equal(member, constant); //x.Id == id
                var finalExpression = Expression.Lambda<Func<TEntity, bool>>(body, parameter); //x => x.I
               
               return finalExpression;
            }
        }

L’expression généré,  si la PK est nommé Id,  correspond donc à :

       Where(x.Id == id);

Pour utiliser mon expression rien de plus simple

db.Blogs.FirstOrDefaultAsync(db.GetPrimaryKeyExpression<Blog>(id));

Pour conclure :

La génération d’expression peut débloquer certains use case que le typage fort de C# et Linq pourrait nous bloquer.
Cependant, la création d’expression à la volé à un cout en termes de performance et doit être utilisé avec parcimonie.

Happy coding. 🙂

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Pin It on Pinterest