LINQ-zu-SQL: Konvertieren von Func zu einem Ausdruck >

c# expression-trees lambda linq-to-sql

Frage

LINQ-to-SQL war eine PITA für mich. Wir verwenden es, um mit der Datenbank zu kommunizieren und dann Entitäten über WCF an eine Silverlight-App zu senden. Alles funktionierte einwandfrei, bis es an der Zeit war, mit der Bearbeitung (CUD) der Entitäten und ihrer zugehörigen Daten zu beginnen.

Ich konnte schließlich zwei For-Schleifen entwickeln, die die CUD erlaubten. Ich habe versucht, sie umzuformulieren, und ich war so nah dran, bis ich erfuhr, dass ich mit L2S nicht immer Lambda machen kann.

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

Angerufen von:

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

Das hat fast funktioniert. Allerdings muss mein Func ein Ausdruck> sein, und da stecke ich fest.

Kann mir jemand sagen, ob das möglich ist? Wir müssen aufgrund von SharePoint 2010 in .NET 3.5 sein.

Akzeptierte Antwort

Ändern Sie einfach den Parameter von:

 Func<T, T, bool> predicate

Zu:

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

Der Ausdruck wird vom Compiler generiert.

Nun, das Problem ist, wie man das benutzt.

In Ihrem Fall benötigen Sie sowohl einen Func als auch einen Expression , da Sie ihn in Enumerable LINQ-Abfragen (func-basiert) sowie in SQL LINQ-Abfragen (ausdrucksbasiert) verwenden.

Im:

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

Der old Parameter ist festgelegt. Also könnten wir den Parameter ändern zu:

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

Dies bedeutet, dass wir ein Argument (das "feste") liefern und einen Ausdruck zurückbekommen können.

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

Wir müssen dies auch in Any . Um einen Func von einem Ausdruck zu erhalten, können wir Compile() aufrufen:

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

Sie können das Gleiche mit dem nächsten Teil tun.

Es gibt zwei Probleme:

  1. Die Leistung könnte durch die Verwendung von Compile() Losen beeinflusst werden. Ich bin nicht sicher, wie viel Wirkung es tatsächlich hätte, aber ich würde es profilieren, um zu überprüfen.
  2. Die Verwendung ist jetzt ein bisschen komisch, da dies ein Curry-Lambda ist. Anstatt (x,y) => ... wirst du x => y => ... . Ich bin mir nicht sicher, ob das für dich eine große Sache ist.

Es könnte einen besseren Weg geben, dies zu tun :)

Hier ist eine alternative Methode , die etwas schneller sein sollte, da der Ausdruck nur einmal kompiliert werden muss. Erstellen Sie einen Rewriter, der ein Argument wie folgt "anwendet":

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

Dann benutze es so:

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


Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow