比如说,我有两个实体:
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
中的可选(可为空)引用(也许系统支持“匿名”订单)。
现在,我想将订单的一些属性投影到 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; }
}
如果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
中,而不是可为 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.
我猜 : 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();
这也不起作用并抛出异常:
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();
这可行,但用虚拟值或显式填充所有属性并不是一个很好的解决方法。
问题:
除了使
CustomerViewModel
的所有属性可为空之外,最后一个代码片段是唯一的解决方法吗?是否根本不可能在投影中具体化对
null
的可选引用?您有其他想法来处理这种情况吗?
(我只是为这个问题设置通用的 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/