LINQ-to-SQL:转换 Func到表达式>

发布于 2025-01-07 04:05:35 字数 1332 浏览 5 评论 0 原文

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 需要是一个 Expression>,这就是我陷入困境的地方。

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

LINQ-to-SQL has been a PITA for me. We're using it to communicate to the database, and then send entities via WCF to a Silverlight app. Everything was working fine, until it came time to start editing (CUD) the entities, and their related data.

I was finally able to devise two for loops that allowed the CUD. I tried to refactor them, and I was so close, until I learned that I can't always do Lambda with L2S.

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

Called by:

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

This almost worked. However, my Func needs to be an Expression>, and that's where I'm stuck.

Is there anyone who can tell me if this is possible? We have to be in .NET 3.5 due to SharePoint 2010.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

凯凯我们等你回来 2025-01-14 04:05:35

只需将参数从: 更改

 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. 现在的用法有点奇怪,因为这是一个柯里化的 lambda。而不是传递 (x,y) => ... 你将传递 x =>; y => ...。我不确定这对你来说是否是件大事。

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

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

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

Just change the parameter from:

 Func<T, T, bool> predicate

To:

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

The Expression is generated by the compiler.

Now, the issue is how to use this.

In your case, you need both a Func and an Expression, since you're using it in Enumerable LINQ queries (func based) as well as the SQL LINQ queries (expression based).

In:

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

The old parameter is fixed. So we could change the parameter to:

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

This means we can supply one argument (the 'fixed' one) and get back an expression.

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

We also need to use this in Any. To get a Func from an Expression we can call Compile():

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

You can do the same thing with the next part.

There are two issues:

  1. The performance might be impacted by using Compile() lots. I'm not sure how much effect it would actually have, but I'd profile it to check.
  2. The usage is now a little weird, since this is a curried lambda. Instead of passing (x,y) => ... you will be passing x => y => .... I'm not sure if this is a big deal for you.

There might be a better way to do this :)

Here's an alternate method, which should be a bit faster, since the Expression only has to be compiled once. Create a rewriter that will 'apply' one argument, like this:

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

Then use it like this:

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);
        }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文