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

发布于 2024-08-30 18:54:49 字数 925 浏览 1 评论 0 原文

我有一个类,用户

Users 有一个 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");

I have a class, Users.

Users has a UserId property.

I have a method that looks something like this:

static IQueryable<User> FilterById(this IQueryable<User> p, Func<int, bool> sel)
{
   return p.Where(m => sel(m.UserId));
}

Inevitably, when I call the function:

var users = Users.FilterById(m => m > 10);

I get the following exception:

Method 'System.Object DynamicInvoke(System.Object[])' has no
supported translation to SQL.

Is there any solution to this problem? How far down the rabbit hole of Expression.KillMeAndMyFamily() might I have to go?

To clarify why I'm doing this: I'm using T4 templates to autogenerate a simple repository and a system of pipes. Within the pipes, instead of writing:

new UserPipe().Where(m => m.UserId > 10 && m.UserName.Contains("oo") && m.LastName == "Wee");

I'd like to generate something like:

new UserPipe()
  .UserId(m => m > 10)
  .UserName(m => m.Contains("oo"))
  .LastName("Wee");

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

喵星人汪星人 2024-09-06 18:54:49

我们以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 和参数 user 上的属性 UserId 并使用转换后的主体和参数 user 创建一个新的 lambda 表达式

您可以使用 ExpressionVisitor 进行替换。

Let's take UserId as an example. You want to write:

new UserPipe().UserId(uid => uid > 10);

and want this to be the same as:

new UserPipe().Where(user => user.UserID > 10);

What you need to do is to take the expression tree of the first version and translate it to the second version.


So, first change the signature of UserId to accept an expression tree instead of a compiled lambda:

public static IQueryable<User> UserId(
    IQueryable<User> source, Expression<Func<int, bool>> predicate)

Then, write a method that converts the first expression tree to the second version. Let's have a look at the two expression trees:

Input:

        Lambda
         uid
          |
       BinaryOp
          >
    /            \
Parameter     Constant
   uid           10

Output:

         Lambda
          user
            |
        BinaryOp
            >
     /            \
 Property         Constant
  UserID             10
     | 
Parameter
   user

As you can see, all you need to do is take the body of the lambda, recursively replace all occurrences of the parameter uid with the property UserIdon the parameter user and create a new lambda expression with the transformed body and the parameter user.

You can use an ExpressionVisitor to do the replacement.

渔村楼浪 2024-09-06 18:54:49

感谢 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);
    }
}

现在,我想,我可以使用类似于下面的代码的内容来转换谓词。我对这段代码做了一些测试;它似乎有效,但我有兴趣听到任何评论/担忧:

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);
}

Thanks to dtb, here's what I came up with:

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);
    }
}

Now, I can convert the predicate, methinks, with something like the code below. I've done a wee bit of testing on this code; it seems to be working, but I'd be interested to hear any comments/concerns:

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);
}
机场等船 2024-09-06 18:54:49

乍一看,您似乎正在构建一些 Linq 不知道如何转换为 T-SQL 的表达式。

我可能会误解您想要执行的操作,但如果您想构建 Linq To Sql 可以理解的可链式表达式,我强烈建议您查看 PredicateBuilder 扩展 此处。即使这并不完全是您想要的,了解它的工作原理也可以让您深入了解您需要在幕后实现什么才能使您正在做的事情发挥作用。

At first glance, it looks like you're building some expressions that Linq isn't going to know how to translate into T-SQL.

I might be misunderstanding what you're trying to do, but if you want to build chain-able expressions that Linq To Sql can understand, I'd highly recommend looking at the PredicateBuilder extensions here. Even if it's not exactly what you want, understanding how it works could give you some insight on what you need to achieve under the covers to make what you're doing work.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文