比如说,我有两个实体:
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
中必需的导航属性,我可以使用以下投影并且它有效,因为我可以确定任何Order
始终存在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
中。这并不奇怪,这个问题可以通过使int?
类型(或通常所有属性都为空)的CustomerViewModel.SalesLevel
来解决。但我实际上更希望
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 异常:
无法创建类型为"客户视图模型"的常量值。只 基元类型(例如"Int32"、"字符串"和"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();
这也不起作用并引发异常:
类型"客户视图模型"出现在两个结构上不兼容的 单个 LINQ to 实体查询中的初始化。类型可以是 在同一查询中的两个位置初始化,但前提是相同 属性在两个位置都设置,这些属性在 相同的顺序。
异常很清楚如何解决问题:
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
不是一个很好的解决方法。
问题:
除了使
CustomerViewModel
的所有属性可为空之外,最后一个代码片段是唯一的解决方法吗?是否根本不可能在投影中实现对
null
的可选引用?您有如何处理这种情况的替代想法吗?
(我只为这个问题设置了通用实体框架标签,因为我想这种行为不是特定于版本的,但我不确定。我已经使用 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();
缺点是您从数据库中获取所有订单和客户列。 您可以通过仅从"顺序"中选择所需的列到匿名类型,然后
...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();