14

I have a generic method to query objects of type TEntity in EF. I Want to add a condition as a where clause if TEntity implements a specific interface. The method I have is:

public TEntity GetByUserID(Guid userID)
{
    var query = this.DbSet;
    if (typeof (TEntity).IsImplementationOf<IDeletableEntity>())
    {
        query = query
            .Where((x => !((IDeletableEntity)x).IsDeleted);
    }
    return query
        .FirstOrDefault(x => x.UserID == userID);
}

IsImplementationOf<>() is method which just returns true/false as the name implies.

When I run this for the entity Address which implement IDeletableEntity, I get an error:

Unable to cast the type 'Address' to type 'IDeletableEntity'. LINQ to Entities only supports casting EDM primitive or enumeration types.

Any ideas how I can go around this limitation of LINQ?

Vladimir
  • 1,425
  • 16
  • 31

3 Answers3

16

This is a working solution:

public TEntity GetByUserID(Guid userID, params Include<TEntity>[] includes)
{
    var query = this.DbSet;
    query = Where<IDeletableEntity>(query, x => !x.IsDeleted);
    return query
        .FirstOrDefault(x => x.UserID == userID);
}

public static IQueryable<TEntity> Where<TPredicateWellKnownType>(IQueryable<TEntity> query, Expression<Func<TPredicateWellKnownType, bool>> predicate)
{
    if (typeof(TEntity).IsImplementationOf<TPredicateWellKnownType>())
    {
        query = ((IQueryable<TPredicateWellKnownType>)query)
            .Where(predicate)
            .Cast<TEntity>();
    }
    return query;
}
Vladimir
  • 1,425
  • 16
  • 31
  • This can't possibly work if TPredicateWellKnownType is not castable to TEntity ... which is why I get an exception to that effect – War Sep 21 '16 at 09:55
  • 1
    Absolute genius! The trouble I was having was finding the syntax to allow the composer to manage the casting without trying to pass that knowledge on to the SQL engine and without modifying the query type. Very elegant solution for something that looks obvious when we see it done for us, but seemed too ugly to attempt ourselves. – Chris Schaller Jul 24 '17 at 03:34
  • 1
    Where `IsImplementationOf` comes from ? – koryakinp Jun 12 '18 at 19:48
-1

If all DbSets has the 'UserID' property then create another interface named 'IUserID' and then try this code:

    protected TEntity GetByUserID<TEntity>(Guid userID) where TEntity : class
    {
        var user = this.Set<TEntity>()
            .ToList()
            .Cast<IDeletableEntity>()
            .Where(u => (!u.IsDeleted))
            .Cast<IUserID>()
            .Where(u => (u.UserID == userID))
            .FirstOrDefault();
        return user;
    }
Junior
  • 313
  • 5
  • 12
  • 1
    Now you're performing the entire operation in memory, rather that in the database. – Servy Jan 16 '17 at 17:09
  • @Servy This can be solved by removing .ToList() method, right? – Junior Jan 17 '17 at 18:43
  • You then also need to make sure that the rest of the code works properly when run by an EF provider, but yes, the `ToList` will force the code to all run in memory. – Servy Jan 17 '17 at 18:51
-2

I think the problem might be the direct cast you are doing in your statement as well as query might be implementing enumeration types and so IDeletable is not implemented as on one entity.

LINQ-to-entities casting issue

proposed this solution.

 return query.ToList()
                       .Cast<IDeletable>()
                       .Where( e => e.Deleted )
                       .Cast<T>();
Community
  • 1
  • 1
COLD TOLD
  • 13,513
  • 3
  • 35
  • 52
  • This will probably work but will first go to the database and get all items, cast them to a list and then filter them in memory. The solution I found sends the condition to the database, i.e. you get only the elements with the correct flag value. – Vladimir Mar 24 '15 at 06:41
  • Again, as I said to Vladimir how can that possibly work unless an IDeletable is castable to a T – War Sep 21 '16 at 09:58