13

说,我有两个实体:

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我可以使用以下投影并且它可以工作,因为我可以确定 aCustomer始终存在于 any 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.SalesLevelNULL并且不能存储在int不可为空的属性CustomerViewModel.SalesLevel中。这并不奇怪,问题可以通过使CustomerViewModel.SalesLevel类型int?(或通常所有属性都可以为空)来解决

  • 但我实际上更喜欢在订单没有客户时实现OrderViewModel.CustomerViewModelnull

为此,我尝试了以下方法:

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”)。

我想这是不允许: null的“恒定值” 。CustomerViewModel

由于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”类型出现在单个 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 测试了上面的代码片段。编辑:两个添加了更多标签。)

4

1 回答 1

3

我也无法让投影在 DbQuery 的 IQueryable 实现上工作。如果您正在寻找解决方法,那么为什么不在从 Db 检索数据后进行投影,它不再是 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 中获取所有 Order 和 Customer 列。您可以通过仅从 Order 中选择您需要的列到匿名类型来限制这一点,然后...

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();
于 2012-09-04T06:13:41.837 回答