LINQ expression with generic class properties

2019-04-10 16:19发布

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.

2条回答
The star\"
2楼-- · 2019-04-10 17:00

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)));
}
查看更多
仙女界的扛把子
3楼-- · 2019-04-10 17:03

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 :(.

查看更多
登录 后发表回答