Confused about passing Expression vs. Func arguments

送分小仙女□ 提交于 2019-12-21 03:20:09

问题


I'm having some trouble understanding the differences between how Expressions and Funcs work. This problem turned up when someone changed a method signature from:

public static List<Thing> ThingList(Func<Thing, bool> aWhere)

To

public static List<Thing> ThingList(Expression<Func<Thing, bool>> aWhere)

Which broke my calling code. The old calling code (which worked) looked like this:

        ...
        object y = new object();
        Func<Thing, bool> whereFunc = (p) => p == y;
        things = ThingManager.ThingList(whereFunc);

The new code (which doesn't work) looks like this:

        ...
        object x = new object();
        Expression<Func<Thing, bool>> whereExpr = (p) => p == x;
        things = ThingManager.ThingList(whereExpr);

This fails inside ThingList(...) on the line utilizing the expression:

        var query = (from t in context.Things.Where(aWhere)
        ...

With the runtime error:

Unable to create a constant value of type 'System.Object'. Only primitive types ('such as Int32, String, and Guid') are supported in this context.

This example is contrived, but my guess is it has something to do with the local object variable x not being properly "copied" into the expression.

Can someone explain how to handle this situation in general, and why the Func works but the Expression doesn't?


回答1:


The reason for the change almost certainly was to "push" the evaluation of your predicate into the underlying store, which backs your context. Instead of bringing all Things into memory and then using Func<Thing,bool> to decide which ones to keep, the author of the changed API decided to use IQueryable, and needed an Expression<Func<Thing,bool>> for that.

You are correct on the origin of the error: unlike in-memory predicates, IQueryable cannot use objects that it does not know, e.g. arbitrary instances of object.

What you need to do is to change the expression to avoid referencing objects of data types not supported by your target data store (I assume the expression eventually makes its way into either an Entity Framework or a Linq2Sql context). For example, instead of saying

object x = new object();
Expression<Func<Thing, bool>> whereExpr = (p) => p == x;
things = ThingManager.ThingList(whereExpr);

you should say

Thing x = new Thing {id = 123};
Expression<Func<Thing, bool>> whereExpr = (p) => p.id == x.id;
things = ThingManager.ThingList(whereExpr);

(your backing store almost certainly understands integers)




回答2:


The difference between Expression and Func is better described in the answers here: Difference between Expression<Func<>> and Func<>

A quick workaround to make this work again would be to compile the expression back into a Func.

var query = (from t in context.Things.Where(aWhere.Compile())


来源:https://stackoverflow.com/questions/8728357/confused-about-passing-expression-vs-func-arguments

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!