Mohamad Lawand
Posted on March 2, 2021
In this article I will be explaining to you what is Entity Framework, what benefits does it provide, will do a quick overview and then we will discuss the new features in .Net 5
So the Agenda for today:
- What do we need?
- What is Ef Core
- Some of the benefits of Ef Core
- Overview of Ef Core
- New Features in Ef 5
- Demo New Features
You can watch the full video on Youtube
If you find this video helpful, please like, share and subscribe as it will really help the channel.
Before we start you will need to have
- Dotnet SDK (https://dotnet.microsoft.com/download)
- Visual Studio Code (https://code.visualstudio.com/)
Source code:
https://github.com/mohamadlawand087/v18-EfCore5
What is Entity Framework?
Entity framework core is a data access API, its an object relational mapper (ORM). It can be used cross platform across different technologies from AspNetCore, WPF, Xamarin, Blazor, Winform.
It has support to many different database technologies:
- SQL Server
- SQLite
- PostgreSQL
- MySQL
- Azure Cosmos
Benefits of Ef
- One of the main benefits of Ef Core is using LINQ (Language Integrated Language)
- It has UOW (Unit of Work) implementation out of the box
- it will track changes
- handle concurrency
- Data binding friendly
Getting Started
dotnet tool install --global dotnet-ef
dotnet new mvc -n "EfCoreDemo" -au none
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
Lets create a customer class inside our Models folder
public class Customer
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Email { get; set; }
public int PhoneNumber { get; set; }
}
Ef Core will be able to figure out automatically that our primary key will be the Id and since it's an Int it will make it auto increment by default.
Then it will look at the data annotation that we have added to every field and get more information on how the database should be setup, for example we have a Required annotation on Name and Email, Ef core will automatically assign not null option in the database based on these annotation.
Now lets create our ApplicationDbContext which is the main part of having Ef core integrated in our application.
Let us create a new folder in the root of our application and add a class to it called ApplicationDbContext.
We inherit from the DbContext class which is the main component in Ef Core
// DbContext is where the magic happens
public class ApplicationDbContext : DbContext
{
// a Db set is where we tell entity framework where to map a class (entity) to a table
public DbSet<Customer> Customers { get; set; }
// This is the run time configuration of
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{ }
// ModelBuilder is the fluent mapping
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>()
.Property(a => a.RowVersion)
.IsRowVersion(); // Cuncurrency property using fluent mapping
base.OnModelCreating(modelBuilder);
}
}
Now lets setup our Startup class to inject Ef core middleware
// We are adding the DI services - We setup a Db Context
services.AddDbContext<ApplicationDbContext>(options =>
// Adding configuration to use Sqlite with our application
options.UseSqlite( // adding the connection string path to our db
Configuration.GetConnectionString("DefaultConnection")));
As well we need to add the connection string to the AppSettings.json
"ConnectionStrings": {
"DefaultConnection": "DataSource=app.db;Cache=Shared"
}
Now lets run the migrations and update the database
dotnet ef migrations add "Initial Migration"
dotnet ef database update
Let us now write the code to get all customer data from the database, inside our HomeController we can add the following to the Index action
private readonly ILogger<HomeController> _logger;
private readonly ApplicationDbContext _context;
public HomeController(ILogger<HomeController> logger, ApplicationDbContext context)
{
_logger = logger;
_context = context;
}
public async Task<IActionResult> Index()
{
var customers = await _context.Customers
.Where(x => x.Name.Contains("a"))
.OrderBy(o => o.Name)
.ToListAsync();
return View(customers);
}
New Features
Now that we have covered a quick overview of Ef core and its features, let us now discuss some of the new changes that came to Ef Core 5
Debug:
Debug view when running the debugger, we can see when hovering over the query a debugView option
So we can see the Query field is a SQL representation of our LINQ query which we can copy directly from there and run it in a SQL management studio to test the output of the query and debug any issue we might face
Another we can get our Query is by using the extension method on the query
.ToQueryString()
// the ToQueryString will generate the SQL code so we can test and debug issues
// without the need to be in debug mode
var sqlStr = customers.ToQueryString();
Console.WriteLine(sqlStr);
The last option is enabling general logs across our app, inside our startup class where we added the ApplicationDbContext in the DbContextOptions we can enable logging from there
services.AddDbContext<ApplicationDbContext>(options =>
// Adding configuration to use Sqlite with our application
options.UseSqlite( // adding the connection string path to our db
Configuration.GetConnectionString("DefaultConnection"))
// Enable the log level and where do we want to output the logs
.LogTo(Console.WriteLine, LogLevel.Information));
Mapping:
Many-to-Many enhancements, what is many to many relationships. Lets say we have a table of customers and a table for groups Many-To-Many relationship means one user can belong to different groups and the same group can have multiple users
The way its done in a database we create a table in the middle between the 2 tables that have the Many-To-Many relations and we call that table the Join Table in our case the Join Table is the CustomerGroup table. And every row inside that table will link 1 customer to 1 group.
Lets see this in code how it was implemented previous to .Net 5 and then we can see how we can update it to take advantage of .Net 5 features , we already have a customer table lets add the 2 other tables and do create the Many-To-Many relationships
Inside the Models folder lets add 2 classes Group and CustomerGroup
public class Group
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<CustomerGroup> Groups {get;set;}
}
Customer group (Join Table)
public class CustomerGroup
{
public int Id { get; set; }
public Customer Customer { get; set; }
public Group Group { get; set; }
}
And we need to update Customer class to the following
public ICollection<CustomerGroup> Groups {get;set;}
The next step is to update the ApplicationDbContext
public DbSet<Group> Groups { get; set; }
public DbSet<CustomerGroup> CustomerGroups { get; set; }
Lets create our migration scripts so our database will be updated
dotnet ef migrations add "Added M2M relationships"
dotnet ef database update
Now let see how we can update this code into .Net 5, the first thing we are going to do is delete the CustomerGroup Table and then we need to do some updates on the Customer and Group Model.
For the customer Model
public ICollection<Group> Groups {get;set;}
For the Group Model
public ICollection<Customer> Customers {get;set;}
We can see here that we have removed the JoinTable and we have delegated the work to EF Core to create and manage the JoinTable for us.
Another update we need to do is to our LINQ query since we don't have have the CustomerGroup table anymore the query will look much simpler and easier to read
var customersElec = _context.Customers.Where(x => x.Groups.Any(g => g.Name == "Electronics"));
So how did this magic happen, EF Core in the background analysed the models that we have and created the JoinTable for us and took control of managing it.
Inheritance Mapping
Its a hierarchy of .Net type base class, sub class and we want to map those to a relational database
Lets us add additional models to see how we used to do before .Net 5 and then we can see how its implemented with the new features in .Net 5
Inside our Models folder lets add 2 new models
public class VipCustomer : Customer
{
public string VipTeir { get; set; }
}
public class CorporateCustomer : Customer
{
public string CompanyName { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
We need to update our ApplicationDbSettings to know about these new models, inside our OnModelCreating method we add the following
// This will inform Ef Core that we want to map these tables
modelBuilder.Entity<VipCustomer>();
modelBuilder.Entity<CorporateCustomer>();
Let us add our migration scripts
dotnet ef migrations add "Adding 2 tables"
dotnet ef database update
If we look at our database we see that instead of having 3 tables that represent the hierarchy that we wanted we have a single table Customer with all the fields inside of it. This method of implementation is called TPH - table per hierarchy.
The way the Ef core differentiate between which row for which table is via the Discriminator field. This is feature has been available in Ef core since previous versions now in EF Core 5 lets see how we can do the separation.
Let us update our ApplicationDbContext
modelBuilder.Entity<VipCustomer>().ToTable("VipCustomers");
modelBuilder.Entity<CorporateCustomer>().ToTable("CorporateCustomers");
Let us add our migrations now
dotnet ef migrations add "Adding 2 tables"
dotnet ef database update
Now if we look at our database the result are really different instead of having 1 big table which represent the 3 tables we can see we have 3 tables, each table representing the model that we have this is called TPT - table per type. So now the customer is spread across different table since we have implemented TPT format.
This implementation is simple to understand as every model has a table which represent it, however the performance on the database could take a hit as every time we need to query a customer joins between tables needs to happen, and joins are heavy operations on the database. For example if we have 6 table hierarchy there would be 6 joins which is a very heavy performance query.
Although this feature has been released the recommended way is still to use TPH by default.
Thank you for reading.
Posted on March 2, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.