在C#中,使用Entity Framework (EF) 框架并不直接涉及到主键的生成策略。主键的生成通常是在数据库层面或者在应用程序层面处理的。雪花主键(Snowflake ID)是一种在分布式系统中生成唯一ID的算法,它通常是由Twitter的雪花算法演变而来。
雪花主键的特点是在不依赖数据库的情况下,能够在分布式系统中生成全局唯一且有序的64位整数ID。这种ID通常用于大数据场景,因为它们可以有效地利用缓存、分片、以及分布式系统中的其他优化策略。
下面是一个简单的C#实现雪花主键生成器的例子:
csharp代码
| public class SnowflakeIdGenerator | |
| { | |
| private const long epoch = 1288834974657L; // 起始时间戳(毫秒) | |
| private const long workerIdBits = 5L; // 机器id所占的位数 | |
| private const long datacenterIdBits = 5L; // 数据中心id所占的位数 | |
| private const long maxWorkerId = -1L ^ (-1L << workerIdBits); // 支持的最大机器id数量 | |
| private const long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); // 支持的最大数据中心id数量 | |
| private const long sequenceBits = 12L; // 序列所占的位数 | |
| private const long workerIdShift = sequenceBits; // 机器ID左移位数 | |
| private const long datacenterIdShift = sequenceBits + workerIdBits; // 数据中心ID左移位数 | |
| private const long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; // 时间戳左移位数 | |
| private const long sequenceMask = -1L ^ (-1L << sequenceBits); // 序列掩码 | |
| private long workerId; // 机器ID | |
| private long datacenterId; // 数据中心ID | |
| private long sequence = 0L; // 序列 | |
| private long lastTimestamp = -1L; // 上次生成ID的时间戳 | |
| public SnowflakeIdGenerator(long workerId, long datacenterId) | |
| { | |
| if (workerId > maxWorkerId || workerId < 0) | |
| { | |
| throw new ArgumentException(string.Format("worker Id can't be greater than {0} or less than 0", maxWorkerId)); | |
| } | |
| if (datacenterId > maxDatacenterId || datacenterId < 0) | |
| { | |
| throw new ArgumentException(string.Format("datacenter Id can't be greater than {0} or less than 0", maxDatacenterId)); | |
| } | |
| this.workerId = workerId; | |
| this.datacenterId = datacenterId; | |
| } | |
| public long NextId() | |
| { | |
| long timestamp = TimeGen(); | |
| if (timestamp < lastTimestamp) | |
| { | |
| throw new Exception(string.Format("Clock moved backwards. Refusing to generate id for {0} milliseconds", lastTimestamp - timestamp)); | |
| } | |
| if (lastTimestamp == timestamp) | |
| { | |
| sequence = (sequence + 1) & sequenceMask; | |
| if (sequence == 0) | |
| { | |
| timestamp = TilNextMillis(lastTimestamp); | |
| } | |
| } | |
| else | |
| { | |
| sequence = 0L; | |
| } | |
| lastTimestamp = timestamp; | |
| return ((timestamp - epoch) << timestampLeftShift) | | |
| (datacenterId << datacenterIdShift) | | |
| (workerId << workerIdShift) | | |
| sequence; | |
| } | |
| protected long TilNextMillis(long lastTimestamp) | |
| { | |
| long timestamp = TimeGen(); | |
| while (timestamp <= lastTimestamp) | |
| { | |
| timestamp = TimeGen(); | |
| } | |
| return timestamp; | |
| } | |
| protected long TimeGen() | |
| { | |
| return (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalMilliseconds; | |
| } | |
| } |
在这个例子中,SnowflakeIdGenerator 类封装了生成雪花主键的逻辑。构造函数接受机器ID(workerId)和数据中心ID(datacenterId)作为参数,这些ID在分布式系统中应该是唯一的。NextId 方法是生成新ID的入口点。它首先检查当前时间戳是否小于上一次生成ID的时间戳,如果是,则抛出一个异常。然后,根据时间戳是否相同来递增序列或等待下一个毫秒。最后,通过位运算组合时间戳、数据中心ID、机器ID和序列来生成最终的雪花主键。
在Entity Framework (EF) 中使用雪花主键生成器,你需要将生成的雪花ID作为实体的主键值。这通常涉及到几个步骤:
- 定义实体类,并在其中指定主键属性。
- 在DbContext中配置实体,以便EF知道如何使用雪花主键生成器。
- 在应用程序启动时初始化雪花主键生成器,并将其注入到需要它的服务中。
下面是一个简单的示例,展示如何在EF Core中使用雪花主键生成器:
首先,定义你的实体类,并指定主键属性:
csharp代码
| public class MyEntity | |
| { | |
| public long SnowflakeId { get; set; } // 这是雪花主键 | |
| // 其他属性... | |
| } |
然后,在DbContext中配置实体,使用HasDefaultValueSql方法来指定在插入新记录时如何生成雪花ID:
csharp代码
| public class MyDbContext : DbContext | |
| { | |
| public DbSet<MyEntity> MyEntities { get; set; } | |
| protected override void OnModelCreating(ModelBuilder modelBuilder) | |
| { | |
| modelBuilder.Entity<MyEntity>() | |
| .HasKey(e => e.SnowflakeId) // 设置SnowflakeId为主键 | |
| .ForSqlServerHasDefaultValueSql("DEFAULT(NEXT VALUE FOR SnowflakeSequence)"); // 使用SQL Server的序列 | |
| // 其他配置... | |
| } | |
| } |
请注意,上面的代码使用了ForSqlServerHasDefaultValueSql方法,这是针对SQL Server数据库的。如果你使用的是其他数据库提供程序,你需要使用相应的方法来配置默认值。
接下来,你需要创建一个雪花ID序列,以便数据库可以生成ID。这通常在数据库迁移脚本中完成。以下是一个SQL Server的示例:
sql代码
| CREATE SEQUENCE SnowflakeSequence | |
| AS BIGINT | |
| START WITH 1 | |
| INCREMENT BY 1; |
然后,在应用程序启动时(例如在Startup.cs或Program.cs中),你需要初始化雪花主键生成器,并将其注入到EF Core的上下文中。这可以通过依赖注入来实现:
csharp代码
| public void ConfigureServices(IServiceCollection services) | |
| { | |
| // ... 其他服务配置 ... | |
| // 配置雪花主键生成器 | |
| services.AddSingleton<ISnowflakeIdGenerator, SnowflakeIdGenerator>(serviceProvider => | |
| { | |
| // 获取配置的服务,例如应用程序设置或配置文件中的workerId和datacenterId | |
| var workerId = // 从配置中获取workerId; | |
| var datacenterId = // 从配置中获取datacenterId; | |
| return new SnowflakeIdGenerator(workerId, datacenterId); | |
| }); | |
| // 添加DbContext,并配置依赖注入 | |
| services.AddDbContext<MyDbContext>(options => | |
| { | |
| // 从依赖注入容器中获取雪花主键生成器 | |
| var snowflakeIdGenerator = serviceProvider.GetService<ISnowflakeIdGenerator>(); | |
| // 配置DbContext使用雪花主键生成器 | |
| options.UseSqlServer(yourConnectionString, sqlServerOptionsAction: sqlOptions => | |
| { | |
| // 配置其他SQL Server选项... | |
| }) | |
| .EnableSensitiveDataLogging(true) // 根据需要启用或禁用 | |
| .UseInternalServiceProvider(serviceProvider); // 使用内部服务提供者来解析依赖项 | |
| }); | |
| // ... 其他服务配置 ... | |
| } |
在这个例子中,ISnowflakeIdGenerator是一个接口,它定义了生成雪花ID的方法。你可以根据你的需要实现这个接口,并在依赖注入容器中注册实现。然后,在配置DbContext时,你可以从服务提供者中获取这个实现,并使用它来配置如何生成主键。
请注意,这个方法将雪花ID的生成逻辑从EF Core中分离出来,允许你更容易地测试和替换主键生成策略。此外,这种方法还允许你在不使用EF Core的情况下生成雪花ID,例如在需要手动插入记录到数据库的场景中。