StackOverflow adventures: Checking key equality with generic primary keys in Entity Framework
You might have come across a design like this: you are using Entity Framework Code First, you have a base class that defines the primary key of the entities, and to have a more generic design, the PK is generic. Something like this:
public abstract class EntityBase<TKey>
{
public TKey Id { get; set; }
}
Now you might want to write a repository for your entities or some generic code that goes like this:
_context.Set<Entity>().AsNoTracking().Where(x=>x.Id==8);
The problem is that this does not always compile, it can give you an error because of the generic TKey might not be a type that has == defined. Just like in this question on stackoverflow:
https://stackoverflow.com/questions/34967116/how-to-combine-find-and-asnotracking
How can you still instruct EF, to still create a query like this? After all, there is absolutely no need for TKey to has the operator defined, it just has to compile the the appropriate SQL query.
Well, you can create the expression yourself, and then pass that to the Where method; or even create an extension method like this:
public static class Extensions
{
public static IQueryable<TEntity> WhereIdEquals<TEntity, TKey>(
this IQueryable<TEntity> source,
Expression<Func<TEntity, TKey>> keyExpression,
TKey otherKeyValue)
where TEntity : KeyedEntityBase<TKey>
{
var memberExpression = (MemberExpression)keyExpression.Body;
var parameter = Expression.Parameter(typeof(TEntity), "x");
var property = Expression.Property(parameter, memberExpression.Member.Name);
var equal = Expression.Equal(property, Expression.Constant(otherKeyValue));
var lambda = Expression.Lambda<Func<TEntity, bool>>(equal, parameter);
return source.Where(lambda);
}
}
And now, you can use it like this:
context.Set<MyEntity>.AsNoTracking().WhereIdEquals(m=>m.Id, 9).ToList();
The static extension method will take a lambda expression, extract the name of the property from that, and then build the expression for the equality. And this compiles to SQL regardless of the type :)