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

标签 .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 中的可选(可为空)引用(也许系统支持“匿名”订单)。

现在,我想将订单的一些属性投影到 View 模型中,包括客户的一些属性如果订单有客户。我有两个 View 模型类:

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中的必需导航属性,我可以使用以下投影并且它可以工作,因为我可以确定任何订单都始终存在客户:

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 中,而不是可为 null 的属性 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 异常:

Unable to create a constant value of type 'CustomerViewModel'. Only primitive types (for instance ''Int32', 'String' und 'Guid'') are supported in this context.

我猜 : 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();

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

The type 'CustomerViewModel' appears in two structurally incompatible initializations within a single LINQ to Entities query. A type can be initialized in two places in the same query, but only if the same properties are set in both places and those properties are set in the same order.

异常已经很清楚如何解决问题了:

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();

这可行,但用虚拟值或显式填充所有属性并不是一个很好的解决方法。

问题:

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

  2. 是否根本不可能在投影中具体化对 null 的可选引用?

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

(我只是为这个问题设置通用的 entity-framework 标签,因为我猜这个行为不是特定于版本的,但我不确定。我已经使用 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();

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

关于.net - 我可以将实体的可选引用投影到投影结果类型的可选引用中吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10904375/

相关文章:

c# - 自动化 SQL 引用和换行

c# - log4net:不同类型的不同信息颜色

c# - 使用sql server在EF6查询中返回空值

c# - 将只读属性分配给 Entity Framework 中的新对象

c# - 无法解析 ASP.NET Core 2.0 中的 DbContext

c# - WPF下载文件并同时保存

c# - 使用原始 SQL 预加载实体

c# - UserManager 不断抛出 System.ArgumentNullException

c# - 获取以下实体之间的差异 Linq to Entities

c# - EF 4.0 中看似无限的堆栈跟踪和负载下的查询性能不佳