ASP.NET Core Identity与EF Core多用户类型实现报错求助
需求背景
我的应用中有Customer和Professional两类用户,使用ASP.NET Identity管理用户。此前仅通过在ApplicationUser中添加标记区分两类用户,操作繁琐。现在希望将通用身份信息存储在Identity框架默认表中,Customer专属信息存于Customers表,Professional专属信息存于Professionals表。
已定义的领域实体
public class Account : IdentityUser<Guid> { public string FullName { get; set; } = string.Empty; public string ImageUrl { get; set; } = string.Empty; public bool HasAcceptedTermsOfUse { get; set; } = default; } public class Customer : Account { // Customer专属属性 } public class Professional : Account { // Professional专属属性 }
修改后的DbContext配置
public class XenDbContext: IdentityDbContext<Account, IdentityRole<Guid>, Guid> { public DbSet<Customer> Customers { get; set; } public DbSet<Professional> Professionals { get; set; } // 其他DbSet public XenDbContext(DbContextOptions<XenDbContext> options) : base(options) { } protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); builder.Entity<Account>(entity => { entity.ToTable("Accounts"); }); builder.Entity<Customer>(entity => { entity.ToTable("Customers"); }); builder.Entity<Professional>(entity => { entity.ToTable("Professionals"); }); builder.Entity<IdentityRole<Guid>>(entity => { entity.ToTable("Roles"); }); builder.Entity<IdentityUserRole<Guid>>(entity => { entity.ToTable("AccountRoles"); }); builder.Entity<IdentityUserClaim<Guid>>(entity => { entity.ToTable("AccountClaims"); }); builder.Entity<IdentityUserLogin<Guid>>(entity => { entity.ToTable("AccountLogins"); }); builder.Entity<IdentityUserToken<Guid>>(entity => { entity.ToTable("AccountTokens"); }); builder.Entity<IdentityRoleClaim<Guid>>(entity => { entity.ToTable("RoleClaims"); }); } }
运行时错误信息
System.InvalidOperationException
HResult=0x80131509
消息: Scheme already exists: Identity.Application
来源: Microsoft.AspNetCore.Authentication.Abstractions调用堆栈:
at Microsoft.AspNetCore.Authentication.AuthenticationOptions.AddScheme(String name, Action1 configureBuilder) at Microsoft.AspNetCore.Authentication.AuthenticationBuilder.<>c__DisplayClass4_02.b__0(AuthenticationOptions o)
at Microsoft.Extensions.Options.ConfigureNamedOptions1.Configure(String name, TOptions options) at Microsoft.Extensions.Options.OptionsFactory1.Create(String name)
at Microsoft.Extensions.Options.UnnamedOptionsManager1.get_Value() at Microsoft.AspNetCore.Authentication.AuthenticationSchemeProvider..ctor(IOptions1 options, IDictionary2 schemes) at Microsoft.AspNetCore.Authentication.AuthenticationSchemeProvider..ctor(IOptions1 options)
at System.RuntimeMethodHandle.InvokeMethod(Object target, Span1& arguments, Signature sig, Boolean constructor, Boolean wrapExceptions) at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor2.VisitCallSite(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope) at Microsoft.Extensions.DependencyInjection.ServiceProvider.CreateServiceAccessor(Type serviceType) at System.Collections.Concurrent.ConcurrentDictionary2.GetOrAdd(TKey key, Func`2 valueFactory)
at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType)
at Microsoft.Extensions.Internal.ActivatorUtilities.ConstructorMatcher.CreateInstance(IServiceProvider provider)
at Microsoft.Extensions.Internal.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanceType, Object[] parameters)
at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass5_0.b__0(RequestDelegate next)
at Microsoft.AspNetCore.Builder.ApplicationBuilder.Build()
at Microsoft.AspNetCore.Builder.WebApplicationBuilder.b__27_0(RequestDelegate next)
at Microsoft.AspNetCore.Builder.ApplicationBuilder.Build()
at Microsoft.AspNetCore.Hosting.GenericWebHostService.d__37.MoveNext()
at Microsoft.Extensions.Hosting.Internal.Host.d__12.MoveNext()
at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.d__4.MoveNext()
at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.d__4.MoveNext()
at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.Run(IHost host)
at Microsoft.AspNetCore.Builder.WebApplication.Run(String url)
at Xen.Presentation.API.Program.Main(String[] args) in C:\Users\Arthur\Development\Xen\src\backend\Presentation\Xen.Presentation.API\Program.cs:line 53
持久化层服务注册代码
public static class PersistenceServiceRegistration { public static void AddPersistenceServices(this IServiceCollection services, IConfiguration configuration) { var connectionString = configuration.GetConnectionString("XenDbLocalConnectionString"); services.AddDbContext<XenDbContext>(options => options.UseSqlServer(connectionString)); services.AddIdentity<Account, IdentityRole<Guid>>(options => { options.Password.RequireDigit = false; }).AddEntityFrameworkStores<XenDbContext>().AddDefaultTokenProviders(); services.AddIdentity<Customer, IdentityRole<Guid>>(options => { options.Password.RequireDigit = false; }).AddEntityFrameworkStores<XenDbContext>().AddDefaultTokenProviders(); services.AddIdentity<Professional, IdentityRole<Guid>>(options => { options.Password.RequireDigit = false; }).AddEntityFrameworkStores<XenDbContext>().AddDefaultTokenProviders(); // 注册仓储 } }
核心疑问
我希望通过Account为两类用户生成令牌,但目前因多次调用AddIdentity导致认证Scheme冲突。注释掉app.UseAuthorization()可以运行,但我需要完整的身份验证和授权功能。请问我的需求是否可行?若可行,该如何实现?
解决方案
你的需求完全可行,错误根源是多次调用AddIdentity会重复注册同名的认证Scheme(Identity.Application),ASP.NET Identity默认只支持一个主用户类型的核心注册,多用户类型需基于基类扩展实现。
1. 修正服务注册代码
移除重复的AddIdentity调用,仅保留基于基类Account的一次注册:
public static class PersistenceServiceRegistration { public static void AddPersistenceServices(this IServiceCollection services, IConfiguration configuration) { var connectionString = configuration.GetConnectionString("XenDbLocalConnectionString"); services.AddDbContext<XenDbContext>(options => options.UseSqlServer(connectionString)); // 仅注册一次基于Account的Identity核心服务 services.AddIdentity<Account, IdentityRole<Guid>>(options => { options.Password.RequireDigit = false; }).AddEntityFrameworkStores<XenDbContext>().AddDefaultTokenProviders(); // 注册自定义业务服务(如用户类型查询、专属逻辑处理) // services.AddScoped<ICustomerService, CustomerService>(); // services.AddScoped<IProfessionalService, ProfessionalService>(); // 注册仓储 } }
2. 理解继承映射逻辑
你当前的DbContext配置使用了**TPT(表每类型)**继承映射,Account作为基类对应Accounts表,Customer和Professional作为子类分别对应Customers和Professionals表,EF Core会自动维护表间的关联关系,无需额外配置。
3. 多用户类型的业务处理
- 用户注册:创建对应类型的实例(如
Customer或Professional),直接调用UserManager<Account>.CreateAsync()即可,EF Core会自动将数据写入基表和对应子表。 - 用户查询:通过
DbContext的Customers/Professionals集合查询专属信息,或先获取Account实例后通过类型转换判断用户类型(如account is Customer customer)。 - 授权控制:
- 登录时通过
ClaimsTransformer添加用户类型的Claim(如new Claim("UserType", "Customer")),后续可通过授权策略验证该Claim。 - 在业务逻辑中直接判断用户类型,实现专属权限控制。
- 登录时通过
4. 令牌生成
由于Customer和Professional都是Account的子类,基于Account注册的Identity服务生成的令牌会自动覆盖两类用户,无需额外配置。
内容的提问来源于stack exchange,提问作者adamasan

