I am trying to implement some base class for a service that fetches objects from the database using EF Core and generics. Consider the following class:
/// <summary>
/// Base entity getter service
/// </summary>
/// <typeparam name="TEntity">The database entity type</typeparam>
/// <typeparam name="TPK">The primary key type for example: int</typeparam>
class Service<TEntity, TPK>{
protected Func<TEntity, TPK, bool> pkFinder;
public Service(string pkname) {
var entityExpr = Expression.Parameter(typeof(TEntity), "instance");
var propertyExpr = Expression.Property(entityExpr, pkname);
var pkExpr = Expression.Parameter(typeof(TPK), "value");
var eqExpr = Expression.Equal(propertyExpr, pkExpr);
pkFinder = Expression.Lambda<Func<TEntity, TPK, bool>>(eqExpr, entityExpr, pkExpr).Compile();
}
public TEntity Get(TPK pk){
return ctx.Set<TEntity>().Where(e=>pkFinder.Invoke(e, pk)).SingleOrDefault();
}
}
The pkname parameter is the name of the property that holds the primary key for that entity (typically "Id")
The ctx object is the EF context.
The above code ( or something very similar as this example is oversimplified for clarity) fails with Entity Framework unable to translate the expression.
The only thing that has worked so far is implementing an override in each derived class for the .Where clause with actual non generic types, however I would like to avoid reimplementing the same code over and over.
Another idea was to make the entity classes to derive from some base class but since they are autogenerated, that could be problematic.
Any new ideas would be greatly appreciated.
CodePudding user response:
You've already got all the code for generating an expression to check the ID against a known value. Just put that into your Get
method and use an Expression.Constant
with the pk
value rather than an Expression.Parameter
for your pkExpr
.
I haven't tested it, but something like this should work:
private readonly string pkname;
public Service(string pkname) {
this.pkname = pkname;
}
public TEntity Get(TPK pk){
var entityExpr = Expression.Parameter(typeof(TEntity), "instance");
var propertyExpr = Expression.Property(entityExpr, pkname);
var pkExpr = Expression.Constant(pk, typeof(TPK));
var eqExpr = Expression.Equal(propertyExpr, pkExpr);
var pkFinder = Expression.Lambda<Func<TEntity, bool>>(eqExpr, entityExpr);
return ctx.Set<TEntity>().Where(pkFinder).SingleOrDefault();
}