Entity Framework Core
Most popular .net core ORMs: EF Core and Dapper. These two ORMs are open source.
Entity Framework Core: Microsoft's official ORM. Simpler to use.
Dapper: Useful if we want to have full control on custom SQL queries, more than EF Core. Faster
One DbSet per table.
One DbContext per database, but it's possible with EF Core 6 to have multiple DbContexts (Example: one DbContext per database schema, module, etc.)
One DbContext can be hard to maintain with many tables and domain classes. Solution: https://www.entityframeworktutorial.net/code-first/move-configurations-to-seperate-class-in-code-first.aspx
Important: DbContext is supposed to be instantiated for a single unit-of-work or a single operation and then dispose the instance as soon as the operation is over
We use Pomelo.EntityFrameworkCore.MySql: it`s the most popular Entity Framework Core provider for MySQL compatible databases.
Data Annotations vs Fluent API
An Entity Framework Core model is a conceptual model of an application's domain.
- Data Annotations: Data Annotations let you actually see how your ORM is configured without digging through a multi- thousand line function. Data Annotations work across multiple ORMs.
- Fluent API: Fluent works when you can't modify the classes. Fluent offers features not exposed through DA.
Code first VS Database first
- Code First: we will first create entity classes with properties defined in it. Entity framework will create the database and tables based on the entity classes defined. So database is generated from the code. When the dot net code is run database will get created.
- Database First: In Database First approach, database and the related tables are created first. After that, you can create an entity data models using database. It is easier to create a database
In the project, we choose to use Database First method. Please refer to README.md file to setup project with this method.
One-to-One relationships and One-to-One-or-Zero
We will use as an example User and StripeCustomer that are are a One-to-One-or-Zero relation.
StripeCustomer has one User and user may have a customer.
In StripeCustomer Model:
[ForeignKey("User")]
public int? UserId { get; set; }
[DeleteBehavior(DeleteBehavior.Restrict)]
public virtual User User { get; set; }
In User Model:
public int? StripeCustomerId { get; set; }
public virtual StripeCustomer StripeCustomer { get; set; }
Important to put [ForeignKey()]
in only one of the two models.
DbContext:
Add the optional relation to User :
entity.HasOne(e => e.StripeCustomer)
.WithOne(c => c.User)
.HasForeignKey<User>(e => e.StripeCustomerId)
.IsRequired(false);
For StripeCustomer, the User is required :
modelBuilder.Entity<StripeCustomer>(entity => entity.HasOne(c => c.User)
.WithOne(u => u.StripeCustomer)
.HasForeignKey<StripeCustomer>(c => c.UserId)
.IsRequired(true)
.OnDelete(DeleteBehavior.Cascade));
One-to-Many relationships
For example, a JobCategory has many Jobs, each with one JobCategory. That relationship is represented as follows:
Job:
if nullable foreign key:
[ForeignKey("JobCategory")] public int? JobCategoryID { get; set; } [DeleteBehavior(DeleteBehavior.Restrict)] public virtual JobCategory JobCategory { get; set; }
if not nullable foreign key:
[Required] [ForeignKey("JobCategory")] public int JobCategoryID { get; set; } [DeleteBehavior(DeleteBehavior.Restrict)] public virtual JobCategory JobCategory { get; set; }
JobCategory:
public ICollection<Job> Jobs { get; set; }
DbContext:
if nullable foreign key:
modelBuilder.Entity<Job>(entity => entity.HasOne(c => c.JobCategory) .WithMany(e => e.Jobs) .HasForeignKey(p => p.JobCategoryID) .IsRequired(false) .OnDelete(DeleteBehavior.SetNull));
if not nullable foreign key:
modelBuilder.Entity<Job>(entity => entity.HasOne(c => c.JobCategory) .WithMany(e => e.Jobs) .HasForeignKey(p => p.JobCategoryID) .IsRequired(true) .OnDelete(DeleteBehavior.Cascade));
Default cascade behavior
In EF, when a relation is required between two tables and we do not specify the delete behavior (cascade, setNull or restrinct), it is cascade by default.
This is a very dangerous behavior!!
So always specify the delete behavior.
[ForeignKey("TodoList")]
public required int TodoListId { get; set; }
[DeleteBehavior(DeleteBehavior.Restrict)]
public virtual TodoList? TodoList { get; set; }
References
- Entity Framework Core
- DbContext Lifetime, Configuration, and Initialization
- Creating a Database with Code First in EF Core
- Learn Entity Framework Core
- Entity Framework Core Tutorial
- Should we split DbContext
- Entity Framework Code First vs Database First vs Model First Approach
- .NET 6.0 - Connect to MySQL Database with Entity Framework Core
- Configuring One To Many Relationships in Entity Framework Core
- One-to-one relationships