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.
Commentaires