Search code examples
c#asp.net-coreasp.net-identityasp.net-core-2.1

Entity type 'IdentityUserToken<string>' is defined with a single key property, but 3 values were passed to the 'DbSet.Find' method


I'm working on .NetCore MVC project where I've had to reverse engineer an existing database and am having a real nightmare getting ASPIdentity to play nicely.

I've had to manually add my Identity DbSets in the Context class that was created but that did not include the Identity tables as I had hoped would happen. I've managed to create Migrations for the ASPIdentity properties that I've wanted (Roles, Claims) and tested the Registration / Login / Manage aspect of the project. I get the error below when I click on the "Two-Factor Authentication" tab in the Manage area:

ArgumentException: Entity type 'IdentityUserToken<string>' is defined with a single key property, but 3 values were passed to the 'DbSet.Find' method.
Microsoft.EntityFrameworkCore.Internal.EntityFinder<TEntity>.FindTracked(object[] keyValues, out IReadOnlyList<IProperty> keyProperties)
Stack Query Cookies Headers 
ArgumentException: Entity type 'IdentityUserToken<string>' is defined with a single key property, but 3 values were passed to the 'DbSet.Find' method.
Microsoft.EntityFrameworkCore.Internal.EntityFinder<TEntity>.FindTracked(object[] keyValues, out IReadOnlyList<IProperty> keyProperties)
Microsoft.EntityFrameworkCore.Internal.EntityFinder<TEntity>.FindAsync(object[] keyValues, CancellationToken cancellationToken)
Microsoft.EntityFrameworkCore.Internal.InternalDbSet<TEntity>.FindAsync(object[] keyValues, CancellationToken cancellationToken)
Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore<TUser, TRole, TContext, TKey, TUserClaim, TUserRole, TUserLogin, TUserToken, TRoleClaim>.FindTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken)
Microsoft.AspNetCore.Identity.UserStoreBase<TUser, TKey, TUserClaim, TUserLogin, TUserToken>.GetTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken)
Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal.TwoFactorAuthenticationModel<TUser>.OnGetAsync()
Microsoft.AspNetCore.Mvc.RazorPages.Internal.ExecutorFactory+GenericTaskHandlerMethod.Convert<T>(object taskAsObject)
Microsoft.AspNetCore.Mvc.RazorPages.Internal.ExecutorFactory+GenericTaskHandlerMethod.Execute(object receiver, object[] arguments)
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker.InvokeHandlerMethodAsync()
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker.InvokeNextPageFilterAsync()
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker.Rethrow(PageHandlerExecutedContext context)
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker.InvokeInnerFilterAsync()
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext)
Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.MigrationsEndPointMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.Invoke(HttpContext httpContext)
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.Invoke(HttpContext httpContext)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

Services code in Startup.cs

services.AddDefaultIdentity<User>()
    .AddRoles<IdentityRole>()
    .AddRoleManager<RoleManager<IdentityRole>>()
    .AddDefaultTokenProviders()
    .AddEntityFrameworkStores<MyContext>();

My onModelBuilding code (ASPIdentity specific code):

public partial class MyContext : DbContext
{
    public MyContext()
    {
    }

    public MyContext(DbContextOptions<MyContext> options)
        : base(options)
    {
    }

    public virtual DbSet<User> User { get; set; }
    public virtual DbSet<IdentityUserClaim<string>> IdentityUserClaim { get; set; }
    public virtual DbSet<IdentityUserToken<string>> IdentityUserToken { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<IdentityUserClaim<string>>().HasKey(p => new { p.Id });

        modelBuilder.Entity<IdentityUserToken<string>>().HasKey(p => new { p.UserId });
    }
}

I can see that I need to include more keys but cannot find any information on what the relationships are. I created a blank .NetCore MVC app with ASPIdentity and used the same ASPIdentity configuration in startup.cs and I'm none-the-wiser, what are the keys that I should be using or how should I build the DbSet?


Solution

  • This post helped immensely with understanding the relationships between the different Identity properties: https://learn.microsoft.com/en-us/ef/core/get-started/aspnetcore/existing-db

    I was able to overcome the issues explained above using the relational information and templates provided in the Microsoft article.

    DbContext OnModelCreating method:

    modelBuilder.Entity<ApplicationUser>(b =>
    {
        // Each User can have many UserClaims
        b.HasMany(e => e.Claims)
            .WithOne(e => e.User)
            .HasForeignKey(uc => uc.UserId)
            .IsRequired();
    
        // Each User can have many UserLogins
        b.HasMany(e => e.Logins)
            .WithOne(e => e.User)
            .HasForeignKey(ul => ul.UserId)
            .IsRequired();
    
        // Each User can have many UserTokens
        b.HasMany(e => e.Tokens)
            .WithOne(e => e.User)
            .HasForeignKey(ut => ut.UserId)
            .IsRequired();
    
        // Each User can have many entries in the UserRole join table
        b.HasMany(e => e.UserRoles)
            .WithOne(e => e.User)
            .HasForeignKey(ur => ur.UserId)
            .IsRequired();
    });
    
    modelBuilder.Entity<ApplicationRole>(b =>
    {
        // Each Role can have many entries in the UserRole join table
        b.HasMany(e => e.UserRoles)
            .WithOne(e => e.Role)
            .HasForeignKey(ur => ur.RoleId)
            .IsRequired();
    
        // Each Role can have many associated RoleClaims
        b.HasMany(e => e.RoleClaims)
            .WithOne(e => e.Role)
            .HasForeignKey(rc => rc.RoleId)
            .IsRequired();
    });
    
    modelBuilder.Entity<ApplicationUserLogin>(b =>
    {
        b.HasKey(l => new { l.LoginProvider, l.ProviderKey, l.UserId });
        b.ToTable("AspNetUserLogins");
    });
    
    modelBuilder.Entity<ApplicationUserRole>(b =>
    {
        b.HasKey(r => new { r.UserId, r.RoleId });
        b.ToTable("AspNetUserRoles");
    });
    
    modelBuilder.Entity<ApplicationUserToken>(b =>
    {
        b.HasKey(t => new { t.UserId, t.LoginProvider, t.Name });
        b.ToTable("AspNetUserTokens");
    });
    

    (I'd created my own ApplicationUser class with custom properties)