Linq to SQL DynamicInvoke(System.Object [])'没有支持的SQL转换

时间:2010-04-28 13:29:29

标签: c# linq-to-sql lambda

我有一个班级用户

用户具有 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");

3 个答案:

答案 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。即使它不是您想要的,但了解它的工作原理可以让您深入了解您需要实现的内容,以使您正在做的工作。