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

9 c# linq asp.net-mvc entity-framework asp.net-mvc-4

我想让具有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();
Run Code Online (Sandbox Code Playgroud)

当我检查_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<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; }
}
Run Code Online (Sandbox Code Playgroud)

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

[{"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",
Run Code Online (Sandbox Code Playgroud)

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

UserTest> Test> Exam> Test> UserTest

我已经努力确保延迟加载已关闭并且调试告诉我它已设置为false.我使用EF6的WebAPI,但不知道是否有差别,因为我在调试C#的水平.

Sla*_*uma 9

无论是否使用急切或延迟加载加载相关实体,都无法避免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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,在我看来,您的设计的缺陷是您尝试序列化实体而不是数据传输对象("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; }
}
Run Code Online (Sandbox Code Playgroud)

然后使用投影加载数据:

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();
Run Code Online (Sandbox Code Playgroud)

您还可以通过仅定义包含视图所需的所有属性的单个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; }
}
Run Code Online (Sandbox Code Playgroud)

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

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();
Run Code Online (Sandbox Code Playgroud)

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