说,我有两个实体:
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
是必需的导航属性,我可以使用以下投影,它可以正常运行,因为我可以确定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'')是 在这种情况下得到支持。
我认为: 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();
这也不起作用并抛出异常:
“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
显式填充所有属性并不是一个非常好的解决方法。
问题:
除了使CustomerViewModel
的所有属性都可以为空之外,最后一个代码段是唯一的解决方法吗?
是否根本无法在投影中实现对null
的可选引用?
您是否有另外的想法如何处理这种情况?
(我只是为这个问题设置了一般的 entity-framework 标签,因为我猜这种行为不是特定于版本的,但我不确定。我已经用EF测试了上面的代码片段4.2 / DbContext
/ Code-First。编辑:添加了两个标签。)
答案 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();