很多人(包括很多企业和开发者)至今仍在广泛使用 Entity Framework Core (EF Core),尽管现在有了更多ORM选择,但它依然非常流行,主要原因如下:
1. 历史传承和生态成熟度
-
出身名门:EF 是微软官方推出的ORM框架,从 .NET Framework 时代的 EF 到现在的 EF Core,已有超过15年的发展历史。无数现有项目(尤其是企业级应用)都基于它构建,迁移成本高。
-
深度集成 :它与整个 .NET 生态(ASP.NET Core、Blazor、Azure服务等)无缝集成, Visual Studio 工具链支持好(如脚手架、数据库迁移可视化)。
2. 开发效率和"Code First"体验
-
强大的"Code First"工作流 :这是EF Core最吸引人的特性之一。开发者可以完全用C#类定义数据模型,通过迁移(
Add-Migration,Update-Database)自动生成和维护数据库结构。这对快速迭代、领域驱动设计(DDD)非常友好。 -
LINQ(语言集成查询):可以用熟悉的C#语法写查询,编译器支持类型检查,智能提示(IntelliSense)好。虽然复杂查询有时需要优化,但日常的CRUD和简单关联查询写起来非常高效、直观。
-
自动变更跟踪 :修改实体属性后,
SaveChanges会自动识别并生成对应的SQL更新语句,大大减少了手动编写更新代码的工作量。
3. 功能全面,开箱即用
-
丰富的特性:
-
贪婪加载(
Include)、延迟加载、显式加载。 -
全局查询过滤器(如自动实现多租户、软删除)。
-
原生支持事务、并发控制(乐观并发令牌)。
-
复杂类型(值对象)、表拆分、继承映射(TPH、TPT、TPC)。
-
-
数据库提供程序丰富 :不仅支持 SQL Server/MySQL/PostgreSQL/SQLite 等主流关系数据库,还通过提供程序支持 Cosmos DB(NoSQL),甚至内存数据库用于测试。
4. 微软的持续投入和性能提升
-
EF Core 是 .NET 的"一等公民":作为微软官方核心框架之一,它随着 .NET 版本持续更新,保持技术先进性(例如.NET 8的新性能优化)。
-
性能已大幅改善:早期EF版本因性能问题饱受诟病,但EF Core经过多次重写,编译时查询、批量操作、更高效的SQL生成等特性,使其性能在大多数业务场景下已足够好。
-
长期支持(LTS):对于企业来说,选择官方LTS版本意味着长期稳定的支持和安全更新。
5. 团队技能和社区资源
-
学习资料和人才丰富:网上教程、书籍、Stack Overflow问答、以及有经验的开发者数量庞大,遇到问题容易找到解决方案。
-
团队上手快:对于已经熟悉 .NET 和 LINQ 的开发者,学习曲线相对平缓。
6. 平衡了抽象与控制力
-
在便利性和控制力之间取得平衡 :虽然像 Dapper 这样的微ORM性能更高、控制更细,但需要手写大量SQL。EF Core允许在需要时:
-
直接执行原始SQL(
FromSqlRaw/ExecuteSqlRaw)。 -
对查询进行精细优化(如使用
.AsNoTracking()禁用跟踪,使用.Select仅查询所需字段)。 -
通过拦截器(
IDbCommandInterceptor)干预SQL生成和执行过程。
-
当然,它并非完美,也有其适用场景和争议点:
主要争议/缺点:
-
复杂查询性能:极度复杂、涉及大量连接和计算的查询,其自动生成的SQL可能不如手写SQL高效。这时需要开发者具备优化知识。
-
"黑盒"效应:过度依赖抽象可能导致开发者对底层数据库行为(如索引、锁、事务隔离级别)不熟悉,从而引发性能问题。
-
启动时间:大型模型在首次运行时,元数据编译和查询计划编译可能耗时(但可以通过预编译解决)。
-
内存消耗:变更跟踪会占用额外内存。
为什么人们没有全部转向Dapper或其他ORM?
这其实是 "开发效率" vs "极致性能/控制力" 的权衡。对于绝大多数业务应用(尤其是内部管理系统、企业级CRUD应用),开发速度、可维护性和减少错误的收益,远大于那一点潜在的性能损失。EF Core很好地满足了这部分需求。
只有当你的应用属于 高性能、高并发核心服务 (如交易系统、高频读写的API),且团队有较强的数据库优化能力时,Dapper 或 SqlKata 等更接近SQL的方案才是更优选择。
总结
EF Core 是一个功能强大、高度集成、生产力至上的全功能ORM。 它特别适合:
-
快速原型开发和业务逻辑复杂的应用。
-
强调领域模型、使用DDD的项目。
-
团队希望减少在基础数据访问层的编码和维护工作量。
-
项目需要支持多种数据库或未来可能更换数据库。
因此,只要它的性能表现能满足项目需求,其带来的开发效率提升和代码可维护性优势 ,就是很多人(包括大公司)继续选择它的核心原因。它不再是"唯一选择",但依然是一个经过验证、可靠且高效的主流选择。
EF Core 原生基础操作方法及优化扩展库
一、EF Core 原生基础操作方法
1. 基础CRUD操作
查询操作:
// 1. 获取所有数据
var list = context.Users.ToList();
// 2. 条件查询
var user = context.Users.FirstOrDefault(u => u.Id == id);
var users = context.Users.Where(u => u.Age > 18).ToList();
// 3. 排序
var ordered = context.Users.OrderBy(u => u.Name).ToList();
// 4. 分页
var paged = context.Users.Skip(10).Take(10).ToList();
// 5. 包含关联数据
var userWithOrders = context.Users
.Include(u => u.Orders)
.ThenInclude(o => o.OrderDetails)
.FirstOrDefault(u => u.Id == id);
添加操作
// 1. 添加单个实体
context.Users.Add(user);
context.SaveChanges();
// 2. 批量添加
context.Users.AddRange(userList);
context.SaveChanges();
更新操作:
// 1. 先查询后修改
var user = context.Users.Find(id);
user.Name = "New Name";
context.SaveChanges();
// 2. 直接更新
context.Entry(existingUser).State = EntityState.Modified;
context.SaveChanges();
// 3. 部分更新
context.Entry(user).Property(u => u.Name).IsModified = true;
删除操作:
// 1. 先查询后删除
var user = context.Users.Find(id);
context.Users.Remove(user);
context.SaveChanges();
// 2. 直接删除
context.Users.Remove(new User { Id = id });
context.SaveChanges();
2. 原始SQL操作
// 1. 查询
var users = context.Users
.FromSqlRaw("SELECT * FROM Users WHERE Age > {0}", 18)
.ToList();
// 2. 执行非查询SQL
context.Database.ExecuteSqlRaw("UPDATE Users SET Status = 1 WHERE Id = {0}", id);
3. 异步操作
// 异步查询
var user = await context.Users.FirstOrDefaultAsync(u => u.Id == id);
// 异步保存
await context.SaveChangesAsync();
二、主要优化扩展库
1. Entity Framework Plus(EF Plus)
最流行的EF Core扩展库之一
主要特性:
// 1. 批量操作 - 高性能
// 批量删除(生成单个DELETE语句)
context.Users
.Where(u => u.CreatedDate < DateTime.Now.AddYears(-1))
.Delete();
// 批量更新(生成单个UPDATE语句)
context.Users
.Where(u => u.Status == "Inactive")
.Update(u => new User { Status = "Archived" });
// 2. 查询未来结果(延迟执行)
var futureQuery = context.Users
.Where(u => u.Active)
.Future();
var futureCount = context.Users
.Where(u => u.Active)
.FutureCount();
// 执行时只访问一次数据库
var users = futureQuery.ToList();
var count = futureCount.Value;
// 3. 查询缓存
var users = context.Users
.Where(u => u.Active)
.FromCache(DateTime.Now.AddMinutes(5));
// 4. 审计跟踪
context.SaveChanges(audit => {
audit.CreatedBy = "System";
});
// 5. 查询过滤器
context.Filter<User>(q => q.Where(u => !u.IsDeleted));
2. Entity Framework Extensions
专注于批量操作的商业库
主要特性:
// 1. 批量插入(比AddRange快50倍)
context.BulkInsert(users);
// 2. 批量更新
context.BulkUpdate(users);
// 3. 批量删除
context.BulkDelete(users);
// 4. 批量合并(Upsert)
context.BulkMerge(users);
// 5. 批量保存
context.BulkSaveChanges(); // 替代SaveChanges
3. Z.EntityFramework.Plus.EFCore
开源高性能扩展
// 1. 批量操作
context.Users.Where(u => u.IsDeleted).Delete();
context.Users.Where(u => u.Status == 1).Update(u => new User { Status = 2 });
// 2. 查询优化
var result = context.Users
.AsNoTracking() // 不跟踪
.DeferredFirstOrDefault(u => u.Id == id)
.Execute();
// 3. 查询过滤器
modelBuilder.Filter("IsDeleted", (User u) => !u.IsDeleted);
4. Microsoft.EntityFrameworkCore.AutoHistory
自动历史记录
// 启用自动历史记录
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.EnableAutoHistory();
}
// 保存时自动记录
context.SaveChangesWithHistory();
5. FlexLabs.EntityFrameworkCore.Upsert
高效的Upsert操作
// 执行Upsert(存在则更新,不存在则插入)
context.Users.Upsert(user)
.On(u => u.Email) // 根据Email判断是否存在
.Run();
6. EfCore.SoftDelete
软删除支持
// 配置软删除
modelBuilder.Entity<User>()
.HasQueryFilter(u => !u.IsDeleted);
// 自动设置软删除标记
context.Remove(user); // 自动设置IsDeleted = true
7. EFCore.BulkExtensions
高性能批量操作(开源)
// 批量插入
await context.BulkInsertAsync(users);
// 批量更新
await context.BulkUpdateAsync(users);
// 批量删除
await context.BulkDeleteAsync(users);
三、性能对比表
| 操作类型 | 原生方法 | EF Plus | EF Extensions | 性能提升 |
|---|---|---|---|---|
| 批量插入 | AddRange | BatchInsert | BulkInsert | 10-50倍 |
| 批量更新 | 循环Update | BatchUpdate | BulkUpdate | 20-60倍 |
| 批量删除 | 循环Remove | BatchDelete | BulkDelete | 15-50倍 |
| 查询缓存 | 无 | FromCache | 无 | 查询时间减少90% |
四、最佳实践建议
1. 配置优化
// DbContext配置
services.AddDbContext<MyContext>(options =>
{
options.UseSqlServer(connectionString)
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking) // 默认不跟踪
.EnableSensitiveDataLogging(false)
.EnableDetailedErrors(true);
});
2. 查询优化技巧
// 1. 使用AsNoTracking
var users = context.Users.AsNoTracking().ToList();
// 2. 选择性Include
var user = context.Users
.Select(u => new {
u.Id,
u.Name,
Orders = u.Orders.Select(o => o.Id).ToList()
})
.FirstOrDefault();
// 3. 分批处理大数据
foreach (var batch in users.Chunk(1000))
{
context.BulkInsert(batch);
}
EF Core原生方法适用于大多数简单场景,但在处理批量数据、复杂查询和性能敏感场景时,扩展库提供了显著的优势。建议:
-
小型项目:使用原生方法 + EF Plus免费功能
-
中型项目:考虑EF Plus完整版
-
大型企业应用:评估EF Extensions商业版
-
特定需求:根据需求选择专门库(如Upsert、软删除等)
选择时需考虑项目需求、性能要求、预算和团队熟悉度等因素。
EF Core 官方版批量操作方法详解
一、EF Core 7.0+ 官方批量操作方法
EF Core 7.0 引入了革命性的批量操作方法,显著提升了性能。
1. ExecuteDelete() - 批量删除
// 1. 基本批量删除
var deletedCount = await context.Users
.Where(u => u.CreatedDate < DateTime.Now.AddYears(-1))
.ExecuteDeleteAsync();
// 2. 带条件的复杂删除
var deletedCount = await context.Orders
.Where(o => o.Status == OrderStatus.Cancelled &&
o.CancelledDate < DateTime.Now.AddMonths(-6))
.ExecuteDeleteAsync();
// 3. 多表关联删除(需要子查询)
var deletedCount = await context.Users
.Where(u => !context.Orders.Any(o => o.UserId == u.Id))
.ExecuteDeleteAsync();
// 4. 使用事务的批量删除
using var transaction = await context.Database.BeginTransactionAsync();
try
{
var deletedCount = await context.LogEntries
.Where(l => l.Date < DateTime.Now.AddYears(-1))
.ExecuteDeleteAsync();
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw;
}
生成的SQL:
-- ExecuteDelete 会生成单个DELETE语句
DELETE FROM [Users]
WHERE [CreatedDate] < DATEADD(year, -1, GETDATE())
2. ExecuteUpdate() - 批量更新
// 1. 基本批量更新
var updatedCount = await context.Products
.Where(p => p.StockQuantity == 0)
.ExecuteUpdateAsync(setters => setters
.SetProperty(p => p.Status, ProductStatus.OutOfStock)
.SetProperty(p => p.UpdatedAt, DateTime.UtcNow));
// 2. 使用计算值的更新
var updatedCount = await context.Employees
.Where(e => e.Salary < 50000)
.ExecuteUpdateAsync(setters => setters
.SetProperty(e => e.Salary, e => e.Salary * 1.1) // 涨薪10%
.SetProperty(e => e.LastSalaryReview, DateTime.UtcNow));
// 3. 多条件更新
var updatedCount = await context.Orders
.Where(o => o.Status == OrderStatus.Pending &&
o.CreatedDate < DateTime.Now.AddDays(-7))
.ExecuteUpdateAsync(setters => setters
.SetProperty(o => o.Status, OrderStatus.Expired)
.SetProperty(o => o.Notes, "自动过期"));
// 4. 复杂条件更新
var updatedCount = await context.BlogPosts
.Where(p => p.ViewCount > 1000 &&
p.PublishedDate.Year == 2023)
.ExecuteUpdateAsync(setters => setters
.SetProperty(p => p.IsPopular, true)
.SetProperty(p => p.Tags, "Popular,2023"));
// 5. 批量递增/递减
var updatedCount = await context.Products
.Where(p => p.Id == productId)
.ExecuteUpdateAsync(setters => setters
.SetProperty(p => p.StockQuantity, p => p.StockQuantity - quantity));
生成的SQL:
-- ExecuteUpdate 会生成单个UPDATE语句
UPDATE [Products]
SET [Status] = 3, -- OutOfStock
[UpdatedAt] = GETUTCDATE()
WHERE [StockQuantity] = 0
3. AddRange() - 批量添加(传统方法)
// 1. 基础批量添加
var users = new List<User>
{
new User { Name = "User1", Email = "user1@example.com" },
new User { Name = "User2", Email = "user2@example.com" },
// ... 更多用户
};
context.Users.AddRange(users);
await context.SaveChangesAsync();
// 2. 批量添加关联实体
var order = new Order
{
OrderNumber = "ORD-001",
OrderDetails = new List<OrderDetail>
{
new OrderDetail { ProductId = 1, Quantity = 2 },
new OrderDetail { ProductId = 2, Quantity = 1 },
new OrderDetail { ProductId = 3, Quantity = 3 }
}
};
context.Orders.Add(order);
await context.SaveChangesAsync();
// 3. 分批批量添加(避免内存和性能问题)
var batchSize = 1000;
var totalUsers = GetLargeUserList(); // 假设有大量数据
for (int i = 0; i < totalUsers.Count; i += batchSize)
{
var batch = totalUsers.Skip(i).Take(batchSize).ToList();
context.Users.AddRange(batch);
await context.SaveChangesAsync();
// 清除跟踪以提高性能
context.ChangeTracker.Clear();
}
4. UpdateRange() - 批量更新(传统方法)
// 1. 先查询后批量更新
var inactiveUsers = await context.Users
.Where(u => u.LastLoginDate < DateTime.Now.AddMonths(-6))
.ToListAsync();
foreach (var user in inactiveUsers)
{
user.Status = UserStatus.Inactive;
user.UpdatedAt = DateTime.UtcNow;
}
context.Users.UpdateRange(inactiveUsers);
await context.SaveChangesAsync();
// 2. 直接更新已知实体
var usersToUpdate = new List<User>
{
new User { Id = 1, Name = "Updated Name 1" },
new User { Id = 2, Name = "Updated Name 2" }
};
context.Users.UpdateRange(usersToUpdate);
await context.SaveChangesAsync();
5. RemoveRange() - 批量删除(传统方法)
// 1. 先查询后批量删除
var softDeletedUsers = await context.Users
.Where(u => u.IsDeleted &&
u.DeletedDate < DateTime.Now.AddYears(-1))
.ToListAsync();
context.Users.RemoveRange(softDeletedUsers);
await context.SaveChangesAsync();
// 2. 直接删除已知实体
var usersToDelete = new List<User>
{
new User { Id = 1 },
new User { Id = 2 }
};
context.Users.RemoveRange(usersToDelete);
await context.SaveChangesAsync();
五、总结对比表
| 操作方法 | EF Core 版本 | 性能 | 适用场景 | 注意事项 |
|---|---|---|---|---|
| ExecuteDelete | 7.0+ | ⭐⭐⭐⭐⭐ | 大量数据删除 | 无法触发软删除逻辑 |
| ExecuteUpdate | 7.0+ | ⭐⭐⭐⭐⭐ | 大量数据更新 | 无法调用SaveChanges事件 |
| AddRange | 所有版本 | ⭐⭐ | 少量数据插入 | 生成多个INSERT语句 |
| UpdateRange | 所有版本 | ⭐⭐ | 少量数据更新 | 需要先加载实体 |
| RemoveRange | 所有版本 | ⭐⭐ | 少量数据删除 | 需要先加载实体 |
六、选择建议
-
EF Core 7.0+ 用户:
-
优先使用
ExecuteUpdate和ExecuteDelete -
适用于不需要复杂业务逻辑的批量操作
-
性能最好,生成的SQL最优化
-
-
旧版本或复杂场景:
-
使用传统
AddRange/UpdateRange/RemoveRange -
需要触发业务逻辑或验证
-
需要软删除等特殊处理
-
-
超大数据量:
-
考虑使用EF Core扩展库(如EF Core Bulk Extensions)
-
或使用原生ADO.NET批量操作
-
或使用SQL Server的BULK INSERT
-
关键点:
-
ExecuteUpdate/ExecuteDelete直接操作数据库,不加载实体到内存 -
传统方法会触发ChangeTracker和所有验证逻辑
-
对于大量数据,总是先测试性能影响
-
考虑使用事务确保数据一致性