Creating Records
Records are added to the database by creating them in code, adding them to their DbSet<T>, and calling SaveChanges()/SaveChangesAsync() on the context. When SaveChanges() is executed, the ChangeTracker reports all the added entities, and EF Core (along with the database provider) creates the appropriate SQL statement(s) to insert the record(s).
Reminder: SaveChanges() executes in an implicit transaction, unless an explicit transaction is used.
Entity State
When an entity is created through code but not yet added to a DbSet<T>, the EntityState is Detached. Once a new entity is added to a DbSet<T>, the EntityState is set to Added. After SaveChanges() executes, the EntityState is set to Unchanged.
Add a Single Record Using Add
var newMake = new Make { Name = "BMW" }; context.Makes.Add(newMake); context.SaveChanges();
All of the values passed into the SQL statement are parameterized to reduce the threat of scripting attacks.
Add a Single Record Using Attach
Add Multiple Records at Once
var cards = new List<Car> { new() { Color = "Yellow" }, new() { Color = "White" }, new() { Color = "Pink" }, }; context.Cards.AddRange(cars); context.SaveChanges();
The AddRange() method of a DbSet<T> property can be used to add multiple records in a single transaction.
Identity Column Considerations When Adding Records
Adding an Object Graph
Child records can be added to the database in the same call as the parent without specifically adding them into their own DbSet<T> by adding them to the collection navigation property of the parent entity.
var anotherMake = new Make { Name = "Honda" }; var car = new Car { Color = "Yellow" }; ((List<Car>) anotherMake.Cars).Add(car); context.Makes.Add(make); context.SaveChanges();
Add Many-to-Many Records
Records can be added directly from one entity to the other without going through the pivot table.
Querying Data
Querying data using EF Core is typically accomplished using LINQ queries. New in EF Core 5, you can call ToQueryString() on most LINQ queries to examine that query that gets executed.
Get All Records
To get all the records for a table, simply use the DbSet<T> property directly without any LINQ statements. For immediate execution, add ToList() to the DbSet<T> property.
IQueryable<Car> cars = context.Cars;
List<Car> cars2 = context.Cars.ToList();
Filter Records
The Where() method is used to filter records from the DbSet<T>. Multiple Where() methods can be fluently chained to dynamically build the query.
IQueryable<Car> cars = context.Cars.Where(c=>c.Color == "Yellow");
IQueryable<Car> cars2 = context.Cars.Where(c => c.Color == "Yellow" && c.PetName == "Clunker");
Sort Records
The OrderBy() and OrderByDescending() methods set the sort(s) for the query in either ascending or descending order, respectively.
IOrderedQueryable<Car> cars = context.Cars.OrderBy(c=>c.Color);
Reverse Sort Records
The Reverse() method reverses the entire sort order.
Paging
Skip() skips the specified number of records while Take() retrieves the specified number of records.
Retrieve a Single Record
There are three main methods for returning a single record with a query: First()/FirstOrDefault(), Last()/LastOrDefault(), and Single()/SingleOrDefault(). All three return a single record.
First() returns the first record that matches and throws an exception if no record is found (EF).
FirstOrDefault() instead returns the default value for the type (null) if no record is found (EF).
Single() is like First() except it also throws an exception if more than one record matches the query (EF).
Last() is like First() but it returns that last record that matches.
Using First
When using the parameter less form of First() and FirstOrDefault(), the first record (based on database order or any preceding ordering clauses) will be returned.
var firstCar = context.Cars.First();
var firstCarByColor = context.Cars.OrderBy(c => c.Color).First();
try { var firstCarNotFound = context.Cars.First(c => c.Id == 27); } catch (InvalidOperationException ex) { Console.WriteLine(ex.Message); }
Using Last
try { context.Cars.Last(); } catch (InvalidOperationException ex) { Console.WriteLine(ex.Message); }
Using Single
Using Find
Aggregation Methods
Any() and All()
Getting Data from Stored Procedures
Querying Related Data
Entity navigation properties are used to load an entity's related data. The related data can be loaded eagerly (one LINQ statement, one SQL query), eagerly with split queries (one LINQ statement, multiple SQL queries), explicitly (multiple LINQ calls, multiple SQL queries), or lazily (one LINQ statement, multiple on-demand SQL queries).
EF Core will automatically fix up entities as they are loaded into the Change Tracker. If all Make records are loaded into the DbSet<Make> collection property (example) and all Car records are loaded into DbSet<Car>, then they will be accessible to each other through navigation properties.
Eager Loading
Filtered Include
Eager Loading with Split Queries
Many-to-Many Queries
You can write the following LINQ statement to get the Car and related Driver records:
var carsAndDrivers = context.Cars.Include(x => x.Drivers).Where(x=>x.Drivers.Any());
Explicit Loading
Lazy Loading
Updating Records
Records are updated by loading them into DbSet<T> as a tracked entity, changing them through code, and then calling SaveChanges(). When SaveChanges() is executed, the ChangeTracker reports all of the modified entities, and EF Core (along with the database provider) creates the appropriate SQL statement(s).
Entity State
When a tracked entity is edited, EntityState is set to Modified. After the changes are successfully saved, the state is returned to Unchanged.
Update Tracked Entities
Updating a single record is much like adding a single record, except that the initial record is retrieved from the database and not created through code. Load the record from the database into a tracked entity, make some changes, and then call SaveChanges().
var car = context.Cars.First(); car.Color = "Green"; context.SaveChanges();
Update Nontracked Entities
EF Core must be notified that the entity should exist in the database and needs to be updated.
There are two ways, and either way, SaveChanges() must be called for the values to persist.
context.Cars.Update(updatedCar);
context.Entry(updatedCar).State = EntityState.Modified;
Deleting Records
One or more entities are marked for deletion by calling Remove() (for a single entity) or RemoveRange() (for a list of entities) on the appropriate DbSet<T> property or by setting the state for the entity/entities to Deleted. The removal process will cause cascade effects on navigation properties based on the rules configured in OnModelCreating() (or by EF Core conventions).
Entity State
Article notes
What is the EntityState of an entity that has been created through code but has not yet been added to a DbSet<T>?
Detached
What method (when called) corresponds with ChangeTracker reporting all of the added entities and EF Core creating the appropriate SQL statements?
SaveChanges()
What is the EntityState of an entity after SaveChanges() executes?
Unchanged
What is the method of a DbSet<T> property that adds a single record?
Add()
What adding an object graph by adding child records into the parent's collection navigation property, does EF Core automatically retrieve the parent record's primary key identifier to include in the Insert statement for the foreign key id of the child record?
Yes
What is the method of a DbSet<T> property that adds multiple records?
AddRange()
What method can be called on most LINQ queries being used in EF Core to examine the query that gets executed?
ToQueryString()
What method is used to filter records from a DbSet<T>?
Where()
When you add child records to the database by putting them in the collection navigation property of their parent (EF) what is a cool term for what you are adding to the database?
An object graph
What method can you call on most LINQ queries to examine the actual query that gets executed against the database (new in EF Core 5)?
ToQueryString()
What method is used to filter records from a DbSet<T> in EF Core?
Where()
What term refers to loading related records from multiple tables in one database call?
Eager loading
Which of the two methods that provide paging capabilities in EF Core skips the specified number of records?
Skip()
Which of the two methods that provide paging capabilities in EF Core retrieves the specified number of records?
Take()