Entity Framework Core Code First: Introduction, Best Practices, Repository Pattern, Clean Architecture
Introduction
This article is the resulting notes I took from a customer project on creating a database from scratch using Entity Framework Core
Please do comment if you see something wrong or if you know a better way of doing things :)
Happy coding!
Agenda
- Basics
- Migrations
- Annotations
- Fluent API
- Using Entity Framework Core in code
- Further topics
- Repository Pattern & Clean architecture
- Tips and Tricks
- Errors and solutions
Repo
Basics
How to work with databases in C#?
C# uses what is a called an Object Relational Mapper, that basically is an interface between the database and your code.
It can be used for all types of databases, SQL, NoSQL, Graph…
What is an Object Relational Mapper (ORM)?
Entity Framework Core is one of the many ORMs used to manage data.
- Interface between code and database
- Generate a database based on models in code (C# classes)
- Removes the need to use SQL and manual mapping
- Secure the connection between the database and the code
- Automates repetitve tasks
Should I use Entity Framework or Entity Framework Core?
After EF x6 was released, all active new developments happen only with EF Core only, so please use EF Core.
“Entity Framework Core is the only actively developed version of Entity Framework and we recommend using it for all new code.” Microsoft Doc
What is the difference between Code First and Database First?
- Code First takes C# code and translates it into a Database
- Database First takes a database and translates it into C# Code
How does Entity Framework Core Code First work?
- 1/ First Code the model in C# (Classes objects)
- 2/ Describe table keys (with annotations or fluentapi)
- 3/ Create the DbContext
- 4/ Setup the connection with the database
- 5/ Install the packages
- 6/ Then Use EF Migrations to create or update the database
- 7/ Finally, use the DbContext in the code to interact with the database
1/ Creating the Models
Each class is its own table in the database.
Good practice: keep the class name singular (e.g. Blog)
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
public int Rating { get; set; }
public List<Post> Posts { get; set; }
}
2/ Creating the DbContext
The DbContext is the client that interacts with the Database
public class BloggingContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
}
}
What is a DbSet?
DbSet creates a table from your class Blog
Best practice: plural name your table (e.g. Blogs)
3/ Set Up the Connection String for testing
Your connection string, for testing (for production use managed identity) you can set it up in your DbContext as:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(@"Server=DVT-CHANGEMENOW\SQLEXPRESS;Database=CodingWiki;TrustServerCertificate=True;Trusted_Connection=True;");
}
- Server= servername
- DataBase= databasename
4/ Migrations
First migration: How to set primary key in EF Core?
For your migrations, the model needs a primary key (PK)
Use the [Key] data annotation to do so (System.ComponentModel.DataAnnotations;)
public class Book
{
[Key] // Sets primary key for IDBook
public int IDBook { get; set; }
public string Title { get; set; }
public string ISBN { get; set; }
public double Price { get; set; }
}
The other way to do it, is simply having a numerical field as “Id” (not case sensitive). Or just having the field ends with Id and starts with class name.
EF Core will automatically set Id as the PK
public class Book
{
public int Id{ get; set; } // automatically sets as PK by EF Core
public string Title { get; set; }
public string ISBN { get; set; }
public double Price { get; set; }
}
This also works
public class Book
{
public int BookId{ get; set; } // automatically sets as PK by EF Core
public string Title { get; set; }
public string ISBN { get; set; }
public double Price { get; set; }
}
5/ Entity Framework Core Packages Explained
- Microsoft.EntityFrameworkCore.SqlServer: Your main package to create the context and work with EF Core (already includes microsoft.entityframeworkcore package)
- Microsoft.EntityFrameworkCore.Tools: gives us the ability to use the dotnet ef command line tool + includes microsoft.entityframeworkcore.design package
- Microsoft.EntityFrameworkCore.Design: for the Web project (the starting project)
6/ Entity Framework Core migration
Once your model and bdContext are built, it’s time to migrate them to the database. For this we use migrations.
Make sure that the default project is where your DbContext is at
In the package manager console, write the commands
Create Migration
- add-migration nameofthemigration
add-migration AddBookToDb
Now you have your first migration, see automatically created ‘Migrations’ folder
Apply Migration
After creating a migration, it must be applied in the database
update-database
For the first migration, it will actually first create the database, then create SQL based on the migration and apply it.
You can now see the database in SSMS
7/ Use DbContext in your code
Example add a new product to the database
var newProduct = new Product { Name = "Laptop", Price = 999.99 };
dbContext.Products.Add(newProduct); // Add your product in the Db
dbContext.SaveChanges(); // Always save your changes!
Conclusion
This is the conclusion to the basic guide. You can see how easy it is to work with Entity Framework Core.
I am sure this has also left you with many questions, so the below sections will try to answer those.
Before going through the details, here are some tips
- Migration
If a migration or an update-database creates an error, ALWAYS first remove the migration and then create a new one with the corrections made in the code.
Make small changes and small migrations
Always check the migration file before updating database
- Annotations vs fluent api, which one to choose?
Use annotations for small projects and fluent api for larger ones
Entity Framework Core Migrations
When to use migration?
- New class/ table added to DB
- New property/ column
- Existing property/ column modified
- Property/ column deleted
- Class/ table deleted
Update the database
How to update the database in EF Core code first?
Simply create a new migration
add-migration yourFavouriteMigrationName
You can see the results in the migation file
Please note that if you don’t run update-database, the database won’t be well…updated
Remove migration
Only use it when the migration hasn’t been pushed the database and you want to revert it
Warning: Use this command only if the migration hasn’t been applied to the database!!!
remove-migration
Do not remove any migration files in the Migrations folder or change any files directly
How to revert back to an old migration?
Copy the name of the migration you previously applied
Here the name is: “AddBookToDb”
Append the name to update-database
update-database AddBookToDb
See reverting results
To come back to the latest version, simply do
update-database
It will reapply all the migrations that have been reverted
How to revert a migration in Entity Franework Core?
Do as previous step: update-data nameofmigrationyouwanttogobackto
update-database nameofmigrationyouwanttogobackto
do the changes you wish to make, make a migration
add-migration undidChanges
How to see what migration has been applied in EntityFramework Core?
In PMC just run:
get-migration
The resulting table will indicate which migration has been applied or not
How to delete the database in EntityFramework Core?
In PMC just run:
drop-database
To get the database back, just run
update-database
All migrations will be re-applied
Entity Framework Core Data Annotations
EF Core lets you work the SQL table directly in code using only annotations!
How to change table or column name in EntityFramework Core?
Tavle names are set in DbContext:
However, we can change this using data annotation:
Then we finish by applying a new migration
How to set a field as required in EntityFramework Core?
By clicking on your project, you can see if nullable is enabled
Because of that, all string properties are required by default, to override this, we need to ask the nullable “?”, e.g.:
public string? GenreName { get; set; }
Without the <Nullable>enable</Nullable> the properties will be nullable by default
In case you don’t have the nullable enabled and you want a property to be required, you can use:
[Required (ErrorMessage = "Genre name is required")]
public string GenreName { get; set; }
How to ensure a property doesn’t get in the table in EntityFramework Core?
Sometimes we just want the field to be available in the code but not as a column, for such case use:
[NotMapped]
public string PriceRange { get; set; }
How to create foreign keys in EntityFramework Core with Data annotations?
1:1 Relationship
In a 1:1 relationship there is a child (dependent class) and parent (main class)
A foreign key, has to be placed in the child class and a reference to the child class must be present in the parent class
public class Book
{
public int BookId { get; set; } // the PK
public BookDetail BookDetail { get; set; } //the child class reference
}
public class BookDetail
{
[Key]
public int BookDetail_Id { get; set; }
[ForeignKey("Book")] // "Book" indicates the class the FK applies to
public int BookId { get; set; } // the FK
public Book Book { get; set; } // the reference to the parent class
See resulting migation
How to make the primary key custom generated in EntityFramework Core Data Annotations?
Use the DatabaseGenerated annotation
- [DatabaseGenerated(DatabaseGeneratedOption.None)]: no DB generated IDs
- [DatabaseGenerated(DatabaseGeneratedOption.Computed)]: DB generated
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)] // the DB won't create the Id
public int DealerID { get; set; }
1:Many Relationship
In our example we want books to have a 1:many relationship to publishers
i.e. a publisher can have many books, a book can only have one publisher
public class Book
{
// [Key]
public int BookId { get; set; }
[ForeignKey("Publisher")]
public int Publisher_Id { get; set; }
public Publisher Publisher { get; set; }
}
public class Publisher
{
[Key]
public int Publisher_Id { get; set; }
public List<Book> Books { get; set; } // add FK object as List
}
Many:Many Relationship
Auhtors can have many books and books can have multiple authors
public class Author
{
[Key]
public int Author_Id { get; set; }
public List<Book> Books { get; set; } // Adds relationship to Book
}
We simply add
public class Book
{
// [Key]
public int BookId { get; set; }
public List<Author> Authors { get; set; } // Adds relationship to Authors
}
EF Core (version 7+) automatically creates an intermediary table (mapping table) beteen Book and Author
How to manually create the intermediate mapping table in EntityFramework Core?
Create the following table with explicit FK for many:many relationship between author and book
public class BookAuthorMap
{
[ForeignKey("Book")]
public int Book_Id { get; set; }
[ForeignKey("Author")]
public int Author_Id { get; set; }
public Book Book { get; set; }
public Author Author { get; set; }
}
Then add mapping to respective classes, adding BookAuthorMap
public class Author
{
[Key]
public int Author_Id { get; set; }
public List<BookAuthorMap> BookAuthorMap { get; set; }
}
public class Book
{
// [Key]
public int BookId { get; set; }
public List<BookAuthorMap> BookAuthorMap{ get; set; }
}
Nevertheless, the table BookAuthorMap needs a composite key, this can be done using Fluent API
How to add composite key in EntityFramework Core Data Annotations?
Since EF Core 7+, data annotation is available for composite key
[PrimaryKey("ProductID", "ModelID")]
internal class ProductModel
{
public int ProductID { get; set; }
public int ModelID { get; set; }
}
Resulting migration with two PKs
And resulting diagram
How to create many:many relationship with skip mapping table in EntityFramework Core Data Annotations?
- Books can have multiple authors
- Authors can have multiple books
No fluent api is even required
public class Fluent_Book
{
[Key]
public int BookId { get; set;}
public List<Fluent_Author>Authors{ get; set; }
}
public class Fluent_Author
{
[Key]
public int Author_Id { get; set; }
public List<Fluent_Book> Books { get; set; }
}
Resulting mapping table (Fluent_AuthorFluent_Book)
Fluent API in EntityFramework Core
Introduction
Data annotations are great but they are also clustered all over your classes, perhaps you would want to have it all one place?
Also, fluent API can add many customisation that Data annotations cannot do,
To use it, simply add code in the OnModelCreating
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Fluent API code comes here
}
How to change table name with EntityFramework Core Fluent API?
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Category>().ToTable("tb_Category");
}
How to change column name with EntityFramework Core Fluent API?
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Category>()
.Property(c => c.ISBN)
.HasColumnName("ISBN");
}
How to make a column required with EntityFramework Core Fluent API?
modelBuilder.Entity<Category>()
.Property(c => c.Title)
.IsRequired();
How to add a primary key with EntityFramework Core Fluent API?
modelBuilder.Entity<Category>()
.HasKey(c => c.CategoryId);
How to set Max Length with EntityFramework Core Fluent API?
modelBuilder.Entity<Category>()
.Property(c => c.CategoryName)
.HasMaxLength(50);
How to set Not Mapped with EntityFramework Core Fluent API?
modelBuilder.Entity<Category>()
.Ignore(c => c.DiscountedPrice);
How to set 1:1 relationship with EntityFramework Core Fluent API?
- Book detail can only have one book (HasOne)
- And book only have one book detail (WithOne)
- The connection (ForeignKey) is BookId
modelBuilder.Entity<Fluent_BookDetail>()
.HasOne(b => b.Book) // Object in Fluent_BookDetail
.WithOne(bd => bd.BookDetail) // Object in Fluent_Book
.HasForeignKey<Fluent_BookDetail>(b => b.BookId);
// Foreign Key can be in either Fluent_BookDetail OR Fluent_Book
public class Fluent_BookDetail
{
[Key]
public int BookDetail_Id { get; set; }
public int BookId { get; set; }
public Fluent_Book Book { get; set; }
}
public class Fluent_Book
{
[Key]
public int BookId { get; set; }
public Fluent_BookDetail BookDetail { get; set; }
}
How to set 1:many relationship with EntityFramework Core Fluent API?
- One publisher can have many books
- One book only has one publisher
modelBuilder.Entity<Fluent_Book>()
.HasOne(u => u.Publisher)
.WithMany(u => u.Books)
.HasForeignKey(u => u.Publisher_Id); // FK in Fluent_Book
// FK always in entity that has the 1 side of the 1-many relationship
public class Fluent_Book
{
[Key]
public int BookId { get; set; }
public int Publisher_Id { get; set; }
public Fluent_Publisher Publisher { get; set; }
}
public class Fluent_Publisher
{
[Key]
public int Publisher_Id { get; set; }
public List<Fluent_Book> Books { get; set; }
}
Composite Keys in Entity Framework Core Fluent API
Composite keys allow two PK to be combined and form a unique PK
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<BookAuthorMap>()
.HasKey(ba => new
{ ba.Book_Id,
ba.Author_Id
});
How to manually create many:many relationship with fluent API in EntityFramework Core?
- Books can have multiple authors
- Authors can have multiple books
- Create mapping table
MappingTables don’t need their own DbSet<MappingTable>
// Mapping Table
public class Fluent_BookAuthorMap
{
[Key]
public int Book_Id { get; set; }
public int Author_Id { get; set; }
public Fluent_Book Book { get; set; }
public Fluent_Author Author { get; set; }
}
public class Fluent_Book
{
[Key]
public int BookId { get; set; }
public List<Fluent_BookAuthorMap> BookAuthorMap { get; set; }
}
public class Fluent_Author
{
[Key]
public int Author_Id { get; set; }
public List<Fluent_BookAuthorMap> BookAuthorMap { get; set; }
}
In DbContext:
- Create Composite key
- 1:many fluent_book to fluent_book_author
- 1:many fluent_book_author to fluent_book
// composite key
modelBuilder.Entity<Fluent_BookAuthorMap>()
.HasKey(ba => new
{
ba.Book_Id,
ba.Author_Id
});
// 1:many between fluent_book & fluent_book_author
modelBuilder.Entity<Fluent_BookAuthorMap>()
.HasOne(b => b.Book)
.WithMany(ba => ba.BookAuthorMap)
.HasForeignKey(b => b.Book_Id);
// 1:many between fluent_author & fluent_book
modelBuilder.Entity<Fluent_BookAuthorMap>()
.HasOne(a => a.Author)
.WithMany(ba => ba.BookAuthorMap)
.HasForeignKey(a => a.Author_Id);
How to set Cascade options in Fluent API EntityFramework Core?
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<Blog>()
.HasOne(e => e.Owner)
.WithOne(e => e.OwnedBlog)
.OnDelete(DeleteBehavior.ClientCascade);
}
What does onDelete: ReferencetialAction.Cascade mean in EntityFramework Core?
Cascade means that if the parent record, here the book, is deleted, then the book detail will also be deleted
Other options include:
onDelete: ReferencetialAction.NoAction:
- If the book is deleted, no action will be performed
onDelete: ReferencetialAction.Restrict:
- Will generate an error message if trying to delete
How to organise clusters in fluent API in EntityFramework Core?
As we add more configurations in protected override void OnModelCreating(ModelBuilder modelBuilder), it might become clustered and hard to read.
The solution is to create its own file for each entity
See here class added for the entity “FluentBookDetail”:
- Uses the interface IEntityTypeConfiguration that takes one entity
public class FluentBookDetailConfig : IEntityTypeConfiguration<Fluent_BookDetail>
{
public void Configure(EntityTypeBuilder<Fluent_BookDetail> modelBuilder)
{
// no need to have Entity<Fluent_BookDetail>() anymore as we are using IEntityTypeConfiguration
modelBuilder.HasOne(b => b.Book)
.WithOne(bd => bd.BookDetail)
.HasForeignKey<Fluent_BookDetail>(b => b.BookId);
}
}
then add the reference to the DbContext
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new FluentBookDetailConfig());
}
Using Entity Framework Core in code
Querying data
var allEntities = dbContext.YourEntities.ToList();
var filteredEntities = dbContext.YourEntities.Where(e => e.SomeProperty == "SomeValue").ToList();
Find vs FirstOrDefault vs SingleOrDefault
// Based on any fields OR none
var book = dbContext.Books.FirstOrDefault(b => b.Id == 123);
// Based on primary key
var book = dbContext.Books.Find(123);
// When there is only one record with this parameter, otherwise throws an exception
var book = dbContext.Books.Single(b => b.Id == 123);
OrderBy and ThenOrderby
// Example query: Order by Name ascending and then by DateCreated descending
var sortedEntities = context.YourEntities
.OrderBy(e => e.Name) // Primary sorting
.ThenByDescending(e => e.DateCreated) // Secondary sorting
.ToList();
Add
var newEntity = new YourEntity { Property1 = "Value1", Property2 = "Value2" };
dbContext.YourEntities.Add(newEntity);
dbContext.SaveChanges();
Update
var book = dbContext.Books.Find(1002)
book.ISBN = "777";
dbContext.SaveChanges();
var entitiesToUpdate = dbContext.YourEntities.Where(e => e.SomeCondition).ToList();
foreach (var entity in entitiesToUpdate)
{
entity.SomeProperty = "NewValue";
}
dbContext.SaveChanges();
Pagination
var books = dbContext.Books.Skip(0).Take(2);
Delete
var book = dbContext.Books.Find(1002)
dbContext.Books.Remove(book);
dbContext.SaveChanges();
Add multiple
List<Book> books;
_db.Books.AddRange(books);
dbContext.SaveChanges();
Remove multiple
List<Book> books;
_db.Books.RemoveRange(books);
dbContext.SaveChanges();
Async Methods
Every method has an async variant
Should you use it?
Worse case scenario it will have the same performance than without async
// GET ALL
var allEntities = await dbContext.YourEntities.ToListAsync();
// ADD
var newEntity = new YourEntity { Property1 = "Value1", Property2 = "Value2" };
dbContext.YourEntities.Add(newEntity);
await dbContext.SaveChangesAsync();
// UPDATE
var entityToUpdate = await dbContext.YourEntities.FindAsync(id);
entityToUpdate.SomeProperty = "NewValue";
await dbContext.SaveChangesAsync();
// DELETE
var entityToDelete = await dbContext.YourEntities.FindAsync(id);
dbContext.YourEntities.Remove(entityToDelete);
await dbContext.SaveChangesAsync();
Further topics
How to seed data in EntityFramework Core? Aka how to add initial data in EntityFramework Core?
In the DbContext:
- add OnModelCreating method
To add a single entry
- add modelBuilder.Entity<T>.HasData(dataentry)
public class ApplicationDbContext : DbContext
{
public DbSet<Book> Books { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Book>().HasData(
new Book
{
BookId = 1,
Title = "Professional C# 7 and .NET Core 2.0",
ISBN = "978-1-119-44926-0",
Price = 45.00M
});
}
To add more entries
public class ApplicationDbContext : DbContext
{
public DbSet<Book> Books { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var bookList = new Book[]
{
new Book { BookId = 2, ISBN = "978-1-119-44926-0", Title = "Professional C# 7 and .NET Core 2.0", Price = 45.00M },
new Book { BookId = 3, ISBN = "978-1-119-44926-0", Title = "Professional C# 7 and .NET Core 2.0", Price = 45.00M },
new Book { BookId = 4, ISBN = "978-1-119-44926-0", Title = "Professional C# 7 and .NET Core 2.0", Price = 45.00M },
};
// add a list of Book objects
modelBuilder.Entity<Book>().HasData(bookList);
}
Finish by adding a migration
Seed data in migration
Create an empty migration in the PM (e.g. add-migration seedingDb)
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("INSERT INTO Categories (Name) VALUES ('Programming')");
}
Nullable Fields
Since .Net 6, in your project cs file, you will find the following
<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>
The <Nullable>enable</Nullable> means that:
- All properties are by default non-nullable reference types
- You can use the
?
suffix to declare a nullable reference type.
e.g.:
[Key]
public int DiscountID { get; set; } // non-nullable by default
public DateTime? EndDate { get; set; } // explicitly set as nullable "DateTime?"
}
DbContext Independency injection
GetConnectionString will automatically get to the appsettings.json
In the program.cs of your start project in the solution
builder.Services.AddDbContext<ApplicationDbContext>
(options => options.UseSqlServer(
builder.Configuration.GetConnectionString("DefaultConnection")));
{"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"DefaultConnection": "Server=DVT-CHANGEMENOW\\SQLEXPRESS;Database=CodingWiki;TrustServerCertificate=True;Trusted_Connection=True;"
}
}
In your appsettings.json, add the DefaultConnection
Then in your dbContext.cs add the ApplicationDbContext constructor
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
{
}
Explicit loading & Eager Loading & Lazy Loading
Problem: the underlying code would make a trip to the database for each book
public async Task<IActionResult> Index()
{
List<Book> objList = _dbContext.Books.ToList();
foreach (var obj in objList)
{;
obj.Publisher = await _dbContext.Publishers.FindAsync(obj.Publisher_Id);
}
return View(objList);
}
Solution 1: use explicit loading, it will look at the object you’re querying, here Publisher, so it reduces the amount of trips to the DB
public async Task<IActionResult> Index()
{
List<Book> objList = _dbContext.Books.ToList();
foreach (var obj in objList)
{;
// use load to explicitly load the data
_dbContext.Entry(obj).Reference(u => u.Publisher).Load();
}
return View(objList);
}
Solution 2: use eager loading, it basically not only queries the entity (here book) but also all the other entities it is related to (here Publisher).
So when retrieving Book, we also retrieve Publisher
public async Task<IActionResult> Index()
{
// eager loading is done using the Include statement
List<Book> objList = _dbContext.Books.Include(u => u.Publisher).ToList();
return View(objList);
}
Eager loading is most optimal solution, as it retrieves both entities using a join and therefore only one query/ trip to the database.
Solution 3: lazy loading the related data is loaded only when accessed
// Assume there is an author with AuthorId = 1 in the database
var author = context.Authors.Find(1);
// At this point, the Books collection is not loaded
// It will be loaded when accessed for the first time
foreach (var book in author.Books)
{
Console.WriteLine($"Book Title: {book.Title}");
}
Which one to choose? Although each situation has its own requirements, eager loading is often the most suitable approach
Deferred Execution
Entity Framework Core is smart about when to execute the query or not. It will only do it when the code actually needs it.
What might seem like some delay is actually a performance optimisation.
public async Task<IActionResult> Playgound()
{
// Get executed right away
var bookTemp = _dbContext.Books.FirstOrDefault();
bookTemp.Price = 100;
// No query is executed
var bookCollection = _dbContext.Books;
decimal totalPrice = 0;
// Now the query is executed
foreach(var book in bookCollection)
{
totalPrice += book.Price;
}
return RedirectToAction(nameof(Index));
}
IQueryable vs IEnumerable
Query with IEnumerable:
- Select all books
- Filter in memory (client side)
Query with IQueryable
- Selects all books with a filter (WHERE statement), (database side)
public async Task<IActionResult> Queryable()
{
IEnumerable<Book> books = _dbContext.Books;
var filteredBooks = books.Where(b => b.Price > 500).ToList();
IQueryable<Book> books2 = _dbContext.Books;
var filteredBooks2 = books2.Where(b => b.Price > 500).ToList();
return View(books2);
}
IQueryable works great when we have lots of records we want to filter
Entit Framework Core tracking
When you execute a query and retrieve entities using methods like Find
, FirstOrDefault
, or ToList
, the retrieved entities are automatically tracked by the DbContext.
// Tracked
var blog = context.Blogs.SingleOrDefault(b => b.BlogId == 1);
blog.Rating = 5;
context.SaveChanges();
// Not tracked
var author = context.Authors.AsNoTracking().FirstOrDefault(a => a.AuthorId == 1);
author.Name = "Updated Author"; // Changes will not be automatically detected
// You need to use other methods to update or insert changes explicitly
So why not tracking everything?
For operations such as READ that wouldn’t need to be audited, we can remove the tracking so that the performance improves.
The tracking behavior can be controlled at various levels, including at the query level (AsNoTracking
), globally for all queries (UseQueryTrackingBehavior
), or when retrieving specific entities.
Adding SQL queries in Entity Framework Core
If there is at some point a real need to use SQL with EF Core, it is possible doing the following two methods:
Method 1:
- Create an empty migration
- In the Up/Down insert your SQL Code
using Microsoft.EntityFrameworkCore.Migrations;
public partial class YourMigration : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
// Your other migration operations...
// Execute a raw SQL statement
migrationBuilder.Sql("INSERT INTO YourTable (ColumnName) VALUES ('SomeValue')");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
// Your migration rollback operations...
// Execute a raw SQL statement for rollback
migrationBuilder.Sql("DELETE FROM YourTable WHERE ColumnName = 'SomeValue'");
}
}
Method 2:
- use context.FromSqlInterpolated
using (var context = new YourDbContext())
{
// Define a parameterized SQL query using string interpolation
var parameterValue = "SomeValue";
var results = context.YourEntity
.FromSqlInterpolated($"SELECT * FROM YourTable WHERE Column = {parameterValue}")
.ToList();
}
Repository Pattern & Clean Architecture
Repository Pattern & Unit of work
EF Core uses the DbContext as a client to speak with the database.
Using a repository pattern enables you to:
- Test the DbContext
- Create specific methods for different classes (Repositories)
1/Db Context
public class ApplicationDbContext: DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options): base(options)
{
}
public DbSet<Villa> Villas { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
}
}
2/ Generic repository
public interface IRepository<T> where T : class
{
Task<List<T>> GetAllAsync(Expression<Func<T, bool>>? filter = null, bool tracked = true);
Task<T> GetAsync(Expression<Func<T, bool>>? filter = null, bool tracked = true);
Task CreateAsync(T entity);
Task RemoveAsync(T entity);
Task SaveAsync();
}
In the context of a generic type constraint in C#, the where T : class
constraint specifies that the generic type T
must be a reference type (i.e., a class, interface, delegate, or array). It excludes value types like struct
.
3/ Implementation
public class Repository<T> : IRepository<T> where T : class
{
private readonly ApplicationDbContext _applicationDbContext;
internal DbSet<T> _dbSet;
public Repository(ApplicationDbContext applicationDbContext)
{
_applicationDbContext = applicationDbContext;
_dbSet = _applicationDbContext.Set<T>();
}
public async Task CreateAsync(T entity)
{
await _dbSet.AddAsync(entity);
await SaveAsync();
}
public async Task<T> GetAsync(Expression<Func<T, bool>> filter = null, bool tracked = true)
{
IQueryable<T> query = _dbSet;
if (!tracked)
{
query = query.AsNoTracking();
}
if (filter != null)
{
query = query.Where(filter);
}
return await query.FirstOrDefaultAsync(); // query will be executed here, deffered execution
}
public async Task<List<T>> GetAllAsync(Expression<Func<T, bool>> filter = null, bool tracked = true)
{
IQueryable<T> query = _dbSet;
if (!tracked)
{
query = query.AsNoTracking();
}
if (filter != null)
{
query = query.Where(filter);
}
return await query.ToListAsync(); // query will be executed here, deffered execution
}
public async Task RemoveAsync(T entity)
{
_dbSet.Remove(entity);
await SaveAsync();
}
public async Task SaveAsync()
{
await _applicationDbContext.SaveChangesAsync();
}
}
Now we have a generic class to interact with our DbContext, we can simply use it by passing a Class and a Db.
Which is great if we have many classes that use the same DbContext but also it enables to test the DbContext!
But what if we want something specific to one class?
This is where the unit of work comes in!
4/ Create Unit of work interface
public interface IUnitOfWork
{
IVillaRepository Villa { get; }
Task SaveAsync();
}
5/ Implement it
public class UnitOfWork : IUnitOfWork
{
private readonly ApplicationDbContext _db;
public IVillaRepository Villa { get; private set; }
public UnitOfWork(ApplicationDbContext db)
{
_db = db;
Villa = new VillaRepository(_db);
}
public async Task SaveAsync()
{
await _db.SaveChangesAsync();
}
}
6/ Use it!
public async Task<ActionResult<APIResponse>> CreateVilla([FromBody]VillaCreateDTO createDto)
{
// some code here
await _unitOfWork.Villa.CreateAsync(model);
// some code there
return CreatedAtAction(nameof(GetVilla), new { id = model.Id }, _response);
}
Clean architecture and Entity Framework Core
Clean architecture is a method to keep your solution well organised.
There are many similar architectural patterns and clean architecture is one of them.
It is great to properly designing your application to good software engineering principles.
In Clean architecture, the DbContext is at the outer layer (in blue).
Therfore, one may ask:
Where do classes used to create the database tables with the DbContext should reside?
In Clean Architectue we learn that domain should reside at the core and not the outter layer. However, these classes are only meant to create the database and should therefore not be shared with the other layers and thus serve as entities for the database mapping.
Mapping between the data layer and the domain is done using a data mapper (e.g. AutoMapper)
Why and How to use Repository pattern with Clean Architecture in .Net?
The repository pattern provides an abstraction layer between the application’s business logic and the data access code, offering a clean and organised way to manage data access in an application.
You can read all about it in my other post with the respective GitHub Repo
The Repo if you need the code
Here is my full post on Repository Pattern!
Entity Framework Core Tips & Tricks
How to connect to SQL Server Mamagement Studio?
You can test locally using your local sql-server:
Go to SSMS (SQL Server Management Studio), don’t forget to install sql-express or to your server explorer in VS
click connect to database (plug icon)
Manually enter the sql-server name, then select the database name
- click Test Connection
- click on advanced to get your connection string
- click ok
add sql-connection string to appsettings.json
{
"sqlDbConnectionString": "XXXXXXXXXX"
}
How to automatically create database diagrams in Visual Studio?
Go to Tools > Extensions > Download “EF Core Power Tools”
Once installed, right click on the project where your DbContext is at and select EF Core Power Tools
Select, Add DbContext Diagram
How to make custom changes to the database in EF Core?
This is what the OnModelCreating method is for.
It can add specific details to the table that aren’t available in annotations.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Book>().Property(b => b.Price).HasPrecision(10, 5);
}
How to remove a table with EF Core?
Just remove the DbSet<T>
E.g. here we comment it out and run a migration
public class ApplicationDbContext : DbContext
{
public DbSet<Book> Books { get; set; }
//public DbSet<Genre> Genres { get; set; }
Resulting migration file
What is the dbo_EFMigrationsHistory file in SSMS?
Keeps track of all migrations
Stores migrations by MigrationId and EF Core Product/Version
The goal is to not apply the previous migration, keep track of changes and only apply changes that haven’t been pushing to the DB yet
What is the ApplicationDbContextModel Snapshot file?
It is for EF Core to track the changes between the code and the database
What is inside an EF Core migration file?
See migration file, e.g.:
Up indicates what to add
Down indicates what to revert (e.g. if something goes wrong)
The action creates a table:
- named “Books” as indicated in the DbSet<Book> Books
- The primary key is set as BookId
- All fields have nullable: false, this is because none properties have nullable (indicated with interrogation mark “?) such as
public string? Title { get; set; } // indicates nullable type
How to rename a column?
Column renamed
Empty migration file
Just remove the migration if that happens
Error List
Error
Introducing FOREIGN KEY constraint ‘FK_’ on table ‘tablename’ may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint or index. See previous errors.
Solution
I have seen many answers on this one, but here is the easiest solution that worked for me
Say you have these underlying tables, when Child1 and Child2 rows get deleted, you want the grandchild row to be deleted too, right?
Well the issue is with the parent, if it gets deleted, then the child gets deleted, then you have a conflic over who deletes the grandchild.
So the solution is to only have one child with Cascade.Delete
Enough explaining, below you can find the code
The below picture explains why it is not fine
On the other hand this is fine
internal class Parent
{
[Key]
public int ParentId{ get; set; }
// Foreign Keys one to many
public List<Child1>? Child1s{ get; set; }
public List<Child2>? Child2s{ get; set; }
}
internal class Brand_FluentConfigurations : IEntityTypeConfiguration<Parent>
{
public void Configure(EntityTypeBuilder<Parent> modelBuilder)
{
modelBuilder
.HasMany<Child1>(b => b.Child1s)
.WithOne(b => b.Parent)
.HasForeignKey(b => b.ParentId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder
.HasMany<Child2>(b => b.Child2s)
.WithOne(b => b.Parent)
.HasForeignKey(b => b.ParentId)
.OnDelete(DeleteBehavior.Cascade);
}
Children
internal class Child1
{
public int Child1Id{ get; set; }
// Foreign Keys one to many
public List<GrandChild>? GrandChildren { get; set; }
}
// If Child1 gets deleted, then GrandChil too
internal class BrandLine_FluentConfigurations : IEntityTypeConfiguration<Child1>
{
public void Configure(EntityTypeBuilder<Child1> modelBuilder)
{
modelBuilder
.HasMany<GrandChild>(b => b.GrandChildren)
.WithOne(b => b.Child1)
.HasForeignKey(b => b.Child1Id)
.OnDelete(DeleteBehavior.Cascade);
}
}
internal class Child2
{
public int Child2Id{ get; set; }
// Foreign Keys one to many
public List<GrandChild>? GrandChildren { get; set; }
}
// If Child2 gets deleted, then nothing happens (no action)
internal class BrandLine_FluentConfigurations : IEntityTypeConfiguration<Child2>
{
public void Configure(EntityTypeBuilder<Child2> modelBuilder)
{
modelBuilder
.HasMany<GrandChild>(b => b.GrandChildren)
.WithOne(b => b.Child2)
.HasForeignKey(b => b.Child2Id)
.OnDelete(DeleteBehavior.NoAction);
}
}
GrandChild
internal class GrandChild
{
public int GrandChildId { get; set; }
// Foreign Keys many to one
public int Child1Id { get; set; }
public Child1 Child1 { get; set; }
public int? Child2Id { get; set; }
public Child2? Child2{ get; set; }
}
Error
The term ‘add-migration’ is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
Solution
Add SDK: Microsoft.EntityFrameworkCore.Tools
Error
Your startup project ‘Plutus.ProductPricing.API’ doesn’t reference Microsoft.EntityFrameworkCore.Design. This package is required for the Entity Framework Core Tools to work. Ensure your startup project is correct, install the package, and try again.
Solution
In your program.cs of your starting project
builder.Services.AddDbContext<ProductPricingDbContext>
(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
In your appsettings
"AllowedHosts": "*",
"ConnectionStrings": {
"DefaultConnection": "Server=;Database=;TrustServerCertificate=True;Trusted_Connection=True;"
}
}
In your DbContext
public class ProductPricingDbContext: DbContext
{
public ProductPricingDbContext(DbContextOptions<ProductPricingDbContext> options): base(options)
{
}
Error
Only the invariant culture is supported in globalization-invariant mode. See https://aka.ms/GlobalizationInvariantMode for more information. (Parameter ‘name’)
en-us is an invalid culture identifier.
Solution
Change to false
Error
Unable to create a ‘DbContext’ of type ‘’. The exception ‘The method or operation is not implemented.’ was thrown while attempting to create an instance. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728
Solution
- Add the assembly name where the dbcontext is located in your DI
builder.Services.AddDbContext<ProductPricingDbContext>
(options => options.UseSqlServer(
builder.Configuration.GetConnectionString("DefaultConnection"),
b => b.MigrationsAssembly("Plutus.ProductPricing.DataAccess")));
// Location of your DbContext
- Make sure your DbContext is set right in your PMC as Default project
- Set your starting project as the one where the dependency injection of your Db Context is done
Error
Entity Framework Core Add-Migration doesn’t create any migration files and doesn’t create the migration folder
Solution
Remove this part in your project file
<ItemGroup>
<Compile Remove="Migrations\**" />
<EmbeddedResource Remove="Migrations\**" />
<None Remove="Migrations\**" />
</ItemGroup>
Error
Unable to create a ‘DbContext’ of type ‘’. The exception ‘The entity type ‘Book’ requires a primary key to be defined. If you intended to use a keyless entity type, call ‘HasNoKey’ in ‘OnModelCreating’.
Solution
See section set Primary Key
Error
The ALTER TABLE statement conflicted with the FOREIGN KEY constraint “FK_Books_Publishers_Publisher_Id”. The conflict occurred in database “CodingWiki”, table “dbo.Publishers”, column ‘Publisher_Id’.
Solution
This is a FK violation, look at the migration file:
- When we update data we set publisher_Id to 0
- That violates FK condition > publisher table doesn’t have a record with publisher_id 0
- It needs a publisher to add migration
Either:
- Seed solution with correct data so a publisher exists with publisher_id 0
- Or delete previous records that don’t have a publisher_id, then update database
Sources
https://www.udemy.com/course/entity-framework-core-the-complete-guide-net-5/
Annotations (Mapping attributes)
This model is built using a set of conventions — heuristics that look for common patterns. The model can then be customized using mapping attributes (also known as data annotations) and/or calls to the ModelBuilder methods (also known as fluent API) in OnModelCreating, both of which will override the configuration performed by conventions.
Fluent API configuration has the highest precedence and will override conventions and data annotations.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.IsRequired();
}
By convention, a property whose .NET type can contain null will be configured as optional, whereas properties whose .NET type cannot contain null will be configured as required. For example, all properties with .NET value types (int, decimal, bool, etc.) are configured as required, and all properties with nullable .NET value types (int?, decimal?, bool?, etc.) are configured as optional.
Primary keys and foreign keys
Indexes
If you just want to enforce uniqueness on a column, define a unique index rather than an alternate key (see Indexes). In EF, alternate keys are read-only and provide additional semantics over unique indexes because they can be used as the target of a foreign key.
Performance
Mapping attributes and foreign keys