04 - Operations
Saving data
Add Data
Use the DbSet.Add method to add new instances of your entity classes. The data will be inserted in the database when you call SaveChanges.
The Add, Attach, and Update methods all work on the full graph of entities passed to them, as described in the Related Data section. Alternately, the EntityEntry.State property can be used to set the state of just a single entity.
For example: context.Entry(blog).State = EntityState.Modified
Updating data
EF will automatically detect changes made to an existing entity that is tracked by the context. This includes entities that you load/query from the database, and entities that were previously added and saved to the database.
Simply modify the values assigned to properties and then call SaveChanges.
Deleting data
Use the DbSet.Remove method to delete instances of your entity classes.
If the entity already exists in the database, it will be deleted during SaveChanges. If the entity has not yet been saved to the database (that is, it is tracked as added) then it will be removed from the context and will no longer be inserted when SaveChanges is called.
Multiple Operations in a single SaveChanges
You can combine multiple Add/Update/Remove operations into a single call to SaveChanges
For most database providers, SaveChanges is transactional (atomic). This means all the operations will either succeed or fail and the operations will never be left partially applied.
Related data
Adding a graph of new entities.
- If you create several new related entities, adding one of them to the context will cause the others to be added too
- If you reference a new entity from the navigation property of an entity that is already tracked by the context, the entity will be discovered and inserted into the database.
- If you change the navigation property of an entity, the corresponding changes will be made to the foreign key column in the database.
Removing relationships
- You can remove a relationship by setting a reference navigation to null, or removing the related entity from a collection navigation.
- Removing a relationship can have side effects on the dependent entity, according to the cascade delete behavior configured in the relationship.
- By default, for required relationships, a cascade delete behavior is configured and the child/dependent entity will be deleted from the database. For optional relationships, cascade delete is not configured by default, but the foreign key property will be set to null.
Cascade delete
EF Core implements several different delete behaviors and allows for the configuration of the delete behaviors of individual relationships. EF Core also implements conventions that automatically configure useful default delete behaviors for each relationship based on the requiredness of the relationship.
There are three actions EF can take when a principal/parent entity is deleted or the relationship to the child is severed:
- The child/dependent can be deleted
- The child's foreign key values can be set to null
- The child remains unchanged
Optional relationships (nullable foreign key)
| Behavior Name | Effect in memory | Effect in DB |
|---|---|---|
| Cascade | Entities are deleted | Entities are deleted |
| ClientSetNull (default) | Foreign key properties are set to null | None |
| SetNull | Foreign key properties are set to null | Foreign key properties are set to null |
| Restrict | None | None |
Required relationships (non-nullable FK)
| Behavior Name | Effect in memory | Effect in DB |
|---|---|---|
| Cascade (Default) | Entities are deleted | Entities are deleted |
| ClientSetNull | SaveChanges throws | None |
| SetNull | SaveChanges throws | SaveChanges throws |
| Restrict | None | None |
If you have entities that cannot exist without a parent, and you want EF to take care for deleting the children automatically, then use Cascade.
- Entities that cannot exist without a parent usually make use of required relationships, for which Cascade is the default.
If you have entities that may or may not have a parent, and you want EF to take care of nulling out the foreign key for you, then use ClientSetNull
- Entities that can exist without a parent usually make use of optional relationships, for which ClientSetNull is the default.
- If you want the database to also try to propagate null values to child foreign keys even when the child entity is not loaded, then use SetNull. However, note that the database must support this, and configuring the database like this can result in other restrictions, which in practice often makes this option impractical. This is why SetNull is not the default.
If you don't want EF Core to ever delete an entity automatically or null out the foreign key automatically, then use Restrict. Note that this requires that your code keep child entities and their foreign key values in sync manually otherwise constraint exceptions will be thrown.
Remove cascade delete totally (my personal choice)
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// disable cascade delete
foreach (var relationship in builder.Model
.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
{
relationship.DeleteBehavior = DeleteBehavior.Restrict;
}
}
Configuring per relationship
Data Annotations – No
Fluent API:
modelBuilder.Entity<Post>()
.HasOne(p => p.Blog)
.WithMany(b => b.Posts)
.OnDelete(DeleteBehavior.Cascade);
Providers
MS providers
- SqlServer, SQLite, InMemory
- Cosmos DB
- Oracle - sample
3rd party
- PostgreSQL - Npgsql.EntityFrameworkCore.PostgreSQL
- MySql/MariaDB - Pomelo.EntityFrameworkCore.MySql
- Oracle - Oracle.ManagedDataAccess.Core
- MySql/MariaDb – Oracle, has problems with Booleans (Boolean is not a datatype in MySql – needs conversion)
Postgres in docker
docker-compose.yml
services:
postgres:
container_name: "postgres"
# https://github.com/baosystems/docker-postgis/pkgs/container/postgis
image: ghcr.io/baosystems/postgis:16
command: postgres -c 'max_connections=1000'
restart: unless-stopped
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
ports:
- "5432:5432"
volumes:
- postgres-data:/var/lib/postgresql/data
- ./pg-dump:/var/lib/postgresql/dump
volumes:
postgres-data:
networks:
default:
name: infra
#!/bin/sh
docker compose --project-name local-dev-infra --file docker-compose.yml up --build --remove-orphans --detach
Self preparation QA
Be prepared to explain topics like these:
- How does EF Core change tracking work for updates? — When entities are loaded via a tracked query, EF stores original values. On
SaveChanges(), EF compares current values to originals and generates UPDATE statements only for changed columns. - Why is
SaveChangestransactional? —SaveChangeswraps all pending operations in a single database transaction. Either all operations succeed or all fail. - What is cascade delete and when should you use Cascade vs. Restrict? — Cascade deletes dependents when the principal is deleted. Use Cascade for entities that cannot exist without a parent. Use Restrict to prevent accidental deletion.
- What happens when you set a navigation property to null or remove from a collection? — For required relationships, the dependent is marked for deletion. For optional relationships, the FK is set to null. The operation happens on
SaveChanges(). - Why might you disable cascade delete globally? — Cascade delete can cause unexpected data loss in complex object graphs. Disabling it forces developers to handle deletions explicitly and intentionally.
- What database providers are available and why does the choice matter? — SQL Server, PostgreSQL, MySQL/MariaDB, SQLite, and InMemory. Each has different type mappings, SQL dialect, and feature support. InMemory does not support transactions.