2012-02-17 6 views
5

LINQ-to-SQL è stato un PITA per me. Lo stiamo utilizzando per comunicare con il database e quindi inviare entità tramite WCF a un'applicazione Silverlight. Tutto andava bene, fino a quando è arrivato il momento di iniziare a modificare (CUD) le entità e i relativi dati.LINQ-to-SQL: Converti Func <T, T, bool> in un'espressione <Func <T, T, bool>>

Sono finalmente riuscito a escogitare due cicli for che hanno consentito il CUD. Ho provato a rifattorizzarli, ed ero così vicino, fino a quando ho imparato che non posso sempre fare Lambda con 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); 
     } 
    } 
} 

Chiamato da:

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

Questo quasi funzionato. Tuttavia, il mio Func deve essere un'espressione>, ed è lì che sono bloccato.

C'è qualcuno che può dirmi se è possibile? Dobbiamo essere in .NET 3.5 a causa di SharePoint 2010.

risposta

10

Basta cambiare il parametro da:

Func<T, T, bool> predicate 

A:

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

l'espressione viene generato dal compilatore.

Ora, il problema è come usare questo.

Nel tuo caso, è necessario sia un Funce un Expression, dal momento che si sta utilizzando in Enumerable query LINQ (basati func), così come le query SQL LINQ (espressione based).

In:

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

Il parametro old è fissa. Così abbiamo potuto cambiare il parametro:

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

Questo significa che possiamo fornire un argomento (la 'fissa' uno) e tornare un'espressione.

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

Abbiamo anche bisogno di usare questo in Any. Per ottenere un Funz da un'espressione che possiamo chiamare Compile():

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

Puoi fare la stessa cosa con la parte successiva.

Ci sono due questioni:

  1. L'andamento potrebbe essere influenzato utilizzando Compile() lotti. Non sono sicuro di quanto effetto avrebbe effettivamente, ma lo definirei per verificare.
  2. L'utilizzo è ora un po 'strano, poiché questo è un lambda al curry. Invece di passare (x,y) => ... passerai davanti allo x => y => .... Non sono sicuro se questo è un grosso problema per te.

Ci potrebbe essere un modo migliore per fare questo :)

Ecco un metodo alternativo, che dovrebbe essere un po 'più veloce, poiché l'espressione deve solo essere compilato una sola volta. Creare un masterizzatore in grado di 'applicare' un argomento, come questo:

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

quindi utilizzarlo in questo modo:

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

Porges, apprezzo la pronta risposta. Tuttavia, quando lo fai e cambia la clausola .Where in ctx.GetTable (). Dove (predicato), non riesco a compilare. Ho bisogno di passare le due variabili (che devono avere le loro proprietà confrontate) con l'espressione/Func. – DaleyKD

+0

Ah, capisco, il tuo problema attuale è all'interno del metodo. Mi sono perso - aggiornerò la risposta. – porges

+0

Holy smokes! Questo ha funzionato completamente. Forse lo rivisiteremo per le prestazioni nella versione 1.1. ;) Grazie! – DaleyKD