Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LINQ expression with generic class properties

I would like to pass an IQueryable and an array of ids to a method which filters the IQueryable based on those ids.

As the ids can be either long's or int's it should be solved generically.

I came up with the following:

public static IEnumerable<T> GetModified<TId, T>(IQueryable<T> objects, TId[] ids) where T : class
{
        return objects.Where(j => ids.Contains((TId)j.GetType().GetProperty("Id").GetValue(j)));
}

Unfortunately I'm getting the exception:

LINQ to Entities does not recognize the method 'System.Object GetValue(System.Object)' method, and this method cannot be translated into a store expression.

like image 422
niklr Avatar asked Jan 13 '23 08:01

niklr


2 Answers

The exception is normal, as getting properties through reflection is something that clearly cannot be translated to SQL.

One thing I would try is to create a generic interface that exposes an Id property of a given type:

public interface HasId<T> {
    T Id { get; set; }
}

Now you could declare your entity as implementing HasId<int>, for example, if the Id was of type int.

The next step is to modify your method like so:

public static IEnumerable<T> GetModified<TId, T>
    (IQueryable<T> objects, TId[] ids) where T : class, HasId<TId>
{
    return objects.Where(j => ids.Contains(j.Id));
}

Note the added generic restriction: where T : class, HasId<TId>. This enables you to write the simplified j.Id, which returns a TId value, instead of resorting to reflection.

Please note that I haven't run or tested this code; it's just an idea that I got when I saw your problem and I hope it helps.

Update:

Here's another possible solution that doesn't require that you declare interfaces or change your classes in any way:

public static IEnumerable<T> GetModified<TId, T>
    (IQueryable<T> objects, TId[] ids, Expression<Func<T, TId>> idSelector) 
    where T : class
{
    return objects.Where(j => ids.Contains(idSelector(j)));
}

What I've done here is add the Expression<Func<T, TId>> idSelector parameter, an expression that can return the Id of a given instance of T.

You would call the method like that:

var modified = GetModified(dbObjects, yourIdArray, entity => entity.Id);

(only the third parameter being new; keep the others as you have them now).

Again, I haven't tested if this works or even compiles, as I don't have a computer with VS here :(.

like image 150
Cristian Lupascu Avatar answered Jan 29 '23 06:01

Cristian Lupascu


Entity Framework doesn't support some of the .NET methods such as GetValue() since it does not translate to SQL (which is the code actually executed to the IQueryable. Try calling ToList to get the CLR object before doing reflection:

public static IEnumerable<T> GetModified<TId, T>(IQueryable<T> objects, TId[] ids) where T : class
{
    return objects.ToList().Where(j => ids.Contains((TId)j.GetType().GetProperty("Id").GetValue(j)));
}
like image 41
rexcfnghk Avatar answered Jan 29 '23 06:01

rexcfnghk