【问题标题】:Problem with Eager Loading Nested Navigation Based on Abstract Entity (EF CTP5)基于抽象实体 (EF CTP5) 的急切加载嵌套导航问题
【发布时间】:2011-07-08 12:54:35
【问题描述】:

我的 EF 模型的一部分如下所示:

总结:

  • 位置有许多帖子
  • Post 是一个抽象
  • 讨论派生来自帖子
  • 讨论有很多评论

现在,我要实现的查询:

获取有关位置 ID 1234 的信息,包括与这些讨论相关的任何讨论和评论。

我可以得到这样的讨论和 cmets:

var discussions = ctx.Posts
                     .OfType<Discussion>()
                     .Include(x => x.Comments)
                     .ToList();

但我似乎无法根据 Location 实体上的 Posts 导航来获取它。

我试过了:

var locationWithDiscussionsAndComments = ctx
                    .Locations
                    .Include(x => x.Posts
                                   .OfType<Discussion>()
                                   .Select(y => y.Comments))
                    .SingleOrDefault();

编译,但我得到错误:

System.ArgumentException:包含路径表达式必须引用实体定义的属性,还可以选择嵌套属性或调用 Select。 参数名称:路径

有什么想法吗?我可能会从帖子中“倒退”:

var locationWithDiscussionsAndComments = ctx
                   .Posts
                   .Include(x => x.Location)
                   .OfType<Discussion>()
                   .Include(x => x.Comments)
                   .Where(x => x.LocationId == 1234)
                   .Select(x => x.Location)
                   .ToList();

但是就我的存储库而言,这既麻烦又在语义上是错误的(我不应该通过发布存储库来获取有关位置的信息)。

有什么想法吗?

编辑

所以在仔细考虑之后,我意识到OfType&lt;T&gt; 是一个过滤操作。正如我们所知,EF 不支持预加载过滤。唯一的选择是检索所有内容,或使用匿名类型投影。

我无法检索所有内容,因为涉及的元数据太多。所以我正在尝试匿名类型投影。

【问题讨论】:

  • 是的,毕竟新的 Include 重载中的 Lambda 表达式只是一个属性选择器,您不能在其中包含任何类型的过滤逻辑。就像你提到的,你最好的选择是在这里使用匿名预测。你在这里使用 DbContext 吗?
  • @Morteza - 是的,我使用DbContext,在Repository&lt;T&gt; 后面,其中T 是聚合根,LocationPost 都是。所以单独的存储库。
  • 好的,那么您可以使用新的 Query 方法在显式加载相关实体时应用过滤器(虽然它不是急切加载),如下所述:blogs.msdn.com/b/adonet/archive/2011/01/31/…
  • @Morteza - 我听说过,但我使用的是 POCO,所以 ctx.PostsICollection&lt;Post&gt;,而不是 DbSet&lt;T&gt;。我将如何处理 POCO?
  • 你确定吗?我觉得应该是ObjectSet&lt;Post&gt;?

标签: c# abstract-class eager-loading entity-framework-ctp5 navigational-properties


【解决方案1】:

新的 Query 方法可能会对您有所帮助:

var location = context.Locations.SingleOrDefault();

context.Entry(location)
       .Collection(l => l.Posts)
       .Query()
       .OfType<Discussion>()
       .Load();


存储库实现:

我们可以向Repository&lt;T&gt; 类添加一个新的LoadProperty 泛型方法,以利用这个新的QUery 方法:

public void LoadProperty<TElement>(T entity, 
        Expression<Func<T, ICollection<TElement>>> navigationProperty,
        Expression<Func<TElement, bool>> predicate) where TElement : class
{
    _context.Set<T>().Attach(entity);

    _context.Entry(entity)         
            .Collection(navigationProperty)
            .Query()
            .Where(predicate)
            .Load();
}

使用 LoadProperty 方法:

Location location = _locationRepository.Find(1);
_locationRepository.LoadProperty(location, l => l.Posts, p => p is Discussion);

【讨论】:

  • Morteza - 但我在一个存储库后面,所以为了访问这些条目,我执行 repository.Find(),它返回 IQueryable&lt;T&gt;,由 EF 在后台实现为 DbSet&lt;T&gt; .我是否需要向我的通用存储库接口添加一个专门的方法来执行此操作?
  • 是的,您需要为此定义一个新方法。请查看我的更新答案。
  • @Morteza - 等等,这是往返一次还是两次?你有locationRepository.Find(1) 返回一个Location,然后是LoadProperty 调用。在这另一个电话?我的存储库上的Find 方法返回IQueryable&lt;T&gt;(所以IQueryable&lt;Location&gt;)。我希望这样做将进行一次数据库旅行,否则如果我要进行两次旅行,我不妨使用另一个专门的电话进行第二次旅行(获取有关定位方法的讨论)。
  • 当然是两趟。如果您正在寻找 1,那么匿名投影就是您要走的路。我的LoadProperty 方法的想法是在 Base Repository 类上有一个通用方法,该方法延迟加载已检索到的对象的导航属性(基于某些标准)。顺便说一句,为什么(通常)您的 Find 方法返回 IQueryable?那你怎么执行呢?
  • @Morteza - 这是一个很长的讨论。 :) 本质上我是“服务层”中间人的粉丝,其中控制器(MVC 应用程序)调用“服务”,它对IQueryable&lt;T&gt; 存储库执行查询。这样,我的存储库就非常简单。好的,所以这是两个电话。没问题,我会接受这个答案,但实际上我会坚持使用我的其他专业方法进行第二次通话。该查询对于匿名投影查询来说太复杂了。谢谢朋友。
猜你喜欢
  • 2011-06-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-28
  • 1970-01-01
  • 2011-07-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多