[英]Why is my code doing lazy loading even after I turned it off at every possible point?
我想让具有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
时它是一个存储库,当我检查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,"examId":1,"title":"Sample Test1",
"userTests":[
{"userTestId":3,
"userId":"0",
请注意,它获取UserTest
对象,然后获取测试对象,然后获取检查对象。 但是,检查对象包含一个测试集合,然后再次向下返回并获得不同的测试和单元测试:
UserTest
> Test
> Exam
> Test
> UserTest
?
我已经努力确保延迟加载已关闭并且调试告诉我它已设置为false
。 我使用EF6和的WebAPI,但不知道是否有差别,因为我在C#水平调试。
无论是否使用急切或延迟加载加载相关实体,都无法避免EF填充反向导航属性。 这种关系修正(已经由@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,
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 <UserTest> to Child <Test> to Parent <Exam>
您的<Exam>
实体被视为一个大孩子,当它看起来是一个Grand-Parent(实际上是一个图形根),其中<Test>
的孩子有<UserTest>
类型的孩子/曾<UserTest>
。
当你急于加载(和序列化?)时,你的<Exam>
应该急于加载它的<Test>
Collection,它应该加载它们的<UserTest>
集合。
通过沿着图表向下工作,您将导致整整一圈。
你的意思是否有相反的关系?
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<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; }
public virtual ICollection<Exam> Exams { get; set; }
}
我对你的数据做了很多假设。 这种关系作为真实世界的实体和您的用法更有意义。 用户有测试和考试,而不是相反。 如果是这样,这种关系应该与您的linq查询一起使用。
如果你尝试这样的事情:
var userTest = _uow.UserTests
.GetAll()
.Where(t => t.UserId == "0" || t.UserId == userId)
.First();
var name = userTest.Test.Title;
您的代码是否会抛出异常,因为尚未加载Test属性? 我怀疑问题是你的存储库使用的是LINQ to SQL而不是LINQ to Entities。 您无法使用LINQ to SQL关闭延迟加载。 在这种情况下,您必须展示存储库如何解决问题。
您是否对您的收藏品使用“虚拟”有什么共鸣? 如果您使用“包含”,我建议摆脱“虚拟”
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.