我有一个班级用户。
用户具有 UserId 属性。
我有一个看起来像这样的方法:
static IQueryable<User> FilterById(this IQueryable<User> p, Func<int, bool> sel)
{
return p.Where(m => sel(m.UserId));
}
当我调用函数时不可避免地:
var users = Users.FilterById(m => m > 10);
我得到以下异常:
方法'System.Object DynamicInvoke(System.Object [])'没有 支持转换为SQL。
这个问题有解决办法吗? Expression.KillMeAndMyFamily()的兔子洞可能需要走多远?
澄清为什么我这样做:我使用T4模板自动生成一个简单的存储库和一个管道系统。在管道内,而不是写:
new UserPipe().Where(m => m.UserId > 10 && m.UserName.Contains("oo") && m.LastName == "Wee");
我想生成类似的东西:
new UserPipe()
.UserId(m => m > 10)
.UserName(m => m.Contains("oo"))
.LastName("Wee");
答案 0 :(得分:15)
我们以UserId
为例。你想写:
new UserPipe().UserId(uid => uid > 10);
并希望它与以下内容相同:
new UserPipe().Where(user => user.UserID > 10);
您需要做的是获取第一个版本的表达式树并将其转换为第二个版本。
因此,首先更改UserId
的签名以接受表达式树而不是编译的lambda:
public static IQueryable<User> UserId(
IQueryable<User> source, Expression<Func<int, bool>> predicate)
然后,编写一个将第一个表达式树转换为第二个版本的方法。我们来看看两个表达式树:
输入:
Lambda uid | BinaryOp > / \ Parameter Constant uid 10
输出:
Lambda user | BinaryOp > / \ Property Constant UserID 10 | Parameter user
正如您所看到的,您需要做的就是获取lambda的主体,以参数uid
上的属性UserId
递归替换参数user
的所有匹配项,并且使用转换后的主体和参数user
创建一个新的lambda表达式。
您可以使用ExpressionVisitor进行替换。
答案 1 :(得分:1)
感谢dtb,这就是我想出的:
public class ExpressionMemberMerger : ExpressionVisitor
{
MemberExpression mem;
ParameterExpression paramToReplace;
public Expression Visit<TMember, TParamType>(
Expression<Func<TParamType, bool>> exp,
Expression<Func<TMember, TParamType>> mem)
{
//get member expression
this.mem = (MemberExpression)mem.Body;
//get parameter in exp to replace
paramToReplace = exp.Parameters[0];
//replace TParamType with TMember.Param
var newExpressionBody = Visit(exp.Body);
//create lambda
return Expression.Lambda(newExpressionBody, mem.Parameters[0]);
}
protected override Expression VisitParameter(ParameterExpression p)
{
if (p == paramToReplace) return mem;
else return base.VisitParameter(p);
}
}
现在,我可以使用下面的代码转换谓词,methinks。我对这段代码做了一些测试;它似乎有效,但我有兴趣听到任何意见/担忧:
static IQueryable<User> FilterById(this IQueryable<User> p, Expression<Func<int, bool>> sel)
{
var merger = new ExpressionMemberMerger();
Expression<Func<User, int>> mem = m => m.UserId;
var expression = (Expression<Func<User, bool>>)merger.Visit(sel, mem);
return p.Where(expression);
}
答案 2 :(得分:0)
乍一看,看起来你正在构建一些Linq不知道如何翻译成T-SQL的表达式。
我可能误解了你要做的事情,但是如果你想构建Linq To Sql可以理解的可链式表达式,我强烈建议你查看PredicateBuilder扩展here。即使它不是您想要的,但了解它的工作原理可以让您深入了解您需要实现的内容,以使您正在做的工作。