2017-07-25 1 views
1

Mein Ziel ist es, komplexe Objekte mit möglichst wenig Aufwand abzufragen und abzubilden. Ich arbeite mit einer großen Datenbank mit vielen verwandten Tabellen. Ich versuche LINQ Auswahl und Projektion zu verwenden, um nur die notwendigen Informationen auszuwählen, die ich brauche, um das Objekt zu erstellen.Komplexe Objekte abfragen und abbilden

Das ist die ursprüngliche Abfrage, die ich hatte, die schnell war und großartig funktionierte.

List<ClientDTO> clientList = dbClients.Select(client => 
new ClientDTO 
{ 
    ID = client.ClientID, 
    FirstName = client.FirstName, 
    LastName = client.LastName, 
    //etc.... 
    Products = client.Products 
     .Select(prod => new ProductDTO 
     { 
      ID = prod.ID, 
      DateOfTransaction = prod.Date, 
      //etc... 
     }).ToList(), 
    Items = client.Items 
     .Select(item => new ItemDTO 
     { 
      ID = item.ID, 
      Date = item.Date, 
      //etc... 
     } 
}); 

Beachten Sie die Tabelle Client verfügt über 50 verknüpften Tabellen, so dass diese Abfrage funktionierte großartig, dass es nur die Felder, die ich ausgewählt habe das Objekt machen musste.

Nun, was ich tun musste, ist Mapper für diese Objekte zu machen und versuchen, die gleiche Abfrage-Anweisung zu erstellen, aber diesmal die Mapper verwenden. Hier ist, womit ich endete.

List<ClientDTO> clients = dbClients.ProjectToClientDTO(); 

diese Mapper Mit

public static List<ClientDTO> ProjectToClientDTO(this IQueryable<Clients> query) 
{ 
    var clientList = query.Select(client => new 
    { 
     ID = client.ClientID, 
     FirstName = client.FirstName, 
     LastName = client.LastName, 
     //etc... 
     Products = client.Products.AsQueryable().ProjectToProductDTO().ToList(), 
     Items = client.Items.AsQueryable().ProjectToItemDTO().ToList() 
    } 

    List<ClientDTO> dtoClientList = new List<ClientDTO>(); 
    foreach (var client in clientList) 
    { 
     ClientDTO clientDTO = new ClientDTO(); 

     clientDTO.EncryptedID = EncryptID(client.ID, client.FirstName, client.LastName); 
     //etc... 
     clientDTO.Products = client.Products; 
     clientDTO.Items = client.Items; 
    } 
    return dtoClientList; 
} 

public static IQueryable<ProductDTO> ProjectToProductDTO(this IQueryable<Products> query) 
{ 
    return query.Select(prod => new ProductDTO 
    { 
     ID = prod.ID, 
     DateOfTransaction = prod.Date, 
     //etc... 
    }); 
} 

public static IQueryable<ItemDTO> ProjectToItemDTO(this IQueryable<Items> query) 
{ 
    return query.Select(item => new ItemDTO 
    { 
     ID = item.ID, 
     Date = item.Date, 
     //etc... 
    }); 
} 

Nach dem Versuch, das ich erhalte die folgende Störung zu laufen.

LINQ to Entities nicht erkennt die Methode 'ProjectToProductDTO (IQueryable [Produkte])', und dieses Verfahren kann nicht in einen Laden Ausdruck übersetzt werden. "}

Kann ich LINQ diese Methoden aufrufen die Abfrage zu bauen? Oder gibt es einen besseren Weg, um diese Objekte abzufragen und Karte ohne 50+ Tabellen unnötiger Daten für Hunderte von Kunden zu greifen?

UPDATE

Benutzer Tuco erwähnt, dass ich versuchen könnte, in Ausdrucksbäume zu suchen. Nachdem ich sie ein wenig gelesen hatte, kam ich auf diese Idee.

public static Expression<Func<Product, ProductDTO>> test = prod => 
     new ProductDTO() 
     { 
      ID= prod.ID, 
      Date= prod.Date, 
      //etc... 
     }; 

Und verwenden Sie es als solches.

Products = client.Products.Select(prod => test.Compile()(prod)), 

Aber läuft dies ich bekomme diesen Fehler.

Der LINQ Ausdruck Knotentyp 'Invoke' in LINQ to Entities nicht

unterstützt
+0

EF kehrt nicht alle die Grafik standardmäßig, müssen Sie für jede Navigationseigenschaft einschließen verwenden – Tuco

+0

Ist das nicht ein träges Laden genannt, und ist es nicht standardmäßig aktiviert? – Moe

+0

Ja, aber es wird nicht die Daten ziehen, bis Sie es anfordern – Tuco

Antwort

1

Sie sind sehr nahe mit Ihrem zweiten Ansatz!

Lassen Sie uns sagen, dass Sie die Projektion der Produkteinheit an die DTO definieren (der Mapper, wie Sie es nennen) wie Sie haben:

Expression<Func<Product, ProductDTO>> productProjection = prod => new ProductDTO 
{ 
    ID = prod.ID, 
    DateOfTransaction = prod.Date 
    // ... 
}; 

und der Projektion der Client-Einheit, um es DTO ist das wie (leicht einfacher, aber logisch äquivalent zu dem, was Sie getan haben):

Expression<Func<Client, ClientDTO>> clientProjection = client => new ClientDTO 
{ 
    ID = client.ClientID, 
    FirstName = client.FirstName, 
    // ... 
    Products = client.Products.Select(productProjection.Compile()).ToList(), 
    // ... 
}; 

die Sie Compiler lassen Sie sich so, aber die abfragbaren wird das nicht verstehen. Was Sie jedoch erreicht haben, ist, dass die productProjection irgendwie in der Ausdrucksbaumstruktur enthalten ist. Alles, was Sie tun müssen, ist eine Ausdrucksmanipulation.

Wenn Sie den Teilbaum betrachten, den der Compiler für das Argument .Select erstellt, finden Sie MethodCallExpression - den Aufruf an .Compile(). Es ist .Object Ausdruck - die Sache, die kompiliert werden - ist ein MemberExpression Zugriff auf ein Feld mit dem Namen productProjection (!) Auf einem ConstantExpression enthält eine Instanz eines seltsam benannten Compiler generiert Schließklasse.

Also: Finden Sie .Compile() Anrufe und ersetzen Sie sie mit, was wäre kompiliert werden würde, am Ende mit dem Ausdruck Baum, den Sie in Ihrer ursprünglichen Version hatten.

Ich unterhalte eine Hilfsklasse für Ausdruck Zeug namens Express. (Siehe eine andere answer, die sich mit .Compile().Invoke(...) für eine ähnliche Situation befasst).

clientProjection = Express.Uncompile(clientProjection); 
var clientList = dbClients.Select(clientProjection).ToList(); 

Hier ist der relevante Ausschnitt der Express Klasse.

public static class Express 
{ 
    /// <summary> 
    /// Replace .Compile() calls to lambdas with the lambdas themselves. 
    /// </summary> 
    public static Expression<TDelegate> Uncompile<TDelegate>(Expression<TDelegate> lambda) 
    => (Expression<TDelegate>)UncompileVisitor.Singleton.Visit(lambda); 

    /// <summary> 
    /// Evaluate an expression to a value. 
    /// </summary> 
    private static object GetValue(Expression x) 
    { 
     switch (x.NodeType) 
     { 
      case ExpressionType.Constant: 
       return ((ConstantExpression)x).Value; 
      case ExpressionType.MemberAccess: 
       var xMember = (MemberExpression)x; 
       var instance = xMember.Expression == null ? null : GetValue(xMember.Expression); 
       switch (xMember.Member.MemberType) 
       { 
        case MemberTypes.Field: 
         return ((FieldInfo)xMember.Member).GetValue(instance); 
        case MemberTypes.Property: 
         return ((PropertyInfo)xMember.Member).GetValue(instance); 
        default: 
         throw new Exception(xMember.Member.MemberType + "???"); 
       } 
      default: 
       // NOTE: it would be easy to compile and invoke the expression, but it's intentionally not done. Callers can always pre-evaluate and pass a member of a closure. 
       throw new NotSupportedException("Only constant, field or property supported."); 
     } 
    } 

    private sealed class UncompileVisitor : ExpressionVisitor 
    { 
     public static UncompileVisitor Singleton { get; } = new UncompileVisitor(); 
     private UncompileVisitor() { } 

     protected override Expression VisitMethodCall(MethodCallExpression node) 
     { 
      if (node.Method.Name != "Compile" || node.Arguments.Count != 0 || node.Object == null || !typeof(LambdaExpression).IsAssignableFrom(node.Object.Type)) 
       return base.VisitMethodCall(node); 
      var lambda = (LambdaExpression)GetValue(node.Object); 
      return lambda; 

      // alternatively recurse on the lambda if it possibly could contain .Compile()s 
      // return Visit(lambda); // recurse on the lambda 
     } 
    } 
} 
Verwandte Themen