【问题标题】:LINQ-to-SQL : Convert Func<T, T, bool> to an Expression<Func<T, T, bool>>LINQ-to-SQL:将 Func<T, T, bool> 转换为 Expression<Func<T, T, bool>>
【发布时间】:2012-02-17 00:38:15
【问题描述】:

LINQ-to-SQL 对我来说是一个 PITA。我们使用它与数据库进行通信,然后通过 WCF 将实体发送到 Silverlight 应用程序。一切正常,直到开始编辑 (CUD) 实体及其相关数据。

我终于能够设计出两个允许 CUD 的 for 循环。我试图重构它们,我是如此接近,直到我了解到我不能总是用 L2S 来做 Lambda。

public static void CudOperation<T>(this DataContext ctx, IEnumerable<T> oldCollection, IEnumerable<T> newCollection, Func<T, T, bool> predicate)
    where T : class
{
    foreach (var old in oldCollection)
    {
        if (!newCollection.Any(o => predicate(old, o)))
        {
            ctx.GetTable<T>().DeleteAllOnSubmit(ctx.GetTable<T>().Where(o => predicate(old, o)));
        }
    }

    foreach (var newItem in newCollection)
    {
        var existingItem = oldCollection.SingleOrDefault(o => predicate(o, newItem));
        if (existingItem != null)
        {
            ctx.GetTable<T>().Attach(newItem, existingItem);
        }
        else
        {
            ctx.GetTable<T>().InsertOnSubmit(newItem);
        }
    }
}

调用者:

ctx.CudOperation<MyEntity>(myVar.MyEntities, newHeader.MyEntities,
    (x, y) => x.PkID == y.PkID && x.Fk1ID == y.Fk1ID && x.Fk2ID == y.FK2ID);

这几乎奏效了。但是,我的 Func 需要是一个表达式>,这就是我遇到的问题。

有没有人可以告诉我这是否可能?由于 SharePoint 2010,我们必须使用 .NET 3.5。

【问题讨论】:

    标签: c# linq-to-sql lambda expression-trees


    【解决方案1】:

    只需更改参数:

     Func<T, T, bool> predicate
    

    收件人:

     Expression<Func<T, T, bool>> predicate
    

    表达式由编译器生成。

    现在,问题是如何使用它。

    在您的情况下,您需要 Func Expression,因为您在 Enumerable LINQ 查询(基于函数)以及 SQL LINQ 查询中使用它(基于表达式)。

    在:

    .Where(o => predicate(old, o))
    

    old 参数已修复。所以我们可以把参数改成:

    Func<T, Expression<Func<T, bool>>> predicate
    

    这意味着我们可以提供一个参数(“固定”参数)并返回一个表达式。

    foreach (var old in oldCollection)
    {
        var condition = predicate(old);
        // ...
        {
            ctx.GetTable<T>().DeleteAllOnSubmit(ctx.GetTable<T>().Where(condition));
        }
    }
    

    我们还需要在Any 中使用它。要从表达式中获取 Func,我们可以调用 Compile():

    foreach (var old in oldCollection)
    {
        var condition = predicate(old);
        if (!newCollection.Any(condition.Compile()))
        {
            ctx.GetTable<T>().DeleteAllOnSubmit(ctx.GetTable<T>().Where(condition));
        }
    }
    

    你可以对下一部分做同样的事情。

    有两个问题:

    1. 使用Compile() 批次可能会影响性能。我不确定它实际上会产生多大的影响,但我会对其进行分析以进行检查。
    2. 现在的用法有点奇怪,因为这是一个 curried lambda。而不是通过(x,y) =&gt; ...,您将通过x =&gt; y =&gt; ...。我不确定这对你来说是不是很重要。

    可能有更好的方法来做到这一点:)

    这是另一种方法,它应该会更快一些,因为表达式只需要编译一次。创建一个将“应用”一个参数的重写器,如下所示:

    class PartialApplier : ExpressionVisitor
    {
        private readonly ConstantExpression value;
        private readonly ParameterExpression replace;
    
        private PartialApplier(ParameterExpression replace, object value)
        {
            this.replace = replace;
            this.value = Expression.Constant(value, value.GetType());
        }
    
        public override Expression Visit(Expression node)
        {
            var parameter = node as ParameterExpression;
            if (parameter != null && parameter.Equals(replace))
            {
                return value;
            }
            else return base.Visit(node);
        }
    
        public static Expression<Func<T2,TResult>> PartialApply<T,T2,TResult>(Expression<Func<T,T2,TResult>> expression, T value)
        {
            var result = new PartialApplier(expression.Parameters.First(), value).Visit(expression.Body);
    
            return (Expression<Func<T2,TResult>>)Expression.Lambda(result, expression.Parameters.Skip(1));
        }
    }
    

    然后像这样使用它:

    public static void CudOperation<T>(this DataContext ctx,
        IEnumerable<T> oldCollection,
        IEnumerable<T> newCollection,
        Expression<Func<T, T, bool>> predicate)
        where T : class
    {
    
        var compiled = predicate.Compile();
    
        foreach (var old in oldCollection)
        {
            if (!newCollection.Any(o => compiled(o, old)))
            {
                var applied = PartialApplier.PartialApply(predicate, old);
                ctx.GetTable<T>().DeleteAllOnSubmit(ctx.GetTable<T>().Where(applied));
            }
        }
    
        foreach (var newItem in newCollection)
        {
            var existingItem = oldCollection.SingleOrDefault(o => compiled(o, newItem));
            if (existingItem != null)
            {
                ctx.GetTable<T>().Attach(newItem, existingItem);
            }
            else
            {
                ctx.GetTable<T>().InsertOnSubmit(newItem);
            }
        }
    }
    

    【讨论】:

    • Porges,感谢您的及时回复。但是,当您这样做并将我的 .Where 子句更改为 ctx.GetTable().Where(predicate) 时,我无法编译。我需要将两个变量(需要比较它们的属性)传递给 Expression/Func。
    • 啊,我明白了,您的实际问题出在方法之内。我错过了 - 我会更新答案。
    • 天哪!那完全奏效了。也许我们会在 1.1 版中重新审视这一点以提高性能。 ;) 谢谢!
    • 只是为了好玩,我添加了另一个版本,它只会编译一次表达式,基于用“固定”值重写表达式。
    • 波吉斯,谢谢!但是,我无法对此进行测试,因为 ExpressionVisitor 仅适用于 .NET 4,而且我坚持使用 SharePoint 2010 (.NET 3.5)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-05
    • 1970-01-01
    • 1970-01-01
    • 2011-10-31
    • 1970-01-01
    相关资源
    最近更新 更多