3

我正在使用 Entity Framework 版本 4。我需要将一个大型(约 100 万条记录)SQL Server 表与从 Web 服务返回的较长(约 2000 个)复杂对象数组进行比较。需要比较五个不同的属性来确定复杂对象的实例是否已经在数据库中。

我创建了一个函数,它返回用于 .Where 和 .Any 方法的表达式。它看起来像这样(其中 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);
}

这行得通。我可以通过这样做来比较 A 的所有 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组合了多个表达式(变量),并且可能抛出了这条消息,因为我只是从第一个predicatesIEnumerable 传递参数。但即使predicates是一个表达式,也会发生这种情况。

我有理由确定,基于我链接到的方法,我可以构建一个表达式,将五个属性中的每一个与一个常量(A.Field1through的值A.Field5)进行比较,而不是传入predicates已经将它们组装成一系列的参数表达式。但我宁愿不这样做,因为这需要我的方法知道它正在使用类型AtblA,这与通用和通用目的相反。(它也很复杂和混乱。)

我希望我所展示的示例能够解释我想要做什么。它可以以通用方式完成吗?

4

1 回答 1

3

您需要将谓词正文中的参数替换为单个参数。像这样的东西应该工作:

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);
}
于 2012-12-19T14:56:13.863 回答