从IdentityServer4升级到Duende 6.2后崩溃:序列包含多个元素
我正在将现有项目从.NET Core 2.2 + IdentityServer4 2.4逐步升级到.NET 7 + Duende IdentityServer 6.2.0:先升级到IdentityServer4 3.0、4.0,为PersistedGrantDbContext和ConfigurationDbContext执行对应版本的迁移,再迁移到Duende 6.2并执行最终迁移。调用connect/token端点时出现如下异常:
System.InvalidOperationException: Sequence contains more than one element. at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleOrDefaultAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleOrDefaultAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken) at Microsoft.AspNetCore.Identity.UserManager`1.FindByEmailAsync(String email) at Microsoft.AspNetCore.Identity.UserValidator`1.ValidateEmail(UserManager`1 manager, TUser user, List`1 errors) at Microsoft.AspNetCore.Identity.UserValidator`1.ValidateAsync(UserManager`1 manager, TUser user) at Microsoft.AspNetCore.Identity.UserManager`1.ValidateUserAsync(TUser user) at Microsoft.AspNetCore.Identity.UserManager`1.UpdateUserAsync(TUser user) at Microsoft.AspNetCore.Identity.UserManager`1.CheckPasswordAsync(TUser user, String password) at Microsoft.AspNetCore.Identity.SignInManager`1.CheckPasswordSignInAsync(TUser user, String password, Boolean lockoutOnFailure) at Duende.IdentityServer.AspNetIdentity.ResourceOwnerPasswordValidator`1.ValidateAsync(ResourceOwnerPasswordValidationContext context) in /_/src/AspNetIdentity/ResourceOwnerPasswordValidator.cs:line 52 at Duende.IdentityServer.Validation.TokenRequestValidator.ValidateResourceOwnerCredentialRequestAsync(NameValueCollection parameters) in /_/src/IdentityServer/Validation/Default/TokenRequestValidator.cs:line 500 at Duende.IdentityServer.Validation.TokenRequestValidator.RunValidationAsync(Func`2 validationFunc, NameValueCollection parameters) in /_/src/IdentityServer/Validation/Default/TokenRequestValidator.cs:line 189 at Duende.IdentityServer.Validation.TokenRequestValidator.ValidateRequestAsync(NameValueCollection parameters, ClientSecretValidationResult clientValidationResult) in /_/src/IdentityServer/Validation/Default/TokenRequestValidator.cs:line 174 at Duende.IdentityServer.Endpoints.TokenEndpoint.ProcessTokenRequestAsync(HttpContext context) in /_/src/IdentityServer/Endpoints/TokenEndpoint.cs:line 98 at Duende.IdentityServer.Endpoints.TokenEndpoint.ProcessAsync(HttpContext context) in /_/src/IdentityServer/Endpoints/TokenEndpoint.cs:line 75 at Duende.IdentityServer.Hosting.IdentityServerMiddleware.Invoke(HttpContext context, IdentityServerOptions options, IEndpointRouter router, IUserSession userSession, IEventService events, IIssuerNameService issuerNameService, ISessionCoordinationService sessionCoordinationService) in /_/src/IdentityServer/Hosting/IdentityServerMiddleware.cs:line 101 at Duende.IdentityServer.Hosting.IdentityServerMiddleware.Invoke(HttpContext context, IdentityServerOptions options, IEndpointRouter router, IUserSession userSession, IEventService events, IIssuerNameService issuerNameService, ISessionCoordinationService sessionCoordinationService) in /_/src/IdentityServer/Hosting/IdentityServerMiddleware.cs:line 117 at Duende.IdentityServer.Hosting.MutualTlsEndpointMiddleware.Invoke(HttpContext context, IAuthenticationSchemeProvider schemes) in /_/src/IdentityServer/Hosting/MutualTlsEndpointMiddleware.cs:line 94 at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) at Duende.IdentityServer.Hosting.DynamicProviders.DynamicSchemeAuthenticationMiddleware.Invoke(HttpContext context) in /_/src/IdentityServer/Hosting/DynamicProviders/DynamicSchemes/DynamicSchemeAuthenticationMiddleware.cs:line 47 at Duende.IdentityServer.Hosting.BaseUrlMiddleware.Invoke(HttpContext context) in /_/src/IdentityServer/Hosting/BaseUrlMiddleware.cs:line 27 at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
相关配置与依赖
Startup.cs配置
services.AddIdentityServer() .AddConfigurationStore(options => { options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly)); }) .AddOperationalStore(options => { options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly)); options.EnableTokenCleanup = true; options.TokenCleanupInterval = 30; }) .AddInMemoryApiScopes(IdentityServerConfig.GetApiScopes()) .AddAspNetIdentity<ApplicationUser>() .AddExtensionGrantValidator<TokenGrantValidator>() .AddProfileService<ProfileService>();
主项目依赖包
<PackageReference Include="Duende.IdentityServer" Version="6.2.0" /> <PackageReference Include="Duende.IdentityServer.AspNetIdentity" Version="6.2.0" /> <PackageReference Include="Duende.IdentityServer.EntityFramework" Version="6.2.0" /> <PackageReference Include="IdentityServer4.AccessTokenValidation" Version="3.0.1" /> <PackageReference Include="Microsoft.AspNetCore.JsonPatch" Version="7.0.1" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.TagHelpers" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.1" />
引用项目依赖包
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="7.0.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.1" /> <PackageReference Include="NLog" Version="5.1.0" /> <PackageReference Include="SqlBulkTools.NetStandard" Version="2.1.30" /> <PackageReference Include="StackExchange.Redis" Version="2.1.30" /> <PackageReference Include="WindowsAzure.Storage" Version="9.3.3" />
核心疑问
我理解Sequence contains more than one element错误的含义,但该异常发生在中间件内部,请问该如何调试排查此类SDK或中间件中的异常?
一、当前异常的直接解决
从堆栈跟踪可以明确,异常触发点是UserManager.FindByEmailAsync——ASP.NET Identity默认要求邮箱唯一,数据库中存在多个邮箱相同的用户记录,导致查询返回多条结果,触发SingleOrDefault的异常。
- 执行SQL查询找出重复数据:
SELECT Email, COUNT(*) FROM AspNetUsers GROUP BY Email HAVING COUNT(*) > 1 - 清理重复记录(保留有效用户,删除冗余数据),同时确保AspNetUsers表的Email字段存在唯一约束,避免后续再出现重复。
二、中间件/SDK内部异常的通用调试方法
1. 启用详细日志定位参数
在appsettings.json中调整日志级别,输出SDK内部的详细调用信息:
"Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore.Identity": "Debug", "Duende.IdentityServer": "Debug" } }
通过日志可以获取当前请求的用户邮箱、客户端信息等关键参数,快速锁定问题数据。
2. 替换默认服务插入调试逻辑
对于ASP.NET Identity或Duende的内置服务,可以自定义子类重写关键方法,添加日志或断点:
比如自定义用户验证器:
public class CustomUserValidator<TUser> : UserValidator<TUser> where TUser : class { public CustomUserValidator(IdentityErrorDescriber errors = null) : base(errors) { } public override async Task<IdentityResult> ValidateAsync(UserManager<TUser> manager, TUser user) { var email = await manager.GetEmailAsync(user); var userId = await manager.GetUserIdAsync(user); // 输出调试信息 Console.WriteLine($"Validating user ID: {userId}, Email: {email}"); var result = await base.ValidateAsync(manager, user); return result; } }
然后在Startup中替换默认验证器:
services.AddIdentity<ApplicationUser, IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddUserValidator<CustomUserValidator<ApplicationUser>>();
3. 关闭“仅我的代码”调试SDK源码
在Visual Studio中关闭“仅我的代码”选项,直接进入SDK内部断点调试:
- 打开工具 -> 选项 -> 调试 -> 常规
- 取消勾选“启用仅我的代码”
- 重启调试,异常抛出时可以单步执行进入
UserManager、Duende中间件的源码,查看具体执行逻辑和数据。
4. 检查迁移与数据库约束
确认升级过程中所有IdentityServer/Duende的迁移脚本都正确执行,重点检查用户表的约束:
- 查看AspNetUsers表是否存在Email字段的唯一索引
- 验证
PersistedGrantDbContext和ConfigurationDbContext的迁移是否完整,没有遗漏的约束或字段变更
内容的提问来源于stack exchange,提问作者Dan Beaulieu

