EntityFramework Core is a very extensible tool, but sometimes you can find yourself blocked from writing an “expression” whereas in SQL it would be simple. 😀
In my case, I had to write a generic method to recover an entity by primary key.
But in this project, the names of the primary keys to my entities are not standardized.
This complicates the task, because without a common interface to all my entities I cannot write my LINQ request in the form:
DbContext.Users.Where(e => e.Id == id)
To solve my problem I did it in two steps:
I've dynamically recovered the PrimaryKey name
– then I generated an expression equivalent to Where(e=> e.MaPrimaryKey == unIdentifier).
Below is the extension method to recover the name of the property being declared as 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}");
}
Once the name of this is recovered I can dynamically create my expression as follows:
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;
}
}
The expression generated if the PK is named Other, therefore corresponds to:
Where(x.Id == id);
To use my expression nothing simpler
db.Blogs.FirstOrDefaultAsync(db.GetPrimaryKeyExpression<Blog>(id));
To conclude:
The expression generation can unlock some use cases that strong typography in C# and Linq could block us.
However, the creation of in-flight expression is costly in terms of performance and must be used sparingly.
Happy coding. &##x1f642;