2

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.

1
  • 1
    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. Commented Dec 10, 2021 at 22:20

1 Answer 1

2

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();
}
Sign up to request clarification or add additional context in comments.

3 Comments

But wouldn't that mean I will have to re-compile the expression on each execution? Or rather EF will recompile it each time?
Yep, it worked and the performance hit is insignificant. Thanks a lot. One of this things like "How haven't I thought of it before...". Must be the late hour. ;-)
Yeah, to answer your earlier question, the expression doesn't need to be "compiled" at all. It only needs to get built, and then interpreted by Entity Framework, all of which is what happens if you have a hard-coded expression anyway. If you look at the IL code generated when you say e => e.Id == id in code, it's actually generating calls to all those Expression methods.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.