C# EF.core 介绍以及高性能使用

很多人(包括很多企业和开发者)至今仍在广泛使用 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),且团队有较强的数据库优化能力时,DapperSqlKata 等更接近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原生方法适用于大多数简单场景,但在处理批量数据、复杂查询和性能敏感场景时,扩展库提供了显著的优势。建议:

  1. 小型项目:使用原生方法 + EF Plus免费功能

  2. 中型项目:考虑EF Plus完整版

  3. 大型企业应用:评估EF Extensions商业版

  4. 特定需求:根据需求选择专门库(如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 所有版本 ⭐⭐ 少量数据删除 需要先加载实体

六、选择建议

  1. EF Core 7.0+ 用户

    • 优先使用 ExecuteUpdateExecuteDelete

    • 适用于不需要复杂业务逻辑的批量操作

    • 性能最好,生成的SQL最优化

  2. 旧版本或复杂场景

    • 使用传统 AddRange/UpdateRange/RemoveRange

    • 需要触发业务逻辑或验证

    • 需要软删除等特殊处理

  3. 超大数据量

    • 考虑使用EF Core扩展库(如EF Core Bulk Extensions)

    • 或使用原生ADO.NET批量操作

    • 或使用SQL Server的BULK INSERT

关键点

  • ExecuteUpdate/ExecuteDelete 直接操作数据库,不加载实体到内存

  • 传统方法会触发ChangeTracker和所有验证逻辑

  • 对于大量数据,总是先测试性能影响

  • 考虑使用事务确保数据一致性

相关推荐
i***132423 分钟前
java进阶1——JVM
java·开发语言·jvm
笃行客从不躺平1 小时前
认识 Java 中的锁升级机制
java·开发语言
weixin_307779131 小时前
Jenkins Branch API插件详解:多分支项目管理的核心引擎
java·运维·开发语言·架构·jenkins
@木辛梓1 小时前
结构体 结构体c++
开发语言·c++
小股虫1 小时前
消息中间件关键技术、设计原理与实现架构总纲
java·开发语言·架构
洲星河ZXH1 小时前
Java,日期时间API
java·开发语言·python
前端老曹1 小时前
Jspreadsheet CE V5 使用手册(保姆版) 二
开发语言·前端·vue.js·学习
秋邱1 小时前
AR 定位技术深度解析:从 GPS 到视觉 SLAM 的轻量化实现
开发语言·前端·网络·人工智能·python·html·ar
Boop_wu1 小时前
[Java EE] 多线程进阶(3) [线程安全集合类]
开发语言·windows·python