Entity Framework Core
Entity Framework Core (EF Core) is a lightweight, extensible, cross-platform ORM for .NET. Supporting both Code First and Database First approaches, it supports SQL Server, Azure SQL, SQLite, PostgreSQL, MySQL, and more, providing powerful query capabilities through LINQ integration.
Library
Entity Framework Core
Overview
Entity Framework Core (EF Core) is a lightweight, extensible, cross-platform ORM for .NET. Supporting both Code First and Database First approaches, it supports SQL Server, Azure SQL, SQLite, PostgreSQL, MySQL, and more, providing powerful query capabilities through LINQ integration.
Details
Entity Framework Core is the standard ORM of the .NET ecosystem developed by Microsoft. It modernizes and lightens the traditional Entity Framework while achieving cross-platform support. It features type-safe queries through LINQ, powerful migration capabilities, and efficient update processing through Change Tracking, widely used from enterprise to cloud-native applications.
Key Features
- LINQ Integration: Type-safe and intuitive query syntax
- Code First/Database First: Flexible development approaches
- Change Tracking: Efficient entity state management
- Migrations: Automatic schema change management
- Cross-Platform: Windows, Linux, macOS support
Pros and Cons
Pros
- Natural and unified development experience for .NET developers
- Powerful and type-safe query capabilities through LINQ
- Excellent integration with Visual Studio and IntelliSense support
- Deep integration with Azure cloud services
- Rich track record in microservices architecture
Cons
- Cannot be used in non-.NET environments
- Performance challenges with large data processing
- Increased memory usage with complex object graphs
- Relatively high learning curve (especially Entity relationship configuration)
Reference Pages
Code Examples
Installation and Basic Setup
# Install EF Core via .NET CLI
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package Microsoft.EntityFrameworkCore.Design
# For PostgreSQL
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
// Program.cs (.NET 6+)
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// Register EF Core services
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
var app = builder.Build();
// appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=MyAppDb;Trusted_Connection=true"
}
}
Basic CRUD Operations (Entity Definition, Create, Read, Update, Delete)
// Models/User.cs
using System.ComponentModel.DataAnnotations;
public class User
{
public int Id { get; set; }
[Required]
[MaxLength(100)]
public string Name { get; set; } = string.Empty;
[Required]
[EmailAddress]
public string Email { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
// Navigation property
public ICollection<Post> Posts { get; set; } = new List<Post>();
}
// Models/Post.cs
public class Post
{
public int Id { get; set; }
[Required]
[MaxLength(200)]
public string Title { get; set; } = string.Empty;
public string? Content { get; set; }
public int AuthorId { get; set; }
public User Author { get; set; } = null!;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
// Data/ApplicationDbContext.cs
using Microsoft.EntityFrameworkCore;
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<User> Users { get; set; }
public DbSet<Post> Posts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Fluent API configuration
modelBuilder.Entity<User>(entity =>
{
entity.HasIndex(e => e.Email).IsUnique();
entity.Property(e => e.CreatedAt).HasDefaultValueSql("GETUTCDATE()");
});
modelBuilder.Entity<Post>(entity =>
{
entity.HasOne(p => p.Author)
.WithMany(u => u.Posts)
.HasForeignKey(p => p.AuthorId)
.OnDelete(DeleteBehavior.Cascade);
});
}
}
// Basic CRUD operations
using Microsoft.EntityFrameworkCore;
public class UserService
{
private readonly ApplicationDbContext _context;
public UserService(ApplicationDbContext context)
{
_context = context;
}
// Create
public async Task<User> CreateUserAsync(string name, string email)
{
var user = new User { Name = name, Email = email };
_context.Users.Add(user);
await _context.SaveChangesAsync();
return user;
}
// Read
public async Task<List<User>> GetAllUsersAsync()
{
return await _context.Users.ToListAsync();
}
public async Task<User?> GetUserByIdAsync(int id)
{
return await _context.Users.FindAsync(id);
}
// Update
public async Task<User?> UpdateUserAsync(int id, string name)
{
var user = await _context.Users.FindAsync(id);
if (user != null)
{
user.Name = name;
await _context.SaveChangesAsync();
}
return user;
}
// Delete
public async Task<bool> DeleteUserAsync(int id)
{
var user = await _context.Users.FindAsync(id);
if (user != null)
{
_context.Users.Remove(user);
await _context.SaveChangesAsync();
return true;
}
return false;
}
}
Advanced Queries and Relationships
// Complex LINQ queries
public class AdvancedQueryService
{
private readonly ApplicationDbContext _context;
public AdvancedQueryService(ApplicationDbContext context)
{
_context = context;
}
// Complex conditional queries
public async Task<List<User>> GetActiveUsersAsync()
{
return await _context.Users
.Where(u => u.Posts.Any(p => p.CreatedAt >= DateTime.UtcNow.AddDays(-30)))
.Where(u => u.Email.EndsWith("@company.com"))
.OrderByDescending(u => u.CreatedAt)
.Take(10)
.ToListAsync();
}
// JOIN queries (Include)
public async Task<List<User>> GetUsersWithPostsAsync()
{
return await _context.Users
.Include(u => u.Posts)
.ToListAsync();
}
// Projection with Select
public async Task<List<object>> GetUserSummaryAsync()
{
return await _context.Users
.Select(u => new
{
u.Id,
u.Name,
u.Email,
PostCount = u.Posts.Count(),
LatestPost = u.Posts.OrderByDescending(p => p.CreatedAt).FirstOrDefault()!.Title
})
.ToListAsync();
}
// Grouping and aggregation
public async Task<List<object>> GetPostCountsByUserAsync()
{
return await _context.Posts
.GroupBy(p => p.Author.Name)
.Select(g => new
{
AuthorName = g.Key,
PostCount = g.Count(),
LatestPostDate = g.Max(p => p.CreatedAt)
})
.OrderByDescending(x => x.PostCount)
.ToListAsync();
}
// Raw SQL queries
public async Task<List<User>> GetUsersByRawSqlAsync(string emailDomain)
{
return await _context.Users
.FromSqlRaw("SELECT * FROM Users WHERE Email LIKE {0}", $"%@{emailDomain}")
.ToListAsync();
}
// Stored procedure call
public async Task<List<User>> GetUsersFromStoredProcAsync(int minPosts)
{
return await _context.Users
.FromSqlRaw("EXEC GetActiveUsers @MinPosts = {0}", minPosts)
.ToListAsync();
}
}
Migrations and Schema Management
# Create initial migration
dotnet ef migrations add InitialCreate
# Apply migrations
dotnet ef database update
# Add new migration
dotnet ef migrations add AddPostsTable
# Remove migration
dotnet ef migrations remove
# Drop database
dotnet ef database drop
// Custom migration
public partial class AddUserIndexes : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateIndex(
name: "IX_Users_Email",
table: "Users",
column: "Email",
unique: true);
migrationBuilder.Sql("CREATE INDEX IX_Users_Name_Email ON Users (Name, Email)");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_Users_Email",
table: "Users");
migrationBuilder.Sql("DROP INDEX IX_Users_Name_Email ON Users");
}
}
// Programmatic migration execution
public class DatabaseManager
{
public static async Task MigrateDatabaseAsync(IServiceProvider serviceProvider)
{
using var scope = serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
await context.Database.MigrateAsync();
}
}
Performance Optimization and Advanced Features
// Transaction management
public class TransactionService
{
private readonly ApplicationDbContext _context;
public TransactionService(ApplicationDbContext context)
{
_context = context;
}
public async Task<User> CreateUserWithPostsAsync(string name, string email, List<string> postTitles)
{
using var transaction = await _context.Database.BeginTransactionAsync();
try
{
var user = new User { Name = name, Email = email };
_context.Users.Add(user);
await _context.SaveChangesAsync();
foreach (var title in postTitles)
{
_context.Posts.Add(new Post
{
Title = title,
Content = $"Content for {title}",
AuthorId = user.Id
});
}
await _context.SaveChangesAsync();
await transaction.CommitAsync();
return user;
}
catch
{
await transaction.RollbackAsync();
throw;
}
}
}
// Batch operations
public class BatchOperationService
{
private readonly ApplicationDbContext _context;
public BatchOperationService(ApplicationDbContext context)
{
_context = context;
}
public async Task BulkInsertUsersAsync(List<(string Name, string Email)> userData)
{
var users = userData.Select(u => new User
{
Name = u.Name,
Email = u.Email
}).ToList();
_context.Users.AddRange(users);
await _context.SaveChangesAsync();
}
public async Task BulkUpdateUsersAsync(List<int> userIds, string newNamePrefix)
{
await _context.Users
.Where(u => userIds.Contains(u.Id))
.ExecuteUpdateAsync(u => u.SetProperty(x => x.Name, x => newNamePrefix + x.Name));
}
}
// Change Tracking optimization
public class OptimizedQueryService
{
private readonly ApplicationDbContext _context;
public OptimizedQueryService(ApplicationDbContext context)
{
_context = context;
}
// Read-only queries (Change Tracking disabled)
public async Task<List<User>> GetUsersReadOnlyAsync()
{
return await _context.Users
.AsNoTracking()
.ToListAsync();
}
// Split queries
public async Task<List<User>> GetUsersWithPostsSplitAsync()
{
return await _context.Users
.AsSplitQuery()
.Include(u => u.Posts)
.ToListAsync();
}
// Compiled queries
private static readonly Func<ApplicationDbContext, int, Task<User?>> GetUserByIdCompiled =
EF.CompileAsyncQuery((ApplicationDbContext context, int id) =>
context.Users.FirstOrDefault(u => u.Id == id));
public async Task<User?> GetUserByIdOptimizedAsync(int id)
{
return await GetUserByIdCompiled(_context, id);
}
}
Framework Integration and Practical Examples
// ASP.NET Core Web API integration
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly UserService _userService;
public UsersController(UserService userService)
{
_userService = userService;
}
[HttpGet]
public async Task<ActionResult<List<User>>> GetUsers()
{
var users = await _userService.GetAllUsersAsync();
return Ok(users);
}
[HttpGet("{id}")]
public async Task<ActionResult<User>> GetUser(int id)
{
var user = await _userService.GetUserByIdAsync(id);
if (user == null)
return NotFound();
return Ok(user);
}
[HttpPost]
public async Task<ActionResult<User>> CreateUser(CreateUserRequest request)
{
var user = await _userService.CreateUserAsync(request.Name, request.Email);
return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
}
}
// Minimal API integration (.NET 6+)
app.MapGet("/api/users", async (ApplicationDbContext db) =>
await db.Users.ToListAsync());
app.MapGet("/api/users/{id}", async (int id, ApplicationDbContext db) =>
await db.Users.FindAsync(id)
is User user
? Results.Ok(user)
: Results.NotFound());
app.MapPost("/api/users", async (CreateUserRequest request, ApplicationDbContext db) =>
{
var user = new User { Name = request.Name, Email = request.Email };
db.Users.Add(user);
await db.SaveChangesAsync();
return Results.Created($"/api/users/{user.Id}", user);
});
// Blazor Server integration
@page "/users"
@inject ApplicationDbContext DbContext
<h3>Users</h3>
@if (users == null)
{
<p>Loading...</p>
}
else
{
<table class="table">
@foreach (var user in users)
{
<tr>
<td>@user.Name</td>
<td>@user.Email</td>
<td>@user.Posts.Count posts</td>
</tr>
}
</table>
}
@code {
private List<User>? users;
protected override async Task OnInitializedAsync()
{
users = await DbContext.Users
.Include(u => u.Posts)
.ToListAsync();
}
}
// Background Service integration
public class DataProcessingService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
public DataProcessingService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using var scope = _serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
// Regular data processing
var unprocessedUsers = await context.Users
.Where(u => !u.IsProcessed)
.Take(100)
.ToListAsync(stoppingToken);
foreach (var user in unprocessedUsers)
{
user.IsProcessed = true;
// Processing logic
}
await context.SaveChangesAsync(stoppingToken);
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
}
}
}