EF - 为什么有时候使用急加载,有时候使用延迟加载

3

我有一个用于搜索的通用基础仓库方法(为了清晰起见进行了修剪):

public class Repository<TRepository> : IRepository<TRepository>
        where TRepository : class, IEntity, new()
{    
     public virtual IQueryable<TRepository> SearchFor(Expression<Func<TRepository, bool>>   predicate, Expression<Func<TRepository, bool>> orderbylinq = null)
    {
        if (orderbylinq == null)
        {
            return DbSet.Where(predicate);
        }
        else
        {
            return DbSet.Where(predicate).OrderBy(orderbylinq);
        }
    }

}

我有一个派生的存储库类:

public class TimeDetailRepository : Repository<TimeDetail>

在我的服务层中,我有一个调用SearchFor方法的类:
private TimeDetailRepository _timeDetailRepository;

        public ManageTimeDetailsAppServ()
            : base(new TimeDetailRepository())
        {
            _timeDetailRepository = new TimeDetailRepository();
        }
IQueryable<TimeDetail> timeDetails2 = _timeDetailRepository.SearchFor(
                    x => x.Id == 3214);

在这种情况下,timeDetails2已经完全加载(所有相关的实体都已加载)。
但是,我有另一个类(基础服务层类)进行相同的SearchFor调用,但它不会加载相关的实体:
IQueryable<TRepository> dbEntity = _repository.SearchFor(x => x.Id == result.Value);

我试图从两个调用中创建一个视图模型,利用相关实体的属性值。为什么在一种情况下它会加载它们,而在另一种情况下它不会呢?这是同一个实体TimeDetail,同一个基本存储库类吗?

下面是你在调试器中看到的图片。为什么有些相关实体已被加载(例如Facility和TimeDetailStatus),而其他实体则没有(如OrderHeader或Customer)。 enter image description here

更新

我查看了生成的查询语句,它们似乎相似,即它们没有连接到其他表...那么可能上下文已经包含一些来自某些相关实体的缓存行,这就是它们被包括的方式吗?


问题标题是什么意思? - Slauma
更新了,希望更加清晰明了。 - crichavin
1
也许 timeDetails2 是在查询链中检索到的,而之前的查询已经检索到了实体? - LukLed
1
快速建议 - 你应该将 TRepository 重命名为 TEntityTRepository 让它看起来像是一个仓储库的类型,而不是实体的类型。 - Joe Enos
我不确定我理解了。timeDetails2在那段代码中是第一次被定义。之前从同一张表中检索的内容是否可以被缓存或其他什么东西?这是你的意思吗? - crichavin
@JoeEnos 很好的观点。当我编写那些通用类时,我非常幼稚...现在还是有点幼稚,但不像以前那么幼稚了 :) - crichavin
1个回答

4
EF会进行延迟加载,除非你在数据集上使用.Include()方法。由于你没有这样做,所以你不会立即得到它们,但当你调用它们时,它们将被懒惰地加载。
然而,这仅在对象上下文仍然打开时有效。如果上下文已经关闭,那么你连接数据库的能力也消失了,所以你的属性都将为空。
我的猜测是你看到的差异与你如何保持上下文有关。由于你没有显示上下文的位置,因此很难确定,但我会从那里开始。
编辑
不确定这是否与你的问题有关,但我只是想澄清一下懒惰加载的东西。这是我最喜欢的EF示例:两个表:作者(作者ID,作者姓名)和书籍(书籍ID,作者ID(外键),书名)。
/*  1 */ static void Main(string[] args)
/*  2 */ {
/*  3 */    Book book;
/*  4 */    using (var context = new SampleDbEntities())
/*  5 */    {
/*  6 */        book = context.Books.Single(b => b.BookId == 1);
/*  7 */    }
/*  8 */
/*  9 */    try
/* 10 */    {
/* 11 */        Console.WriteLine(book.Author.AuthorName);
/* 12 */    }
/* 13 */    catch (Exception ex)
/* 14 */    {
/* 15 */        Console.WriteLine(ex.Message);
/* 16 */    }
/* 17 */ 
/* 18 */    Console.ReadLine();
/* 19 */ }

如果您按照原样运行此代码,将会发生以下情况:
- 第6行仅查询Books表,并使用匹配的记录填充book变量。在数据库中不涉及Authors表。 - 在第7行之后,上下文已被释放,这意味着数据访问已经终止。 - 第11行抛出异常,试图访问Author属性。
然而,如果您使用调试器并停留在第7行,只是为了短暂地查看书籍变量,它将立即在数据库中查询Authors表(当您在调试器中时),并在该对象上填充Author属性。这就是我所说的延迟加载 - 当您实际访问属性时,即使在调试器中,只要上下文存在,它就会进行新的数据库调用。现在您可以继续代码,第11行将会很好,作者名称将被打印。
所以基本上,当涉及到EF时,不要相信调试器。它即使看起来没有这样做,也会进行延迟加载数据。最好的方法是启动SQL Profiler并查看运行的查询。

感谢Joe的解释,但我似乎遇到了混合问题...即一些相关实体加载,而其他实体则没有加载。请查看我的更新问题,并附有调试器显示的屏幕截图。为什么会出现其中一些能加载而另一些不能加载呢? - crichavin
@ChadRichardson 我能想到的唯一可能性就是你和LukLed提到的,即上下文中已经存在的实体。如果是这样,那么你肯定将上下文保持开放状态很长时间——我的意见是你不应该这样做——它会导致数据陈旧以及存储在内存中的许多不必要的内容。我个人几乎建议在所有情况下都创建一个新的上下文并在调用完成后立即销毁它。关于这个问题,这篇文章写得非常好。 - Joe Enos
1
@KomengeMwandila 我不知道为什么他们这样做,但它只接受表示导航属性的字符串,而不是lambda表达式。所以假设你的User有一个名为Contact的属性,你可以这样做:context.Users.Include("Contact"),运行的查询将连接Contact表并急切地加载该数据。不过,有人构建了一个扩展方法来允许lambda表达式 - 看看这个 - 你可以将其添加到你的项目中,然后你就可以用lambda表达式的方式来做你想做的事情了。 - Joe Enos
@JoeEnos 我只是在想为什么我在上一个项目中可以使用 .Include(c => c.Contact),但在当前项目中却不能,但我想我已经弄清楚了。你需要有 using System.Data.Entity; 才能让它工作。 - Komengem
可能是这样。我没有大量查询已经在上下文中的内容的经验,因为我通常一直打开和关闭新的上下文 - 但我知道当您查询已经拥有的内容时,可能会发生奇怪的事情。所以那肯定是有道理的。 - Joe Enos
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接