5

Sto cercando di rendere un po 'più leggibile il codice e non sto affatto imparando come strutturare il metodo di estensione e/o l'espressione per farlo. Al momento abbiamo molte entità che hanno un RecordStatusTypeId su di loro (implementato da un'interfaccia di IRecordStatus)LINQ to Entities non riconosce il metodo (su un'entità correlata)

public interface IRecordStatus 
{ 
    int RecordStatusTypeId { get; set; } 
    RecordStatusType RecordStatusType { get; set; } 
} 

L'obiettivo qui è quello di sostituire una dichiarazione come .Where(RecordStatusTypeId != (int)RecordStatusTypes.Deleted) con un metodo di estensione come .ActiveRecords()

sono in grado per ottenere questo risultato con il seguente metodo di estensione:

public static IQueryable<T> ActiveRecords<T>(this DbSet<T> entitySet) 
    where T : class, IRecordStatus 
{ 
    return entitySet.Where(e => e.RecordStatusTypeId != (int)RecordStatusTypes.Deleted); 
} 

* ho questo metodo di estensione per DbSet<T>, IQueryable<T>, ICollection<T>, e IEnumerable<T>

Questa grande opera per dichiarazioni come MyDbContext.Entities.Where(e => e.RecordStatusTypeId != (int)RecordStatusTypes.Deleted), ma ottengo l'errore "LINQ to Entities non riconosce il metodo del" se provo a sostituire qualcosa come:

MyDbContext.Entities.Where(e => e.RelatedEntity.Where(re => re.RecordStatusTypeId != (int)RecordStatusTypes.Deleted)); 

con quello che ho' d piace fare:

MyDbContext.Entities.Where(e => e.RelatedEntity.ActiveRecords().Any()); 

come posso cambiare i miei metodi di estensione (o aggiungere un Expression) in modo che possa filtrare per i record attivi sul DbSet e su un'entità correlata all'interno di una clausola di LINQ?

+0

Entity Framework Plus può fare ciò: https://github.com/zzzprojects/EntityFramework-Plus –

+0

@CyrilIselin, grazie, controllerò. –

+1

Non è stato possibile convertire il metodo in T-SQL, ma Linq in entità non è stato in grado di riconoscerlo. Aggiungi 'AsEnumerable' alle tue' Entities' e riprova: 'MyDbContext.Entities.AsEnumerable(). Where ...'. –

risposta

2

Sembra che tu abbia sbagliato la conversione. dovrebbe:

MyDbContext.Entities.Where(e => e.RelatedEntity.Where(re => re.RecordStatusTypeId != (int)RecordStatusTypes.Deleted)); 

convertito a questo:

MyDbContext.Entities.Where(e => e.RelatedEntity.ActiveRecords().Any()); 
+0

Sì, hai ragione. Sfortunatamente, è stato solo un errore da parte mia nel tentativo di semplificare il post. Ho risolto la domanda. –

1

hai trovato l'un'eccezione perché Queryable.Where aspetta un'espressione che può essere tradotto a SQL, e ActiveRecords non può essere tradotto a SQL.

È necessario aggiornare l'espressione per espandere la chiamata a ActiveRecords a una chiamata a .Where(e => e.RecordStatusTypeId != (int)RecordStatusTypes.Deleted).

Ho intenzione di fornire una bozza per una soluzione che funziona. È molto specifico dell'esempio che hai fornito. Probabilmente dovresti lavorarci per renderlo generico.

Il seguente visitatore espressione sarà essenzialmente modificare la chiamata al ActiveRecords alla chiamata al Where con l'espressione appropriata come argomento:

public class MyVisitor : ExpressionVisitor 
{ 
    protected override Expression VisitMethodCall(MethodCallExpression m) 
    { 
     if (m.Method.Name == "ActiveRecords") 
     { 
      var entityType = m.Method.GetGenericArguments()[0]; 
      var whereMethod = genericWhereMethod.MakeGenericMethod(entityType); 

      var param = Expression.Parameter(entityType); 
      var expressionToPassToWhere = 
       Expression.NotEqual(
        Expression.Property(param, "RecordStatusTypeId"), 
        Expression.Constant((int)RecordStatusTypes.Deleted)); 

      Expression newExpression = 
       Expression.Call(
        whereMethod, 
        m.Arguments[0], 
        Expression.Lambda(
         typeof(Func<,>).MakeGenericType(entityType, typeof(bool)), 
         expressionToPassToWhere, 
         param)); 

      return newExpression; 
     } 

     return base.VisitMethodCall(m); 
    } 

    //This is reference to the open version of `Enumerable.Where` 
    private static MethodInfo genericWhereMethod; 

    static MyVisitor() 
    { 
     genericWhereMethod = typeof (Enumerable).GetMethods(BindingFlags.Public | BindingFlags.Static) 
      .Where(x => x.Name == "Where" && x.GetGenericArguments().Length == 1) 
      .Select(x => new {Method = x, Parameters = x.GetParameters()}) 
      .Where(x => x.Parameters.Length == 2 && 
         x.Parameters[0].ParameterType.IsGenericType && 
         x.Parameters[0].ParameterType.GetGenericTypeDefinition() == typeof (IEnumerable<>) && 
         x.Parameters[1].ParameterType.IsGenericType && 
         x.Parameters[1].ParameterType.GetGenericTypeDefinition() == typeof (Func<,>)) 
      .Select(x => x.Method) 
      .Single(); 
    } 
} 

Si potrebbe quindi creare uno speciale metodo di WhereSpecial per visitare l'espressione prima di passarlo al reale Where metodo Queryable:

public static class ExtentionMethods 
{ 
    public static IQueryable<T> WhereSpecial<T>(this IQueryable<T> queryable, Expression<Func<T,bool>> expression) 
    { 
     MyVisitor visitor = new MyVisitor(); 

     var newBody = visitor.Visit(expression.Body); 

     expression = expression.Update(newBody, expression.Parameters); 

     return queryable.Where(expression); 
    } 
} 

E poi si può usare in questo modo:

var result = MyDbContext.Entities.WhereSpecial(e => e.RelatedEntity.ActiveRecords().Any()); 
+0

Sembra che tutto questo vanifichi l'intero scopo dell'uso del metodo helper ActiveRecords :) – Evk

+0

@Evk, probabilmente è vero :) Forse un visitatore più generico che accetta un dizionario di nomi di metodi e espressioni lambda corrispondenti lo renderà vale la pena fare tutto questo –

+0

@YacoubMassad, non ho avuto il tempo di provare ed esplorare se qualcosa del genere sarebbe valsa la pena, ma grazie per la risposta. Quando avrò tempo di rivedere, tornerò ad aggiornare. –

0

Prova a rendere la parte principale della query IQueryable, quindi tag sulla clausola where ...

Qualcosa di simile:

IQueryable temp = from x in MyDbContext.Entities 
        select x; 

temp = temp.Where(e => e.RelatedEntity.ActiveRecords().Any()); 

o valutare la parte principale e quindi eseguire il dove in memoria.

+0

Grazie per il suggerimento, ma ho questa interfaccia applicata a un diverso insieme di oggetti entità, quindi non sono in grado di richiamare il contenuto della query senza prima filtrare per ActiveRecords (almeno non in tutti i casi). –