我想要获得具有 UserTest 实体的考试和测试实体,其 UserId 等于“0”或等于提供的值。我有很多建议,但到目前为止都没有奏效。一个建议是从获取 UserTest 数据开始,另一个解决方案是从获取 Exam 数据开始。这是我使用 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
。
这是我的类(class):
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
对象,然后获取了一个测试对象,然后是一个考试对象。然而,exam 对象包含一个测试集合,然后它再次返回并获取不同的测试和其中的单元测试:
用户测试
> 测试
> 考试
> 测试
> 用户测试
?
我已努力确保延迟加载已关闭,调试告诉我它已设置为 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;
}
}
}
但是,在我看来,您设计的缺陷在于您尝试序列化实体,而不是专门针对 View 的数据传输对象(“DTO”)您要将数据发送到的位置。通过使用 DTO,您可以避免完全不想要的反向导航属性以及 View 中不需要的其他实体属性。您将定义三个 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();
您还可以通过仅定义一个包含 View 所需的所有属性的单个 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 数据一起序列化。
关于c# - 为什么我的代码在每个可能的点都关闭它后仍然在进行延迟加载?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22626976/