如何在 Entity Framework 6.1 中仅加载子对象的某些字段?

Posted

技术标签:

【中文标题】如何在 Entity Framework 6.1 中仅加载子对象的某些字段?【英文标题】:How to only load certain fields of a child object in Entity Framework 6.1? 【发布时间】:2016-08-14 10:05:21 【问题描述】:

我正在研究一个有两个类的模型,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 运算符 属性

编辑澄清: 我想要实现的实际上是在加载Transaction 时加载TransactionIdCustomer

【问题讨论】:

您不能“不加载”TransactionIdCustomer,因为它们已经被加载,因为它们不是导航属性。即使你能做到这一点,你也不会从中获得任何好处...... 【参考方案1】:

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

作为最后的建议,我建议你看看Automapper。将实体与其各自的 DTO 映射后,您的查询可能是这样的:

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

更多关于ProjectTo扩展方法的信息在这个link

【讨论】:

【参考方案2】:

你也可以试试Anonymous projection

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

【讨论】:

每次选择都会产生额外的括号(列表),有没有办法处理这个问题?在您的代码中将有 [ [ [ .. 3 个括号,因为有 2 个选择,然后有 1 个 ToList。你能告诉我如何去掉那些多余的括号吗?【参考方案3】:

我认为使用 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

【讨论】:

什么是“实体”? Product 已经是一个实体。 @GertArnold 符合要求的实体,与示例无关。问题是如果您在查询阶段实例化一个实体作为安全卫士,LinqToQuery 会抱怨 这是一个假设性的案例。如果我想要客户,我会从数据库中获取它们,而不是从产品中转换它们。我认为接受的答案已经涉及所有相关方面。顺便说一句,EF-core 放弃了你提到的限制。 @GertArnold 我不确定我会说这是假设性的。如果您想要一种方法来查询上下文而不从数据库中加载所有字段而不使用 DTO,那么您就是这样做的。尽管如此,我还是强烈建议您使用 DTO,因为这种方法确实放弃了很多类型安全【参考方案4】:

除了其他答案,如果不想重新实现DTO类,可以这样做:

public class ProductDTO : Product


所以 DTO 类将具有必填字段,EF 不会抛出任何异常。

【讨论】:

以上是关于如何在 Entity Framework 6.1 中仅加载子对象的某些字段?的主要内容,如果未能解决你的问题,请参考以下文章

使用 Entity Framework 6.1 fluent API 创建唯一索引

使用 Entity Framework 6.1 和 MVC 5 从数据库中使用 Code First 后如何同步模型?

如何将针对 Entity Framework .Net 4.6.1 的库与 .Net Core 应用程序一起使用

linq to sql 鍜?entity framework 杈撳嚭sql璇彞

Entity Framework 学习系列 - 认识理解Entity Framework

C# Entity-Framework:如何在模型对象上组合 .Find 和 .Include?