跟踪匹配的表达式数量.[英] Tracking number of expressions matched before .ToListing an EF Linq Query

本文是小编为大家收集整理的关于跟踪匹配的表达式数量.的处理/解决方法,可以参考本文帮助大家快速定位并解决问题,中文翻译不准确的可切换到English标签页查看源文。

问题描述

说我在查询的选择语句中:

context.Orders.Where(expr).Select(o => new OrderInfo{ ... }).ToList();

我有一个表达式列表(expr中表达式的子集),而不是所有要满足的内容(想法是它们是在OrElse条件下添加的所有内容):

List<Expression<Func<Order, bool>>> exprAppliedList;

您可以想像他们这样的:

o => o.BillingFirstName == xStr,
o => o.BillingLastName == xStr,
o => o.ShippingFirstName == xStr,
o => o.ShippingLastName == xStr,

o => o.BillingFirstName == yStr,
o => o.BillingLastName == yStr,
o => o.ShippingFirstName == yStr,
o => o.ShippingLastName == yStr,

诀窍是,在i .tolist()it之前,我想以某种方式索引 ,我在该单数顺序上匹配的或表达式. 如果我输入"约翰",并且有人有BillingFirstName = "John" BillingLastName = "John", ShippingFirstName = "John", and ShippingLastName = "John"我可以分配他的东西,例如SearchValue = 4,

我有很多想在.tolist()之前要这样做的原因,一个是我实际上根本不需要这些字段,而不是通过过滤.另一个是,如果我想在事实之后进行比较,我将不得不生成一组不同的表达式,因为它们将是类型List<Expression<Func<OrderInfo, bool>>>.

我意识到EF Linq不会接受任何胡说八道,自定义功能甚至许多扩展方法.但这已经是Lambda表达式的清单,似乎是Linq吃早餐的食用.

我确实认为我可能会在tolist()之后做到这一点,也许每个人都会说这是我应该做的..但是我也很好奇,如果可能的话,因为我是一个好奇的人.我认为这在许多情况下可能很有用.

我尝试了一些古怪的东西:

context.Orders.Where(expr).Select(o => new OrderInfo
{ 
  ... 
  SearchValue = ExpressionMatchList.Any() 
     ? ExpressionMatchList.Count(e => e.Compile().Invoke(o)) 
     : 0,
}).ToList();

,但Linq在某个子登台层上爆炸.最有意义的错误似乎是说

'Unable to process the type '.....[Order].....', because it has no known mapping to the value layer.

> StackTrace: at
> System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.NewArrayInitTranslator.TypedTranslate(ExpressionConverter
> parent, NewArrayExpression linq)

无论如何...我感谢您对此的想法和考虑,即使事实证明这是一个完全不适的工作.

推荐答案

是可能的,但是您需要更深入地研究表达树.

首先,您需要的是这样的动态表达:

(match1 ? 1 : 0) + (match2 ? 1 : 0) + ... + (matchN ? 1 : 0)

然后,您需要将其插入选择器表达式中.您可以通过使用Expression类方法手动创建选择器,或者使用我称之为原型表达式的内容来以一种艰难的方式来做到这一点.它们是编译时间lambda表达式,其其他参数用作占位符,后来被其他表达式代替.

在任何一种情况下,您都需要使用其他表达式替换参数表达式的助手方法(例如,确保所有ExpressionMatchList项目都使用一个和相同的参数实例,这在EF6中很重要).将其视为等效string.Replace的表达式.有很多有关如何做到这一点的示例(全部基于ExpressionVisitor),这是我使用的一个:

public static class ExpressionExtensions
{
    public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
    {
        return new ParameterReplacer { Source = source, Target = target }.Visit(expression);
    }

    class ParameterReplacer : ExpressionVisitor
    {
        public ParameterExpression Source;
        public Expression Target;
        protected override Expression VisitParameter(ParameterExpression node) =>
            node == Source ? Target : base.VisitParameter(node);
    }
}

现在让实施上述概念.

首先,原型表达式:

Expression<Func<Order, int, OrderInfo>> selectorPrototype = (o, matchCount) => new OrderInfo
{
    // ...
    SearchValue = matchCount,
};
var selectorBody = selectorPrototype.Body;
var selectorParameter = selectorPrototype.Parameters[0];
var matchCountParameter = selectorPrototype.Parameters[1];

因此,这是您的原始lambda表达式选择器,带有附加matchCount参数,我们将用ExpressionMatchList动态构建的所需表达式代替.让我们这样做:

var zero = Expression.Constant(0);
var one = Expression.Constant(1);
var matchCountValue = !ExpressionMatchList.Any() ? zero : ExpressionMatchList
    .Select(match => Expression.Condition(
        match.Body.ReplaceParameter(match.Parameters[0], selectorParameter),
        one, zero))
    .Aggregate<Expression>(Expression.Add);

selectorBody = selectorBody.ReplaceParameter(matchCountParameter, matchCountValue);

现在我们准备创建所需的选择器:

var selector = Expression.Lambda<Func<Order, OrderInfo>>(selectorBody, selectorParameter);

并使用它:

var result = context.Orders.Where(expr).Select(selector).ToList();

本文地址:https://www.itbaoku.cn/post/1556818.html

问题描述

Say I am in the select statement of a query:

context.Orders.Where(expr).Select(o => new OrderInfo{ ... }).ToList();

And I have a list of expressions ( a subset of what is expressed in expr ), not all which will be fulfilled ( the idea being they were all the ones added under the OrElse condition ):

List<Expression<Func<Order, bool>>> exprAppliedList;

You could imagine them like this:

o => o.BillingFirstName == xStr,
o => o.BillingLastName == xStr,
o => o.ShippingFirstName == xStr,
o => o.ShippingLastName == xStr,

o => o.BillingFirstName == yStr,
o => o.BillingLastName == yStr,
o => o.ShippingFirstName == yStr,
o => o.ShippingLastName == yStr,

The trick is, before I .ToList() it, I would like to somehow index how many, of the or Expressions I matched on that singular order. If I type in "John" and someone has BillingFirstName = "John" BillingLastName = "John", ShippingFirstName = "John", and ShippingLastName = "John" I could assign him something like SearchValue = 4,

I have a lot of reasons for wanting to do this before .ToList(), one being I simply don't actually need those fields other than by filtering. Another is that I would have to generate a set of different expressions if I wanted to compare it after the fact since they would be of type List<Expression<Func<OrderInfo, bool>>> instead.

I realize that EF Linq won't accept any nonsense, custom functions or even a lot of extension methods. But this is already a list of lambda expressions, which seems to be what Linq eats for breakfast.

I do think I could probably do it after ToList()ing , and perhaps everyone will just say that is what I should do.. But I am also curious if it is possible, because I am a curious guy.. And I think it could be useful in a lot of scenarios.

I tried something wacky like:

context.Orders.Where(expr).Select(o => new OrderInfo
{ 
  ... 
  SearchValue = ExpressionMatchList.Any() 
     ? ExpressionMatchList.Count(e => e.Compile().Invoke(o)) 
     : 0,
}).ToList();

but then Linq exploded at some sub-dungeon layer. The most meaningful error seemed to say something like

'Unable to process the type '.....[Order].....', because it has no known mapping to the value layer.

> StackTrace: at
> System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.NewArrayInitTranslator.TypedTranslate(ExpressionConverter
> parent, NewArrayExpression linq)

Anyway... I appreciate your thoughts and consideration on this, even if it turns out to be a totally whack job idea..

推荐答案

It's possible, but you need to dig more deeper into expression trees.

First, what you need is a dynamic expression like this:

(match1 ? 1 : 0) + (match2 ? 1 : 0) + ... + (matchN ? 1 : 0)

Then you need to plug it into the selector expression. You can do that in a hard way by manually creating the selector using the Expression class methods, or much easier by using what I call prototype expressions. They are compile time lambda expressions with additional parameters which serve as placeholders and later are replaced with other expressions.

In either case you need a helper method for replacing a parameter expression with another expression (for instance to make sure all the ExpressionMatchList items use one and the same parameter instance, which is important in EF6). Think of it as expression equivalent of string.Replace. There are many examples of how to do that (all based on ExpressionVisitor), here is the one I use:

public static class ExpressionExtensions
{
    public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
    {
        return new ParameterReplacer { Source = source, Target = target }.Visit(expression);
    }

    class ParameterReplacer : ExpressionVisitor
    {
        public ParameterExpression Source;
        public Expression Target;
        protected override Expression VisitParameter(ParameterExpression node) =>
            node == Source ? Target : base.VisitParameter(node);
    }
}

Now let implement the aforementioned concept.

First, the prototype expression:

Expression<Func<Order, int, OrderInfo>> selectorPrototype = (o, matchCount) => new OrderInfo
{
    // ...
    SearchValue = matchCount,
};
var selectorBody = selectorPrototype.Body;
var selectorParameter = selectorPrototype.Parameters[0];
var matchCountParameter = selectorPrototype.Parameters[1];

So it's your original lambda expression selector with additional matchCount parameter which we are going to replace with the desired expression, dynamically built from ExpressionMatchList. Let do that:

var zero = Expression.Constant(0);
var one = Expression.Constant(1);
var matchCountValue = !ExpressionMatchList.Any() ? zero : ExpressionMatchList
    .Select(match => Expression.Condition(
        match.Body.ReplaceParameter(match.Parameters[0], selectorParameter),
        one, zero))
    .Aggregate<Expression>(Expression.Add);

selectorBody = selectorBody.ReplaceParameter(matchCountParameter, matchCountValue);

Now we are ready to create the desired selector:

var selector = Expression.Lambda<Func<Order, OrderInfo>>(selectorBody, selectorParameter);

and use it:

var result = context.Orders.Where(expr).Select(selector).ToList();