在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,例如在需要手动插入记录到数据库的场景中。