How EF Core works?
EF Core 8 uses a metadata model to describe how entity types are mapped to the underlying database. This model is built using entity classes, a context object, set of conventions and additional configurations. The configurations could be from data annotation attributes or done using the fluent API.
When the model is built the mapping between the entity models and the underlying tables is created.
The meta model can later be used to query and save/delete the data from the database.
Querying data
The meta-model has its own implementation of LINQ methods. These LINQ methods are used to write C# strongly typed queries. EF Core passes the LINQ query to the database provider. The database provider translates the queries to database specific script. When the results of the LINQ query are consumed the queries are then executed against the database. Finally the tabular result set (this is the set returned from the database as rows) is materialised into C# objects. Finally EF Core will add tracking details to the DBContext instance.
There are a number of ways to force query execution.
- Iterating the LINQ result is one of them.
- Using operators such as ToList, ToArray, Single, SingleOrDefault, First, FirstOrDefault, Count.
Saving data
EF Core has 2 ways for adding, modifying and removing data:
1. Tracking changes and SaveChanges()
Often when working with data, we need to pull the data from the database, make some modifications on it and save the modifications.
Let's look at this example:
using (var context = new StoreContext())
{
var store = context.Stores.Single(s => s.Name == "Example Store");
store.Name = "Updated Example Store";
context.SaveChanges();
}
How EF Core works:
- An Entity record is pulled from the database.
- An Entity record property values is modified.
- SaveChanges() is called. What SaveChanges does under the hood is compare the Entity object with a copy of the object from the moment it was loaded. If it finds any differences, it will create an update statement and execute it.
In the case of modification we don't have to explicitly notify the dbContext for the changes that were made. These changes will be pick up automatically. When we want to add the entity we need to notify the change tracker for the addition. This is done by calling DbSet<TEntity>.Add. The same is true when we want to remove an entity. In this case we should call DbSet<TEntity>.Remove. Both Add and Remove methods will change the entity State to EntityState.Added or EntityState.Deleted
context.Entry(supermarket).State = EntityState.Added;
context.Entry(supermarket).State = EntityState.Deleted;
2. ExecuteUpdate and ExecuteDelete ("bulk update")
Sometimes you may need to do an update or delete on multiple records at once. If you use the first approach you must first pull each one of the records and then update them one by one. This could be time consuming and inefficient. In this case you can use the bulk update or bulk delete functionality. This will create only one sql statement that will be executed at once and not tacking of the object will be done inside the dbContext.
public class Store
{
public int Id { get; set; }
public string Name { get; set; }
public int OpenHours { get; set; }
public bool IsDeleted { get; set; }
}
public class StoreContext : DbContext
{
public DbSet<Store> Stores { get; set; }
}
public class StoreDataAccess
{
public StoreContext Context { get; }
public StoreDataAccess(StoreContext context)
{
Context = context;
}
public void UpdateStores()
{
Context.Stores
.Where(s => s.OpenHours < 24) // Find stores with less than 24 open hours
.ExecuteUpdate(setters => setters
.SetProperty(s => s.IsDeleted, false) // Set IsDeleted to false
.SetProperty(s => s.OpenHours, 24)); // Set OpenHours to 24
}
}
public void ExecuteUpdateExample()
{
var context = new StoreContext();
var storeDataAccess = new StoreDataAccess(context);
storeDataAccess.UpdateStores();
}
UPDATE [s]
SET [s].[OpenHours] = 24,
[s].[IsDeleted] = CAST(0 AS bit)
FROM [Stores] AS [s]
WHERE [s].[OpenHours] < 24