彻底解决主键冲突:EF Core数据库序列生成终极指南

彻底解决主键冲突:EF Core数据库序列生成终极指南

【免费下载链接】efcore efcore: 是 .NET 平台上一个开源的对象关系映射(ORM)框架,用于操作关系型数据库。适合开发者使用 .NET 进行数据库操作,简化数据访问和持久化过程。 【免费下载链接】efcore 项目地址: https://gitcode.com/GitHub_Trending/ef/efcore

你是否还在为分布式系统中的主键冲突头疼?是否在寻找比自增ID更灵活的主键方案?本文将带你掌握EF Core中数据库序列(Database Sequence)的完整应用,从基础配置到高并发场景处理,让你彻底摆脱主键管理的困扰。读完本文你将获得:

  • 序列生成的3种核心配置方式
  • 多数据库实例共享序列的实现方案
  • 50线程并发下的序列安全保障措施
  • 官方测试用例中的最佳实践提炼

序列生成 vs 自增ID:为什么选择序列?

在传统的数据库设计中,自增ID(Identity Column)是最常用的主键生成方式。但在分布式系统或需要手动插入ID的场景下,自增ID的局限性就会凸显:无法跨数据库实例同步、难以处理数据迁移、插入显式ID时容易冲突。

数据库序列(Sequence)作为一种独立的数据库对象,提供了更灵活的ID生成机制:

  • 可跨表共享同一序列
  • 支持预分配ID段提高性能
  • 允许手动干预序列值
  • 天然支持分布式系统

EF Core通过UseHiLo()方法实现了对数据库序列的封装,其核心测试用例可参考test/EFCore.SqlServer.FunctionalTests/SequenceEndToEndTest.cs。该测试类包含了从基础用法到并发场景的11个完整测试方法,覆盖了序列生成的各种边界情况。

基础配置:3步实现序列主键

1. 实体类定义

首先创建包含序列主键的实体类,以测试用例中的Pegasus类为例:

private class Pegasus
{
    public int Identifier { get; set; } // 序列主键
    public string Name { get; set; }
}

2. 模型配置

DbContextOnModelCreating方法中,使用UseHiLo()配置序列主键:

protected override void OnModelCreating(ModelBuilder modelBuilder)
    => modelBuilder.Entity<Pegasus>(b =>
    {
        b.HasKey(e => e.Identifier);
        b.Property(e => e.Identifier).UseHiLo(); // 核心配置
    });

UseHiLo()方法会自动创建名为EntityFrameworkHiLoSequence的数据库序列,并设置默认的增量(1)和块大小(10)。

3. 数据库上下文配置

最后配置数据库连接,完成序列主键的设置:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseInternalServiceProvider(_serviceProvider)
        .UseSqlServer(connectionString);

通过这三步,EF Core会自动管理序列的创建和ID生成过程。当调用SaveChanges()时,EF Core会从数据库序列获取下一个值作为主键。

高级配置:自定义序列参数

自定义序列名称和增量

默认情况下,EF Core使用固定的序列名称和参数。通过HasSequence()方法可以自定义序列的各种属性:

modelBuilder.HasSequence<int>("CustomSequence")
    .StartsAt(100)  // 起始值
    .IncrementsBy(2) // 增量
    .HasMin(100)     // 最小值
    .HasMax(1000);   // 最大值

modelBuilder.Entity<Pegasus>(b =>
{
    b.Property(e => e.Identifier)
        .HasDefaultValueSql("NEXT VALUE FOR CustomSequence");
});

多数据库实例共享序列

在微服务架构中,有时需要多个数据库实例共享同一序列。测试用例中的Can_use_sequence_end_to_end_on_multiple_databases方法展示了具体实现:

var dbOne = TestStore.Name + "1";
var dbTwo = TestStore.Name + "2";

foreach (var dbName in new[] { dbOne, dbTwo })
{
    using var context = new BronieContext(serviceProvider, dbName);
    context.Database.EnsureCreatedResiliently(); // 为每个数据库创建序列
}

通过在不同数据库中创建同名序列,结合分布式事务或消息队列,可以实现跨数据库实例的ID唯一性。

并发安全:50线程下的序列保障

高并发场景下的序列生成需要特别注意线程安全。EF Core的测试用例通过Can_use_sequence_end_to_end_from_multiple_contexts_concurrently_async方法验证了50个并发线程下的序列安全性:

const int threadCount = 50;
var tests = new Func<Task>[threadCount];

for (var i = 0; i < threadCount; i++)
{
    var closureProvider = serviceProvider;
    tests[i] = () => AddEntitiesAsync(closureProvider, TestStore.Name);
}

var tasks = tests.Select(Task.Run).ToArray();
foreach (var t in tasks)
{
    await t;
}

测试结果验证了在50个并发线程同时插入数据的情况下,序列生成的ID依然保持唯一性。这得益于EF Core内部的并发控制机制和数据库序列的原子性操作。

特殊场景处理

插入显式ID值

有时需要手动指定ID值,同时保持序列的连续性。测试用例中的Can_use_explicit_values方法展示了如何安全地混合使用自动生成和手动指定的ID:

AddEntitiesWithIds(serviceProvider, 0, TestStore.Name); // 自动生成
AddEntitiesWithIds(serviceProvider, 2, TestStore.Name); // 手动指定起始ID

实现方法是在插入时显式设置ID值,EF Core会自动跳过这些值,从序列的当前值继续生成新ID。

可空类型主键

对于可空类型的主键,EF Core同样支持序列生成。测试用例中的Can_use_sequence_with_nullable_key_end_to_end方法验证了可空主键的序列生成:

private class Unicon
{
    public int? Identifier { get; set; } // 可空序列主键
    public string Name { get; set; }
}

配置方式与非可空类型完全一致,EF Core会自动处理null值情况,从序列获取新值填充。

性能优化:序列预分配策略

EF Core的UseHiLo()方法默认使用"高-低"算法(Hi-Lo Algorithm),通过预分配ID段来减少数据库访问次数。默认情况下,每次从数据库获取10个ID(块大小),当客户端用完这10个ID后,再从数据库获取下一个块。

这种机制在网络延迟较高或并发量大的场景下能显著提升性能。可以通过UseHiLo()的参数调整块大小:

b.Property(e => e.Identifier).UseHiLo("SequenceName", "Schema", 50); // 块大小50

块大小的设置需要权衡:较大的块大小减少数据库访问次数,但可能导致更多ID浪费;较小的块大小则相反。

官方测试用例解析:从实践中学习

EF Core的官方测试用例为我们提供了序列生成的最佳实践参考。test/EFCore.SqlServer.FunctionalTests/SequenceEndToEndTest.cs包含以下关键测试场景:

测试方法核心功能难度级别
Can_use_sequence_end_to_end基础序列生成★☆☆☆☆
Can_use_sequence_end_to_end_on_multiple_databases多数据库共享序列★★★☆☆
Can_use_sequence_end_to_end_async异步序列生成★★☆☆☆
Can_use_sequence_end_to_end_from_multiple_contexts_concurrently_async并发序列生成★★★★★
Can_use_explicit_values显式ID插入★★☆☆☆
Can_use_sequence_with_nullable_key_end_to_end可空类型主键★★☆☆☆

其中,Can_use_sequence_end_to_end_from_multiple_contexts_concurrently_async方法通过50个并发线程测试了序列的安全性,是高并发场景下的重要参考。该测试创建50个并行任务,每个任务插入20条记录,最终验证所有ID的唯一性。

总结与最佳实践

序列生成作为EF Core中强大的ID管理方案,适用于从简单应用到分布式系统的各种场景。根据官方测试用例和实践经验,我们总结出以下最佳实践:

  1. 基础场景:直接使用UseHiLo()默认配置,快速实现序列主键
  2. 分布式系统:为每个服务创建独立序列,避免跨服务ID冲突
  3. 高并发场景:适当增大块大小(如50-100)减少数据库访问
  4. 数据迁移:使用SetHiLoSequenceFilter()方法排除序列对象
  5. 测试验证:参考官方测试用例设计序列相关的单元测试

EF Core的序列生成功能经过了充分的测试验证,在test/EFCore.SqlServer.FunctionalTests/SequenceEndToEndTest.cs的并发测试中,50个线程同时插入数据,最终所有ID均保持唯一,证明了其在高并发场景下的可靠性。

掌握序列生成不仅能解决主键管理的各种问题,更能让你深入理解EF Core的设计思想。希望本文能帮助你在实际项目中更好地应用这一强大功能。

点赞+收藏+关注,获取更多EF Core深度技术解析。下期预告:《EF Core 批量操作性能优化:从1000条/秒到10000条/秒的突破》

【免费下载链接】efcore efcore: 是 .NET 平台上一个开源的对象关系映射(ORM)框架,用于操作关系型数据库。适合开发者使用 .NET 进行数据库操作,简化数据访问和持久化过程。 【免费下载链接】efcore 项目地址: https://gitcode.com/GitHub_Trending/ef/efcore

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值