说,我有两个实体:

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


CustomerOrder中的可选(可空)引用(可能系统支持“匿名”命令)。

现在,我想将订单的某些属性投影到视图模型中,如果订单中有客户,则包括客户的某些属性。然后,我有两个视图模型类:

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


如果CustomerOrder中的必需导航属性,则可以使用以下投影,它可以正常工作,因为我可以确定任何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是可选的并且ID为someOrderId的订单没有客户时不起作用:


EF抱怨o.Customer.SalesLevel的物化值是NULL,无法存储在int中,不能存储为空值CustomerViewModel.SalesLevel。这不足为奇,并且可以通过使类型为CustomerViewModel.SalesLevelint?(或通常所有属性为可空)解决问题。
但是我实际上更希望在没有客户的订单中将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 to Entities异常:


无法创建类型“ CustomerViewModel”的常量值。只要
基本类型(例如“ Int32”,“ String”和“ Guid”)是
在这种情况下受支持。


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

由于似乎不允许分配null,因此我尝试在CustomerViewModel中引入marker属性:

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”以两种结构不兼容的形式出现
在单个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显式填充所有属性并不是一个很好的解决方法。

问题:


除了使CustomerViewModel的所有属性都可为空之外,最后的代码片段是唯一的解决方法吗?
是否无法在投影中具体实现对null的可选引用?
您是否有其他想法来应对这种情况?


(我只为此问题设置了通用的实体框架标签,因为我猜这种行为不是特定于版本的,但是我不确定。我已经使用EF 4.2 / DbContext / Code-First测试了上面的代码段。编辑:添加了两个标签。)

最佳答案

我也无法在DbQuery的IQueryable实现上进行投影。如果您正在寻找解决方法,那么为什么在从Db中检索数据并且不再是E.F. 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();

07-28 14:18