如何避免在 LINQ 表达式中进行动态调用

How to avoid dynamic invoke in LINQ expression

请问如何避免LINQ表达式中Dynamic Invoke连接出错

存在错误:

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

代码示例 (LINQPad):

已编辑:

void Main()
{
var entity = new[] 
{     
    new Customers    {  CustomerID = "ALFKI", CompanyName = "Alfreds Futterkiste", ContactName = "Maria Anders"},
    new Customers    {  CustomerID = "ANATR", CompanyName = "Ana Trujillo Emparedados y helados", ContactName = "Ana Trujillo"},
    new Customers    {  CustomerID = "2", CompanyName = "Louis Vuiton2", ContactName = "Dom2"}};

    var result = Exists(entity, x => x.CustomerID + " " + x.CompanyName); 
}


IDictionary<string, bool> Exists(IEnumerable<Customers> data, Func<Customers, object> predicate)
{  
   Expression<Func<Customers, object>> expression = x => predicate(x);
   var ids = data.AsQueryable().Select(x=>predicate(x));        
   var existing = Customers.Where(x => ids.Contains(expression)).ToList(); //Error line.

   //I do not want to materialize query after Customers for example Customers.ToList()[...]

   data.ToList().ForEach(x => existing.Any(y=> predicate(y).ToString() == predicate(x).ToString()));
   var dictionary = data.ToDictionary(x => x.CustomerID.ToString(), x => existing.Any(y => predicate(y).ToString() == predicate(x).ToString()));               

   return dictionary; 
}   

编辑: var existing 应该 return:

enter image description here

但 return 无效。

以下是如何避免对字符串进行任何令人讨厌的转换。您真正想要的是获取数组并将其转换为可应用于 Customer 对象的表达式,然后使用 Expression.OrElse 聚合这些表达式以创建一个可应用于数据库的查询表达式。

这并不简单,但这是如何做到的。

你会像这样在最后调用方法:

var result = Exists(Customers.AsQueryable(), 
    entity, 
    (q) => c => (q.CustomerID == c.CustomerID && q.CompanyName == c.CompanyName)); 

与您比较的 'stringfying' 所有内容相比,这有几个优点。一方面,数据库可以使用索引优化查询。另一方面,如果您愿意,您可以传递更复杂的表达式来进行简单的字符串比较,例如c.CustomerID > q.CustomerID.

我将 CustomerQuery class 与 CustomerClass 分开,因为它们不同(并修复了复数形式)。

完成这项工作的实际方法非常简单。之前的所有方法都是用不同的参数重写 Expressions 来创建你想要创建的 OrElse 表达式。这些方法通常在您想要操作表达式和理解基础 ExpressionVisitor class 以及参数替换的工作原理时很有用。请注意它如何使用 FuncCustomerQuery 映射到可以应用于 Customer 数据库的 Expression

/// <summary>
/// An ExpressionVisitor for parameter substitution
/// </summary>
internal class ExpressionParameterSubstitute : ExpressionVisitor
{
    private readonly ParameterExpression from;
    private readonly Expression to;

    /// <summary>
    /// Creates a new instance of the <see cref="ExpressionParameterSubstitute"/> visitor
    /// </summary>
    public ExpressionParameterSubstitute(ParameterExpression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }

    /// <summary>
    /// Visit a Lambda Expression
    /// </summary>
    protected override Expression VisitLambda<T>(Expression<T> node)
    {
        if (node.Parameters.All(p => p != this.from))
            return node;

        // We need to replace the `from` parameter, but in its place we need the `to` parameter(s)
        // e.g. F<DateTime,Bool> subst F<Source,DateTime> => F<Source,bool>
        // e.g. F<DateTime,Bool> subst F<Source1,Source2,DateTime> => F<Source1,Source2,bool>

        if (to is LambdaExpression toLambda)
        {
            var substituteParameters = toLambda?.Parameters ?? Enumerable.Empty<ParameterExpression>();

            ReadOnlyCollection<ParameterExpression> substitutedParameters
                = new ReadOnlyCollection<ParameterExpression>(node.Parameters
                    .SelectMany(p => p == this.from ? substituteParameters : Enumerable.Repeat(p, 1))
                    .ToList());

            var updatedBody = this.Visit(node.Body); // which will convert parameters to 'to'
            return Expression.Lambda(updatedBody, substitutedParameters);
        }
        else
        {
            // to is not a lambda expression so simple substitution can work
            ReadOnlyCollection<ParameterExpression> substitutedParameters
                = new ReadOnlyCollection<ParameterExpression>(node.Parameters
                    .Where(p => p != this.from)
                    .ToList());

            var updatedBody = this.Visit(node.Body); // which will convert parameters to 'to'

            if (substitutedParameters.Any())
                return Expression.Lambda(updatedBody, substitutedParameters);
            else
                return updatedBody;
        }
    }

    /// <summary>
    /// Visit a ParameterExpression
    /// </summary>
    protected override Expression VisitParameter(ParameterExpression node)
    {
        var toLambda = to as LambdaExpression;
        if (node == from) return toLambda?.Body ?? to;
        return base.VisitParameter(node);
    }
}

 public static Expression<Func<T, bool>> OrElse<T>(
    Expression<Func<T, bool>> expr1,
    Expression<Func<T, bool>> expr2)
{
    var parameter = Expression.Parameter(typeof (T));

    var leftVisitor = new ExpressionParameterSubstitute(expr1.Parameters[0], parameter);
    var left = leftVisitor.Visit(expr1.Body);

    var rightVisitor = new ExpressionParameterSubstitute(expr2.Parameters[0], parameter);
    var right = rightVisitor.Visit(expr2.Body);

    return Expression.Lambda<Func<T, bool>>(
        Expression.OrElse(left, right), parameter);
}

public static IDictionary<string, bool> Exists(IQueryable<Customer> customers, IEnumerable<CustomerQuery> data, Func<CustomerQuery, Expression<Func<Customer, bool>>> predicate)
{  
   Expression<Func<Customer, bool>> expression = x => false;
   foreach (var item in data)
   {
       var exprForOne = predicate.Invoke(item);
       expression = OrElse(expression, exprForOne);
   }

   var split = customers.GroupBy(expression).SelectMany(g => g.Select(c => new {c, g.Key})).ToDictionary(x => x.c.CustomerID, x => x.Key);
   return split;
}