EF Core深入与性能优化
大约 18 分钟约 5301 字
EF Core深入与性能优化
简介
Entity Framework Core(简称EF Core)是微软官方推出的ORM框架,基于.NET Standard开发,支持跨平台运行。它通过Code First方式将C#对象模型映射到关系型数据库,开发者可以使用LINQ进行数据库查询,无需手写SQL。EF Core在ASP.NET Core项目中是最常用的数据访问层技术,掌握其高级特性和性能优化技巧对构建高性能应用至关重要。
特点
优点
缺点
Code First 模型定义
实体类定义
/// <summary>
/// 用户实体
/// </summary>
public class User
{
public int Id { get; set; }
public string UserName { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string PasswordHash { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? LastLoginAt { get; set; }
public bool IsActive { get; set; } = true;
// 导航属性
public UserProfile? Profile { get; set; }
public ICollection<Order> Orders { get; set; } = new List<Order>();
public ICollection<UserRole> UserRoles { get; set; } = new List<UserRole>();
}
/// <summary>
/// 用户详情(一对一)
/// </summary>
public class UserProfile
{
public int Id { get; set; }
public string RealName { get; set; } = string.Empty;
public string Avatar { get; set; } = string.Empty;
public string Phone { get; set; } = string.Empty;
public DateTime BirthDate { get; set; }
// 外键
public int UserId { get; set; }
public User User { get; set; } = null!;
}
/// <summary>
/// 订单(一对多)
/// </summary>
public class Order
{
public int Id { get; set; }
public string OrderNo { get; set; } = string.Empty;
public decimal TotalAmount { get; set; }
public OrderStatus Status { get; set; }
public DateTime CreatedAt { get; set; }
// 外键
public int UserId { get; set; }
public User User { get; set; } = null!;
// 导航属性
public ICollection<OrderItem> Items { get; set; } = new List<OrderItem>();
}
public enum OrderStatus
{
Pending = 0,
Paid = 1,
Shipped = 2,
Completed = 3,
Cancelled = 4
}
/// <summary>
/// 订单明细
/// </summary>
public class OrderItem
{
public int Id { get; set; }
public string ProductName { get; set; } = string.Empty;
public decimal Price { get; set; }
public int Quantity { get; set; }
public int OrderId { get; set; }
public Order Order { get; set; } = null!;
}
/// <summary>
/// 角色(多对多中间表方式)
/// </summary>
public class Role
{
public int Id { get; set; }
public string RoleName { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public ICollection<UserRole> UserRoles { get; set; } = new List<UserRole>();
}
/// <summary>
/// 用户角色关联(多对多)
/// </summary>
public class UserRole
{
public int UserId { get; set; }
public User User { get; set; } = null!;
public int RoleId { get; set; }
public Role Role { get; set; } = null!;
public DateTime AssignedAt { get; set; } = DateTime.UtcNow;
}DbContext 定义
/// <summary>
/// 数据库上下文
/// </summary>
public class AppDbContext : DbContext
{
public DbSet<User> Users => Set<User>();
public DbSet<UserProfile> UserProfiles => Set<UserProfile>();
public DbSet<Order> Orders => Set<Order>();
public DbSet<OrderItem> OrderItems => Set<OrderItem>();
public DbSet<Role> Roles => Set<Role>();
public DbSet<UserRole> UserRoles => Set<UserRole>();
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// 一对一:User -> UserProfile
modelBuilder.Entity<User>()
.HasOne(u => u.Profile)
.WithOne(p => p.User)
.HasForeignKey<UserProfile>(p => p.UserId)
.OnDelete(DeleteBehavior.Cascade);
// 一对多:User -> Order
modelBuilder.Entity<User>()
.HasMany(u => u.Orders)
.WithOne(o => o.User)
.HasForeignKey(o => o.UserId)
.OnDelete(DeleteBehavior.Restrict);
// 一对多:Order -> OrderItem
modelBuilder.Entity<Order>()
.HasMany(o => o.Items)
.WithOne(i => i.Order)
.HasForeignKey(i => i.OrderId)
.OnDelete(DeleteBehavior.Cascade);
// 多对多:User <-> Role(通过中间表 UserRole)
modelBuilder.Entity<UserRole>()
.HasKey(ur => new { ur.UserId, ur.RoleId });
modelBuilder.Entity<UserRole>()
.HasOne(ur => ur.User)
.WithMany(u => u.UserRoles)
.HasForeignKey(ur => ur.UserId);
modelBuilder.Entity<UserRole>()
.HasOne(ur => ur.Role)
.WithMany(r => r.UserRoles)
.HasForeignKey(ur => ur.RoleId);
// 配置索引
modelBuilder.Entity<User>()
.HasIndex(u => u.Email)
.IsUnique();
modelBuilder.Entity<User>()
.HasIndex(u => u.UserName)
.IsUnique();
modelBuilder.Entity<Order>()
.HasIndex(o => o.OrderNo)
.IsUnique();
modelBuilder.Entity<Order>()
.HasIndex(o => o.UserId);
// 配置默认值
modelBuilder.Entity<User>()
.Property(u => u.CreatedAt)
.HasDefaultValueSql("GETUTCDATE()");
modelBuilder.Entity<User>()
.Property(u => u.IsActive)
.HasDefaultValue(true);
// 配置字段长度和约束
modelBuilder.Entity<User>()
.Property(u => u.UserName)
.HasMaxLength(50)
.IsRequired();
modelBuilder.Entity<User>()
.Property(u => u.Email)
.HasMaxLength(100)
.IsRequired();
// 全局查询过滤器(软删除)
modelBuilder.Entity<User>()
.HasQueryFilter(u => u.IsActive);
// 使用 Value Converter 存储枚举为字符串
modelBuilder.Entity<Order>()
.Property(o => o.Status)
.HasConversion<string>();
}
}Fluent API 配置详解
使用 IEntityTypeConfiguration 分离配置
/// <summary>
/// 用户实体配置类 — 保持 DbContext 整洁
/// </summary>
public class UserConfiguration : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.ToTable("Users");
builder.HasKey(u => u.Id);
builder.Property(u => u.UserName)
.HasColumnName("UserName")
.HasMaxLength(50)
.IsRequired();
builder.Property(u => u.Email)
.HasMaxLength(100)
.IsRequired();
builder.Property(u => u.PasswordHash)
.HasMaxLength(256)
.IsRequired();
builder.HasIndex(u => u.Email).IsUnique();
builder.HasIndex(u => u.UserName).IsUnique();
builder.HasQueryFilter(u => u.IsActive);
}
}
/// <summary>
/// 在 DbContext 中批量应用配置
/// </summary>
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// 自动加载程序集中所有 IEntityTypeConfiguration
modelBuilder.ApplyConfigurationsFromAssembly(
typeof(AppDbContext).Assembly
);
}多对多配置(EF Core 5.0+ 原生支持)
/// <summary>
/// EF Core 5.0+ 可以直接配置多对多,无需中间实体
/// </summary>
public class Tag
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public ICollection<Post> Posts { get; set; } = new List<Post>();
}
public class Post
{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
public ICollection<Tag> Tags { get; set; } = new List<Tag>();
}
// 在 OnModelCreating 中
modelBuilder.Entity<Post>()
.HasMany(p => p.Tags)
.WithMany(t => t.Posts)
.UsingEntity(j => j.ToTable("PostTags"));Migrations 迁移管理
常用迁移命令
# 安装工具(如果尚未安装)
dotnet tool install --global dotnet-ef
# 创建迁移
dotnet ef migrations add InitCreate
# 创建迁移(指定输出目录)
dotnet ef migrations add AddOrderTable --output-dir Data/Migrations
# 更新数据库到最新迁移
dotnet ef database update
# 回滚到指定迁移
dotnet ef database update InitCreate
# 移除最后一次迁移(未应用到数据库时)
dotnet ef migrations remove
# 生成SQL脚本(不执行)
dotnet ef migrations script
# 生成从某个版本到另一个版本的SQL脚本
dotnet ef migrations script AddOrderTable AddUserTable --output script.sql自定义迁移逻辑
/// <summary>
/// 迁移中执行自定义SQL
/// </summary>
public partial class AddSeedData : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
// 创建表
migrationBuilder.CreateTable(
name: "Roles",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
RoleName = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
Description = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Roles", x => x.Id);
});
// 插入种子数据
migrationBuilder.InsertData(
table: "Roles",
columns: new[] { "RoleName", "Description" },
values: new object[,]
{
{ "Admin", "系统管理员" },
{ "User", "普通用户" },
{ "Guest", "访客" }
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(name: "Roles");
}
}在应用启动时自动迁移
/// <summary>
/// Program.cs 中自动执行迁移
/// </summary>
var app = builder.Build();
// 自动迁移
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
db.Database.Migrate();
}
app.Run();查询优化实战
AsNoTracking — 只读查询必用
/// <summary>
/// AsNoTracking 跳过变更追踪,提升查询性能
/// 适用于不需要更新实体的只读场景
/// </summary>
public class UserService
{
private readonly AppDbContext _context;
public UserService(AppDbContext context)
{
_context = context;
}
// 只读查询 — 使用 AsNoTracking
public async Task<List<User>> GetAllUsersAsync()
{
return await _context.Users
.AsNoTracking()
.Where(u => u.IsActive)
.OrderBy(u => u.UserName)
.ToListAsync();
}
// 根据ID查询 — 只读
public async Task<User?> GetUserByIdAsync(int id)
{
return await _context.Users
.AsNoTracking()
.FirstOrDefaultAsync(u => u.Id == id);
}
// 需要更新的查询 — 不用 AsNoTracking
public async Task<User> UpdateUserEmailAsync(int id, string newEmail)
{
var user = await _context.Users.FindAsync(id);
if (user != null)
{
user.Email = newEmail;
await _context.SaveChangesAsync();
}
return user!;
}
}Select Loading — 只查需要的字段
/// <summary>
/// 使用 Select 投影,只查询需要的列,减少数据传输量
/// </summary>
public class UserQueryService
{
private readonly AppDbContext _context;
public UserQueryService(AppDbContext context)
{
_context = context;
}
// 不好的做法:查询全部字段
public async Task<List<User>> GetUsersBadAsync()
{
return await _context.Users
.Include(u => u.Profile)
.Include(u => u.Orders)
.ThenInclude(o => o.Items)
.ToListAsync(); // 加载了大量不需要的数据
}
// 好的做法:使用 DTO 投影
public async Task<List<UserDto>> GetUserListAsync()
{
return await _context.Users
.AsNoTracking()
.Where(u => u.IsActive)
.Select(u => new UserDto
{
Id = u.Id,
UserName = u.UserName,
Email = u.Email,
RealName = u.Profile != null ? u.Profile.RealName : "",
OrderCount = u.Orders.Count
})
.ToListAsync();
}
// 分页查询 + 投影
public async Task<PagedResult<UserDto>> GetUsersPagedAsync(int page, int pageSize)
{
var query = _context.Users
.AsNoTracking()
.Where(u => u.IsActive);
var total = await query.CountAsync();
var items = await query
.OrderBy(u => u.Id)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(u => new UserDto
{
Id = u.Id,
UserName = u.UserName,
Email = u.Email
})
.ToListAsync();
return new PagedResult<UserDto>
{
Items = items,
Total = total,
Page = page,
PageSize = pageSize
};
}
}
public class UserDto
{
public int Id { get; set; }
public string UserName { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string RealName { get; set; } = string.Empty;
public int OrderCount { get; set; }
}
public class PagedResult<T>
{
public List<T> Items { get; set; } = new();
public int Total { get; set; }
public int Page { get; set; }
public int PageSize { get; set; }
public int TotalPages => (int)Math.Ceiling(Total / (double)PageSize);
}加载策略对比
/// <summary>
/// 三种加载策略:Eager Loading / Lazy Loading / Explicit Loading
/// </summary>
public class OrderQueryService
{
private readonly AppDbContext _context;
public OrderQueryService(AppDbContext context)
{
_context = context;
}
// 1. Eager Loading(贪婪加载)— 使用 Include,一次性 JOIN 查询
public async Task<List<Order>> GetOrdersWithItemsEagerAsync()
{
return await _context.Orders
.AsNoTracking()
.Include(o => o.User)
.Include(o => o.Items)
.Where(o => o.Status == OrderStatus.Paid)
.ToListAsync();
}
// 2. Lazy Loading(延迟加载)— 需要安装 Microsoft.EntityFrameworkCore.Proxies
// 配置:optionsBuilder.UseLazyLoadingProxies()
// 实体导航属性必须加 virtual
// 注意:容易导致 N+1 查询问题,不推荐在生产中使用
public async Task<Order?> GetOrderLazyAsync(int orderId)
{
var order = await _context.Orders.FindAsync(orderId);
// 访问 order.Items 时才去数据库查询(产生额外SQL)
var firstItemName = order?.Items.FirstOrDefault()?.ProductName;
return order;
}
// 3. Explicit Loading(显式加载)— 按需手动加载导航属性
public async Task<Order?> GetOrderExplicitAsync(int orderId)
{
var order = await _context.Orders.FindAsync(orderId);
if (order != null)
{
// 显式加载关联的用户
await _context.Entry(order)
.Reference(o => o.User)
.LoadAsync();
// 显式加载订单明细(可带过滤条件)
await _context.Entry(order)
.Collection(o => o.Items)
.Query()
.Where(i => i.Quantity > 0)
.LoadAsync();
}
return order;
}
}| 加载策略 | 方式 | 适用场景 | 注意事项 |
|---|---|---|---|
| Eager Loading | Include / ThenInclude | 确定需要关联数据 | 注意避免加载过多数据 |
| Lazy Loading | virtual + Proxy | 不确定是否需要关联数据 | 容易产生N+1问题 |
| Explicit Loading | Entry().Reference/Collection | 按需精确控制加载 | 代码略繁琐但可控 |
避免 N+1 查询问题
/// <summary>
/// N+1 查询问题演示与解决
/// </summary>
public class NPlusOneDemo
{
private readonly AppDbContext _context;
public NPlusOneDemo(AppDbContext context)
{
_context = context;
}
// 错误示例:N+1 查询
// 1次查询获取所有订单 + N次查询获取每个订单的用户 = N+1 次
public async Task BadNPlusOneAsync()
{
var orders = await _context.Orders.ToListAsync(); // 1次查询
foreach (var order in orders)
{
// 每次循环都触发一次查询 — 查了N次!
var userName = order.User.UserName;
Console.WriteLine($"Order: {order.OrderNo}, User: {userName}");
}
}
// 解决方案1:Include 贪婪加载
public async Task FixWithIncludeAsync()
{
var orders = await _context.Orders
.Include(o => o.User)
.ToListAsync(); // 1次JOIN查询搞定
foreach (var order in orders)
{
Console.WriteLine($"Order: {order.OrderNo}, User: {order.User.UserName}");
}
}
// 解决方案2:Select 投影
public async Task FixWithSelectAsync()
{
var result = await _context.Orders
.AsNoTracking()
.Select(o => new
{
o.OrderNo,
UserName = o.User.UserName
})
.ToListAsync(); // 1次查询搞定
foreach (var item in result)
{
Console.WriteLine($"Order: {item.OrderNo}, User: {item.UserName}");
}
}
// 解决方案3:拆分查询(EF Core 5.0+)
public async Task FixWithSplitQueryAsync()
{
var orders = await _context.Orders
.Include(o => o.Items)
.AsSplitQuery() // 拆分为多条SQL,避免笛卡尔爆炸
.ToListAsync();
}
}并发控制
乐观并发控制
/// <summary>
/// 乐观并发 — 使用并发令牌(Concurrency Token)
/// </summary>
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
public int Stock { get; set; }
// 并发令牌 — 更新时自动检查
public int Version { get; set; }
}
// Fluent API 配置并发令牌
modelBuilder.Entity<Product>()
.Property(p => p.Version)
.IsConcurrencyToken()
.IsRowVersion(); // SQL Server 自动递增
// 或者使用 Timestamp 特性
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
public int Stock { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
}
/// <summary>
/// 处理并发冲突
/// </summary>
public class ProductService
{
private readonly AppDbContext _context;
public ProductService(AppDbContext context)
{
_context = context;
}
public async Task<bool> UpdatePriceAsync(int productId, decimal newPrice)
{
var product = await _context.Products.FindAsync(productId);
if (product == null) return false;
product.Price = newPrice;
try
{
await _context.SaveChangesAsync();
return true;
}
catch (DbUpdateConcurrencyException ex)
{
// 方式1:使用数据库的值(客户端放弃)
ex.Entries.Single().State = EntityState.Unchanged;
return false;
}
}
// 自定义并发冲突解决策略
public async Task<ConcurrencyResult> UpdatePriceWithResolveAsync(
int productId, decimal newPrice)
{
var product = await _context.Products.FindAsync(productId);
if (product == null)
return ConcurrencyResult.NotFound;
product.Price = newPrice;
try
{
await _context.SaveChangesAsync();
return ConcurrencyResult.Success;
}
catch (DbUpdateConcurrencyException ex)
{
var entry = ex.Entries.Single();
var databaseValues = await entry.GetDatabaseValuesAsync();
if (databaseValues == null)
return ConcurrencyResult.Deleted;
var dbProduct = (Product)databaseValues.ToObject();
// 让用户决定如何合并
// 这里选择:数据库的Stock优先,客户端的Price优先
entry.OriginalValues.SetValues(databaseValues);
product.Price = newPrice; // 使用客户端值
try
{
await _context.SaveChangesAsync();
return ConcurrencyResult.Resolved;
}
catch
{
return ConcurrencyResult.Failed;
}
}
}
}
public enum ConcurrencyResult
{
Success,
NotFound,
Deleted,
Resolved,
Failed
}原始 SQL 查询
FromSqlRaw 与 ExecuteSqlRaw
/// <summary>
/// 原始SQL查询 — 处理EF Core LINQ无法表达的复杂场景
/// </summary>
public class RawSqlService
{
private readonly AppDbContext _context;
public RawSqlService(AppDbContext context)
{
_context = context;
}
// 1. FromSqlRaw — 查询返回实体
public async Task<List<User>> GetActiveUsersRawAsync()
{
return await _context.Users
.FromSqlRaw("SELECT * FROM Users WHERE IsActive = 1")
.ToListAsync();
}
// 2. FromSqlInterpolated — 安全的参数化查询
public async Task<List<User>> SearchUsersAsync(string keyword)
{
// 使用插值语法自动参数化,防止SQL注入
return await _context.Users
.FromSqlInterpolated(
$"SELECT * FROM Users WHERE UserName LIKE {"%" + keyword + "%"}")
.ToListAsync();
}
// 3. FromSqlRaw + LINQ 组合
public async Task<List<User>> GetUsersRawWithLinqAsync(string role)
{
return await _context.Users
.FromSqlRaw("SELECT * FROM Users WHERE IsActive = 1")
.Where(u => u.UserRoles.Any(r => r.Role.RoleName == role))
.OrderBy(u => u.UserName)
.Take(50)
.ToListAsync();
}
// 4. SqlQuery — 查询返回非实体类型(EF Core 7.0+)
public async Task<List<UserCountDto>> GetUserOrderCountsAsync()
{
var sql = @"
SELECT u.Id, u.UserName, COUNT(o.Id) AS OrderCount
FROM Users u
LEFT JOIN Orders o ON u.Id = o.UserId
GROUP BY u.Id, u.UserName
HAVING COUNT(o.Id) > 0
ORDER BY OrderCount DESC";
return await _context.Database
.SqlQueryRaw<UserCountDto>(sql)
.ToListAsync();
}
// 5. ExecuteSqlRaw — 执行非查询SQL(更新/删除)
public async Task<int> DeactivateInactiveUsersAsync(int daysInactive)
{
return await _context.Database.ExecuteSqlRawAsync(
"UPDATE Users SET IsActive = 0 WHERE LastLoginAt < DATEADD(day, -{0}, GETUTCDATE())",
daysInactive);
}
// 6. ExecuteSqlInterpolated — 安全参数化的非查询SQL
public async Task<int> BatchUpdatePriceAsync(decimal factor)
{
return await _context.Database
.ExecuteSqlInterpolatedAsync(
$"UPDATE Products SET Price = Price * {factor}");
}
}
public class UserCountDto
{
public int Id { get; set; }
public string UserName { get; set; } = string.Empty;
public int OrderCount { get; set; }
}批量操作
批量删除与更新(EF Core 7.0+)
/// <summary>
/// 批量操作 — 直接在数据库端执行,不加载到内存
/// </summary>
public class BatchOperationService
{
private readonly AppDbContext _context;
public BatchOperationService(AppDbContext context)
{
_context = context;
}
// 批量删除(EF Core 7.0+ 原生支持)
public async Task<int> DeleteCancelledOrdersAsync()
{
return await _context.Orders
.Where(o => o.Status == OrderStatus.Cancelled)
.ExecuteDeleteAsync();
}
// 批量更新(EF Core 7.0+ 原生支持)
public async Task<int> MarkOldOrdersAsCompletedAsync()
{
return await _context.Orders
.Where(o => o.Status == OrderStatus.Shipped
&& o.CreatedAt < DateTime.UtcNow.AddDays(-30))
.ExecuteUpdateAsync(setters => setters
.SetProperty(o => o.Status, OrderStatus.Completed)
);
}
// 批量更新多个字段
public async Task<int> BulkPriceUpdateAsync(decimal discount)
{
return await _context.OrderItems
.Where(i => i.Order.Status == OrderStatus.Pending)
.ExecuteUpdateAsync(setters => setters
.SetProperty(i => i.Price, i => i.Price * discount)
);
}
// 使用第三方库 EFCore.BulkExtensions 处理大量数据插入
// dotnet add package EFCore.BulkExtensions
public async Task BulkInsertUsersAsync(List<User> users)
{
await _context.BulkInsertAsync(users);
}
public async Task BulkUpdateUsersAsync(List<User> users)
{
await _context.BulkUpdateAsync(users);
}
// 批量插入 + 更新(Upsert)
public async Task BulkInsertOrUpdateAsync(List<User> users)
{
await _context.BulkInsertOrUpdateAsync(users, options =>
{
options.SetOutputIdentity = true; // 获取自增ID
options.UpdateBy = new List<string> { "Email" }; // 按Email判断是否存在
});
}
}编译查询(Compiled Queries)
/// <summary>
/// 编译查询 — 缓存查询表达式树,减少重复编译开销
/// 适用于高频调用的查询场景
/// </summary>
public class CompiledQueryService
{
private readonly AppDbContext _context;
public CompiledQueryService(AppDbContext context)
{
_context = context;
}
// 定义编译查询(静态字段,只编译一次)
private static readonly Func<AppDbContext, int, Task<User?>> _getUserById =
EF.CompileAsyncQuery((AppDbContext context, int id) =>
context.Users.FirstOrDefault(u => u.Id == id));
private static readonly Func<AppDbContext, string, Task<User?>> _getUserByEmail =
EF.CompileAsyncQuery((AppDbContext context, string email) =>
context.Users.FirstOrDefault(u => u.Email == email));
private static readonly Func<AppDbContext, int, int, Task<List<User>>> _getUsersPaged =
EF.CompileAsyncQuery((AppDbContext context, int skip, int take) =>
context.Users
.Where(u => u.IsActive)
.OrderBy(u => u.Id)
.Skip(skip)
.Take(take)
.ToList());
// 使用编译查询
public Task<User?> GetByIdAsync(int id)
{
return _getUserById(_context, id);
}
public Task<User?> GetByEmailAsync(string email)
{
return _getUserByEmail(_context, email);
}
public Task<List<User>> GetPagedAsync(int page, int pageSize)
{
return _getUsersPaged(_context, (page - 1) * pageSize, pageSize);
}
}性能优化要点总结
全局查询过滤器与性能
/// <summary>
/// 软删除过滤 + 租户过滤等全局过滤器
/// </summary>
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// 软删除过滤器
modelBuilder.Entity<User>()
.HasQueryFilter(u => u.IsActive);
modelBuilder.Entity<Order>()
.HasQueryFilter(o => !o.IsDeleted);
// 多租户过滤器
modelBuilder.Entity<Order>()
.HasQueryFilter(o => o.TenantId == _currentTenantId);
}
// 需要绕过过滤器时(管理员查看已删除数据)
public async Task<List<User>> GetAllIncludingDeletedAsync()
{
return await _context.Users
.IgnoreQueryFilters() // 忽略全局过滤器
.ToListAsync();
}连接池配置
/// <summary>
/// Program.cs 中的 DbContext 性能配置
/// </summary>
var builder = WebApplication.CreateBuilder(args);
// 启用数据库连接池(推荐)
builder.Services.AddDbContextPool<AppDbContext>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("Default"),
sqlOptions =>
{
// 配置命令超时
sqlOptions.CommandTimeout(30);
// 启用重试策略
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 3,
maxRetryDelay: TimeSpan.FromSeconds(10),
errorNumbersToAdd: null);
// 设置程序集用于Migration
sqlOptions.MigrationsAssembly(typeof(AppDbContext).Assembly.FullName);
});
// 开发环境启用敏感数据日志
if (builder.Environment.IsDevelopment())
{
options.EnableSensitiveDataLogging();
options.EnableDetailedErrors();
}
}, poolSize: 128); // 连接池大小,默认128
// 如果不用连接池(不推荐)
// builder.Services.AddDbContext<AppDbContext>(options => { ... });性能优化清单
/// <summary>
/// EF Core 性能优化最佳实践汇总
/// </summary>
public class EfCorePerformanceTips
{
private readonly AppDbContext _context;
// 1. 只读查询必须加 AsNoTracking
public async Task<List<User>> GetUsersReadOnlyAsync()
{
return await _context.Users.AsNoTracking().ToListAsync();
}
// 2. 使用 Select 投影,避免 SELECT *
public async Task<List<string>> GetUserNamesAsync()
{
return await _context.Users
.AsNoTracking()
.Select(u => u.UserName)
.ToListAsync();
}
// 3. 分页查询 — 必须先排序
public async Task<List<User>> GetUsersPageAsync(int page, int size)
{
return await _context.Users
.AsNoTracking()
.OrderBy(u => u.Id)
.Skip((page - 1) * size)
.Take(size)
.ToListAsync();
}
// 4. 使用 FindAsync 代替 FirstOrDefaultAsync 查主键
public async Task<User?> GetUserAsync(int id)
{
// FindAsync 先查本地缓存,没有再去数据库
return await _context.Users.FindAsync(id);
}
// 5. 批量操作使用 ExecuteUpdate / ExecuteDelete
public async Task<int> DeleteOldOrdersAsync()
{
return await _context.Orders
.Where(o => o.CreatedAt < DateTime.UtcNow.AddYears(-1))
.ExecuteDeleteAsync();
}
// 6. 高频查询使用编译查询(见上方 CompiledQueryService)
// 7. 合理使用索引
// 在 OnModelCreating 中配置:
// modelBuilder.Entity<User>().HasIndex(u => u.Email).IsUnique();
// modelBuilder.Entity<Order>().HasIndex(o => new { o.UserId, o.Status });
// 8. 避免 Include 嵌套过深
// 不好:.Include(u => u.Orders).ThenInclude(o => o.Items).ThenInclude(i => i.Product)
// 好:按需 Select 投影
// 9. 大数据量使用 AsSplitQuery 避免笛卡尔爆炸
public async Task<List<Order>> GetOrdersWithItemsSplitAsync()
{
return await _context.Orders
.Include(o => o.Items)
.AsSplitQuery()
.ToListAsync();
}
// 10. 批量插入大量数据使用 EFCore.BulkExtensions
}| 优化手段 | 场景 | 性能提升 |
|---|---|---|
| AsNoTracking | 只读查询 | 减少内存和CPU开销 |
| Select投影 | 不需要全部字段 | 减少数据传输量 |
| 编译查询 | 高频相同查询 | 省去表达式编译时间 |
| 分页查询 | 列表页面 | 减少数据加载量 |
| 索引优化 | WHERE/JOIN/ORDER BY | 加速数据库查询 |
| 连接池 | 所有场景 | 减少连接创建开销 |
| SplitQuery | Include集合导航属性 | 避免笛卡尔积 |
| 批量操作 | 批量更新/删除 | 减少数据库往返 |
在 ASP.NET Core 中注册使用
/// <summary>
/// Program.cs 完整配置示例
/// </summary>
var builder = WebApplication.CreateBuilder(args);
// 注册 DbContext
builder.Services.AddDbContextPool<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("Default")));
// 注册 Repository
builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
// 注册 Service
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<IOrderService, OrderService>();
var app = builder.Build();
// 自动迁移
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
// 应用所有待执行的迁移
db.Database.Migrate();
}
app.Run();
/// <summary>
/// 通用泛型 Repository 基类
/// </summary>
public class Repository<T> : IRepository<T> where T : class
{
protected readonly AppDbContext _context;
protected readonly DbSet<T> _dbSet;
public Repository(AppDbContext context)
{
_context = context;
_dbSet = context.Set<T>();
}
public async Task<T?> GetByIdAsync(int id) => await _dbSet.FindAsync(id);
public async Task<List<T>> GetAllAsync() =>
await _dbSet.AsNoTracking().ToListAsync();
public async Task<T> AddAsync(T entity)
{
await _dbSet.AddAsync(entity);
await _context.SaveChangesAsync();
return entity;
}
public async Task UpdateAsync(T entity)
{
_dbSet.Update(entity);
await _context.SaveChangesAsync();
}
public async Task DeleteAsync(T entity)
{
_dbSet.Remove(entity);
await _context.SaveChangesAsync();
}
public async Task<List<T>> GetPagedAsync(int page, int pageSize)
{
return await _dbSet
.AsNoTracking()
.OrderBy(e => EF.Property<int>(e, "Id"))
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
}
}
public interface IRepository<T> where T : class
{
Task<T?> GetByIdAsync(int id);
Task<List<T>> GetAllAsync();
Task<T> AddAsync(T entity);
Task UpdateAsync(T entity);
Task DeleteAsync(T entity);
Task<List<T>> GetPagedAsync(int page, int pageSize);
}总结
EF Core 是 .NET 官方 ORM,适合大多数 CRUD 场景。核心优势:LINQ 查询、迁移管理、变更追踪。性能优化关键是:AsNoTracking 只读查询、批量操作、合理使用 Include 避免 N+1、编译查询缓存。复杂报表和批量操作场景建议用 Dapper 补充。
关键知识点
- 先分清这个主题位于请求链路、后台任务链路还是基础设施链路。
- 服务端主题通常不只关心功能正确,还关心稳定性、性能和可观测性。
- 任何框架能力都要结合配置、生命周期、异常传播和外部依赖一起看。
- 先把数据模型、访问模式和执行代价绑定起来理解。
项目落地视角
- 画清请求进入、业务执行、外部调用、日志记录和错误返回的完整路径。
- 为关键链路补齐超时、重试、熔断、追踪和结构化日志。
- 把配置与敏感信息分离,并明确不同环境的差异来源。
- 保留执行计划、样本 SQL、索引定义和优化前后指标。
常见误区
- 只会堆中间件或组件,不知道它们在链路中的执行顺序。
- 忽略生命周期和线程池、连接池等运行时资源约束。
- 没有监控和测试就对性能或可靠性下结论。
- 脱离真实数据分布设计索引。
进阶路线
- 继续向运行时行为、可观测性、发布治理和微服务协同深入。
- 把主题和数据库、缓存、消息队列、认证授权联动起来理解。
- 沉淀团队级模板,包括统一异常处理、配置约定和基础设施封装。
- 继续深入存储引擎、复制机制、归档与冷热分层治理。
适用场景
- 当你准备把《EF Core深入与性能优化》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合 API 服务、后台任务、实时通信、认证授权和微服务协作场景。
- 当需求开始涉及稳定性、性能、可观测性和发布流程时,这类主题会成为基础设施能力。
落地建议
- 先定义请求链路与失败路径,再决定中间件、过滤器、服务边界和依赖方式。
- 为关键链路补日志、指标、追踪、超时与重试策略。
- 环境配置与敏感信息分离,避免把生产参数写死在代码或镜像里。
排错清单
- 先确认问题发生在路由、模型绑定、中间件、业务层还是基础设施层。
- 检查 DI 生命周期、配置来源、序列化规则和认证上下文。
- 查看线程池、连接池、缓存命中率和外部依赖超时。
复盘问题
- 如果把《EF Core深入与性能优化》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《EF Core深入与性能优化》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《EF Core深入与性能优化》最大的收益和代价分别是什么?
