2012-02-17 2 views
5

LINQ-to-SQL a été un PITA pour moi. Nous l'utilisons pour communiquer avec la base de données, puis envoyer des entités via WCF à une application Silverlight. Tout fonctionnait bien, jusqu'à ce qu'il soit temps de commencer à éditer (CUD) les entités, et leurs données connexes.LINQ-to-SQL: Convertir Func <T, T, bool> en une expression <Func <T, T, bool>>

J'ai finalement réussi à concevoir deux boucles pour le CUD. J'ai essayé de les refactoriser, et j'étais si proche, jusqu'à ce que j'apprenne que je ne peux pas toujours faire Lambda avec 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); 
     } 
    } 
} 

Appelé par:

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

Cela a presque fonctionné. Cependant, mon Func doit être une Expression>, et c'est là que je suis coincé.

Y a-t-il quelqu'un qui peut me dire si c'est possible? Nous devons être dans .NET 3.5 en raison de SharePoint 2010.

Répondre

10

il suffit de changer le paramètre de:

Func<T, T, bool> predicate 

Pour:

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

L'expression est généré par le compilateur.

Maintenant, le problème est de savoir comment l'utiliser.

Dans votre cas, vous avez besoin à la fois un Funcet un Expression, puisque vous utilisez dans Enumerable requêtes LINQ (Func base), ainsi que les requêtes LINQ SQL (base d'expression).

In:

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

Le paramètre old est fixé. On pourrait donc changer le paramètre à:

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

Cela signifie que nous pouvons fournir un argument (la « fixe » un) et retourner une expression. Nous avons également besoin d'utiliser ceci dans Any. Pour obtenir un Func d'une expression que nous pouvons appeler Compile():

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

Vous pouvez faire la même chose avec la partie suivante.

Il y a deux questions:

  1. La performance pourrait être impacté par l'utilisation Compile() lots. Je ne suis pas sûr de l'effet que cela aurait réellement, mais je le profilerais pour vérifier.
  2. L'utilisation est maintenant un peu étrange, car il s'agit d'un lambda au curry. Au lieu de passer (x,y) => ... vous passerez x => y => .... Je ne suis pas sûr que ce soit un gros problème pour vous.

Il pourrait y avoir une meilleure façon de le faire :)

Voici une autre méthode, ce qui devrait être un peu plus rapide, puisque l'expression ne doit être compilé une fois. Créer un rewriter qui 'appliquer' un argument, comme ceci:

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

ensuite l'utiliser comme ceci:

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, je vous remercie de la réponse rapide. Cependant, lorsque vous faites cela, et modifier ma clause .Where à ctx.GetTable () .Where (prédicat), je ne peux pas compiler. J'ai besoin de passer les deux variables (dont les propriétés doivent être comparées) à l'Expression/Func. – DaleyKD

+0

Ah, je vois, votre problème réel est dans la méthode. J'ai raté ça - je vais mettre à jour la réponse. – porges

+0

Saint fume! Cela a totalement fonctionné. Nous reviendrons peut-être sur ce point pour les performances dans la version 1.1. ;) Je vous remercie! – DaleyKD

Questions connexes