我正在使用实体框架版本4。我需要将一个大的(〜100万个记录)SQL Server表与长(〜2000)从Web服务返回的复杂对象的长度(〜2000)数组。需要比较五个不同的属性,以确定复杂对象的实例是否已经在数据库中。
i创建了一个函数,该函数返回一个表达式,以便在任何方法和任何方法中使用。看起来像这样(其中a是复杂的对象,而tbla是EF类):
function Expression<tblA, bool> GetSearchPredicate(A a)
{
return ta => ta.Field1.Equals(a.Field1)
&& ta.Field2.Equals(a.Field2)
&& ta.Field3.Equals(a.Field3)
&& ta.Field4.Equals(a.Field4)
&& ta.Field5.Equals(a.Field5);
}
这有效。我可以通过这样做比较所有2000个实例:
IEnumerable<A> objects = [web service call];
var result = objects.Select(a => !db.tblA.Any(GetSearchPredicate(a)));
也有效。但这很慢。因此,我研究了一个可以构建可以通过EF直接传输到数据库的表达式的实用方法。
我在这个问题中使用了代码作为构建该实用程序方法的基础。该问题中的示例显示了将单个属性与一系列常数进行比较,而我的版本必须将多个属性与多个常数进行比较。我最好的努力是以下:
public static IQueryable<TEntity> WhereIn<TEntity>
(
this ObjectQuery<TEntity> query,
IEnumerable<Expression<Func<TEntity, bool>>> predicates
)
{
if (predicates == null) throw new ArgumentNullException("predicates");
IEnumerable<ParameterExpression> p = predicates.Select(pred => pred.Parameters.Single()).ToArray();
IEnumerable<Expression> equals = predicates.Select(value =>
(Expression)value.Body);
Expression bigEqual = equals.Aggregate((accumulate, equal) =>
Expression.Or(accumulate, equal));
var result1 = Expression.Lambda<Func<TEntity, bool>>(bigEqual, p.First());
var result = query.Where(result1);
return result;
}
这将被这样调用:
IEnumerable<A> objects = [web service call];
var result = db.tblA.WhereIn(objects.Select(a => GetSearchPredicate(a)));
我得到的是一条消息,说" ta"(tentity对象的占位符)没有约束。我以为这是因为我组合了多个表达式(变量predicates
),也许是因为我只是从 predicates
iEnumerable中的第一个传递了参数,因此也可能被抛出。但是,即使predicates
是一个表达式,也会发生这种情况。
我可以根据我链接到的方法合理地确定,我可以构建一个表达式,将五个属性中的每个属性与常数(A.Field1
至A.Field5
的值)进行比较,而不是传递已经具有的参数predicates
他们组装成一系列表达式。但是我宁愿不想,因为那将需要我的方法知道它正在与A
和tblA
类型一起使用,这与通用和通用用途相反。(这也很复杂且混乱。)
我希望我展示的例子解释了我想做什么。可以以通用的方式完成吗?
您需要用单个参数替换谓词物体中的参数。这样的事情应该有效:
public static Expression<Func<T, bool>> BuildOr<T>(
IEnumerable<Expression<Func<T, bool>>> predicates)
{
Expression body = null;
ParameterExpression p = null;
Expression<Func<T, bool>> first = null;
foreach (Expression<Func<T, bool>> item in predicates)
{
if (first == null)
{
first = item;
}
else
{
if (body == null)
{
body = first.Body;
p = first.Parameters[0];
}
var toReplace = item.Parameters[0];
var itemBody = ReplacementVisitor.Transform(item, toReplace, p);
body = Expression.OrElse(body, itemBody);
}
}
if (first == null)
{
throw new ArgumentException("Sequence contains no elements.", "predicates");
}
return (body == null) ? first : Expression.Lambda<Func<T, bool>>(body, p);
}
private sealed class ReplacementVisitor : ExpressionVisitor
{
private IList<ParameterExpression> SourceParameters { get; set; }
private Expression ToFind { get; set; }
private Expression ReplaceWith { get; set; }
public static Expression Transform(
LambdaExpression source,
Expression toFind,
Expression replaceWith)
{
var visitor = new ReplacementVisitor
{
SourceParameters = source.Parameters,
ToFind = toFind,
ReplaceWith = replaceWith,
};
return visitor.Visit(source.Body);
}
private Expression ReplaceNode(Expression node)
{
return (node == ToFind) ? ReplaceWith : node;
}
protected override Expression VisitConstant(ConstantExpression node)
{
return ReplaceNode(node);
}
protected override Expression VisitBinary(BinaryExpression node)
{
var result = ReplaceNode(node);
if (result == node) result = base.VisitBinary(node);
return result;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (SourceParameters.Contains(node)) return ReplaceNode(node);
return SourceParameters.FirstOrDefault(p => p.Name == node.Name) ?? node;
}
}
您的WhereIn
方法然后变为:
public static IQueryable<TEntity> WhereIn<TEntity>(
this ObjectQuery<TEntity> query,
IEnumerable<Expression<Func<TEntity, bool>>> predicates)
{
if (predicates == null) throw new ArgumentNullException("predicates");
var predicate = BuildOr(predicates);
return query.Where(predicate);
}