IdentityServer4+ASP Identity对接SQL Server示例种子数据问题求助
我完全理解你的困扰——视频里说改个连接字符串就能迁移,但实际操作中踩了一堆主键为空的坑,这其实是SQLite和SQL Server在主键生成机制上的差异导致的,咱们一步步来解决:
问题根源
SQLite默认支持整数主键自动递增,哪怕你不给Id赋值,它也会自动生成。但SQL Server的整数主键需要显式配置自增策略(比如IDENTITY),或者你手动给每个实体的Id赋值。IdentityServer4的EF存储实现,在SQLite环境下默认适配了自动递增,但切换到SQL Server时,这个配置没跟上,导致种子数据插入时主键为空报错。
解决方案一:配置实体主键自增(推荐)
这是最优雅的方式,让SQL Server自动生成主键值,不需要手动修改种子数据。你需要在IdentityServer4的实体配置类中,给所有相关实体的Id添加自增配置:
比如在自定义的ClientConfiguration类中(或者通过EF Core的Fluent API在DbContext里配置):
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using IdentityServer4.EntityFramework.Entities; public class ClientConfiguration : IEntityTypeConfiguration<Client> { public void Configure(EntityTypeBuilder<Client> builder) { // 配置Client的Id为SQL Server自增列 builder.Property(c => c.Id) .UseIdentityColumn(); // 其他默认配置... } }
同样的,你需要给所有关联实体(ClientGrantTypes、ClientRedirectUris、ClientScopes、ClientPostLogoutRedirectUris等)都做类似配置:
public class ClientGrantTypesConfiguration : IEntityTypeConfiguration<ClientGrantType> { public void Configure(EntityTypeBuilder<ClientGrantType> builder) { builder.Property(g => g.Id) .UseIdentityColumn(); } }
配置完成后,在你的ConfigurationDbContext中应用这些配置:
protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.ApplyConfiguration(new ClientConfiguration()); modelBuilder.ApplyConfiguration(new ClientGrantTypesConfiguration()); // 依次添加其他实体的配置类 }
之后重新生成针对SQL Server的迁移并更新数据库:
# 重新生成ConfigurationDbContext的迁移 dotnet ef migrations add UpdateSqlServerIdentityColumns -c ConfigurationDbContext # 更新数据库 dotnet ef database update -c ConfigurationDbContext
这样处理后,你的种子数据代码就不需要手动给Id赋值了,SQL Server会自动生成主键值。
解决方案二:手动为所有关联实体赋值Id(繁琐但快速)
如果不想修改实体配置,那你需要给Client及其所有关联集合的实体都手动设置Id值——因为ClientGrantTypes、ClientRedirectUris这些表的Id都是独立的主键,不是外键,所以必须赋值:
if (!context.Clients.Any()) { Console.WriteLine("Clients being populated"); int clientId = 1; int grantTypeId = 1; int redirectUriId = 1; int scopeId = 1; foreach (var client in Config.GetClients().ToList()) { var clientEntity = client.ToEntity(); clientEntity.Id = clientId++; context.Clients.Add(clientEntity); // 处理ClientGrantTypes foreach (var grantType in clientEntity.GrantTypes) { grantType.Id = grantTypeId++; } // 处理ClientRedirectUris foreach (var uri in clientEntity.RedirectUris) { uri.Id = redirectUriId++; } // 处理ClientScopes foreach (var scope in clientEntity.AllowedScopes) { scope.Id = scopeId++; } // 同样需要处理ClientPostLogoutRedirectUris、ClientClaims等其他关联集合 } context.SaveChanges(); } else { Console.WriteLine("Clients already populated"); }
这种方法需要覆盖所有关联实体,比较繁琐,适合临时快速测试用。
关键迁移步骤补充
你之前可能复用了SQLite的迁移文件,这会导致SQL Server的表结构不符合预期。正确的迁移流程应该是:
- 删除项目中所有针对SQLite的迁移文件(在Migrations文件夹下)
- 针对SQL Server重新生成三个上下文的迁移:
# ConfigurationDbContext(存储配置数据:客户端、资源等) dotnet ef migrations add InitialSqlServerConfig -c ConfigurationDbContext # PersistedGrantDbContext(存储操作数据:令牌、授权同意等) dotnet ef migrations add InitialSqlServerOperational -c PersistedGrantDbContext # ApplicationDbContext(ASP.NET Identity的数据) dotnet ef migrations add InitialSqlServerIdentity -c ApplicationDbContext - 依次执行数据库更新:
dotnet ef database update -c ConfigurationDbContext dotnet ef database update -c PersistedGrantDbContext dotnet ef database update -c ApplicationDbContext
为什么视频里说只改连接字符串就行?
大概率是视频中使用的IdentityServer4版本已经默认给所有实体配置了跨数据库的自增策略,或者他们的种子数据已经自动处理了Id赋值。但在你的场景中,默认的种子数据转换(client.ToEntity())没有给关联实体的Id赋值,而SQL Server又不允许主键为空,所以才会报错。
内容的提问来源于stack exchange,提问作者user2600177

