为什么即使我在每个可能的点关闭它之后我的代码都在进行延迟加载?

我想让具有UserTest实体的Exams和Test实体具有等于“0”的UserId或提供的值。 我有很多建议,但到目前为止还没有任何建议。 一个建议是从获取UserTest数据开始,另一个解决方案是从获取考试数据开始。 这是我使用UserTests作为源起点时所拥有的。

我有以下LINQ:

var userTests = _uow.UserTests .GetAll() .Include(t => t.Test) .Include(t => t.Test.Exam) .Where(t => t.UserId == "0" || t.UserId == userId) .ToList(); 

当我用调试器检查_uow.UserTests时它是一个存储库,当我检查dbcontextconfiguration.lazyloading它被设置为false

这是我的课程:

 public class Exam { public int ExamId { get; set; } public int SubjectId { get; set; } public string Name { get; set; } public virtual ICollection Tests { get; set; } } public class Test { public int TestId { get; set; } public int ExamId { get; set; } public string Title { get; set; } public virtual ICollection UserTests { get; set; } } public class UserTest { public int UserTestId { get; set; } public string UserId { get; set; } public int TestId { get; set; } public int QuestionsCount { get; set; } } 

当我看到输出时,我看到这样的事情:

 [{"userTestId":2, "userId":"0", "testId":12, "test":{ "testId":12,"examId":1, "exam":{ "examId":1,"subjectId":1, "tests":[ {"testId":13,"examId":1,"title":"Sample Test1", "userTests":[ {"userTestId":3, "userId":"0", 

请注意,它获取UserTest对象,然后获取测试对象,然后获取检查对象。 但是,检查对象包含一个测试集合,然后再次向下返回并获得不同的测试和unit testing:

UserTest > Test > Exam > Test > UserTest

我已经努力确保延迟加载已关闭并且调试告诉我它被设置为false 。 我正在使用EF6WebAPI但不确定这是否有所不同,因为我在C#级别进行调试。

无论是否使用急切或延迟加载加载相关实体,都无法避免EF填充反向导航属性。 这种关系修正(已经由@Colin解释)是一个你无法关闭的function。

您可以通过在查询完成后使未完成的反向导航属性无效来解决问题:

 foreach (var userTest in userTests) { if (userTest.Test != null) { userTest.Test.UserTests = null; if (userTest.Test.Exam != null) { userTest.Test.Exam.Tests = null; } } } 

但是,在我看来,您的设计缺陷是您尝试序列化实体而不是数据传输对象 (“DTO”),这些对象专门用于您要将数据发送到的视图。 通过使用DTO,您可以避免完全不需要的反向导航属性,也可以避免在视图中不需要的其他实体属性。 您将定义三个DTO类,例如:

 public class ExamDTO { public int ExamId { get; set; } public int SubjectId { get; set; } public string Name { get; set; } // no Tests collection here } public class TestDTO { public int TestId { get; set; } public string Title { get; set; } // no UserTests collection here public ExamDTO Exam { get; set; } } public class UserTestDTO { public int UserTestId { get; set; } public string UserId { get; set; } public int QuestionsCount { get; set; } public TestDTO Test { get; set; } } 

然后使用投影加载数据:

 var userTests = _uow.UserTests .GetAll() .Where(ut => ut.UserId == "0" || ut.UserId == userId) .Select(ut => new UserTestDTO { UserTestId = ut.UserTestId, UserId = ut.UserId, QuestionsCount = ut.QuestionsCount, Test = new TestDTO { TestId = ut.Test.TestId, Title = ut.Test.Title, Exam = new ExamDTO { ExamId = ut.Test.Exam.ExamId, SubjectId = ut.Test.Exam.SubjectId, Name = ut.Test.Exam.Name } } }) .ToList(); 

您还可以通过仅定义包含视图所需的所有属性的单个DTO类来“展平”对象图:

 public class UserTestDTO { public int UserTestId { get; set; } public string UserId { get; set; } public int QuestionsCount { get; set; } public int TestId { get; set; } public string TestTitle { get; set; } public int ExamId { get; set; } public int ExamSubjectId { get; set; } public string ExamName { get; set; } } 

投影会变得更简单,看起来像这样:

 var userTests = _uow.UserTests .GetAll() .Where(ut => ut.UserId == "0" || ut.UserId == userId) .Select(ut => new UserTestDTO { UserTestId = ut.UserTestId, UserId = ut.UserId, QuestionsCount = ut.QuestionsCount, TestId = ut.Test.TestId, TestTitle = ut.Test.Title, ExamId = ut.Test.Exam.ExamId, ExamSubjectId = ut.Test.Exam.SubjectId, ExamName = ut.Test.Exam.Name }) .ToList(); 

通过使用DTO,您不仅可以避免反向导航属性的问题,还可以遵循良好的安全实践,明确地将数据库中公开的属性值“白名单”。 想象一下,您将向Test实体添加测试访问Password属性。 使用您的代码序列化所有属性的热切加载的完整实体,密码也将被序列化并通过网络运行。 您不必为此更改任何代码,在最坏的情况下,您不会意识到您在HTTP请求中公开密码。 另一方面,当您定义DTO时,如果将此属性显式添加到DTO类,则只会使用Json数据序列化新的实体属性。

您的查询将把所有UserTest加载到UserId == "0" || UserId == userId的上下文中 UserId == "0" || UserId == userId ,您已经急切地加载了相关的Test及其相关的Exams

现在在调试器中,您可以看到Exams链接到内存中的一些Tests ,并假设这是因为它们已经延迟加载。 不对。 它们在内存中,因为您将所有UserTests加载到UserId == "0" || UserId == userId的上下文中 UserId == "0" || UserId == userId ,您已经急切地加载了相关的Test它们与导航属性相关联,因为EF根据外键执行“修复”

Exam.Tests导航属性将包含使用正确的外键加载到上下文中的任何实体,但不一定包含链接到数据库中的Exam所有Tests ,除非您急切地加载它或打开延迟加载

我相信延迟执行不会导致任何事情发生,除非实际从userTests读取userTests 。 尝试包含var userTestsAsList = userTests.ToList()并检查调试器是否userTestsAsList包含所需的序列。

至于我可以阅读您的POCO关系和您的查询,您的回购将返回您的要求。 但是你知道你问过这件事吗?

 You are navigating from Grand-Child  to Child  to Parent  

您的实体被视为一个大孩子,当它看起来是一个Grand-Parent(实际上是一个图形根),其中的孩子有类型的孩子/子女。

当你急于加载(和序列化?)时,你的当然应该加载它的 Collection,它应该加载它们的集合。

通过沿图表向下工作,您将导致整整一圈。


你的意思是否有相反的关系?

 public class Exam { public int ExamId { get; set; } public int TestId { get; set; } public int SubjectId { get; set; } public string Name { get; set; } } public class Test { public int TestId { get; set; } public int ExamId { get; set; } public string Title { get; set; } public virtual ICollection UserTests { get; set; } } public class UserTest { public int UserTestId { get; set; } public string UserId { get; set; } public int TestId { get; set; } public int QuestionsCount { get; set; } public virtual ICollection Exams { get; set; } } 

我对你的数据做了很多假设。 这种关系作为真实世界的实体和您的用法更有意义。 用户有测试和考试,而不是相反。 如果是这样,这种关系应该与您的linq查询一起使用。

如果你尝试这样的事情:

 var userTest = _uow.UserTests .GetAll() .Where(t => t.UserId == "0" || t.UserId == userId) .First(); var name = userTest.Test.Title; 

您的代码是否会抛出exception,因为尚未加载Test属性? 我怀疑问题是你的存储库使用LINQ to SQL而不是LINQ to Entities。 您无法使用LINQ to SQL关闭延迟加载。 在这种情况下,您必须展示存储库如何解决问题。

您是否对您的collections品使用“虚拟”有任何共鸣? 如果你使用“include”,我会建议摆脱“虚拟”