[英]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.