简体   繁体   中英

How does DbSet actually work under the hood?

I am confused how DbSet is set the value in Entity Framework.

Let's say I have a code snippet as following in DataContext class (our own class inherited from DbContext class)?

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

Then I can actually retrieve values from the table (Values table) via the controller with dependency injection as following. Now my question is how DbSet is set values? I don't see how values property is set before retrieving the values from it.

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

There are two major issues when talking about how a DbSet works:

  • Querying data: use interface IQueryable<...>
  • Change the data: Add / Remove / Update

IQueryable

An object of a class that implements IQueryable<TSource> does not represent a sequence of similar items, it represents the potential to get an enumerable sequence of similar items .

To do this, an IQueryable has two properties: an Expression and a Provider . The Expression holds in some generic format information about what data must be fetched, the Provider knows who must fetch the data (usually a Database Management Systems DBMS), and what language is used to communicate with the DBMS (usually SQL).

At its lowest level, to get the enumerable sequence, you need to call IQueryable.GetEnumerator() . This will send the Expression to the Provider, who will translate the Expression into a format that the DBMS understands. The provider will execute the query and return the fetched data as an Enumerable<TResult> .

To access the fetched items, you repeatedly call MoveNext() , and as long as it returns true, you can use property Current to get the fetched TResult.

To access the database, the DbSet object knows which DbContext it belongs to. The DbContext has a property Database , which can execute the translated SQL statement.

Most people will seldom use GetEnumerator / MoveNext / Current, they use foreach , which deep inside uses this method to enumerate the elements.

If you look closely to LINQ you'll see that there are two groups of IQueryable methods. Those that return an IQueryable<...> and the others.

The methods that return IQueryable<...> won't execute the query. These methods use delayed execution, or lazy execution: Only the expression is changed. Those methods are fast, they hardly cost any processing power.

The other methods, like ToList() / ToDictionary() / FirstOrDefault() / Sum() / Any() will actually execute the query: the Expression is sent to the database and the fetched data is returned accordingly.

ToList will be something like this:

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;
}

Any

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
    }
}

You should be able to understand by now, why your code doesn't work if you still have an IQueryable<...> and dispose the context: the Expression has been made, but it hasn't been executed yet. After disposing the dbContext the connection to the database can't be opened again:

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

You should also understand by now, that if you have a query which you want to enumerate several times, that it is wise to make it a List, otherwise the query will be executed twice

Change items that are in the database

Every DbSet knows in which DbContext it is. Every DbContext has a ChangeTracker , who keeps track of all fetched items and all changes made to them.

If you use Find to find an item, or use a query to get complete items, the original values of these items are stored in the ChangeTracker.

The changeTracker contains all three cUstomer and customer1. You can access them using the following code:

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 will contain a DbEntityEntry for every customer without orders and for customer.

Every DbEntityEntry holds for every property the original value and the current value. If you ask the DbEntityEntry for its state, it will check the original and the current value to determine if it has been changed.

Add / Remove items from the database

If you want to remove an item from the database, you'll first have to fetch it. This ensures that it is also in the DbChangeTracker.

dbContext.Customers.Remove(fetchedCustomer);

This will set the State of the DbEntityEntry of the fetchedCustomer to Deleted .

Added items are also in the DbChangeTracker. They have a state equal to Added

DbContext.SaveChanges

SaveChanges will fetch all DbEntityEntries to see which items are Added / Removed / Changed, and will execute the required SQL statements.

Because the DbEntityEntry knows the original value as well as the current value of each property, it knows which values to update.

If you clone the Entity Framework Core source code, you will see how the process works. The reason you don't "see" where the properties are set is because that code is hidden away in the Microsoft.EntityFrameworkCore.dll library.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM