Overview of Entity Framework Core
EF Core is the official .NET data access technology. It’s an ORM (Object Relational Mapper), providing some framework for how to work with the data layer. It allows accessing Tables, Stored Procedures, Views, and more.
Microsoft.EntityFrameworkCore- just the core logic, no providers;
Microsoft.EntityFrameworkCore.SqlServer- SQL Server provider, installing it will also pull in the above package as its dependency.
Microsoft.EntityFrameworkCore.Design- a package that is needed for invoking migrations
The tables names are inferred from the
DbContext tracks changes so that they can be applied when we call
SaveChanges() on it.
SaveChanges() call applies all the requests to the database wrapped in a
transaction, so failure of some requests will not corrupt the database.
In some cases, we’re not interested in
DbContext’s tracking capabilities,
especially when building web APIs where we often fire some query to the DB and
dispose of the connection. In such cases we can enable NoTracking to improve
We can do that in a few ways:
in queries - useful when we normally want tracking, but for some specific query we don’t:
DbContext- makes all queries NoTracking by default:
in Dependency Injection registration:
In EF Core we need to explicitly provide info on which provider to use and what is the connection string. Here’s the simplest way to do it:
In a real scenario, we’d obviously not hardcode the connection string.
EF Core has a bunch of conventions that are applied by default. Here’re some of them:
<Class>Idwill become a Primary Key
for every Foreign Key, an index is created
stringtype is turned into
nvarchar(MAX)in the DB (that’s a default from SQL Server provider, not the EF Core itself)
When C#‘s Nullable feature is enabled, reference types are “not nullable” by default - the same applies to DB table’s columns
DB tables names come from
DbSet’s name or, if
DbSetwas not defined, from a class’s name.
The conventions can be tweaked with:
- Data Annotations
- Fluent API (more powerful)
Every change of data model is a prompt to do a migration. This way the database’s shape corresponds to our code model. Migrations are files and they are supposed to land in the VCS.
Migrations history is kept in the DB itself, in the “__EFMigrationsHistory” table.
dotnet ef migrations add <NAME>. It should be invoked from a
directory of a .NET project containig
When adding a migration, the file
Migrations/*ModelSnapshot.cs is loaded and
compared with the current
DbContext. Based on the difference between these, a
new migration file is generated.
A migration file’s name contains a timestamp and migrations’s name. It is a class with 2 methods:
Up- work to do when the migration is applied
Down- work to do when the particular migration is revoked
The EF Tool can either execute the migration (good in DEV), or it can generate an SQL file with all the migration steps (good for PROD) - we can then execute it later on.
Generating a script:
dotnet ef migrations script <FROM> <TO> - the SQL is
printed to stdout. We can specify the migration to start from and the one we
want to get to. By default it would generate SQL for all the migrations.
The migration tools can be installed as a NuGet package, or as a dotnet CLI tool
dotnet tool install --global dotnet-ef). The CLI tool is invoked with
Additionally, we’d need to install the
NuGet package to access the Migration APIs from code.
The “Code First” approach is recommended, but it is also possible to kind of
reverse engineer a DB into models. The EF Tool has the
scaffold subcommand for
such a usecase.
If an entity contains a collection of another entity, EF Core treats it as a one-to-many relationship.
The child entity can optionally have a property of a type of a parent - it will be a reference. The child may also have an ID property that would be a Foreign Key to the parent. EF Core will initialize these properties for us.
To create many-to-many, we just need two entities referring to each other via collections:
EF Core has a convention that understands it and it will create three tables:
- BattleSamurai - association of BattleIds with SamuraiIds (the PK will be a combination of these two FKs)
It’s called Skip Navigation, because in code we can forget about the intermediary table and just work with the two entities being related.
For simplest cases, the above is enough. For some more advanced scenarios, we need to create additional class that joins our entities together.
Here’s an example of a class joining two entites (with some metadata):
This class represents the connection between a Samurai and a Battle. It’s
similar to the SQL table that EF Core creates behind the sceness. We’ve added a
DateJoined parameter as some metadata about the connection (often called
Additionally, we have to update the
DbContext to explicitly specify the
For the simplest cases we wouldn’t have to do such configuration.
By default, the “principal” does not have to have any “dependant”, while the “dependant” has to have a “principal”.
In the DB, the “dependant” will have FK to the “principal”. The “principal” will not have FK to the “dependant”.
EF Core automatically generates
SELECT queries when accessing objects. We can
also customize the query that EF Core executes on the
The following methods allow us to query on
Single()- expects just one result to be found in the DB, throws otherwise
Last()- should be preceed by the
LastOrDefault()- should be preceed by the
Find(PK_value)- not LINQ, it’s
DbSet’s method that looks for a row with a specified key
When invoking queries on the DB, the parameters that we provide may be parametrized or not. If we use a variable to provide some parameter, SQL will be parametrized. If we provide a hardcoded string, it will not be parametrized.
By default, when pulling some entities, their related entities are not fetched. E.g., if I have a 1-to-Many relationship between samurais and quotes, if I query for the samurai, the list of quotes will be empty.
With Eager Loading and its
Include method, we can specify Navigation
Properties that should be populated with the query:
When including some child, we can filter it as well:
Here are some other sceanarios for Eager Loading:
Include children and grandchildren:
Include just grandchildren:
Include different children:
Explicit Loading allows to load related data when the “base” entity is already loaded:
Lazy Loading fetches the data from the DB as soon as we try to access some related data via a Navigation Property.
We can limit the properties to be returned with
In this case a list of anoymous objects will be returned, but we could use some known type as well.
An interesting usecase of that is to bring in just the count of some related data:
We can use SQL’s
LIKE with the
Here’s how to remove some entity
Here’s how to remove a Many-to-Many relationship:
Include above is crucial. If we fetched battles without samurais,
DbContext would not “know” that any samurais were in the battle, and the
Remove call would not have any result.
In case when we’re using a more explicit Many-to-Many relationship, with a Join table class defined, we can remove an association as follows:
We can add new rows:
We can update the entities as follows:
Since the same
DbContext instance was used to retrieve the object and to
update it, EF Core knows which properties have been updated. The SQL sent to the
DB will contain only the modified values.
In disconnected scenarios, the case is that we have to update some entity without retrieving it first (e.g., in some Web API). Then, we can do that as follows:
In such a case, since the context is fresh and does not “know” which properties have been changed, the SQL sent to the DB will contain all the properties of the entity to be updated.
Update in the disconnected scenarios with related data, EF Core is
going to send unneeded requests to the DB. Here’s an example:
In this case, not only a new quote will be created, but also samurai’s
properties will be updated (all of them). The
newContext is fresh, it did not
track anything, and it doesn’t “know” what exactly has changed. To make sure
that DB is in proper state it will send to it everything.
We can use
Attach to circumvent that.
newContext in this case) will not treat
the provided data as “Modified”. Instead, it will treat it as “Unchanged”.
However, EF Core sees that one of the quotes in the samurai does not have a
value for the
Id (PK), and for the
SamuraiId (FK). It will understand that
this data needs to be sent to the DB.
Another approach would be to add the
Quote to the DB directly, instead of
doing that via a samurai. For this to work, we need to have a FK property of
Updating Related Entity
A similar issue that we had with a single entity when updating it in a Disconnected scenario occurs if we want to update some entity with relations:
Update() call here might be very “heavy”. It will not only update the
quote, but also the samurai, and all the other quotes that it has! A fresh
DbContext instance does not know what the previous state was, so it will try
to update everything.
Attach method will not help in this case, because it
will just mark all entities as “Unmodified” and EF Core will not issue any
updates at all. The modified quote already has
SamuraiId, so the
previous solution doesn’t work. We should use the following approach:
We’re manually informing EF Core that this quote was modified. Only this one
entity will be updated, related data is left unchanged - just one
command will be sent to the DB.
EF Core 3.0 introduced the possibility to have entities without PK. Here’s how we can use it:
Create an entity:
DbSet to the
Configure the entity to be keyless in
OnModelCreating of the
In case the
SamuraiBattleStat is supposed to be a View in the DB, we need
to configure it as follows:
Without that, EF Core would create a new table.
In special cases, we can use raw SQL in EF Core. It might be more performant than relying on EF Core’s queries.
For example, we might want to have a View or a Stored Procedure in our DB. To create that, we’d create an empty migration and add SQL to that migration. Here’s an example:
Here’s an example of how we can invoke raw SQL with a
Since we used a
DbSet, the result will be mapped to the entity of that
DbSet. The entities will be tracked as well.
There is also another way to invoke SQL commands:
As a response we’ll get just a number of rows affected by the command. We could use it to execute some stored procedure that does not return data.
We can configure logging in multiple ways.
Every interaction with the
DbContext may be tagged with a comment that will be
visible on the SQL Server side.
DbContext class may be configured with a
We’ll see logs generated by EF Core in the console.
By default, all values are hidden, since they might be sensitive. We can disable that hiding with:
For apps using the Logging infrastructure, we don’t have to do anything, logs will already be there.
If at least 4 operations have been added to the
DbContext, EF Core will
execute a batch request instead of sending these operations/requests separately.