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

Sla*_*uma 13 .net linq-to-entities entity-framework

说,我有两个实体:

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; }
}
Run Code Online (Sandbox Code Playgroud)

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; }
}
Run Code Online (Sandbox Code Playgroud)

如果它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();
Run Code Online (Sandbox Code Playgroud)

但是,当Customer可选且带有Id的订单someOrderId没有客户时,这不起作用:

  • EF抱怨物化价值o.Customer.SalesLevelNULL和不能存储在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();
Run Code Online (Sandbox Code Playgroud)

但这会引发臭名昭着的LINQ to Entities异常:

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

我猜这是不允许: null的"常数值" CustomerViewModel.

由于null似乎不允许分配,我尝试在以下位置引入标记属性CustomerViewModel:

public class CustomerViewModel
{
    public bool IsNull { get; set; }
    //...
}
Run Code Online (Sandbox Code Playgroud)

然后投影:

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();
Run Code Online (Sandbox Code Playgroud)

这也不起作用并抛出异常:

"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();
Run Code Online (Sandbox Code Playgroud)

这有效,但是使用虚拟值或null显式填充所有属性并不是一个非常好的解决方法.

问题:

  1. 除了制作CustomerViewModel可空的所有属性之外,最后一个代码片段是唯一的解决方法吗?

  2. 是否根本无法null在投影中实现可选参考?

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

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

Qui*_*rdt 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();
Run Code Online (Sandbox Code Playgroud)

缺点是您要从数据库中获取所有订单和客户列。您可以通过仅从 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();
Run Code Online (Sandbox Code Playgroud)