5

Ich versuche, etwas Code lesbarer zu machen, und verstehe nicht ganz, wie man die Erweiterungsmethode und/oder den Ausdruck dafür strukturiert. Wir haben derzeit viele Unternehmen, die eine RecordStatusTypeId auf sie haben (von einer Schnittstelle von IRecordStatus implementiert)LINQ to Entities erkennt die Methode (für eine verbundene Entität) nicht

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

Das hier Ziel, eine Aussage wie .Where(RecordStatusTypeId != (int)RecordStatusTypes.Deleted) mit einem Verlängerungsverfahren wie .ActiveRecords()

ersetzen soll ich bin in der Lage dies mit der folgenden Erweiterungsmethode zu erreichen:

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

* ich diese Erweiterung Methode habe für DbSet<T>, IQueryable<T>, ICollection<T>, und IEnumerable<T>

Dies funktioniert gut für Aussagen wie MyDbContext.Entities.Where(e => e.RecordStatusTypeId != (int)RecordStatusTypes.Deleted), aber ich habe den Fehler „LINQ to Entities erkennt nicht die Methode“, wenn ich versuche, wie etwas zu ersetzen:

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

mit dem, was ich d gerne tun:

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

wie kann ich meine Erweiterungsmethoden ändern (oder einen Ausdruck hinzufügen), so dass ich für aktive Aufzeichnungen über die DbSet sowie auf eine damit verbundene Einrichtung in einer Linq-Klausel filtern können?

+0

Entity Framework Plus kann dies tun: https://github.com/zzzprojects/EntityFramework-Plus –

+0

@CyrilIselin, Danke, ich werde es überprüfen. –

+1

Ihre Methode konnte nicht in T-SQL übersetzt werden, Linq zu Entitäten konnte sie nicht erkennen. Fügen Sie 'AsEnumerable' zu ​​Ihren' Entitäten' hinzu und versuchen Sie es erneut: 'MyDbContext.Entities.AsEnumerable(). Where ...'. –

Antwort

2

Es sieht so aus, als hätten Sie Ihre Konvertierung falsch gemacht. Sollte:

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

convert dazu:

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

Ja, Sie haben Recht. Leider war das nur ein Fehler meinerseits, den Post zu vereinfachen. Ich habe die Frage behoben. –

1

Sie erhalten die Ausnahme, weil Queryable.Where einen Ausdruck erwartet, die in SQL übersetzt werden kann, und ActiveRecords kann nicht in SQL übersetzt werden.

Was Sie tun müssen, ist den Ausdruck zu aktualisieren, um den Anruf auf ActiveRecords zu einem Anruf zu .Where(e => e.RecordStatusTypeId != (int)RecordStatusTypes.Deleted zu erweitern).

Ich werde einen Entwurf für eine Lösung bereitstellen, die funktioniert. Es ist sehr spezifisch für das von Ihnen bereitgestellte Beispiel. Sie sollten wahrscheinlich daran arbeiten, um es generisch zu machen. mit dem entsprechenden Ausdruck als Argument

Die folgende Ausdruck Besucher werden im Grunde um den Anruf zu Where auf den Aufruf zu ActiveRecords ändern:

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

Man könnte dann eine spezielle WhereSpecial Methode erstellen Sie den Ausdruck zu besuchen, bevor es vorbei auf die reale Where Methode der 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); 
    } 
} 

Und dann können Sie es wie folgt verwendet werden:

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

Es scheint, dass dies alles den gesamten Zweck zunichte macht, die ActiveRecord-Helfermethode zu verwenden :) – Evk

+0

@Evk, das ist wahrscheinlich wahr :) Vielleicht wird ein generischer Besucher, der ein Wörterbuch von Methodennamen und entsprechenden Lambda-Ausdrücken aufnimmt, es schaffen es lohnt sich, all das zu tun. –

+0

@YacoubMassad, Ich hatte keine Zeit zu versuchen und zu erkunden, ob so etwas die Mühe wert wäre, aber danke für die Antwort. Wenn ich wieder Zeit habe, werde ich wieder updaten. –

0

Versuchen Sie, den Hauptteil der Abfrage IQueryable, dann Tag auf der Where-Klausel ...

Etwas wie:

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

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

oder den Hauptteil bewerten und dann die, wo im Speicher durchzuführen.

+0

Danke für den Vorschlag, aber ich habe diese Schnittstelle auf eine Vielzahl von Entitätsobjekten angewendet, so dass ich den Inhalt der Abfrage nicht ziehen kann, ohne zuerst nach ActiveRecords zu filtern (zumindest nicht in allen Fällen). –

Verwandte Themen