如何在实体框架 6.1 中仅加载子对象的某些字段

本文关键字:对象 加载 字段 实体 框架 | 更新日期: 2024-10-25 12:38:45

我正在研究一个有两个类的模型,ProductTransaction

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);

但它抛出了一个异常:

包含路径表达式必须引用导航属性 在类型上定义。使用虚线路径进行参考导航 属性和用于集合导航的 Select 运算符 性能

编辑澄清:我想要实现的实际上是加载TransactionId,并且在加载TransactionCustomer

如何在实体框架 6.1 中仅加载子对象的某些字段

要实现您需要的内容,除了将查询投影到匿名类型或 DTO 之外,您别无选择。如您所见,在 Include 扩展方法中,您可以只指定要加载的相关实体,这些实体在带有表的内部联接中转换(或多个联接,请参阅引用链接中的 T备注 部分),但这并不意味着您将从相关实体加载所有属性。如果调用 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列。此时,数据不会仍然加载,您只是定义一个稍后转换为 sql 的 linq 查询。何时发生?,何时调用ToList扩展方法。

作为最后一个建议,我建议您查看Automapper。将实体与其各自的 DTO 映射后,查询可以如下所示:

var product = db.Products.Where(p => p.ProductId == productId)
                         .ProjectTo<ProductDTO>()    
                         .ToList();

有关此链接ProjectTo扩展方法的详细信息

你也可以尝试匿名投影

var product = db.Products.Where(p => p.ProductId = productId)
                         .Select(pr=> new 
                         {
                           product = pr,
                           transactionDates = pr.Transactions.Select(tr=>tr.Date),
                         }.ToList();

我认为使用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 类,您可以这样做:

public class ProductDTO : Product
{
}

因此,DTO 类将具有必填字段,EF 不会引发任何异常。