c# - 为什么我的代码在每个可能的点都关闭它后仍然在进行延迟加载?

标签 c# asp.net-mvc linq entity-framework asp.net-mvc-4

我想要获得具有 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 时,它是一个存储库,当我检查 dbcontextconfiguration.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。我正在使用 EF6WebAPI 但不确定这是否会有所不同,因为我在 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/

相关文章:

c# - 何时使用 BlockingCollection 以及何时使用 ConcurrentBag 而不是 List<T>?

c# - 如何按比例调整表格大小? C#

jquery - 复制 stackoverflow 问题页面中使用的标签文本框的最简单方法是什么

c# - 由于 DataGridViewImageColumn,DataGridView 在默认错误对话框中抛出异常

asp.net-mvc - 在 MVC4 中创建子对象 - 父对象的信息未传递给 Create() Controller

c# - 将 MVC 站点从本地迁移到 Azure,遇到 SQL 问题

c# - 解析字符串以进行谓词

c# - 使用 LINQ 在字符串中查找单词

c# - 如何在 Win 10 UWP 项目上找到本地 IP 地址

c# - ASP.Net MVC Core 2 - 区域路由