Jamie
Jamie

Reputation: 4960

Entity Framework Generic Predicate

I need to share a predicate over 4-5 difference entity sets preferably without duplicating the code. All of the entities implement the interface IEntity.

private Expression<Func<T, bool>> GetUpdatedPredicate<T>(DateTime? lastUpdated) where T : IEntity {
    if (lastUpdated.HasValue) {
        return x => (
            x.CreatedAt >= lastUpdated ||
            (x.UpdatedAt.HasValue && x.UpdatedAt >= lastUpdated)
        );
    }

    return x => true;
}

Then used like this, across multiple entity sets:

(
     from s in Db.EntityA.Where(GetUpdatedPredicate<EntityA>(lastUpdated))
     where (
         s.User.Id == 1
     )
     select s
 ).Future();

But gives the error: Unable to cast the type 'EntityA' to type 'IEntity'. LINQ to Entities only supports casting EDM primitive or enumeration types. Is there a way to do this without duplicating the code?

Upvotes: 0

Views: 1112

Answers (3)

Moeri
Moeri

Reputation: 9294

Entity Framework has issues with your interface type constraint here:

where T : IEntity

You need to add an additional constraint that T needs to be a class:

where T : class, IEntity

Also, if you're starting to discover LINQ expressions, I can highly recommend the LinqKit library. It has some really, really nifty stuff.

Upvotes: 3

Maarten
Maarten

Reputation: 22945

EF now tries to include the call to your method into the generated expression, and that will not work. You have to exclude the method-call from the expression.

Try to create the expression from your method before you define your query (no, I haven't tested it).

DateTime lastUpdated = ...;
var updatedPredicate = GetUpdatedPredicate<EntityA>(lastUpdated);
var query = 
(
     from s in Db.EntityA.Where(updatedPredicate)
     where (
         s.User.Id == 1
     )
     select s
 ).Future();

Upvotes: 0

Gang Gao
Gang Gao

Reputation: 1079

I am proposing an alternative approach.

Firstly, instead of having a method to return an expression, you will have a private static method like:

private static bool Evaluate(IEntity x, DateTime? lastUpdated) {
    if (lastUpdated.HasValue) {
        return x.CreatedAt >= lastUpdated ||
            (x.UpdatedAt.HasValue && x.UpdatedAt >= lastUpdated)
        );
    }

    return true;
}

Obviously you can name the mention better. Then use it like:

var result = Db.EntityA.Where(i => i.Evaluate(i, lastUpdated));

Upvotes: 0

Related Questions