涉及复杂对象的实体框架的LINQ表达式构建



我正在使用实体框架版本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.Field1A.Field5的值)进行比较,而不是传递已经具有的参数predicates他们组装成一系列表达式。但是我宁愿不想,因为那将需要我的方法知道它正在与AtblA类型一起使用,这与通用和通用用途相反。(这也很复杂且混乱。)

我希望我展示的例子解释了我想做什么。可以以通用的方式完成吗?

您需要用单个参数替换谓词物体中的参数。这样的事情应该有效:

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