Appearance
数据库提供程序
概述
EF Core 通过**提供程序(Provider)**模式支持多种数据库。每个提供程序负责将 EF Core 的通用操作翻译为特定数据库的 SQL 方言和特性。
支持的数据库
| 数据库 | 提供程序包 | 成熟度 | 适用场景 |
|---|---|---|---|
| SQL Server | Microsoft.EntityFrameworkCore.SqlServer | ✅ 官方 | Windows 企业应用 |
| PostgreSQL | Npgsql.EntityFrameworkCore.PostgreSQL | ✅ 社区 | Linux/跨平台首选 |
| MySQL | Pomelo.EntityFrameworkCore.MySql | ✅ 社区 | Web 应用广泛使用 |
| SQLite | Microsoft.EntityFrameworkCore.Sqlite | ✅ 官方 | 移动/桌面/测试 |
| Cosmos DB | Microsoft.EntityFrameworkCore.Cosmos | ✅ 官方 | NoSQL/文档数据库 |
| Oracle | Oracle.EntityFrameworkCore | ✅ 官方 | Oracle 生态系统 |
| Firebird | FirebirdSql.EntityFrameworkCore.Firebird | ⚠️ 社区 | 遗留系统 |
| IBM Db2 | IBM.EntityFrameworkCore | ⚠️ 官方 | 大型企业系统 |
SQL Server
安装
bash
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools # 迁移工具配置
csharp
// Program.cs
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionString, sqlOptions =>
{
// 启用重试策略(推荐生产环境)
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 3,
maxRetryDelay: TimeSpan.FromSeconds(10),
errorNumbersToAdd: null);
// 命令超时
sqlOptions.CommandTimeout(30);
// 迁移程序集
sqlOptions.MigrationsAssembly(typeof(AppDbContext).Assembly.FullName);
}));SQL Server 特有功能
1. 行版本(乐观并发)
csharp
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; }
}
// 生成的列:
// [RowVersion] ROWVERSION NOT NULL2. 计算列
csharp
modelBuilder.Entity<Order>(entity =>
{
entity.Property(o => o.TotalAmount)
.HasComputedColumnSql("[Quantity] * [UnitPrice]", stored: true);
});3. 序列(Sequence)
csharp
// 创建序列
modelBuilder.HasSequence<int>("OrderNumberSequence")
.StartsAt(1000)
.IncrementsBy(1);
// 使用序列
modelBuilder.Entity<Order>(entity =>
{
entity.Property(o => o.OrderNumber)
.HasDefaultValueSql("NEXT VALUE FOR OrderNumberSequence");
});4. 临时表(Temporal Tables)
csharp
modelBuilder.Entity<Product>(entity =>
{
entity.ToTable(b => b.IsTemporal());
});
// 查询历史数据
var history = await context.Products
.TemporalAsOf(DateTime.UtcNow.AddDays(-7))
.Where(p => p.Id == 1)
.ToListAsync();5. 全文搜索
csharp
var products = await context.Products
.Where(p => EF.Functions.FreeText(p.Description, "laptop computer"))
.ToListAsync();
// 生成的 SQL:
// WHERE FREETEXT([p].[Description], N'laptop computer')PostgreSQL
安装
bash
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite # GIS 支持(可选)配置
csharp
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseNpgsql(connectionString, npgsqlOptions =>
{
npgsqlOptions.EnableRetryOnFailure(
maxRetryCount: 3,
maxRetryDelay: TimeSpan.FromSeconds(10));
npgsqlOptions.CommandTimeout(30);
npgsqlOptions.MigrationsAssembly(typeof(AppDbContext).Assembly.FullName);
}));PostgreSQL 特有功能
1. JSONB 支持
csharp
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
// JSONB 列
public JsonDocument Specifications { get; set; }
}
// 配置
modelBuilder.Entity<Product>(entity =>
{
entity.Property(p => p.Specifications)
.HasColumnType("jsonb");
});
// 查询 JSON 字段
var products = await context.Products
.Where(p => p.Specifications.RootElement.GetProperty("color").GetString() == "red")
.ToListAsync();2. 数组类型
csharp
public class Blog
{
public int Id { get; set; }
public string[] Tags { get; set; }
}
// 配置
modelBuilder.Entity<Blog>(entity =>
{
entity.Property(b => b.Tags)
.HasColumnType("text[]");
});
// 查询数组
var blogs = await context.Blogs
.Where(b => b.Tags.Contains("ef-core"))
.ToListAsync();3. 范围类型
csharp
public class Event
{
public int Id { get; set; }
public NpgsqlRange<DateTime> Duration { get; set; }
}
// 查询重叠的时间范围
var events = await context.Events
.Where(e => e.Duration.Overlaps(
new NpgsqlRange<DateTime>(startDate, endDate)))
.ToListAsync();4. ILIKE (不区分大小写搜索)
csharp
var products = await context.Products
.Where(p => EF.Functions.ILike(p.Name, "%laptop%"))
.ToListAsync();
// 生成的 SQL:
// WHERE [p].[Name] ILIKE '%laptop%'5. PostGIS (地理空间数据)
bash
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuitecsharp
using NetTopologySuite.Geometries;
public class Location
{
public int Id { get; set; }
public Point Coordinates { get; set; }
}
// 配置
modelBuilder.Entity<Location>(entity =>
{
entity.Property(l => l.Coordinates)
.HasColumnType("geometry(point)");
});
// 查询附近的点
var userLocation = new Point(longitude, latitude) { SRID = 4326 };
var nearby = await context.Locations
.OrderBy(l => l.Coordinates.Distance(userLocation))
.Take(10)
.ToListAsync();MySQL
安装
bash
dotnet add package Pomelo.EntityFrameworkCore.MySql配置
csharp
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseMySql(connectionString,
ServerVersion.AutoDetect(connectionString),
mysqlOptions =>
{
mysqlOptions.EnableRetryOnFailure(
maxRetryCount: 3,
maxRetryDelay: TimeSpan.FromSeconds(10));
mysqlOptions.CommandTimeout(30);
mysqlOptions.MigrationsAssembly(typeof(AppDbContext).Assembly.FullName);
}));MySQL 特有功能
1. 字符集配置
csharp
modelBuilder.Entity<Product>(entity =>
{
entity.UseCollation("utf8mb4_unicode_ci");
});2. 自增主键
csharp
public class Product
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
}
// 生成的 SQL:
// `Id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY3. FULLTEXT 索引
csharp
modelBuilder.Entity<Article>(entity =>
{
entity.HasIndex(a => a.Content)
.HasMethod("FULLTEXT");
});
// 全文搜索
var articles = await context.Articles
.Where(a => EF.Functions.Match(a.Content, "search term"))
.ToListAsync();4. JSON 支持(MySQL 5.7+)
csharp
public class Product
{
public int Id { get; set; }
public string Specifications { get; set; } // JSON 字符串
}
// 查询 JSON 字段
var products = await context.Products
.Where(p => EF.Functions.JsonContains(
p.Specifications,
"{\"color\": \"red\"}"))
.ToListAsync();SQLite
安装
bash
dotnet add package Microsoft.EntityFrameworkCore.Sqlite配置
csharp
// 开发/测试环境
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlite("Data Source=app.db"));
// 内存数据库(测试用)
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlite("Data Source=:memory:"));SQLite 特有考虑
1. 并发限制
csharp
// ⚠️ SQLite 默认不支持高并发写入
// 解决方案: 启用 WAL 模式
var connection = new SqliteConnection(connectionString);
connection.Open();
using var command = connection.CreateCommand();
command.CommandText = "PRAGMA journal_mode=WAL;";
command.ExecuteNonQuery();2. 数据类型灵活性
csharp
// SQLite 是弱类型,所有整数都是 INTEGER
// 所有浮点数都是 REAL
// 注意: 不会强制检查类型约束
public class Product
{
public int Id { get; set; } // INTEGER
public decimal Price { get; set; } // REAL
public DateTime CreatedAt { get; set; } // TEXT (ISO 8601)
}3. 迁移限制
csharp
// ⚠️ SQLite 不支持某些 ALTER TABLE 操作
// 例如: 删除列、修改列类型
// 解决方案: 重新创建表
// dotnet ef migrations add MigrationName
// 手动编辑迁移文件,使用 RebuildTable 模式4. 内存数据库测试
csharp
[Fact]
public async Task TestWithInMemorySqlite()
{
var connection = new SqliteConnection("Data Source=:memory:");
connection.Open();
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseSqlite(connection)
.Options;
using var context = new AppDbContext(options);
await context.Database.EnsureCreatedAsync();
// 执行测试...
connection.Close();
}Cosmos DB
安装
bash
dotnet add package Microsoft.EntityFrameworkCore.Cosmos配置
csharp
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseCosmos(
"https://localhost:8081",
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==",
databaseName: "MyDatabase"));Cosmos DB 特性
1. 无架构设计
csharp
public class Order
{
public string Id { get; set; } // 必须是 string
public string CustomerId { get; set; }
public List<OrderItem> Items { get; set; }
}
// 存储为 JSON 文档:
// {
// "id": "order-123",
// "customerId": "cust-456",
// "items": [...],
// "_rid": "...",
// "_ts": 1234567890
// }2. 分区键
csharp
modelBuilder.Entity<Order>(entity =>
{
entity.ToContainer("Orders");
entity.HasPartitionKey(o => o.CustomerId); // 分区键
entity.HasKey(o => o.Id);
});3. 嵌入文档
csharp
public class Order
{
public string Id { get; set; }
public Address ShippingAddress { get; set; } // 嵌入对象
}
public class Address
{
public string Street { get; set; }
public string City { get; set; }
}
// 存储为嵌套 JSON:
// {
// "id": "order-123",
// "shippingAddress": {
// "street": "123 Main St",
// "city": "Seattle"
// }
// }多数据库支持
策略 1: 条件注册
csharp
var dbType = builder.Configuration["Database:Type"];
if (dbType == "SqlServer")
{
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionString));
}
else if (dbType == "PostgreSQL")
{
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseNpgsql(connectionString));
}
else if (dbType == "MySQL")
{
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)));
}策略 2: 抽象工厂
csharp
public interface IDbContextFactory
{
AppDbContext Create();
}
public class SqlServerDbContextFactory : IDbContextFactory
{
private readonly string _connectionString;
public AppDbContext Create()
{
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseSqlServer(_connectionString)
.Options;
return new AppDbContext(options);
}
}
public class PostgreSqlDbContextFactory : IDbContextFactory
{
private readonly string _connectionString;
public AppDbContext Create()
{
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseNpgsql(_connectionString)
.Options;
return new AppDbContext(options);
}
}策略 3: 避免数据库特定代码
csharp
// ❌ 错误: 使用 SQL Server 特定函数
.Where(p => SqlFunctions.DateDiff("day", p.CreatedAt, DateTime.Now) > 30)
// ✅ 正确: 使用标准 LINQ
.Where(p => EF.Functions.DateDiffDay(p.CreatedAt, DateTime.Now) > 30)
// ✅ 更好: 完全可移植
var cutoffDate = DateTime.Now.AddDays(-30);
.Where(p => p.CreatedAt < cutoffDate)提供程序对比
功能支持矩阵
| 功能 | SQL Server | PostgreSQL | MySQL | SQLite |
|---|---|---|---|---|
| LINQ 翻译 | ✅ 完整 | ✅ 完整 | ✅ 完整 | ✅ 基本 |
| 迁移支持 | ✅ 完整 | ✅ 完整 | ✅ 完整 | ⚠️ 部分 |
| 并发控制 | ✅ RowVersion | ✅ xmin | ⚠️ 有限 | ❌ 无 |
| JSON 支持 | ✅ JSON | ✅ JSONB | ✅ JSON | ⚠️ TEXT |
| 全文搜索 | ✅ FreeText | ✅ tsvector | ✅ MATCH | ❌ 无 |
| 空间数据 | ✅ Geography | ✅ PostGIS | ⚠️ 有限 | ⚠️ 有限 |
| 批量操作 | ✅ 优秀 | ✅ 优秀 | ✅ 良好 | ⚠️ 一般 |
| 性能(读) | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 性能(写) | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ |
| 并发能力 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ |
选择建议
项目需求
│
├─ Windows 企业环境?
│ └─ ✅ SQL Server
│
├─ Linux/云原生?
│ └─ ✅ PostgreSQL
│
├─ Web 应用/WordPress 迁移?
│ └─ ✅ MySQL
│
├─ 移动/桌面应用?
│ └─ ✅ SQLite
│
├─ 需要 NoSQL/文档模型?
│ └─ ✅ Cosmos DB
│
└─ 不确定?
└─ ✅ PostgreSQL (最佳性价比)最佳实践
✅ 推荐做法
1. 使用连接字符串配置
json
// appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=MyApp;..."
},
"Database": {
"Type": "SqlServer",
"EnableRetry": true,
"CommandTimeout": 30
}
}2. 启用重试策略(生产环境)
csharp
options.UseSqlServer(connectionString, sqlOptions =>
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 3,
maxRetryDelay: TimeSpan.FromSeconds(10)));3. 避免数据库特定代码
csharp
// ✅ 使用标准 EF Core API
modelBuilder.Entity<Product>(entity =>
{
entity.Property(p => p.Name).IsRequired();
entity.HasIndex(p => p.Name).IsUnique();
});
// ❌ 避免直接使用原生 SQL 特性4. 测试时使用 SQLite
csharp
// 单元测试
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseSqlite("Data Source=:memory:")
.Options;
using var context = new AppDbContext(options);
await context.Database.EnsureCreatedAsync();❌ 常见陷阱
1. 忽略提供程序差异
csharp
// ❌ SQL Server 语法
.Take(10) // 生成 TOP 10
// ⚠️ 在不同数据库中行为可能不同
// 始终测试目标数据库2. 不使用参数化查询
csharp
// ❌ SQL 注入风险
context.Products.FromSqlRaw($"SELECT * FROM Products WHERE Name = '{name}'");
// ✅ 参数化
context.Products.FromSqlRaw("SELECT * FROM Products WHERE Name = {0}", name);3. 忽略时区问题
csharp
// ❌ DateTime 可能有时区问题
.Where(p => p.CreatedAt > DateTime.Now)
// ✅ 使用 UTC
.Where(p => p.CreatedAt > DateTime.UtcNow)总结
提供程序选择决策树
开始
│
├─ 已有数据库?
│ └─ 使用该数据库的提供程序
│
├─ 新项目?
│ ├─ Windows 企业 → SQL Server
│ ├─ Linux/云 → PostgreSQL
│ ├─ Web 托管 → MySQL
│ ├─ 移动/桌面 → SQLite
│ └─ 全球 scale → Cosmos DB
│
└─ 需要多数据库支持?
└─ 抽象 DbContext 配置,避免数据库特定代码核心要点
- 选择合适的提供程序: 根据部署环境和需求
- 启用重试策略: 提高系统韧性
- 避免数据库锁定: 使用标准 EF Core API
- 测试目标数据库: 不要只在 InMemory 测试
- 监控性能差异: 不同数据库性能特征不同