C# · 12月 26, 2021

c# – 为什么我的代码执行懒惰加载,即使我把它关闭在所有可能的点?

我想获得具有UserId的等级为“0”的UserTest实体的考试和测试实体或提供的值.我有一些建议,但到目前为止还没有工作.有一个建议是从获取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时,它是一个存储库,当我检查dbcontext的configuration.lazyloading时,它被设置为false.

这是我的课程:

public class Exam{ public int ExamId { get; set; } public int SubjectId { get; set; } public string Name { get; set; } public virtual ICollection<Test> Tests { get; set; }}public class Test{ public int TestId { get; set; } public int ExamId { get; set; } public string Title { get; set; } public virtual ICollection<UserTest> 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,”title”:”Sample Test1″,”userTests”:[ {“userTestId”:3,

请注意,它获取一个UserTest对象,然后获取一个测试对象,然后获取一个考试对象.然而,考试对象包含一个测试集合,然后再次返回,并获得不同的测试和单元测试:

UserTest>测试>考试>测试> UserTest?

我已经努力确保懒惰加载是关闭和调试告诉我它被设置为假.我正在使用EF6和WebAPI,但不知道如果这是有区别的,因为我正在调试C#级别.

解决方法 您不能避免反向导航属性由EF填充,无论您是否加载相关的实体加载或加载.这个关系fixup(已经由@Colin解释)是一个您无法关闭的功能.

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

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,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,您不仅可以避免反向导航属性的问题,还可以遵循良好的安全措施,从而明确地从数据库中“显示”暴露的属性值.想象一下,您将向测试实体添加测试访问密码属性.使用您的代码,将所有属性的加载完整实体序列化,密码也将被序列化并运行在线上.您不需要更改任何代码来实现,在最坏的情况下,您不会意识到您在HTTP请求中泄露密码.另一方面,当您定义DTO时,如果将此属性显式添加到DTO类,则新实体属性将仅与Json数据序列化.