适配SqlServer与Sqlite:EF Core复合主键自增的Sqlite触发器实现求助
解决Sqlite下复合主键内SectionId按Document自增的问题
我正好踩过这个Sqlite的坑,它确实不支持复合主键里的字段自增,但用触发器完全能实现你要的「每个Document实例对应的SectionId按序自增」需求,下面是具体的落地步骤:
1. 调整EF Core实体配置
先区分SqlServer和Sqlite的配置逻辑:SqlServer继续用原生自增,Sqlite则关闭自动自增,交给触发器处理。在DbContext的OnModelCreating方法里添加如下配置:
protected override void OnModelCreating(ModelBuilder modelBuilder) { // 配置复合主键 modelBuilder.Entity<DocumentSection>() .HasKey(ds => new { ds.DocumentId, ds.SectionId }); // SqlServer环境下,SectionId沿用自增特性 modelBuilder.Entity<DocumentSection>() .Property(ds => ds.SectionId) .UseIdentityColumn() .HasAnnotation("SqlServer:Identity", "1, 1"); // Sqlite环境下,关闭自动自增,由触发器接管 modelBuilder.Entity<DocumentSection>() .Property(ds => ds.SectionId) .HasAnnotation("Sqlite:Autoincrement", false); }
2. 编写Sqlite触发器
我们需要一个触发器,在插入DocumentSection时,自动计算当前DocumentId对应的最大SectionId,再加1作为新的SectionId值。触发器SQL代码如下:
CREATE TRIGGER IF NOT EXISTS Trigger_DocumentSection_SectionId_AutoIncrement AFTER INSERT ON DocumentSection FOR EACH ROW WHEN NEW.SectionId IS NULL OR NEW.SectionId = 0 BEGIN UPDATE DocumentSection SET SectionId = ( SELECT IFNULL(MAX(SectionId), 0) + 1 FROM DocumentSection WHERE DocumentId = NEW.DocumentId ) WHERE rowid = NEW.rowid; END;
触发器逻辑说明:
AFTER INSERT:在插入操作完成后执行触发器WHEN条件:仅当插入的SectionId为null或0时触发,保留手动指定SectionId的灵活性- 用Sqlite自带的
rowid定位刚插入的行(所有Sqlite表默认都有这个内部行标识符) IFNULL(MAX(SectionId), 0)处理该Document还没有任何Section的情况,此时从1开始自增
3. 通过EF Core迁移部署触发器
我们可以借助EF Core迁移,自动在Sqlite环境下创建触发器:
- 生成空迁移文件:
dotnet ef migrations add AddDocumentSectionAutoIncrementTrigger
- 打开生成的迁移文件,修改
Up和Down方法:
protected override void Up(MigrationBuilder migrationBuilder) { // 仅在Sqlite环境下创建触发器 if (migrationBuilder.ActiveProvider == "Microsoft.EntityFrameworkCore.Sqlite") { migrationBuilder.Sql(@" CREATE TRIGGER IF NOT EXISTS Trigger_DocumentSection_SectionId_AutoIncrement AFTER INSERT ON DocumentSection FOR EACH ROW WHEN NEW.SectionId IS NULL OR NEW.SectionId = 0 BEGIN UPDATE DocumentSection SET SectionId = ( SELECT IFNULL(MAX(SectionId), 0) + 1 FROM DocumentSection WHERE DocumentId = NEW.DocumentId ) WHERE rowid = NEW.rowid; END; "); } } protected override void Down(MigrationBuilder migrationBuilder) { // 回滚迁移时删除触发器 if (migrationBuilder.ActiveProvider == "Microsoft.EntityFrameworkCore.Sqlite") { migrationBuilder.Sql("DROP TRIGGER IF EXISTS Trigger_DocumentSection_SectionId_AutoIncrement;"); } }
- 执行迁移,将触发器部署到数据库:
dotnet ef database update
4. 实际使用方式
插入DocumentSection时,只需要设置DocumentId,SectionId留空或设为0即可,触发器会自动填充正确的自增值:
var newSection = new DocumentSection { DocumentId = existingDocument.Id // SectionId 可以不赋值,或者设为0 }; context.DocumentSections.Add(newSection); await context.SaveChangesAsync(); // 此时newSection.SectionId已经是当前Document下的最新自增值 Console.WriteLine($"新Section的编号: {newSection.SectionId}");
注意事项
- 并发场景:Sqlite默认使用串行化事务隔离级别,多个请求同时插入同一个Document的Section时,不会出现重复SectionId的问题
- 手动指定SectionId:如果需要自定义SectionId,只要给一个非0的值,触发器就不会执行,保留你设置的数值
内容的提问来源于stack exchange,提问作者Michael Schnerring
相关产品推荐
相关产品推荐

