簡體   English   中英

DbSet 到底是如何工作的?

[英]How does DbSet actually work under the hood?

我很困惑 DbSet 如何在實體框架中設置值。

假設我在 DataContext 類(我們自己的類繼承自 DbContext 類)中有如下代碼片段?

public DbSet<Values> Values {get; set;} 

然后我實際上可以通過依賴注入的控制器從表(值表)中檢索值,如下所示。 現在我的問題是 DbSet 如何設置值? 在從中檢索值之前,我看不到 values 屬性是如何設置的。

this.context.Values.FirstOrDefaultAsync(list => list.id == id);

在談論 DbSet 的工作原理時,有兩個主要問題:

  • 查詢數據:使用接口IQueryable<...>
  • 更改數據:添加/刪除/更新

可查詢

實現IQueryable<TSource>的類的對象不代表相似項的序列,它代表獲得相似項的可枚舉序列可能性

為此,一個 IQueryable 有兩個屬性:一個Expression和一個Provider 表達式以某種通用格式保存有關必須獲取哪些數據的信息,提供程序知道誰必須獲取數據(通常是數據庫管理系統 DBMS),以及使用什么語言與 DBMS 通信(通常是 SQL)。

在最低級別,要獲取可枚舉序列,您需要調用IQueryable.GetEnumerator() 這會將表達式發送給提供程序,提供程序將表達式轉換為 DBMS 理解的格式。 提供者將執行查詢並將獲取的數據作為Enumerable<TResult>

要訪問獲取的項目,您需要反復調用MoveNext() ,只要它返回 true,就可以使用屬性Current獲取獲取的 TResult。

為了訪問數據庫,DbSet 對象知道它屬於哪個 DbContext。 DbContext 有一個屬性Database ,它可以執行翻譯后的 SQL 語句。

大多數人很少使用 GetEnumerator / MoveNext / Current ,他們使用foreach ,其內部使用此方法枚舉元素。

如果您仔細觀察 LINQ,您會發現有兩組 IQueryable 方法。 那些返回IQueryable<...>和其他的。

返回IQueryable<...>的方法不會執行查詢。 這些方法使用延遲執行或延遲執行:僅更改表達式。 這些方法速度很快,幾乎不需要任何處理能力。

其他方法,如 ToList() / ToDictionary() / FirstOrDefault() / Sum() / Any() 將實際執行查詢:將表達式發送到數據庫並相應地返回獲取的數據。

ToList將是這樣的:

List<TSource> ToList<TSource>(this IQueryable<TSource> source)
{
    List<TSource> result = new List<TSource>();
    using (IEnumerator<TSource> enumerator = source.GetEnumerator())
    while (enumerator.MoveNext())
    {
        // There is still an item; add it to the list
        TSource item = enumerator.Current;
        result.Add(item);
    }
    return result;
}

任何

bool Any<TSource>(this IQueryable<TSource> source)
{
    using (IEnumerator<TSource> enumerator = source.GetEnumerator())
    {
        return enumerator.MoveNext(); // I only need to know if I can get the first element
    }
}

您現在應該能夠理解,如果您仍然有IQueryable<...>並處理上下文,為什么您的代碼不起作用:表達式已創建,但尚未執行。 處理 dbContext 后,無法再次打開與數據庫的連接:

IQueryable<Customer> newYorkCustomers;
using (var dbContext = new CustomerDbContext(...))
{
    newYorkCustomers = dbContext.Customers.Where(customer => customer.City == "New York");
}

var result = newYorkCustomers.ToList();
// Expect exception: DbContext is disposed

你現在也應該明白,如果你有一個查詢想要多次枚舉,明智的做法是將它設為 List,否則查詢將被執行兩次

更改數據庫中的項目

每個 DbSet 都知道它在哪個 DbContext 中。 每個 DbContext 都有一個ChangeTracker ,它跟蹤所有獲取的項目以及對它們所做的所有更改。

如果使用Find查找項目,或使用查詢獲取完整項目,則這些項目的原始值存儲在 ChangeTracker 中。

changeTracker 包含所有三個客戶和客戶 1。 您可以使用以下代碼訪問它們:

using (var dbContext = new CustomerDbContext(...))
{
    var customersWithoutOrders = dbContext.Customers
        .Where(customer => !customer.Orders.Any())
        .ToList();
    Customer customer = dbContext.Customers.Find(1);
    var changeTracker = dbContext.ChangeTracker;
    var fetchedCustomers = changeTracker.Entries<Customer>();

fetchedCustomers 將為每個沒有訂單的客戶和客戶包含一個DbEntityEntry

每個DbEntityEntry為每個屬性保存原始值和當前值。 如果您向 DbEntityEntry 詢問其狀態,它將檢查原始值和當前值以確定它是否已更改。

從數據庫中添加/刪除項目

如果您想從數據庫中刪除一個項目,您首先必須獲取它。 這確保它也在 DbChangeTracker 中。

dbContext.Customers.Remove(fetchedCustomer);

這會將 fetchedCustomer 的 DbEntityEntry 的 State 設置為Deleted

添加的項目也在 DbChangeTracker 中。 它們的狀態等於已Added

DbContext.SaveChanges

SaveChanges 將獲取所有 DbEntityEntries 以查看添加/刪除/更改了哪些項目,並將執行所需的 SQL 語句。

因為 DbEntityEntry 知道每個屬性的原始值和當前值,所以它知道要更新哪些值。

如果您克隆 Entity Framework Core 源代碼,您將看到該過程是如何工作的。 您沒有“看到”屬性設置的位置是因為該代碼隱藏在Microsoft.EntityFrameworkCore.dll庫中。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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