使用AsNoTracking()的Entity Framework延迟加载

15
我们目前在使用Entity Framework的懒加载时遇到了“内存不足异常”。出现这种异常的原因是Linq查询加载了大量数据,而后期使用懒加载来加载导航属性。但由于我们没有使用“NoTrackingChanges”,因此Entity Framework缓存很快就会增加,导致内存不足错误。
我对EF的理解是,除非您要更新查询返回的对象,否则应始终在查询上使用“NoTrackingChanges”。
然后我尝试使用“NoChangeTracking”:
var account = _dbcontext.Account
                        .AsNoTracking()
                        .SingleOrDefault(m => m.id == 1); 
var contactName = account.Contact.Name

但是我遇到了以下错误:
System.InvalidOperationException:当使用NoTracking合并选项返回对象时,只有在EntityCollection或EntityReference不包含对象时才能调用Load方法。

请提供导致此异常的代码。 - Erik Philips
尝试禁用代理创建 - Steve Greene
1
@SteveGreene 如果我禁用代理创建,则惰性加载不会起作用。这是真的吗? - John
2
请不要将代码或直接相关的评论作为注释添加,只需更新您的问题,以便其他用户无需浏览评论即可理解问题。 - Erik Philips
Configuration.ProxyCreationEnabled 设为 false 解决了我的问题。https://dev59.com/kl3Va4cB1Zd3GeqPC7Fn - Mahbubur Rahman
显示剩余2条评论
1个回答

21
您已指定EF不跟踪您实例化的Account值:
var account = _dbcontext.Account.AsNoTracking().SingleOrDefault(m=>m.id == 1);

因此,试图访问它们的导航属性将永远不起作用:

var contactName = account.Contact.Name

您可以使用Include()明确包含想要的导航属性。因此,以下代码应该可行:

var account = _dbcontext.Account
  .Include(a => a.Contact)
  .AsNoTracking()
  .SingleOrDefault(m=>m.id == 1);

var contactName = account.Contact.Name;  // no exception, it's already loaded

我真的不相信使用 AsNoTracking 会阻止使用延迟加载。

这可以很快地进行测试:

DotNetFiddle 完整示例

public static void Main()
{
    var actor1 = new Actor { Id = 1, Name = "Vin Diesel" }; 
    var movie1 = new Movie { Id = 1, Title = "Fast and Furious", PrimaryActor = actor1 };
    using (var context = new MovieDb())
    {

        Console.WriteLine("========= Start Add: movie1 ==============");
        context.Movies.Add(movie1);
        context.SaveChanges();
        Console.WriteLine("========= END Add: movie1 ==============");

        var m1 = context.Movies.First();
        Console.WriteLine(m1.PrimaryActor.Name);

        var m2 = context.Movies.Include(m => m.PrimaryActor).AsNoTracking().First();
        Console.WriteLine(m2.PrimaryActor.Name);

        var m3 = context.Movies.AsNoTracking().First();
        Console.WriteLine(m3.PrimaryActor.Name);
    }
}

输出:

========= 开始添加: movie1 ==============
========= 结束添加: movie1 ==============
Vin Diesel
Vin Diesel
运行时异常(第31行):对象引用未设置为对象实例。

变量m1在上下文中被跟踪,因此它可以惰性加载导航属性并打印值。 m2没有被跟踪,但我已经显式包含了导航属性,因此它打印了值。 m3没有被跟踪,也没有显式包含它,因此该值为null,我们得到一个NRE。


6
当你需要以无跟踪方式加载递归实体时,这确实会导致一个进退两难的局面... - Douglas Gaskell
@DouglasGaskell 一点也不。 - Erik Philips
@ErikPhilips 谢谢你的回答。我并不完全相信使用 AsNoTracking 就能防止使用延迟加载,但是很难找到相关信息。你能否提供一些关于你所说的内容的参考资料呢? - antoninod
3
这里也是一样的情况。使用AsNoTracking的惰性加载...你可以抛硬币来决定它是否有效。有时候有效,有时候无效。 - m12lrpv
另一个stackoverflow问题的被接受答案中声称_EF 6仍将延迟加载非跟踪结果中的引用实体,前提是启用了延迟加载并且导航属性是虚拟的。_这很奇怪。想知道谁是正确的? - Jyunhao Shih
@JyunhaoShih 这不是正确与否的问题,而是实现的问题。虚拟仍然允许延迟加载,但我绝不建议同时使用虚拟和 AsNoTracking(),这是没有意义的。这就像给一辆标明不应用于拖车的汽车加上拖车架,当然你可以这样做,但这并不推荐且可能会导致意外副作用。 - Erik Philips

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