2017-07-25 53 views
1

我的目標是查詢和複雜的對象儘可能少開銷儘可能映射。我正在處理一個有大量相關表的大型數據庫。我正在嘗試使用LINQ選擇和投影來選擇只需要製作對象所需的信息。查詢和地圖複雜對象

這是原始查詢我這是快速和偉大的工作。

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

記住客戶表中有超過50個相關的表,所以此查詢,它只是選擇我需要使對象下地幹活很大。

現在我需要做的就是讓這些對象映射器,並嘗試建立相同的查詢語句,但這次使用的映射器。這是我最終的結果。

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

使用這些映射器

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

試圖運行此我得到以下錯誤後。

LINQ實體無法識別方法「ProjectToProductDTO(IQueryable的[產品])」,並且這種方法不能被翻譯成商店表達。「}

我可以LINQ調用這些方法建立查詢? 還是有更好的方式來查詢這些對象沒有抓住不必要的數據50+表的數百家客戶的地圖嗎?

UPDATE

用戶圖科提到,我可以嘗試尋找到表達式樹。讀了一下他們之後,我想出了這個。

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

並以此爲準。

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

但運行此我收到此錯誤。

的LINQ表達式節點類型「調用」不LINQ支撐到實體

+0

EF默認不返回所有的圖形,你必須使用包括每個導航屬性 – Tuco

+0

這難道不叫延遲加載,而不是默認啓用? – Moe

+0

是的,但它不會提取數據,直到您請求它 – Tuco

回答

1

您是非常接近你的第二個方法!

比方說,你定義產品實體的投影到DTO(如你把它映射器)像你這樣:

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

和客戶端實體的投影,它的DTO像這樣(略簡單,但在邏輯上等同於你做了什麼):

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

編譯器讓我們這樣做,但可查詢不會明白。但是,您已經實現的是productProjection以某種方式包含在表達式樹中。你只需要做一些表情操作。

如果您查看編譯器針對.Select參數構建的子樹,您會發現MethodCallExpression - 致電.Compile()。它的.Object表達式 - 即將編譯的內容 - 是一個MemberExpression訪問名爲productProjection(!)的一個ConstantExpression,其中包含一個奇怪命名的編譯器生成的閉包類的實例。

所以:找到.Compile()調用,並用進行編譯來代替它們,最後得到的是原始版本中的表達式樹。

我正在維護一個名爲Express的表達式幫助類。 (另見answer,處理.Compile().Invoke(...)的情況類似)。

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

下面是關於Express類的相關截圖。

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