我可以将实体的可选引用投影到投影结果类型的可选引用中吗?

时间:2012-06-05 20:23:35

标签: .net entity-framework linq-to-entities

说,我有两个实体:

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

如果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是可选的且ID为someOrderId的订单没有客户时,这不起作用:

  • EF抱怨o.Customer.SalesLevel的具体化值为NULL,无法存储在int,而不是可存储的属性CustomerViewModel.SalesLevel中。这并不奇怪,问题可以通过CustomerViewModel.SalesLevel类型int?(或通常所有属性可以为空)来解决

  • 但是,如果订单没有客户,我实际上更希望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中引入标记属性:

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. 您是否有另外的想法如何处理这种情况?

  4. (我只是为这个问题设置了一般的 entity-framework 标签,因为我猜这种行为不是特定于版本的,但我不确定。我已经用EF测试了上面的代码片段4.2 / DbContext / Code-First。编辑:添加了两个标签。)

1 个答案:

答案 0 :(得分:3)

我无法将投影用于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获取所有订单和客户列。您可以通过仅选择从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();