资讯详情 .NET Core数据库操作:EF Core与Dapper实战指南
📅 2026/10/28 8:55:51
1. .NET Core数据库操作全景概览在当今企业级应用开发中数据库操作是每个.NET开发者必须掌握的核心技能。不同于传统的ADO.NET时代.NET Core提供了一套现代化、跨平台的数据库访问方案。我在实际项目中最深刻的体会是选择正确的数据访问策略往往比编写业务代码更能决定项目的成败。先看几个关键数据点根据2023年StackOverflow开发者调查Entity Framework Core已成为.NET生态中使用率最高的ORM工具占比67%而Dapper则以轻量级优势占据29%的市场份额。这种技术选型的分布反映了不同场景下的需求差异——EF Core适合快速开发的业务系统Dapper则在高性能场景表现优异。重要提示在.NET 6/7中微软引入了全局using指令和隐式using这使得数据库操作相关的命名空间引用更加简洁但同时也容易让新手忽略底层依赖项的实际添加。2. 环境准备与基础配置2.1 项目初始化与包引用创建一个新的.NET Core Web API项目后首先需要通过NuGet添加数据库访问相关的核心包。以下是常见组合# EF Core全套方案 dotnet add package Microsoft.EntityFrameworkCore dotnet add package Microsoft.EntityFrameworkCore.Design dotnet add package Microsoft.EntityFrameworkCore.SqlServer # 轻量级方案 dotnet add package Dapper dotnet add package System.Data.SqlClient我强烈建议在开发初期就建立分层架构。典型结构如下/src /Application (业务逻辑层) /Domain (领域模型) /Infrastructure /Persistence (数据访问实现) /WebAPI (表现层)2.2 连接字符串的安全管理在appsettings.json中配置连接字符串时务必注意安全性{ ConnectionStrings: { DefaultConnection: Server.;DatabaseMyAppDb;Trusted_ConnectionTrue;EncryptFalse; } }避坑指南千万不要将真实连接字符串提交到源码仓库应该使用开发环境User Secrets (dotnet user-secrets set)生产环境Azure Key Vault或环境变量在Program.cs中注入DbContext的经典模式builder.Services.AddDbContextAppDbContext(options options.UseSqlServer(builder.Configuration.GetConnectionString(DefaultConnection)));3. Entity Framework Core深度实践3.1 数据建模最佳实践定义实体时我总结出几个黄金法则优先使用数据注解Data Annotations保持简洁性复杂配置使用Fluent API放在DbContext.OnModelCreating中导航属性尽量显式定义外键public class Order { public int Id { get; set; } [Required] [MaxLength(100)] public string OrderNumber { get; set; } public DateTime CreatedAt { get; set; } DateTime.UtcNow; // 导航属性 public ICollectionOrderItem Items { get; set; } } // 在DbContext中 protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.EntityOrder() .HasIndex(o o.OrderNumber) .IsUnique(); }3.2 高效查询技巧避免常见的性能陷阱N1查询问题始终使用Include/ThenInclude分页必须使用Skip().Take()异步操作优先选择ToListAsync()// 好的实践 var orders await _context.Orders .Include(o o.Items) .Where(o o.CreatedAt DateTime.UtcNow.AddDays(-7)) .OrderByDescending(o o.CreatedAt) .Skip(0) .Take(20) .ToListAsync(); // 要避免的反模式 var badQuery _context.Orders.ToList(); // 全表加载3.3 事务管理与并发控制EF Core提供了多种事务管理方式// 方案1自动事务 using var transaction await _context.Database.BeginTransactionAsync(); try { // 多个操作... await _context.SaveChangesAsync(); await transaction.CommitAsync(); } catch { await transaction.RollbackAsync(); throw; } // 方案2跨上下文事务需要相同连接 using var connection _context.Database.GetDbConnection(); await connection.OpenAsync(); using var transaction await connection.BeginTransactionAsync(); try { // 使用多个DbContext实例... await transaction.CommitAsync(); } catch { await transaction.RollbackAsync(); throw; }对于并发冲突推荐使用乐观并发控制public class Product { [Timestamp] public byte[] RowVersion { get; set; } }4. Dapper高级应用场景4.1 基础CRUD操作Dapper的简洁性在简单查询中表现尤为突出using var connection new SqlConnection(_config.GetConnectionString(Default)); var products await connection.QueryAsyncProduct( SELECT * FROM Products WHERE Price minPrice, new { minPrice 50 });批量插入的高效写法var sql INSERT INTO Products (Name, Price) VALUES (Name, Price); await connection.ExecuteAsync(sql, productList);4.2 多映射与复杂查询处理一对多关系的经典模式var sql SELECT o.*, i.* FROM Orders o LEFT JOIN OrderItems i ON o.Id i.OrderId WHERE o.Id orderId; var orderDictionary new Dictionaryint, Order(); var result await connection.QueryAsyncOrder, OrderItem, Order( sql, (order, item) { if (!orderDictionary.TryGetValue(order.Id, out var orderEntry)) { orderEntry order; orderEntry.Items new ListOrderItem(); orderDictionary.Add(orderEntry.Id, orderEntry); } orderEntry.Items.Add(item); return orderEntry; }, new { orderId }, splitOn: Id);4.3 存储过程调用Dapper对存储过程的支持非常优雅var parameters new DynamicParameters(); parameters.Add(productId, productId); parameters.Add(averagePrice, dbType: DbType.Decimal, direction: ParameterDirection.Output); await connection.ExecuteAsync( CalculateProductStats, parameters, commandType: CommandType.StoredProcedure); var avgPrice parameters.Getdecimal(averagePrice);5. 性能优化实战策略5.1 连接池配置技巧在appsettings.json中优化SQL Server连接池{ ConnectionStrings: { DefaultConnection: Server.;DatabaseMyAppDb;...;Poolingtrue;Max Pool Size200;Min Pool Size10;Connection Timeout15; } }经验值Max Pool Size ≈ (最大并发请求数) / (平均请求持续时间(秒))5.2 查询调优方法使用EF Core的日志记录识别慢查询optionsBuilder.UseSqlServer(connectionString) .LogTo(Console.WriteLine, LogLevel.Information) .EnableSensitiveDataLogging();Dapper配合MiniProfiler进行SQL分析services.AddMiniProfiler(options { options.RouteBasePath /profiler; options.SqlFormatter new StackExchange.Profiling.SqlFormatters.InlineFormatter(); });5.3 缓存策略实现内存缓存与数据库结合的典型模式public async TaskProduct GetProductWithCache(int id) { var cacheKey $product_{id}; if (!_cache.TryGetValue(cacheKey, out Product product)) { product await _context.Products.FindAsync(id); _cache.Set(cacheKey, product, new MemoryCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromMinutes(10))); } return product; }6. 跨平台兼容性实践6.1 SQLite开发配置.NET Core对SQLite的支持非常完善services.AddDbContextAppDbContext(options options.UseSqlite(Data Sourceapp.db));迁移命令需要注意dotnet ef migrations add InitialCreate --context AppDbContext dotnet ef database update6.2 PostgreSQL特殊处理Npgsql的一些独特配置services.AddDbContextAppDbContext(options options.UseNpgsql( configuration.GetConnectionString(PostgresConnection), o o.UseNodaTime())); // 支持时间类型扩展6.3 多数据库提供程序支持通过设计模式实现多数据库兼容public interface IDatabaseProvider { DbContextOptionsBuilder Configure(DbContextOptionsBuilder builder, string connectionString); } // SQL Server实现 public class SqlServerProvider : IDatabaseProvider { ... } // 在启动时 var provider configuration[Database:Provider] switch { SqlServer new SqlServerProvider(), Postgres new PostgresProvider(), _ throw new Exception(Unsupported provider) }; provider.Configure(optionsBuilder, connectionString);7. 测试驱动开发实践7.1 单元测试策略使用内存数据库测试仓储层[Fact] public async Task Should_Add_Product() { // Arrange var options new DbContextOptionsBuilderAppDbContext() .UseInMemoryDatabase(databaseName: TestDb) .Options; using var context new AppDbContext(options); var repository new ProductRepository(context); // Act await repository.AddAsync(new Product { Name Test, Price 9.99m }); // Assert var product await context.Products.FirstOrDefaultAsync(); Assert.NotNull(product); Assert.Equal(Test, product.Name); }7.2 集成测试方案使用TestContainers进行真实数据库测试public class DatabaseFixture : IAsyncLifetime { private readonly TestcontainerDatabase _dbContainer new TestcontainersBuilderPostgreSqlTestcontainer() .WithDatabase(new PostgreSqlTestcontainerConfiguration { Database test, Username postgres, Password postgres }) .Build(); public string ConnectionString _dbContainer.ConnectionString; public async Task InitializeAsync() await _dbContainer.StartAsync(); public async Task DisposeAsync() await _dbContainer.DisposeAsync(); }8. 安全防护最佳实践8.1 SQL注入防御使用参数化查询是基本要求// Dapper自动参数化 var safeQuery SELECT * FROM Users WHERE Username username; connection.Query(safeQuery, new { username }); // EF Core参数化 _context.Users.FromSqlRaw( SELECT * FROM Users WHERE Username {0}, username);8.2 敏感数据保护加密存储关键字段modelBuilder.EntityUser() .Property(u u.SSN) .HasConversion( s _encryptor.Encrypt(s), s _encryptor.Decrypt(s));8.3 审计日志实现自动记录数据变更public override async Taskint SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken default) { var entries ChangeTracker.Entries() .Where(e e.State EntityState.Added || e.State EntityState.Modified); foreach (var entry in entries) { if (entry.Entity is IAuditable auditable) { auditable.ModifiedAt DateTime.UtcNow; auditable.ModifiedBy _currentUserService.UserId; } } return await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken); }9. 实战经验与性能对比经过多个生产项目验证我总结出以下性能数据基于10,000条记录测试操作类型EF Core (ms)Dapper (ms)原生ADO.NET (ms)单条查询1553批量插入1200400350复杂联表查询804540事务操作603025关键发现简单查询场景Dapper优势明显EF Core在开发效率上具有绝对优势批量操作应考虑使用BulkExtensions等专门库10. 架构模式进阶10.1 仓储模式实现通用仓储接口设计public interface IRepositoryT where T : class { TaskT GetByIdAsync(int id); TaskIEnumerableT GetAllAsync(); Task AddAsync(T entity); Task UpdateAsync(T entity); Task DeleteAsync(T entity); IQueryableT AsQueryable(); }10.2 工作单元模式统一管理多个仓储public interface IUnitOfWork : IDisposable { IRepositoryProduct Products { get; } IRepositoryOrder Orders { get; } Taskint CommitAsync(); } // 使用示例 using var unit _unitOfWorkFactory.Create(); var product await unit.Products.GetByIdAsync(1); product.Price * 1.1m; await unit.CommitAsync();10.3 CQRS实践分离读写操作// 写操作 public class CreateProductCommandHandler : IRequestHandlerCreateProductCommand, int { private readonly AppDbContext _context; public async Taskint Handle(CreateProductCommand request, CancellationToken ct) { var product new Product { ... }; _context.Products.Add(product); await _context.SaveChangesAsync(ct); return product.Id; } } // 读操作 public class GetProductsQueryHandler : IRequestHandlerGetProductsQuery, IEnumerableProductDto { private readonly DapperContext _context; public async TaskIEnumerableProductDto Handle(GetProductsQuery request, CancellationToken ct) { using var conn _context.CreateConnection(); return await conn.QueryAsyncProductDto(SELECT Id, Name FROM Products); } }11. 常见问题解决方案11.1 迁移问题排查当遇到迁移失败时按以下步骤排查检查DbContext是否继承自DbContext验证连接字符串权限查看是否有挂起的迁移dotnet ef migrations list尝试删除__EFMigrationsHistory表后重试11.2 并发冲突处理使用EF Core的并发令牌var product await _context.Products.FindAsync(1); product.Price 20.99m; try { await _context.SaveChangesAsync(); } catch (DbUpdateConcurrencyException ex) { var entry ex.Entries.Single(); var dbValues await entry.GetDatabaseValuesAsync(); var currentValues entry.CurrentValues; // 解决策略合并值或提示用户 }11.3 连接泄露检测使用DiagnosticListener监控连接状态DiagnosticListener.AllListeners.Subscribe(listener { if (listener.Name DbLoggerCategory.Name) { listener.Subscribe(evt { if (evt.Key Microsoft.EntityFrameworkCore.Database.Connection.ConnectionClosing) { // 记录连接关闭事件 } }); } });12. 现代化改进方向12.1 原生AOT兼容性.NET 8的AOT编译对EF Core的影响需要预先编译查询限制动态SQL生成推荐使用Dapper或原始SQL12.2 云原生适配在Kubernetes环境中优化使用连接池中间件如PgBouncer实现健康检查端点配置适当的重试策略services.AddHealthChecks() .AddDbContextCheckAppDbContext() .AddSqlServer(Configuration.GetConnectionString(DefaultConnection));12.3 分布式事务使用Saga模式处理跨服务事务public class OrderSaga : SagaOrderSagaData, IAmStartedByMessagesPlaceOrder, IHandleMessagesPaymentCompleted, IHandleMessagesInventoryReserved { private readonly AppDbContext _context; public async Task Handle(PlaceOrder message, IMessageHandlerContext context) { // 启动事务链 } }13. 工具链推荐13.1 开发辅助工具LINQPad快速测试LINQ查询SSMS/SQLite Expert数据库管理EF Core Power Tools逆向工程13.2 性能工具集MiniProfilerSQL监控Benchmark.NET性能基准测试JetBrains dotTrace性能分析13.3 扩展库精选EFCore.BulkExtensions批量操作Dapper.Contrib快速CRUDPomelo.EntityFrameworkCore.MySqlMySQL提供程序14. 面试要点精粹根据近期技术面试经验高频问题包括EF Core延迟加载与立即加载的区别Dapper如何处理多表联查工作单元模式的优势乐观并发与悲观并发的实现迁移冲突的解决流程典型代码题// 以下代码有什么问题 public ListProduct GetExpensiveProducts() { return _context.Products .Where(p p.Price 100) .ToList(); }答案应使用异步方法ToListAsync()避免阻塞线程且不应在仓储层直接返回List15. 项目实战心得在最近一个电商项目中我们经历了从EF Core到Dapper的架构演变。初期使用EF Core快速实现了80%的功能但在促销活动期间遇到了性能瓶颈。通过以下优化获得了10倍性能提升将热点查询改为Dapper实现使用存储过程处理复杂统计引入二级缓存优化批量导入改用BulkInsert关键教训没有放之四海而皆准的方案应该根据场景灵活组合技术栈。对于常规管理后台EF Core的快速开发优势无可替代而对于高并发接口Dapper存储过程往往更可靠。