Loupe

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

EntityFramework Core est un outil très extensible, mais parfois on peut se retrouver bloqué à écrire une “expression” alors qu’en SQL ce serait beaucoup plus 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 primaires de mes entités n'est 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 :

1
DbContext.Users.Where(e => e.Id == id && e.IsEnabled)

A noter :
Si j'avais besoin de filter que par la clef primaire la méthode Find ou FindAsync suffirait.

 

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ée comme PrimaryKey :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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éré, je peux créer dynamiquement mon expression de la façon suivante :

1
2
3
4
5
6
7
8
9
10
11
12
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ée, si la PK est nommé Id, correspond donc à :

1
Where(x.Id == id);

Pour utiliser mon expression, rien de plus simple :

1
db.Blogs.Where(e => e.IsEnabled).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 bloquer.
Cependant, la création d’expression à la volée à un coût en termes de performance et doit être utilisée avec parcimonie.

Happy coding. 

Photo de profil

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus