2017-07-25 80 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 
     } 
    } 
}