如何避免在 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 以及参数替换的工作原理时很有用。请注意它如何使用 Func
将 CustomerQuery
映射到可以应用于 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;
}
请问如何避免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 以及参数替换的工作原理时很有用。请注意它如何使用 Func
将 CustomerQuery
映射到可以应用于 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;
}