2012-06-05 50 views
12

说,我有两个实体:我可以将实体的可选引用投影到投影结果类型的可选引用吗?

public class Customer 
{ 
    public int Id { get; set; } 
    public int SalesLevel { get; set; } 
    public string Name { get; set; } 
    public string City { get; set; } 
} 

public class Order 
{ 
    public int Id { get; set; } 
    public DateTime DueDate { get; set; } 
    public string ShippingRemark { get; set; } 

    public int? CustomerId { get; set; } 
    public Customer Customer { get; set; } 
} 

Customer可选(可为空)在Order引用(也许该系统支持 “匿名” 订单)。

现在,我想将订单的一些属性投影到视图模型中,其中包括客户的一些属性如果订单有客户。我有那么两种查看模型类:

public class CustomerViewModel 
{ 
    public int SalesLevel { get; set; } 
    public string Name { get; set; } 
} 

public class OrderViewModel 
{ 
    public string ShippingRemark { get; set; } 
    public CustomerViewModel CustomerViewModel { get; set; } 
} 

如果Customer将是Order一个需要导航属性我可以用下面的投影和它的作品,因为我可以肯定的是一个Customer总是存在任何Order

OrderViewModel viewModel = context.Orders 
    .Where(o => o.Id == someOrderId) 
    .Select(o => new OrderViewModel 
    { 
     ShippingRemark = o.ShippingRemark, 
     CustomerViewModel = new CustomerViewModel 
     { 
      SalesLevel = o.Customer.SalesLevel, 
      Name = o.Customer.Name 
     } 
    }) 
    .SingleOrDefault(); 

但当Customer是可选的,与标识someOrderId顺序不会有一个客户,这并不工作:

  • EF抱怨为o.Customer.SalesLevel物化值是NULL且不能存储在int,不能为空属性CustomerViewModel.SalesLevel。这并不奇怪,问题可以通过使int?CustomerViewModel.SalesLevel(或一般所有特性可为空)

  • 得到解决,但我真的宁愿OrderViewModel.CustomerViewModel被物化为null当订单有没有客户。

为了实现这个我试过如下:

OrderViewModel viewModel = context.Orders 
    .Where(o => o.Id == someOrderId) 
    .Select(o => new OrderViewModel 
    { 
     ShippingRemark = o.ShippingRemark, 
     CustomerViewModel = (o.Customer != null) 
      ? new CustomerViewModel 
       { 
        SalesLevel = o.Customer.SalesLevel, 
        Name = o.Customer.Name 
       } 
      : null 
    }) 
    .SingleOrDefault(); 

但这抛出了臭名昭著的LINQ到实体例外:

无法创建类型的常量值 'CustomerViewModel'。在此上下文中仅支持 原始类型(例如'Int32','String'和'Guid'') 。

我想: nullCustomerViewModel这是不允许的“常数值”。

由于分配null似乎并没有被允许我试图引进在CustomerViewModel标记属性:

public class CustomerViewModel 
{ 
    public bool IsNull { get; set; } 
    //... 
} 

然后投影:

OrderViewModel viewModel = context.Orders 
    .Where(o => o.Id == someOrderId) 
    .Select(o => new OrderViewModel 
    { 
     ShippingRemark = o.ShippingRemark, 
     CustomerViewModel = (o.Customer != null) 
      ? new CustomerViewModel 
       { 
        IsNull = false, 
        SalesLevel = o.Customer.SalesLevel, 
        Name = o.Customer.Name 
       } 
      : new CustomerViewModel 
       { 
        IsNull = true 
       } 
    }) 
    .SingleOrDefault(); 

这不工作,要么并抛出例外:

类型'CustomerViewModel'出现在两个str在单个LINQ to Entities查询内部初始化不相容 。一个类型可以是 在同一个查询中的两个位置初始化,但前提是两个位置都设置了相同的 属性,并且这两个属性的设置顺序是 。

唯一的例外是很清楚如何来解决这个问题:

OrderViewModel viewModel = context.Orders 
    .Where(o => o.Id == someOrderId) 
    .Select(o => new OrderViewModel 
    { 
     ShippingRemark = o.ShippingRemark, 
     CustomerViewModel = (o.Customer != null) 
      ? new CustomerViewModel 
       { 
        IsNull = false, 
        SalesLevel = o.Customer.SalesLevel, 
        Name = o.Customer.Name 
       } 
      : new CustomerViewModel 
       { 
        IsNull = true, 
        SalesLevel = 0, // Dummy value 
        Name = null 
       } 
    }) 
    .SingleOrDefault(); 

这工作,但它不是一个非常好的解决办法虚拟值或null明确地填写所有属性。

问题:

  1. 是最后的代码使CustomerViewModel可为空的所有属性片断中,唯一的解决方法,一边?

  2. 难道在投影中实现对null的可选引用是不可能的吗?

  3. 你有另一种想法如何处理这种情况?

(我只设置了这个问题一般实体框架标签,因为我想这种行为是不特定的版本,但我不知道。我与EF 4.2测试上面的代码片段/ DbContext/Code-First。编辑:添加了两个标签。)

+0

您是否曾经为此找到过解决方案?如果是的话,你会介意分享:) –

+0

@QuintonBernhardt:不,我没有找到解决方案。我主要通过使所有嵌套的视图模型属性为空来解决这个问题。 – Slauma

回答

3

我无法让投影在DbQuery的IQueryable实现上工作。如果你正在寻找一个解决办法,那么为什么不能做投影的数据已经从数据库中检索后它不是一个EF的DBQuery了...

OrderViewModel viewModel = context.Orders 
    .Where(o => o.Id == someOrderId) 
    // get from db first - no more DbQuery 
    .ToList() 
    .Select(o => new OrderViewModel 
    { 
     ShippingRemark = o.ShippingRemark, 
     CustomerViewModel = o.Customer == null ? null : new CustomerViewModel 
     { 
      SalesLevel = o.Customer.SalesLevel, 
      Name = o.Customer.Name 
     } 
    }) 
    .SingleOrDefault(); 

的缺点是你获取的所有订单和来自Db的客户列。您可以通过从订单中只选择需要的列转换为匿名类型来限制此限制,然后...

OrderViewModel viewModel = context.Orders 
    .Where(o => o.Id == someOrderId) 
    .Select(o => new { ShippingRemark = o.ShippingRemark, Customer = o.Customer }) 
    // get from db first - no more DbQuery 
    .ToList() 
    .Select(o => new OrderViewModel 
    { 
     ShippingRemark = o.ShippingRemark, 
     CustomerViewModel = o.Customer == null ? null : new CustomerViewModel 
     { 
      SalesLevel = o.Customer.SalesLevel, 
      Name = o.Customer.Name 
     } 
    }) 
    .SingleOrDefault(); 
+1

我知道两种解决方法。第一个是可怕的,说实话。除了加载所有的订单属性(或导致多个查询的延迟加载)之外,我还必须为客户使用'Include',然后在投影时将大部分加载的属性放在内存中。第二个是可以的,只需要编写更多的代码,但不是我真正想要的。尽管如此,第2部分中的示例为+1 :)顺便说一句:我通常使用'AsEnumerable()'而不是'ToList()'从LINQ切换到实体切换到对象。它的开销少一点。 – Slauma

+0

谢谢,很高兴知道。 –