简体   繁体   中英

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 ?

This post helped immensely with understanding the relationships between the different Identity properties: https://docs.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)

This is an old question, but in case someone else needs this.

I don't think your issue is with the db schema. Something in the click event from your MVC app is ultimately using dbset.Find incorrectly.In your case;

var tokenEntity = MyContext.UserTokens.Find(user.Id)

Instead you could just use Linq;

var tokenEntity = MyContext.UserTokens.FirstOrDefault(ut => ut.UserId == user.Id);

Just also add;

using System.Linq

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM