说,我有两个实体:
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
中引入marker属性: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
的可选引用?您是否有其他想法来应对这种情况?
(我只为此问题设置了通用的实体框架标签,因为我猜这种行为不是特定于版本的,但是我不确定。我已经使用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();
缺点是您要从Db中获取所有“订单”和“客户”列。您可以通过从“订单”中仅选择所需的列为匿名类型来限制此行为,然后...
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();