Command & Query
Concepts
Deferred Execution
Projection
Projection is a way of translating a full entity into a C# class with a subset of those properties.
It is used to create a query that selects from a set of entities in your model but returns results that are of a different type.
Example:
var customer = context.Customers
.Select(cust => new
{
Id = cust.CustomerId,
FullName = cust.FirstName + cust.LastName,
}).ToList();
In this example, the customer data is projected to an anonymous type which contains Id
and FullName
.
IQueryable vs IEnumerable
Entity States
Tracked entities can be in one of four states. The state of an entity determines how it is processed when SaveChanges is called.
- Added: The entity does not yet exist in the database. SaveChanges should insert it
- Unchanged: The entity exists in the database and has not been modified on the client. SaveChanges should ignore it
- Modified: The entity exists in the database and has been modified on the client. SaveChanges should send updates for it
- Deleted: The entity exists in the database but should be deleted. SaveChanges should delete it
- Detached: The Detached state is given to any entity that is not being tracked by the context
Graph
The main entity and all the entities that has relation with it (should be included while query)
Tracking & No-Tracking Query
EF Core tracks the entities that you retrieve.
- If an entity is tracked, any changes detected in the entity will be persisted to the database during
SaveChanges()
. -
There is an object named ChangeTracker inside each DbContext object that will store this data:
_db.ChangeTracker.Entries()
Tracking:
- By default, queries that return entity types are tracking.
- In the following example, the change to the book will be detected and persisted to the database during
SaveChanges()
.
var book = _db.Books.SingleOrDefault(b => b.BookId == 1);
book.Price = 555;
_db.SaveChanges();
No-Tracking:
- No-tracking queries are useful when the results are used in a read-only scenario.
- They're quicker to execute because there's no need to set up the change tracking information.
var blogs = _db.Books.AsNoTracking().ToList();
Command
Add
Add puts all entities in the graph into the Added state. This means they will be inserted into the database on SaveChanges.
Attach
Attach puts all entities in the graph into the Unchanged state. However, entities will be put in the Added state if they have store-generated keys (e.g. Identity column) and no key value has been set.
This means that when exclusively using store-generated keys, Attach can be used to start tracking a mix of new and existing entities where the existing entities have not changed. The new entities will be inserted while the existing entities will not be saved other than to update any necessary FK values.
Update
Update works the same as Attach except that entities are put in the Modified state instead of the Unchanged state.
This means that when exclusively using store-generated keys, Update can be used to start tracking a mix of new and existing entities where the existing entities may have some modifications. The new entities will be inserted while the existing entities will be updated.
Remove
Remove affects only the entity passed to the method. It does not traverse the graph of reachable entities.
If the entity is in the Unchanged or Modified state, indicating that it exists in the database, then it is put in the Deleted state.
If the entity is in the Added state, indicating that it has not yet been inserted, then it is detached from the context and is no longer tracked.
Remove is usually used with entities that are already being tracked. If the entity is not tracked, then it is first Attached and then placed in the Deleted state.
Range Methods
The AddRange, AttachRange, UpdateRange, and RemoveRange methods work exactly the same as calling the non-Range methods multiple times. They are slightly more efficient than multiple calls to the non-Range methods, but the efficiency gain is very small because none of these methods now automatically call DetectChanges.
Query
Include (Eager Loading)
- Will run proper join command
-
Over FK or many-to-one or one-to-one relation
_db.Books.Include(u => u.Publisher)
-
Over one-to-many or many-to-many relation
_db.Books.Include(u => u.Publisher) .Include(u => u.BookAuthors)
-
Over one-to-many or many-to-many relation with inner query
_db.Books.Include(e => e.BookAuthors.Where(p => p.Author_Id == 5))
-
Over nested relation
_db.Books.Include(u => u.Publisher) .Include(u => u.BookAuthors).ThenInclude(u => u.Author)
Include vs Join
- An Included is intended to retain the original object structures and graphs.
- A Join is needed to project a flattened representation of the object graph or to join types which are not naturally related through the graph (ie. join the customer's city with a shipping facility's city)
AsNoTracking
- Will tell to the DbContext to don't track returned entities
- Is useful for read-only queries