如何在 Entity Framework 6.1 中仅加载子对象的某些字段?
How to only load certain fields of a child object in Entity Framework 6.1?
我正在开发一个包含两个 类、Product
和 Transaction
的模型。
public class Product
{
[DataMember]
public Guid ProductId {get; set;}
[DataMember]
public virtual ICollection<Transaction> Transactions { get; set; }
}
public class Transaction
{
[DataMember]
public Guid TransactionId {get; set;}
[DataMember]
public DateTimeOffset Date { get; set; }
[DataMember]
public String Customer { get; set; }
}
如何进行查询以检索产品及其交易日期?我试过类似
var product = db.Products.Include(p => p.Transactions.Select(t => new { t.Date })).Where(p => p.ProductId = productId);
但是抛出异常:
The Include path expression must refer to a navigation property
defined on the type. Use dotted paths for reference navigation
properties and the Select operator for collection navigation
properties
编辑澄清:
我想要实现的实际上是 不是 加载 TransactionId
和 Customer
当 Transaction
加载时
你也可以试试Anonymous projection
var product = db.Products.Where(p => p.ProductId = productId)
.Select(pr=> new
{
product = pr,
transactionDates = pr.Transactions.Select(tr=>tr.Date),
}.ToList();
要实现您的需要,您别无选择,只能将您的查询投射到匿名类型或 DTO。如您所见,在 Include
扩展方法中,您可以只指定要加载的相关实体,这些实体在带有 table 的内部连接中转换(或多个连接,请参阅 Remarks 部分引用 link),但这并不意味着您要从相关实体加载所有属性。如果您调用 Select
方法,您可以选择要投影的列,但不能使用实体类型投影 Linq to Entities 查询,您必须使用我在上面评论的两个选项之一。因此,我的建议是在您的业务逻辑层中创建一组 类 (DTO) 来投影您的查询结果,例如:
public class ProductDTO
{
[DataMember]
public Guid ProductId {get; set;}
[DataMember]
public virtual IEnumerable<DateTime> TransactionDates { get; set; }
}
以后你可以这样做:
var product = db.Products.Where(p => p.ProductId = productId)
.Select(pr=> new ProductDTO
{
ProductId = pr.ProductId,
TransactionDates = pr.Transactions.Select(tr=>tr.Date),
}.ToList();
请注意,在这种情况下我不需要调用 Include
扩展方法,因为在 Select
中我正在投影来自 Transactions
table 的列。到那时,数据还没有加载,您只是定义了一个 linq 查询,稍后将其转换为 sql。什么时候发生?,当你调用 ToList
扩展方法时。
作为最后的推荐,我建议你看看Automapper。将实体与其各自的 DTO 映射后,您的查询可能是这样的:
var product = db.Products.Where(p => p.ProductId == productId)
.ProjectTo<ProductDTO>()
.ToList();
此 link
中有关 ProjectTo
扩展方法的更多信息
我认为使用 DTO 是最好的模式。如果您不返回值,匿名投影效果很好。
另一种选择是映射到匿名类型,然后创建实体
public MyEntity Products => db.Products.Where(p => p.ProductId = productId)
.Select(pr=> new {
product = pr,
transactionDates = pr.Transactions.Select(tr=>tr.Date),
}
.AsEnumerable()
.Select(e => new MyEntity { ... }); // Initialize a Linq entity here
除了其他答案,如果您不想重新实现 DTO class,您可以这样做:
public class ProductDTO : Product
{
}
因此 DTO class 将具有必需的字段并且 EF 不会抛出任何异常。
我正在开发一个包含两个 类、Product
和 Transaction
的模型。
public class Product
{
[DataMember]
public Guid ProductId {get; set;}
[DataMember]
public virtual ICollection<Transaction> Transactions { get; set; }
}
public class Transaction
{
[DataMember]
public Guid TransactionId {get; set;}
[DataMember]
public DateTimeOffset Date { get; set; }
[DataMember]
public String Customer { get; set; }
}
如何进行查询以检索产品及其交易日期?我试过类似
var product = db.Products.Include(p => p.Transactions.Select(t => new { t.Date })).Where(p => p.ProductId = productId);
但是抛出异常:
The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties
编辑澄清:
我想要实现的实际上是 不是 加载 TransactionId
和 Customer
当 Transaction
加载时
你也可以试试Anonymous projection
var product = db.Products.Where(p => p.ProductId = productId)
.Select(pr=> new
{
product = pr,
transactionDates = pr.Transactions.Select(tr=>tr.Date),
}.ToList();
要实现您的需要,您别无选择,只能将您的查询投射到匿名类型或 DTO。如您所见,在 Include
扩展方法中,您可以只指定要加载的相关实体,这些实体在带有 table 的内部连接中转换(或多个连接,请参阅 Remarks 部分引用 link),但这并不意味着您要从相关实体加载所有属性。如果您调用 Select
方法,您可以选择要投影的列,但不能使用实体类型投影 Linq to Entities 查询,您必须使用我在上面评论的两个选项之一。因此,我的建议是在您的业务逻辑层中创建一组 类 (DTO) 来投影您的查询结果,例如:
public class ProductDTO
{
[DataMember]
public Guid ProductId {get; set;}
[DataMember]
public virtual IEnumerable<DateTime> TransactionDates { get; set; }
}
以后你可以这样做:
var product = db.Products.Where(p => p.ProductId = productId)
.Select(pr=> new ProductDTO
{
ProductId = pr.ProductId,
TransactionDates = pr.Transactions.Select(tr=>tr.Date),
}.ToList();
请注意,在这种情况下我不需要调用 Include
扩展方法,因为在 Select
中我正在投影来自 Transactions
table 的列。到那时,数据还没有加载,您只是定义了一个 linq 查询,稍后将其转换为 sql。什么时候发生?,当你调用 ToList
扩展方法时。
作为最后的推荐,我建议你看看Automapper。将实体与其各自的 DTO 映射后,您的查询可能是这样的:
var product = db.Products.Where(p => p.ProductId == productId)
.ProjectTo<ProductDTO>()
.ToList();
此 link
中有关ProjectTo
扩展方法的更多信息
我认为使用 DTO 是最好的模式。如果您不返回值,匿名投影效果很好。
另一种选择是映射到匿名类型,然后创建实体
public MyEntity Products => db.Products.Where(p => p.ProductId = productId)
.Select(pr=> new {
product = pr,
transactionDates = pr.Transactions.Select(tr=>tr.Date),
}
.AsEnumerable()
.Select(e => new MyEntity { ... }); // Initialize a Linq entity here
除了其他答案,如果您不想重新实现 DTO class,您可以这样做:
public class ProductDTO : Product
{
}
因此 DTO class 将具有必需的字段并且 EF 不会抛出任何异常。