簡體   English   中英

為什么即使我在每個可能的點關閉它之后我的代碼都在進行延遲加載?

[英]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時它是一個存儲庫,當我檢查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; }
}

當我看到輸出時,我看到這樣的事情:

[{"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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM