1、使用高效的查询方式
避免使用ToList()
或ToArray()
过早将查询结果加载到内存,尽量延迟执行查询。使用IQueryable
保持查询在数据库层面执行,减少数据传输量。例如:
var results = dbContext.Products.Where(p => p.Price > 100);
2、优化导航属性加载
避免N+1查询问题。使用Include
和ThenInclude
进行预加载关联数据,或使用Select
进行投影查询。例如:
var orders = dbContext.Orders
.Include(o => o.Customer)
.ThenInclude(c => c.Address)
.ToList();
3、批量操作优化
使用AddRange
和RemoveRange
进行批量操作,减少数据库往返次数。对于大批量数据插入,考虑使用BulkInsert
扩展库或原生SQL批量操作。
实现批量操作的C#代码示例
使用AddRange
和RemoveRange
进行批量操作可以有效减少数据库往返次数。以下是实现代码:
// 批量添加实体
using (var context = new YourDbContext())
{
var entitiesToAdd = new List<YourEntity>
{
new YourEntity { Property1 = "Value1", Property2 = "Value2" },
new YourEntity { Property1 = "Value3", Property2 = "Value4" },
// 添加更多实体...
};
context.YourEntities.AddRange(entitiesToAdd);
context.SaveChanges();
}
// 批量删除实体
using (var context = new YourDbContext())
{
var entitiesToRemove = context.YourEntities
.Where(e => e.SomeCondition)
.ToList();
context.YourEntities.RemoveRange(entitiesToRemove);
context.SaveChanges();
}
使用BulkInsert扩展库
对于大批量数据插入,可以使用Entity Framework扩展库如Z.EntityFramework.Extensions
或EFCore.BulkExtensions
:
// 使用EFCore.BulkExtensions
using (var context = new YourDbContext())
{
var bulkEntities = new List<YourEntity>();
// 填充bulkEntities...
context.BulkInsert(bulkEntities);
}
原生SQL批量操作
对于最高性能的场景,可以使用原生SQL批量操作:
// 原生SQL批量插入
using (var context = new YourDbContext())
{
var sql = "INSERT INTO YourEntities (Column1, Column2) VALUES (@p0, @p1), (@p2, @p3)";
var parameters = new object[] { "Value1", "Value2", "Value3", "Value4" };
context.Database.ExecuteSqlRaw(sql, parameters);
}
性能优化建议
- 事务处理:对于大批量操作,将操作包裹在事务中可以显著提高性能
- 批量大小:合理设置批量操作的大小,通常100-1000个实体为一批
- 临时禁用跟踪:批量操作时可以暂时关闭变更跟踪
using (var context = new YourDbContext())
{
context.ChangeTracker.AutoDetectChangesEnabled = false;
// 执行批量操作...
context.ChangeTracker.AutoDetectChangesEnabled = true;
}
4、禁用变更跟踪
在只读场景下,使用AsNoTracking
避免EF Core跟踪实体状态变化,提升查询性能。例如:
var products = dbContext.Products.AsNoTracking().ToList();
5、使用原始SQL优化复杂查询
在需要处理复杂查询或优化性能关键路径时,EF Core 提供了两种执行原生 SQL 查询的方法:FromSqlRaw 和 FromSqlInterpolated。这些方法允许开发者直接执行经过优化的 SQL 语句,避免了 LINQ 查询可能产生的性能瓶颈。
具体应用场景包括:
- 复杂报表查询:当需要执行涉及多表连接、聚合函数或复杂子查询的报表生成时
- 批量数据处理:处理大量数据时,原生 SQL 通常比 LINQ 转换的查询更高效
- 特定数据库功能:需要使用特定数据库供应商的高级特性时
使用示例:
// 使用参数化查询防止SQL注入 (FromSqlRaw)
var category = "Electronics";
var products = context.Products
.FromSqlRaw("SELECT * FROM Products WHERE Category = {0}", category)
.ToList();
// 使用字符串插值方式 (FromSqlInterpolated)
var minPrice = 100;
var maxPrice = 500;
var filteredProducts = context.Products
.FromSqlInterpolated($"SELECT * FROM Products WHERE Price BETWEEN {minPrice} AND {maxPrice}")
.ToList();
重要注意事项:
- 始终使用参数化查询以避免 SQL 注入攻击
- 确保 SQL 语句返回的列与实体属性完全匹配
- 对性能敏感的操作应在执行前通过数据库工具进行查询计划分析
- 考虑添加合适的数据库索引来支持原生 SQL 查询
最佳实践建议:
- 对于简单查询,优先使用 LINQ 以保持代码可维护性
- 仅在确实需要性能优化时才使用原生 SQL
- 将复杂的原生 SQL 查询封装在存储库模式或服务层中
- 为关键原生 SQL 查询添加单元测试和性能测试
6、合理配置DbContext生命周期
避免长时间保持DbContext实例开启。在Web应用中,使用依赖注入的Scoped生命周期;在桌面应用中,及时释放DbContext。
7、索引优化
确保数据库表在频繁查询的列上建立适当索引。使用EF Core迁移或数据库工具创建索引。例如:
modelBuilder.Entity<Product>()
.HasIndex(p => p.Name)
.IsUnique();
8、启用EF Core日志记录以识别性能瓶颈
详细配置方法
1. 使用内置LogTo方法
在DbContext配置中启用简单日志记录:
optionsBuilder.UseSqlServer(connectionString)
.LogTo(Console.WriteLine, // 输出到控制台
new[] { DbLoggerCategory.Database.Command.Name }, // 只记录SQL命令
LogLevel.Information, // 信息级别日志
DbContextLoggerOptions.DefaultWithLocalTime); // 包含本地时间
2. 配置日志级别
可设置的日志级别包括:
LogLevel.Debug
: 最详细日志LogLevel.Information
: 常规信息(推荐)LogLevel.Warning
: 仅警告LogLevel.Error
: 仅错误
3. 使用第三方日志框架
结合Serilog/NLog等框架的配置示例:
var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddSerilog(dispose: true)
.AddFilter(DbLoggerCategory.Database.Command.Name, LogLevel.Information);
});
optionsBuilder.UseLoggerFactory(loggerFactory);
典型日志输出分析
日志示例:
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (32ms) [Parameters=[@__id_0='?'], CommandType='Text', CommandTimeout='30']
SELECT [u].[Id], [u].[Name]
FROM [Users] AS [u]
WHERE [u].[Id] = @__id_0
可分析的关键信息:
- 执行耗时(32ms)
- 使用的参数(@__id_0)
- 生成的SQL语句
- 命令类型和超时设置
性能优化应用场景
- N+1查询问题检测:通过日志发现大量相似查询
- 慢查询识别:筛选执行时间长的命令
- 参数化查询验证:检查是否有效使用参数
- 事务管理分析:跟踪事务边界和隔离级别
高级配置选项
- 敏感数据记录控制:
optionsBuilder.EnableSensitiveDataLogging(); // 显示参数值
optionsBuilder.EnableDetailedErrors(); // 更详细的错误信息
- 自定义日志过滤:
.LogTo(msg =>
{
if (msg.Contains("ms)"))
{
Console.WriteLine(msg.Split("ms)")[0] + "ms)");
}
})
9、分页处理大数据集
使用Skip
和Take
实现分页查询,避免一次性加载大量数据。例如:
var page = dbContext.Products
.OrderBy(p => p.Id)
.Skip(pageNumber * pageSize)
.Take(pageSize)
.ToList();
10、模型配置优化
通过Fluent API配置模型关系可以显著提升Entity Framework Core的性能和可预测性。以下是具体实现方式:
- 配置主键
modelBuilder.Entity<Product>()
.HasKey(p => p.ProductId);
- 配置外键关系(一对多)
modelBuilder.Entity<Order>()
.HasOne(o => o.Customer)
.WithMany(c => c.Orders)
.HasForeignKey(o => o.CustomerId);
- 配置一对一关系
modelBuilder.Entity<Employee>()
.HasOne(e => e.Office)
.WithOne(o => o.Employee)
.HasForeignKey<Office>(o => o.EmployeeId);
- 配置多对多关系
modelBuilder.Entity<Student>()
.HasMany(s => s.Courses)
.WithMany(c => c.Students)
.UsingEntity<StudentCourse>(
j => j.HasOne(sc => sc.Course),
j => j.HasOne(sc => sc.Student)
);
优势说明:
- 性能提升:避免运行时反射操作,启动时间平均减少30%
- 明确性:开发者可以清晰看到所有关系配置
- 可维护性:配置集中在一个位置(通常在DbContext的OnModelCreating方法中)
- 灵活性:可以配置复杂的关系类型和约束
典型应用场景:
- 大型企业应用:包含数百个实体和复杂关系
- 性能敏感系统:要求快速启动和高效查询
- 领域驱动设计(DDD)项目:需要精确控制模型映射