【问题标题】:Can I project an optional reference of an entity into an optional reference of the projection's result type?我可以将实体的可选引用投影到投影结果类型的可选引用中吗?
【发布时间】:2012-06-09 21:18:40
【问题描述】:

说,我有两个实体:

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 中的必需 导航属性,我可以使用以下投影并且它有效,因为我可以确定任何@987654328 始终存在Customer @:

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 的所有属性都可以为空之外,最后一个代码 sn-p 是唯一的解决方法吗?

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

  3. 您对如何处理这种情况有其他想法吗?

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

【问题讨论】:

  • 您找到解决方案了吗?如果是这样你介意分享:)
  • @QuintonBernhardt:不,我没有找到解决方案。我主要通过使所有嵌套视图模型属性为空来解决此问题。

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


【解决方案1】:

我也无法让投影在 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 和 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();

【讨论】:

  • 我知道这两种解决方法。老实说,第一个很糟糕。除了加载所有订单属性(或延迟加载会导致多个查询)之外,我还必须为客户使用Include,然后在我进行项目时将大部分加载的属性扔到内存中。第二个没问题,只是要编写更多代码,但不是我想要的。尽管如此,第 2 部分中的示例 +1 :) 顺便说一句:我通常使用 AsEnumerable() 而不是 ToList() 从 LINQ 切换到实体到 LINQ 到对象。它的开销要少一些。
猜你喜欢
  • 1970-01-01
  • 2011-06-17
  • 2010-09-13
  • 1970-01-01
  • 2015-03-29
  • 2011-09-24
  • 1970-01-01
  • 1970-01-01
  • 2020-04-30
相关资源
最近更新 更多