Modern software applications depend heavily on efficient data management. As applications become larger and more complex, developers need reliable solutions that simplify database communication while maintaining performance and scalability.
Entity Framework Core (EF Core) is Microsoft's modern Object Relational Mapping (ORM) framework that enables .NET developers to work with databases using C# objects instead of writing large amounts of database-specific SQL code.
What is Entity Framework Core?
Entity Framework Core is an open-source, lightweight, and cross-platform ORM framework designed for modern .NET applications. It acts as a bridge between application code and database systems.
Instead of manually writing SQL queries for every database operation, developers can use C# classes and LINQ expressions to perform data operations. EF Core automatically converts these operations into optimized SQL commands.
Why Use Entity Framework Core?
- Reduces database development effort
- Provides strongly typed database access
- Supports LINQ queries
- Automatically manages object relationships
- Provides database migration support
- Works with multiple database providers
- Integrates seamlessly with ASP.NET Core
Entity Framework Core Architecture
Understanding EF Core architecture helps developers design applications that are maintainable, testable, and scalable.
The main components of Entity Framework Core include:
- Entity Classes
- DbContext
- DbSet
- Database Provider
- Change Tracker
- LINQ Query Provider
Entity Classes
Entity classes represent the application's data models. Each entity normally maps to a database table.
For example, a Product entity represents a Product table in the database.
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public int StockQuantity { get; set; }
}
EF Core uses these classes to create database structures and perform database operations.
DbContext in Entity Framework Core
DbContext is the most important class in EF Core. It manages database connections, tracks entity changes, and executes database operations.
It acts as a session between the application and the database.
Creating DbContext
using Microsoft.EntityFrameworkCore;
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options): base(options)
{
}
public DbSet<Product> Products { get; set; }
}
DbSet in EF Core
DbSet represents a database table. Each DbSet property allows developers to query, insert, update, and delete records.
public DbSet<Customer> Customers { get; set; }
public DbSet<Order> Orders { get; set; }
Installing Entity Framework Core
EF Core packages can be installed using NuGet Package Manager or .NET CLI.
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
After installing required packages, applications can configure EF Core with their preferred database provider.
Configuring SQL Server with EF Core
builder.Services.AddDbContext<ApplicationDbContext>
(
options => options.UseSqlServer("Server=localhost;Database=ShopDb;Trusted_Connection=True;")
);
Configuring Entities in Entity Framework Core
Entity Framework Core provides multiple ways to configure entity behavior, database mapping rules, relationships, constraints, and validation requirements. The two primary configuration approaches are Data Annotations and Fluent API.
Data Annotations in EF Core
Data Annotations are attributes applied directly to entity classes. They provide a simple way to configure database mapping and validation rules.
Example: Data Annotation Configuration
using System.ComponentModel.DataAnnotations;
public class Customer
{
[Key]
public int CustomerId { get; set; }
[Required]
[MaxLength(100)]
public string Name { get; set; }
[EmailAddress]
public string Email { get; set; }
}
Common Data Annotation attributes include:
- [Key] - Defines the primary key
- [Required] - Makes a property mandatory
- [MaxLength] - Defines maximum length
- [Column] - Maps property to a database column
- [Table] - Maps entity to a database table
Fluent API Configuration in EF Core
Fluent API provides advanced configuration capabilities and is usually preferred for large enterprise applications because it keeps database configuration separate from entity classes.
Fluent API configurations are written inside the OnModelCreating method of DbContext.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>()
.HasKey(x => x.Id);
modelBuilder.Entity<Product>()
.Property(x => x.Name)
.HasMaxLength(200)
.IsRequired();
}
Advantages of Fluent API
- Better separation of concerns
- Supports complex database mappings
- Keeps entity classes clean
- Suitable for enterprise applications
Entity Framework Core Migrations
Database migrations allow developers to maintain database schema changes directly from application code.
Whenever entities are modified, migrations can update the database structure without manually writing SQL scripts.
Creating First Migration
Add-Migration InitialCreate
Using .NET CLI:
dotnet ef migrations add InitialCreate
Updating Database
Update-Database
Using .NET CLI:
dotnet ef database update
Migration Workflow
- Create or modify entity classes
- Generate migration
- Review migration changes
- Apply migration to database
- Deploy updated application
CRUD Operations in Entity Framework Core
CRUD represents the four basic database operations:
- Create
- Read
- Update
- Delete
Create Data Using EF Core
New records can be inserted using the Add method.
var product = new Product
{
Name = "Laptop",
Price = 1200,
StockQuantity = 15
};
_context.Products.Add(product);
_context.SaveChanges();
Reading Data Using EF Core
EF Core uses LINQ queries to retrieve data from databases.
var products = _context.Products.ToList();
Filtering Records
var expensiveProducts = _context.Products.Where(x => x.Price > 1000).ToList();
Updating Data Using EF Core
EF Core automatically tracks entity changes and updates modified records.
var product = _context.Products.FirstOrDefault(x => x.Id == 1);
product.Price = 1500;
_context.SaveChanges();
Deleting Data Using EF Core
var product = _context.Products.FirstOrDefault(x => x.Id == 1);
_context.Products.Remove(product);
_context.SaveChanges();
LINQ Queries in Entity Framework Core
LINQ (Language Integrated Query) allows developers to write strongly typed queries using C# syntax.
Basic LINQ Query
var customers = _context.Customers.Where(c => c.IsActive).ToList();
Selecting Specific Columns
var customerNames = _context.Customers.Select(x => x.Name).ToList();
Sorting Data
var products = _context.Products.OrderBy(x => x.Name).ToList();
Pagination with LINQ
var products = _context.Products.Skip(20).Take(10).ToList();
Understanding Change Tracking
Change Tracking is a powerful EF Core feature that automatically monitors changes made to entity objects.
When SaveChanges() is executed, EF Core detects modified objects and generates appropriate SQL statements.
Entity States
- Added - New entity waiting for insertion
- Modified - Existing entity with changes
- Deleted - Entity marked for removal
- Unchanged - No changes detected
- Detached - Not tracked by context
Improving Read Performance with AsNoTracking()
For read-only operations, disabling tracking improves performance because EF Core does not need to maintain object state information.
var products = _context.Products.AsNoTracking().ToList();
Entity Relationships in Entity Framework Core
Real-world applications usually contain multiple related entities. For example, a customer can have multiple orders, and an order can contain multiple products. EF Core provides built-in support for managing these relationships.
One-to-One Relationship
A one-to-one relationship means one entity is associated with exactly one related entity.
Example: A User has one Profile.
User Entity
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public UserProfile Profile { get; set; }
}
User Profile Entity
public class UserProfile
{
public int Id { get; set; }
public string Address { get; set; }
public int UserId { get; set; }
public User User { get; set; }
}
Fluent API Configuration
modelBuilder.Entity<User>()
.HasOne(x => x.Profile)
.WithOne(x => x.User)
.HasForeignKey<UserProfile>(x => x.UserId);
One-to-Many Relationship
One-to-many is the most commonly used relationship in business applications.
Example: One customer can have many orders.
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Order> Orders { get; set; }
}
public class Order
{
public int Id { get; set; }
public DateTime OrderDate { get; set; }
public int CustomerId { get; set; }
public Customer Customer { get; set; }
}
Relationship Configuration
modelBuilder.Entity<Order>()
.HasOne(x => x.Customer)
.WithMany(x => x.Orders)
.HasForeignKey(x => x.CustomerId);
Many-to-Many Relationship
Many-to-many relationships allow multiple records from one entity to connect with multiple records from another entity.
Example: Students and Courses.
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Course> Courses { get; set; }
}
public class Course
{
public int Id { get; set; }
public string Title { get; set; }
public ICollection<Student> Students { get; set; }
}
Loading Related Data in EF Core
EF Core provides different approaches for loading related entities.
- Eager Loading
- Lazy Loading
- Explicit Loading
Eager Loading
Eager loading retrieves related data together with the main entity using Include().
var orders = _context.Orders.Include(x => x.Customer).ToList();
This generates a query that retrieves orders and their customers together.
Lazy Loading
Lazy loading automatically loads related data when the navigation property is accessed.
To enable lazy loading, install:
Microsoft.EntityFrameworkCore.Proxies
Enable Lazy Loading
options.UseLazyLoadingProxies();
Explicit Loading
Explicit loading allows developers to manually load related entities when needed.
var customer = _context.Customers.First();
_context.Entry(customer).Collection(x => x.Orders).Load();
Repository Pattern with EF Core
The Repository Pattern creates an abstraction layer between application logic and database operations.
It improves maintainability, testing, and separation of responsibilities.
Repository Interface
public interface IRepository<T>
{
IEnumerable<T> GetAll();
T GetById(int id);
void Add(T entity);
void Update(T entity);
void Delete(T entity);
}
Repository Implementation
public class Repository<T> : IRepository<T> where T : class
{
private readonly ApplicationDbContext _context;
public Repository(ApplicationDbContext context)
{
_context = context;
}
public IEnumerable<T> GetAll()
{
return _context.Set<T>().ToList();
}
public void Add(T entity)
{
_context.Set<T>().Add(entity);
_context.SaveChanges();
}
}
Dependency Injection with EF Core
ASP.NET Core provides built-in dependency injection support. EF Core DbContext is normally registered through the service container.
builder.Services.AddDbContext<ApplicationDbContext>
(
options => options.UseSqlServer(connectionString)
);
Using EF Core in ASP.NET Core Web API
EF Core works naturally with ASP.NET Core Web APIs for building modern REST applications.
API Controller Example
[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
private readonly ApplicationDbContext _context;
public ProductsController(ApplicationDbContext context)
{
_context = context;
}
[HttpGet]
public IActionResult GetProducts()
{
var products = _context.Products.ToList();
return Ok(products);
}
}
Transactions in Entity Framework Core
Transactions ensure multiple database operations either complete successfully or fail together.
using var transaction = _context.Database.BeginTransaction();
try
{
_context.Products.Add(product);
_context.SaveChanges();
transaction.Commit();
}
catch
{
transaction.Rollback();
}
Raw SQL Queries in EF Core
Although LINQ is recommended, EF Core also supports executing raw SQL queries.
var products = _context.Products.FromSqlRaw("SELECT * FROM Products").ToList();
Global Query Filters
Global Query Filters automatically apply conditions to every query for an entity.
A common example is implementing soft delete functionality.
modelBuilder.Entity<Product>()
.HasQueryFilter(
x => !x.IsDeleted
);
Entity Framework Core Performance Optimization
Performance is an important consideration when building enterprise applications. EF Core provides several features that help developers create fast and scalable database solutions.
Use AsNoTracking for Read Operations
Tracking is useful when updating entities, but it creates additional overhead for read-only scenarios.
var customers = _context.Customers.AsNoTracking().ToList();
Using AsNoTracking improves query performance and reduces memory usage.
Avoid Loading Unnecessary Data
Applications should retrieve only the required columns instead of loading complete entities.
var customerNames = _context.Customers.Select(x => new{x.Id,x.Name}).ToList();
Use Pagination for Large Data Sets
Loading thousands of records at once can reduce application performance. Pagination improves response time and database efficiency.
var products = _context.Products.OrderBy(x => x.Id).Skip(pageSize * pageNumber).Take(pageSize).ToList();
Compiled Queries in EF Core
Compiled queries improve performance for frequently executed queries by caching the query translation process.
private static readonly Func<ApplicationDbContext,int,Product> GetProduct = EF.CompileQuery(
(context, id) =>
context.Products.First(x => x.Id == id)
);
Split Queries
When loading multiple related collections, EF Core may generate large joins. Split queries execute multiple SQL statements to improve performance.
var customers = _context.Customers.Include(x => x.Orders).AsSplitQuery().ToList();
Handling Concurrency in EF Core
Concurrency handling prevents users from accidentally overwriting changes made by other users.
Using RowVersion
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; }
}
EF Core checks the original version before updating the record.
Value Converters in EF Core
Value converters transform property values when storing and retrieving data.
Example: Saving an enum as a string.
modelBuilder.Entity<Order>()
.Property(x => x.Status)
.HasConversion<string>();
Owned Entity Types
Owned entities represent objects that do not have their own identity and belong to another entity.
public class Address
{
public string City { get; set; }
public string Country { get; set; }
}
public class Customer
{
public int Id { get; set; }
public Address Address { get; set; }
}
Testing Entity Framework Core Applications
Testing database logic is important for building reliable applications. EF Core supports different testing approaches.
- In-memory database testing
- SQLite testing database
- Integration testing with real databases
Using In-Memory Database
services.AddDbContext<ApplicationDbContext>
(
options => options.UseInMemoryDatabase("TestDatabase")
);
Common EF Core Mistakes to Avoid
- Using tracking for every query
- Loading unnecessary related data
- Putting business logic inside DbContext
- Ignoring database indexing
- Creating extremely large DbContext classes
- Skipping migration management
- Using raw SQL unnecessarily
Entity Framework Core Best Practices
- Use separate projects for Domain, Application, Infrastructure, and API layers.
- Keep DbContext focused on database operations.
- Use Fluent API for complex configurations.
- Apply migrations carefully in production environments.
- Use asynchronous methods for scalable applications.
- Optimize queries before increasing infrastructure resources.
- Use dependency injection instead of creating DbContext manually.
Async Operations in EF Core
Asynchronous database operations improve application scalability by freeing server threads while waiting for database responses.
var products = await _context.Products.ToListAsync();
Frequently Asked Questions About EF Core
Is Entity Framework Core free?
Yes. Entity Framework Core is an open-source framework provided by Microsoft and can be used in commercial applications.
Is EF Core faster than traditional ADO.NET?
ADO.NET can provide lower-level database control, but EF Core provides higher developer productivity and includes many optimization features.
Can EF Core work with databases other than SQL Server?
Yes. EF Core supports multiple database providers including PostgreSQL, MySQL, SQLite, Oracle, and Cosmos DB.
Should I use Repository Pattern with EF Core?
It depends on application complexity. Many enterprise applications use repositories for abstraction, testing, and separation of responsibilities.
Conclusion
Entity Framework Core has become one of the most important technologies in the .NET ecosystem for building modern database-driven applications.
With features such as LINQ queries, migrations, relationship management, change tracking, and performance optimization, EF Core allows developers to build scalable and maintainable enterprise solutions.
A strong understanding of EF Core architecture and best practices helps development teams create applications that are easier to maintain, test, and expand in the future.