2012-02-17 10 views
5

LINQ-to-SQL ha sido un PITA para mí. Lo estamos usando para comunicarnos a la base de datos, y luego enviar entidades a través de WCF a una aplicación de Silverlight. Todo funcionaba bien, hasta que llegó el momento de comenzar a editar (CUD) las entidades y sus datos relacionados.LINQ-to-SQL: Convertir Func <T, T, bool> en una expresión <Func <T, T, bool>>

Finalmente pude diseñar dos bucles para permitir el CUD. Traté de refactorizarlos, y estaba tan cerca, hasta que supe que no siempre puedo hacer 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); 
     } 
    } 
} 

Llamado por:

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

Esta casi funcionó. Sin embargo, mi Func necesita ser una Expresión>, y ahí es donde estoy atascado.

¿Hay alguien que pueda decirme si esto es posible? Tenemos que estar en .NET 3.5, debido a SharePoint 2010.

Respuesta

10

Sólo cambia el parámetro de:

Func<T, T, bool> predicate 

Para:

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

La expresión es generado por el compilador.

Ahora, el problema es cómo usar esto.

En su caso, se necesita tanto un Func y un Expression, ya que se está usando en Enumerable consultas LINQ (basados ​​func), así como las consultas LINQ SQL (expresión basados).

En:

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

El parámetro old es fijo. Así que podríamos cambiar el parámetro a:

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

Esto significa que podemos suministrar un argumento (el 'fijo' uno) y volver a una expresión.

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

también tenemos que utilizar esto en Any. Para obtener una funcionalidad desde una expresión que podemos llamar Compile():

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

Usted puede hacer lo mismo con la siguiente parte.

Hay dos cuestiones:

  1. El rendimiento puede verse afectado por el uso de Compile() lotes. No estoy seguro de cuánto efecto tendría realmente, pero lo perfilaría para verificarlo.
  2. El uso ahora es un poco raro, ya que esta es una lambda al curry. En lugar de pasar (x,y) => ... pasará x => y => .... No estoy seguro de si esto es un gran problema para ti.

Puede haber una mejor manera de hacer esto :)

He aquí un método alternativo, lo que debería ser un poco más rápido, ya que la expresión sólo tiene que ser compilado una vez. Crear una regrabadora de que 'aplicar' un argumento, como esto:

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

luego usarlo como esto:

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, agradezco la pronta respuesta. Sin embargo, cuando haces eso, y cambias mi cláusula .Where a ctx.GetTable () .Where (predicado), no puedo compilar. Necesito pasar las dos variables (que deben comparar sus propiedades) a Expression/Func. – DaleyKD

+0

Ah, ya veo, su problema real está dentro del método. Me perdí eso, actualizaré la respuesta. – porges

+0

Holy smokes! Eso funcionó por completo. Tal vez volvamos a visitar esto para el rendimiento en la versión 1.1. ;) ¡Gracias! – DaleyKD

Cuestiones relacionadas