ORM 对比选型
大约 12 分钟约 3627 字
ORM 对比选型
简介
在 .NET 生态中,ORM(对象关系映射)框架是连接应用程序与数据库的核心组件。不同的 ORM 框架在性能、功能丰富度、易用性和学习曲线方面各有侧重。本文将对 EF Core、Dapper 和 RepoDB 三款主流 .NET ORM 框架进行全方位对比分析,帮助开发团队根据项目需求做出合理的技术选型决策。
特点
EF Core
Entity Framework Core 是微软官方推出的 ORM 框架,支持 Code First、Database First 等开发模式。
实体配置与 DbContext
// 实体类定义
public class Order
{
public int OrderId { get; set; }
public string OrderNo { get; set; } = string.Empty;
public DateTime OrderDate { get; set; }
public decimal TotalAmount { get; set; }
public OrderStatus Status { get; set; }
public int CustomerId { get; set; }
public Customer Customer { get; set; } = null!;
public List<OrderDetail> OrderDetails { get; set; } = new();
}
// DbContext 配置
public class AppDbContext : DbContext
{
public DbSet<Order> Orders => Set<Order>();
public DbSet<Customer> Customers => Set<Customer>();
public DbSet<OrderDetail> OrderDetails => Set<OrderDetail>();
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.UseSqlServer("Server=.;Database=ShopDB;Trusted_Connection=True;")
.EnableSensitiveDataLogging()
.LogTo(Console.WriteLine, LogLevel.Information);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>(entity =>
{
entity.HasKey(e => e.OrderId);
entity.Property(e => e.OrderNo).HasMaxLength(50).IsRequired();
entity.Property(e => e.TotalAmount).HasPrecision(18, 2);
entity.HasIndex(e => e.OrderNo).IsUnique();
entity.HasOne(e => e.Customer)
.WithMany(c => c.Orders)
.HasForeignKey(e => e.CustomerId);
});
}
}CRUD 操作示例
// 新增
using var context = new AppDbContext();
var order = new Order
{
OrderNo = "ORD-2024-0001",
OrderDate = DateTime.UtcNow,
TotalAmount = 1299.00m,
Status = OrderStatus.Pending,
CustomerId = 1
};
context.Orders.Add(order);
await context.SaveChangesAsync();
// 查询(包含关联数据)
var orders = await context.Orders
.Include(o => o.Customer)
.Include(o => o.OrderDetails)
.ThenInclude(od => od.Product)
.Where(o => o.OrderDate >= DateTime.UtcNow.AddDays(-30))
.OrderByDescending(o => o.TotalAmount)
.Select(o => new
{
o.OrderId,
o.OrderNo,
CustomerName = o.Customer.Name,
o.TotalAmount,
ItemCount = o.OrderDetails.Sum(d => d.Quantity)
})
.Take(50)
.AsNoTracking()
.ToListAsync();
// 批量更新(EF Core 7+)
await context.Orders
.Where(o => o.Status == OrderStatus.Pending && o.OrderDate < DateTime.UtcNow.AddDays(-7))
.ExecuteUpdateAsync(setters => setters
.SetProperty(o => o.Status, OrderStatus.Cancelled)
.SetProperty(o => o.Remark, "超时自动取消"));Dapper
Dapper 是一个轻量级的对象映射器,通过扩展 IDbConnection 接口提供高效的数据访问能力。
基础查询与映射
using Dapper;
using System.Data.SqlClient;
// 基础查询
using var connection = new SqlConnection("Server=.;Database=ShopDB;Trusted_Connection=True;");
await connection.OpenAsync();
// 简单查询
var orders = await connection.QueryAsync<Order>(
"SELECT * FROM Orders WHERE OrderDate >= @StartDate AND Status = @Status",
new { StartDate = DateTime.UtcNow.AddDays(-30), Status = "Pending" }
);
// 多表关联查询(使用 SplitOn)
var orderList = await connection.QueryAsync<Order, Customer, Order>(
@"SELECT o.*, c.CustomerId, c.Name, c.Email
FROM Orders o
INNER JOIN Customers c ON o.CustomerId = c.CustomerId
WHERE o.OrderDate >= @StartDate",
(order, customer) =>
{
order.Customer = customer;
return order;
},
new { StartDate = DateTime.UtcNow.AddDays(-30) },
splitOn: "CustomerId"
);
// 查询返回动态类型
var summary = await connection.QueryAsync(
@"SELECT Status, COUNT(*) AS OrderCount, SUM(TotalAmount) AS TotalAmount
FROM Orders
WHERE OrderDate >= @StartDate
GROUP BY Status",
new { StartDate = DateTime.UtcNow.AddMonths(-1) }
);批量操作与存储过程
// 批量插入
var orderDetails = new List<OrderDetail>
{
new() { OrderId = 1, ProductId = 101, Quantity = 2, UnitPrice = 99.9m },
new() { OrderId = 1, ProductId = 102, Quantity = 1, UnitPrice = 199.0m },
new() { OrderId = 1, ProductId = 103, Quantity = 5, UnitPrice = 49.9m }
};
await connection.ExecuteAsync(
@"INSERT INTO OrderDetails (OrderId, ProductId, Quantity, UnitPrice)
VALUES (@OrderId, @ProductId, @Quantity, @UnitPrice)",
orderDetails
);
// 调用存储过程
var result = await connection.QueryMultipleAsync(
"sp_GetOrderReport",
new { StartDate = "2024-01-01", EndDate = "2024-12-31" },
commandType: CommandType.StoredProcedure
);
var summary = await result.ReadAsync<OrderSummary>();
var details = await result.ReadAsync<OrderDetail>();
// 使用事务
using var transaction = connection.BeginTransaction();
try
{
await connection.ExecuteAsync(
"UPDATE Accounts SET Balance = Balance - @Amount WHERE AccountId = @FromId",
new { Amount = 500, FromId = 1 }, transaction
);
await connection.ExecuteAsync(
"UPDATE Accounts SET Balance = Balance + @Amount WHERE AccountId = @ToId",
new { Amount = 500, ToId = 2 }, transaction
);
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}RepoDB
RepoDB 是一个高性能的 .NET ORM 框架,在批量操作和缓存方面表现突出。
配置与基础操作
using RepoDb;
using RepoDb.Connections;
using System.Data.SqlClient;
// 初始化配置(应用启动时执行一次)
RepoDb.Bootstrap.Initialize();
RepoDb.Bootstrap.SetupForSqlServer();
// 如果需要缓存
var cache = new MemoryCache(new MemoryCacheOptions());
RepoDb.Bootstrap.SetupForCache(cache);
// 基础 CRUD
using var connection = new SqlConnection("Server=.;Database=ShopDB;Trusted_Connection=True;");
// 插入单条
var orderId = await connection.InsertAsync<Order, int>(new Order
{
OrderNo = "ORD-2024-0002",
OrderDate = DateTime.UtcNow,
TotalAmount = 2599.00m,
Status = OrderStatus.Pending,
CustomerId = 2
});
// 批量插入(高性能)
var orders = Enumerable.Range(1, 10000).Select(i => new Order
{
OrderNo = $"ORD-2024-{i:D6}",
OrderDate = DateTime.UtcNow,
TotalAmount = Random.Shared.Next(100, 5000),
Status = OrderStatus.Pending,
CustomerId = Random.Shared.Next(1, 100)
}).ToList();
await connection.BulkInsertAsync(orders);
// 查询
var pendingOrders = await connection.QueryAsync<Order>(
o => o.Status == OrderStatus.Pending && o.OrderDate >= DateTime.UtcNow.AddDays(-30)
);
// 更新
await connection.UpdateAsync<Order>(
new { Status = OrderStatus.Shipped },
o => o.OrderId == orderId
);
// 删除
await connection.DeleteAsync<Order>(o => o.Status == OrderStatus.Cancelled && o.OrderDate < DateTime.UtcNow.AddYears(-1));批量操作与缓存查询
// 批量合并(Upsert)
var ordersToUpdate = new List<Order>
{
new() { OrderId = 1, Status = OrderStatus.Shipped, TotalAmount = 1500 },
new() { OrderId = 2, Status = OrderStatus.Delivered, TotalAmount = 2800 },
new() { OrderId = 3, Status = OrderStatus.Cancelled, TotalAmount = 0 }
};
var mergedCount = await connection.MergeAllAsync(ordersToUpdate);
// 使用缓存查询(自动缓存结果,减少数据库访问)
var cachedOrders = await connection.QueryAllAsync<Order>(
cacheKey: "all_active_orders",
cacheItemExpiration: TimeSpan.FromMinutes(30),
where: o => o.Status != OrderStatus.Cancelled
);
// 批量更新
await connection.BatchQueryAsync<Order>(
page: 1,
rowsPerBatch: 1000,
orderBy: OrderField.Parse(new { OrderDate = Order.Descending }),
where: o => o.Status == OrderStatus.Pending
);
// 使用 DbField 缓存优化
var fields = await connection.GetFieldsAsync<Order>();全面对比
性能基准测试参考
| 操作类型 | EF Core 8 | Dapper | RepoDB | 原生 ADO.NET |
|---|---|---|---|---|
| 单条查询 | 800 μs | 120 μs | 150 μs | 100 μs |
| 列表查询(500条) | 3.5 ms | 1.2 ms | 1.4 ms | 1.0 ms |
| 单条插入 | 900 μs | 150 μs | 160 μs | 110 μs |
| 批量插入(1万条) | 15 s | 8 s | 2 s | 6 s |
| 批量更新(1万条) | 20 s | 10 s | 3 s | 8 s |
| 复杂关联查询 | 2.0 ms | 1.5 ms | 不支持 | 1.2 ms |
功能对比
| 功能特性 | EF Core | Dapper | RepoDB |
|---|---|---|---|
| LINQ 查询 | 完整支持 | 不支持 | 基础表达式树 |
| 变更追踪 | 自动追踪 | 无 | 无 |
| 迁移管理 | 内置 | 无 | 无 |
| 延迟加载 | 支持 | 不支持 | 不支持 |
| 多表关联 | 自动处理 | 手动 SplitOn | 有限支持 |
| 批量操作 | 中等(7+改进) | 需手动优化 | 内置高性能 |
| 缓存支持 | 二级缓存(插件) | 无 | 内置支持 |
| 存储过程 | 支持 | 原生支持 | 支持 |
| 数据库支持 | 多种 | 多种 | SQL Server/MySQL/PG |
| 学习曲线 | 较高 | 低 | 中等 |
| NuGet 大小 | 较大 | 极小 | 中等 |
选型建议
// 场景一:管理后台、CRM 系统 -> 推荐 EF Core
// 特点:复杂业务逻辑、大量关联查询、需要迁移管理
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionString,
sqlOptions => sqlOptions.EnableRetryOnFailure(
maxRetryCount: 3,
maxRetryDelay: TimeSpan.FromSeconds(10),
errorNumbersToAdd: null
)
)
);
// 场景二:高性能 API、微服务 -> 推荐 Dapper
// 特点:查询频繁、响应时间敏感、SQL 可控
services.AddScoped<IDbConnection>(sp =>
new SqlConnection(connectionString)
);
// 场景三:数据导入导出、报表批量处理 -> 推荐 RepoDB
// 特点:大批量数据操作、需要缓存、追求极致性能
services.AddScoped<IOrderRepository>(sp =>
new OrderBulkRepository(connectionString)
);
// 场景四:混合使用(最佳实践)
// EF Core 处理核心业务 CRUD
// Dapper 处理复杂报表查询
// RepoDB 处理批量数据导入EF Core 进阶用法
全局查询过滤器
// 软删除全局过滤器
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>().HasQueryFilter(o => !o.IsDeleted);
modelBuilder.Entity<Customer>().HasQueryFilter(c => c.TenantId == _currentTenantId);
}
// 忽略全局过滤器(管理后台需要查看已删除数据)
var allOrders = await context.Orders
.IgnoreQueryFilters()
.Where(o => o.IsDeleted)
.ToListAsync();并发控制
// 乐观并发控制
public class Order
{
public int OrderId { get; set; }
public string OrderNo { get; set; }
public decimal TotalAmount { get; set; }
// 并发令牌
[Timestamp]
public byte[] RowVersion { get; set; }
}
// 处理并发冲突
try
{
context.Orders.Update(order);
await context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException ex)
{
// 策略1:客户端获胜
var entry = ex.Entries.Single();
entry.OriginalValues.SetValues(
(await entry.GetDatabaseValuesAsync()).ToObject());
// 策略2:数据库获胜
// 刷新实体并提示用户
// 策略3:合并
// 保留数据库值,仅更新非冲突字段
}分页封装
public class PagedResult<T>
{
public List<T> Items { get; set; } = new();
public int TotalCount { get; set; }
public int PageIndex { get; set; }
public int PageSize { get; set; }
public int TotalPages => (int)Math.Ceiling(TotalCount / (double)PageSize);
public bool HasPreviousPage => PageIndex > 1;
public bool HasNextPage => PageIndex < TotalPages;
}
public static async Task<PagedResult<T>> ToPagedAsync<T>(
this IQueryable<T> query, int pageIndex, int pageSize)
{
var totalCount = await query.CountAsync();
var items = await query
.Skip((pageIndex - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
return new PagedResult<T>
{
Items = items,
TotalCount = totalCount,
PageIndex = pageIndex,
PageSize = pageSize
};
}
// 使用
var result = await context.Orders
.Where(o => o.Status == OrderStatus.Pending)
.OrderByDescending(o => o.OrderDate)
.ToPagedAsync(pageIndex: 1, pageSize: 20);Dapper 进阶用法
类型处理器
// 自定义 JSON 类型映射
public class JsonTypeHandler<T> : SqlMapper.TypeHandler<T>
{
public override T Parse(object value)
{
return JsonSerializer.Deserialize<T>((string)value);
}
public override void SetValue(IDbDataParameter parameter, T value)
{
parameter.Value = JsonSerializer.Serialize(value);
}
}
// 注册
SqlMapper.AddTypeHandler(new JsonTypeHandler<List<string>>());
SqlMapper.AddTypeHandler(new JsonTypeHandler<Dictionary<string, object>>());
// 实体中使用
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
// 自动序列化/反序列化为 JSON
public List<string> Tags { get; set; }
public Dictionary<string, object> Metadata { get; set; }
}多结果集处理
// 一次查询返回多个结果集
using var multi = await connection.QueryMultipleAsync(@"
SELECT * FROM Orders WHERE OrderId = @OrderId;
SELECT * FROM OrderDetails WHERE OrderId = @OrderId;
SELECT * FROM OrderLogs WHERE OrderId = @OrderId;",
new { OrderId = 1 });
var order = await multi.ReadFirstOrDefaultAsync<Order>();
var details = (await multi.ReadAsync<OrderDetail>()).ToList();
var logs = (await multi.ReadAsync<OrderLog>()).ToList();
// Dapper Contrib 扩展(CRUD 简化)
// Install-Package Dapper.Contrib
// connection.GetAll<Order>()
// connection.Get<Order>(1)
// connection.Insert(new Order { ... })
// connection.Update(order)
// connection.Delete(order)混合使用最佳实践
// 注册服务时同时配置多种 ORM
public static class DataAccessExtensions
{
public static IServiceCollection AddDataAccess(
this IServiceCollection services, string connectionString)
{
// EF Core 用于核心业务
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionString));
// Dapper 用于报表和复杂查询
services.AddScoped<IDbConnection>(sp =>
new SqlConnection(connectionString));
// 仓储模式封装
services.AddScoped<IOrderRepository, OrderRepository>();
return services;
}
}
// 仓储模式:在统一接口下混合使用
public class OrderRepository : IOrderRepository
{
private readonly AppDbContext _context;
private readonly IDbConnection _dapper;
public OrderRepository(AppDbContext context, IDbConnection dapper)
{
_context = context;
_dapper = dapper;
}
// 简单 CRUD 用 EF Core
public async Task<Order> GetByIdAsync(int id)
{
return await _context.Orders.FindAsync(id);
}
public async Task AddAsync(Order order)
{
_context.Orders.Add(order);
await _context.SaveChangesAsync();
}
// 复杂报表查询用 Dapper
public async Task<OrderReport> GetMonthlyReportAsync(int year, int month)
{
return await _dapper.QueryFirstOrDefaultAsync<OrderReport>(@"
SELECT COUNT(*) AS OrderCount,
SUM(TotalAmount) AS TotalRevenue,
AVG(TotalAmount) AS AvgOrderValue
FROM Orders
WHERE YEAR(OrderDate) = @Year AND MONTH(OrderDate) = @Month",
new { Year = year, Month = month });
}
// 批量操作用 Dapper
public async Task BulkUpdateStatusAsync(List<int> orderIds, OrderStatus status)
{
await _dapper.ExecuteAsync(@"
UPDATE Orders SET Status = @Status
WHERE OrderId IN @OrderIds",
new { Status = status.ToString(), OrderIds = orderIds });
}
}混合使用建议:
- 主体用 EF Core:开发效率高,LINQ 查询强类型
- 报表用 Dapper:复杂 SQL 更可控,性能更好
- 批量操作用 Dapper/RepoDB:避免 EF Core 的 ChangeTracker 开销
- 通过仓储模式封装:上层代码不需要知道底层用的是哪种 ORM
- 统一事务管理:混合使用时注意 DbContext 和 IDbConnection 的事务协调ORM 性能优化清单
EF Core 性能优化:
1. 查询时使用 AsNoTracking()(只读场景)
2. 避免 N+1 查询:合理使用 Include/ThenInclude
3. 投影查询:用 Select() 只查需要的字段
4. 批量操作使用 ExecuteUpdate/ExecuteDelete(EF Core 7+)
5. 分页总是先 Count 再 Skip/Take
6. 使用 CompileQuery 缓存高频查询
7. 合理配置 DbContext 生命周期(Scoped)
8. 大批量插入考虑使用 EFCore.BulkExtensions
Dapper 性能优化:
1. 批量操作时使用事务
2. 大批量插入使用 SqlBulkCopy(SQL Server)
3. 查询时只 SELECT 需要的列
4. 使用存储过程减少网络传输
5. 连接字符串启用 MultiActiveResultSets(MARS)
通用优化:
1. 始终使用参数化查询,防止 SQL 注入
2. 连接池配置:Max Pool Size、Min Pool Size
3. 异步操作:避免同步阻塞
4. 监控慢查询日志
5. 数据库端索引优化优点
缺点
总结
ORM 框架的选型没有绝对的最优解,关键在于匹配项目需求和团队能力。对于快速迭代的业务系统,EF Core 的全面功能可以显著提升开发效率;对于追求极致性能的 API 服务,Dapper 的轻量级特性是最佳选择;对于批量数据处理场景,RepoDB 的高性能批量操作具有明显优势。在实际项目中,推荐以一个 ORM 为主,在特定场景下引入其他 ORM 作为补充,在保持架构一致性的同时兼顾性能需求。
关键知识点
- 数据库主题一定要同时看数据模型、读写模式和执行代价。
- 很多性能问题不是 SQL 语法问题,而是索引、统计信息、事务和数据分布问题。
- 高可用、备份、迁移和治理与查询优化同样重要。
- 先把数据模型、访问模式和执行代价绑定起来理解。
项目落地视角
- 所有优化前后都保留执行计划、样本 SQL 和关键指标对比。
- 上线前准备回滚脚本、备份点和校验方案。
- 把连接池、锁等待、慢查询和容量增长纳入日常巡检。
- 保留执行计划、样本 SQL、索引定义和优化前后指标。
常见误区
- 脱离真实数据分布讨论索引或分片。
- 只看单条 SQL,不看整条业务链路的事务和锁。
- 把测试环境结论直接等同于生产环境结论。
- 脱离真实数据分布设计索引。
进阶路线
- 继续向执行计划、存储引擎、复制机制和数据治理层深入。
- 把主题与 ORM、缓存、消息队列和归档策略联动起来思考。
- 沉淀成数据库设计规范、SQL 审核规则和变更流程。
- 继续深入存储引擎、复制机制、归档与冷热分层治理。
适用场景
- 当你准备把《ORM 对比选型》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合数据建模、查询优化、事务控制、高可用和迁移治理。
- 当系统开始遇到慢查询、锁冲突、热点数据或容量增长时,这类主题价值最高。
落地建议
- 先分析真实查询模式、数据量级和写入特征,再决定索引或分片策略。
- 所有优化结论都结合执行计划、样本数据和监控指标验证。
- 高风险操作前准备备份、回滚脚本与校验 SQL。
排错清单
- 先确认瓶颈在 CPU、I/O、锁等待、网络还是 SQL 本身。
- 检查执行计划是否走错索引、是否发生排序或全表扫描。
- 排查长事务、隐式类型转换、统计信息过期和参数嗅探。
复盘问题
- 如果把《ORM 对比选型》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《ORM 对比选型》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《ORM 对比选型》最大的收益和代价分别是什么?
