☰
EFCore数据库操作:从基础到高级实践
2026/10/15 7:11:22 网站建设 项目流程

1. EFCore数据库操作概述

作为一名.NET开发者,我使用Entity Framework Core(简称EFCore)进行数据库操作已经有五年多的时间。EFCore是微软推出的轻量级、可扩展、跨平台的对象关系映射(ORM)框架,它让我们可以用面向对象的方式操作数据库,而不用直接编写SQL语句。在实际项目中,EFCore极大地提高了开发效率,特别是在快速迭代的业务场景中。

EFCore支持多种数据库引擎,包括SQL Server、MySQL、PostgreSQL、SQLite等,这使得它成为.NET生态中最受欢迎的数据库访问技术之一。通过DbContext这个核心类,我们可以定义数据模型、执行查询、跟踪变更并保存到数据库。EFCore还提供了强大的迁移功能,可以随着业务需求的变化而演进数据库架构。

提示:虽然EFCore简化了数据库操作,但理解其底层工作原理对于编写高效、可靠的代码至关重要。特别是在处理复杂查询或大数据量时,了解EFCore如何将LINQ转换为SQL可以帮助我们避免性能陷阱。

2. EFCore核心概念与配置

2.1 DbContext与DbSet

DbContext是EFCore的核心,它代表与数据库的会话,负责:

  • 管理数据库连接
  • 跟踪实体变更
  • 执行数据库操作
  • 提供LINQ查询支持

一个典型的DbContext定义如下:

public class AppDbContext : DbContext { public DbSet<Product> Products { get; set; } public DbSet<Category> Categories { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=MyShop;Trusted_Connection=True;"); } }

每个DbSet属性对应数据库中的一个表。EFCore通过约定优于配置的原则,自动推断表名、主键等元数据,同时也支持通过Fluent API或数据注解进行自定义配置。

2.2 实体配置方式

EFCore提供了三种配置实体和关系的方式:

  1. 约定:默认行为,如Id属性自动成为主键
  2. 数据注解:通过特性标注,如[Key]、[Required]
  3. Fluent API:在DbContext的OnModelCreating方法中配置

对于复杂模型,我推荐使用Fluent API,因为它提供了最灵活和集中的配置方式:

protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Product>() .HasKey(p => p.ProductId); modelBuilder.Entity<Product>() .Property(p => p.Name) .IsRequired() .HasMaxLength(100); modelBuilder.Entity<Product>() .HasOne(p => p.Category) .WithMany(c => c.Products) .HasForeignKey(p => p.CategoryId); }

3. 基本CRUD操作

3.1 添加数据

添加新记录非常简单,只需创建实体实例并添加到对应的DbSet:

using (var context = new AppDbContext()) { var category = new Category { Name = "Electronics" }; context.Categories.Add(category); var product = new Product { Name = "Smartphone", Price = 599.99m, Category = category }; context.Products.Add(product); int recordsAffected = context.SaveChanges(); Console.WriteLine($"Saved {recordsAffected} records."); }

注意:SaveChanges()是原子操作,要么全部成功,要么全部回滚。对于大批量插入,考虑使用AddRange()和批量操作优化。

3.2 查询数据

EFCore支持强大的LINQ查询功能:

// 基本查询 var cheapProducts = context.Products .Where(p => p.Price < 100) .ToList(); // 包含关联数据 var productsWithCategory = context.Products .Include(p => p.Category) .ToList(); // 投影查询 var productNames = context.Products .Select(p => new { p.Name, p.Price }) .ToList();

3.3 更新数据

更新操作遵循"查询-修改-保存"模式:

var product = context.Products.Find(1); if (product != null) { product.Price *= 1.1m; // 涨价10% context.SaveChanges(); }

3.4 删除数据

删除同样需要先加载实体:

var product = context.Products.Find(1); if (product != null) { context.Products.Remove(product); context.SaveChanges(); }

或者使用EntityState直接标记删除:

var product = new Product { ProductId = 1 }; context.Entry(product).State = EntityState.Deleted; context.SaveChanges();

4. 高级查询技术

4.1 延迟加载与立即加载

EFCore支持两种加载关联数据的方式:

  1. 立即加载(Eager Loading):使用Include方法预先加载关联数据
var orders = context.Orders .Include(o => o.Customer) .Include(o => o.OrderItems) .ThenInclude(oi => oi.Product) .ToList();
  1. 延迟加载(Lazy Loading):访问导航属性时自动加载,需安装Microsoft.EntityFrameworkCore.Proxies包并启用:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseLazyLoadingProxies(); }

提示:延迟加载可能导致N+1查询问题,在大数据量场景下应谨慎使用。

4.2 原始SQL查询

当LINQ无法满足复杂查询需求时,可以使用原始SQL:

var products = context.Products .FromSqlRaw("SELECT * FROM Products WHERE Price > {0}", 50) .ToList();

对于存储过程:

var products = context.Products .FromSqlRaw("EXEC GetExpensiveProducts @minPrice={0}", 100) .ToList();

4.3 全局查询过滤器

EFCore支持定义全局查询条件,这在多租户或软删除场景中特别有用:

protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Product>() .HasQueryFilter(p => !p.IsDeleted); }

5. 性能优化技巧

5.1 批量操作

默认情况下,SaveChanges()对每个变更生成单独的SQL语句。使用AddRange()可以优化插入操作:

var products = new List<Product>(); for (int i = 0; i < 1000; i++) { products.Add(new Product { Name = $"Product {i}" }); } context.Products.AddRange(products); context.SaveChanges(); // 仍然生成1000条INSERT语句

对于EF Core 5.0+,可以使用批量操作:

context.BulkInsert(products); // 使用第三方库如EFCore.BulkExtensions

5.2 异步操作

所有数据库操作都有异步版本,适合Web应用:

var product = await context.Products.FindAsync(1); var products = await context.Products.ToListAsync(); await context.SaveChangesAsync();

5.3 查询优化

  1. 只选择需要的字段:
var productNames = await context.Products .Select(p => p.Name) .ToListAsync();
  1. 禁用变更跟踪:
var products = await context.Products .AsNoTracking() .ToListAsync();
  1. 编译查询:
private static readonly Func<AppDbContext, int, Task<Product>> GetProductById = EF.CompileAsyncQuery((AppDbContext context, int id) => context.Products.FirstOrDefault(p => p.ProductId == id)); // 使用 var product = await GetProductById(context, 1);

6. 事务管理

6.1 隐式事务

每个SaveChanges()调用自动创建一个事务:

using (var context = new AppDbContext()) { context.Products.Add(new Product { Name = "New Product" }); context.SaveChanges(); // 自动创建并提交事务 }

6.2 显式事务

对于跨多个操作的事务:

using (var transaction = context.Database.BeginTransaction()) { try { context.Products.Add(new Product { Name = "Product A" }); context.SaveChanges(); context.Products.Add(new Product { Name = "Product B" }); context.SaveChanges(); transaction.Commit(); } catch { transaction.Rollback(); throw; } }

6.3 分布式事务

在.NET Core 3.1+中,可以使用TransactionScope:

using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) { // 跨多个DbContext的操作 await context1.SaveChangesAsync(); await context2.SaveChangesAsync(); scope.Complete(); }

7. 数据库迁移

7.1 创建迁移

安装EF Core工具后,通过命令行创建迁移:

dotnet ef migrations add InitialCreate

这会生成迁移文件,包含Up(应用迁移)和Down(回滚迁移)方法。

7.2 应用迁移

可以通过代码或命令行应用迁移:

context.Database.Migrate();

或命令行:

dotnet ef database update

7.3 自定义迁移

对于复杂变更,可以手动编辑迁移文件:

protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.AddColumn<decimal>( name: "Price", table: "Products", type: "decimal(10,2)", nullable: false, defaultValue: 0m); migrationBuilder.CreateIndex( name: "IX_Products_CategoryId", table: "Products", column: "CategoryId"); }

8. 日志记录与诊断

8.1 配置日志记录

EFCore可以与各种日志框架集成:

optionsBuilder.UseSqlServer(connectionString) .LogTo(Console.WriteLine, LogLevel.Information) .EnableSensitiveDataLogging();

8.2 性能诊断

使用Diagnostics监听器监控查询性能:

var listener = new DiagnosticListener("Microsoft.EntityFrameworkCore"); var subscription = listener.Subscribe(new MyDiagnosticListener()); public class MyDiagnosticListener : IObserver<KeyValuePair<string, object>> { public void OnNext(KeyValuePair<string, object> value) { if (value.Key == "Microsoft.EntityFrameworkCore.Database.Command.CommandExecuting") { var command = (string)value.Value.GetType().GetProperty("CommandText").GetValue(value.Value); Console.WriteLine($"Executing: {command}"); } } public void OnCompleted() {} public void OnError(Exception error) {} }

9. 常见问题与解决方案

9.1 并发冲突

处理并发更新的两种方式:

  1. 乐观并发控制:
modelBuilder.Entity<Product>() .Property(p => p.RowVersion) .IsRowVersion();
  1. 处理并发异常:
try { await context.SaveChangesAsync(); } catch (DbUpdateConcurrencyException ex) { var entry = ex.Entries.Single(); var databaseValues = await entry.GetDatabaseValuesAsync(); var clientValues = entry.Entity as Product; // 解决冲突逻辑 }

9.2 连接池问题

典型症状是连接耗尽错误。解决方案:

  • 确保DbContext及时释放
  • 增加连接池大小
  • 减少连接存活时间
services.AddDbContextPool<AppDbContext>(options => options.UseSqlServer(connectionString, sqlOptions => sqlOptions.EnableRetryOnFailure()));

9.3 查询性能问题

常见原因和解决方案:

问题解决方案
N+1查询使用Include加载关联数据
大量数据分页查询(.Skip().Take())
复杂查询使用原始SQL或存储过程
缺少索引通过迁移添加适当索引

10. 实际项目经验分享

在我最近的一个电商项目中,我们使用EFCore处理了每天数十万的订单。以下是一些关键经验:

  1. 批量插入优化:使用EFCore.BulkExtensions将订单明细批量插入,性能提升20倍。

  2. 读写分离:为DbContext配置不同的连接字符串,读操作使用只读副本。

  3. 查询缓存:对常用但不常变的数据(如产品分类)实现二级缓存。

  4. DbContext生命周期:在Web应用中,通常将DbContext配置为Scoped生命周期,每个请求一个实例。

  5. 复杂查询分解:将复杂报表查询分解为多个较小查询,在内存中组合结果。

重要提示:EFCore虽然强大,但并非所有场景都适用。对于超高性能要求的场景,可能需要考虑Dapper或直接ADO.NET。关键是根据业务需求选择合适的技术。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询