EntityType 'IdentityUserLogin'에 정의 된 키가 없습니다. 이 EntityType에 대한 키 정의


105

Entity Framework Code First 및 MVC 5로 작업하고 있습니다. 개별 사용자 계정 인증을 사용 하여 응용 프로그램을 만들었을 때 계정 컨트롤러와 함께 Indiv 사용자 계정 인증이 작동하는 데 필요한 모든 필수 클래스 및 코드가 제공되었습니다. .

이미 적용된 코드는 다음과 같습니다.

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext() : base("DXContext", throwIfV1Schema: false)
    {

    }

    public static ApplicationDbContext Create()
    {
        return new ApplicationDbContext();
    }
}

하지만 그런 다음 먼저 코드를 사용하여 내 컨텍스트를 만들었으므로 이제 다음과 같이합니다.

public class DXContext : DbContext
{
    public DXContext() : base("DXContext")
    {
        
    }

    public DbSet<ApplicationUser> Users { get; set; }
    public DbSet<IdentityRole> Roles { get; set; }
    public DbSet<Artist> Artists { get; set; }
    public DbSet<Paintings> Paintings { get; set; }        
}

마지막으로 개발하는 동안 작업 할 데이터를 추가하는 다음과 같은 시드 방법이 있습니다.

protected override void Seed(DXContext context)
{
    try
    {

        if (!context.Roles.Any(r => r.Name == "Admin"))
        {
            var store = new RoleStore<IdentityRole>(context);
            var manager = new RoleManager<IdentityRole>(store);
            var role = new IdentityRole { Name = "Admin" };

            manager.Create(role);
        }

        context.SaveChanges();

        if (!context.Users.Any(u => u.UserName == "James"))
        {
            var store = new UserStore<ApplicationUser>(context);
            var manager = new UserManager<ApplicationUser>(store);
            var user = new ApplicationUser { UserName = "James" };

            manager.Create(user, "ChangeAsap1@");
            manager.AddToRole(user.Id, "Admin");
        }

        context.SaveChanges();

        string userId = "";

        userId = context.Users.FirstOrDefault().Id;

        var artists = new List<Artist>
        {
            new Artist { FName = "Salvador", LName = "Dali", ImgURL = "http://i62.tinypic.com/ss8txxn.jpg", UrlFriendly = "salvador-dali", Verified = true, ApplicationUserId = userId },
        };

        artists.ForEach(a => context.Artists.Add(a));
        context.SaveChanges();

        var paintings = new List<Painting>
        {
            new Painting { Title = "The Persistence of Memory", ImgUrl = "http://i62.tinypic.com/xx8tssn.jpg", ArtistId = 1, Verified = true, ApplicationUserId = userId }
        };

        paintings.ForEach(p => context.Paintings.Add(p));
        context.SaveChanges();
    }
    catch (DbEntityValidationException ex)
    {
        foreach (var validationErrors in ex.EntityValidationErrors)
        {
            foreach (var validationError in validationErrors.ValidationErrors)
            {
                Trace.TraceInformation("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
            }
        }
    }
    
}

내 솔루션은 잘 빌드되지만 데이터베이스에 액세스해야하는 컨트롤러에 액세스하려고하면 다음 오류가 발생합니다.

DX.DOMAIN.Context.IdentityUserLogin : : EntityType 'IdentityUserLogin'에 정의 된 키가 없습니다. 이 EntityType에 대한 키를 정의하십시오.

DX.DOMAIN.Context.IdentityUserRole : : EntityType 'IdentityUserRole'에 정의 된 키가 없습니다. 이 EntityType에 대한 키를 정의하십시오.

내가 뭘 잘못하고 있죠? 두 가지 컨텍스트가 있기 때문입니까?

최신 정보

Augusto의 답변을 읽은 후 옵션 3선택했습니다 . 내 DXContext 클래스는 다음과 같습니다.

public class DXContext : DbContext
{
    public DXContext() : base("DXContext")
    {
        // remove default initializer
        Database.SetInitializer<DXContext>(null);
        Configuration.LazyLoadingEnabled = false;
        Configuration.ProxyCreationEnabled = false;

    }

    public DbSet<User> Users { get; set; }
    public DbSet<Role> Roles { get; set; }
    public DbSet<Artist> Artists { get; set; }
    public DbSet<Painting> Paintings { get; set; }

    public static DXContext Create()
    {
        return new DXContext();
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<User>().ToTable("Users");
        modelBuilder.Entity<Role>().ToTable("Roles");
    }

    public DbQuery<T> Query<T>() where T : class
    {
        return Set<T>().AsNoTracking();
    }
}

또한 a User.csRole.cs클래스를 추가했는데 다음과 같습니다.

public class User
{
    public int Id { get; set; }
    public string FName { get; set; }
    public string LName { get; set; }
}

public class Role
{
    public int Id { set; get; }
    public string Name { set; get; }
}

기본 ApplicationUser에는 해당 필드와 다른 필드가 있기 때문에 사용자에 대한 암호 속성이 필요한지 확실하지 않았습니다!

어쨌든 위의 변경 사항은 잘 빌드되지만 응용 프로그램이 실행될 때 다시이 오류가 발생합니다.

잘못된 열 이름 UserId

UserId 내 정수 속성입니다 Artist.cs

답변:


116

문제는 ApplicationUser 가 다음과 같이 정의 된 IdentityUser 에서 상속 한다는 것입니다 .

IdentityUser : IdentityUser<string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>, IUser
....
public virtual ICollection<TRole> Roles { get; private set; }
public virtual ICollection<TClaim> Claims { get; private set; }
public virtual ICollection<TLogin> Logins { get; private set; }

기본 키는 IdentityDbContext 클래스의 OnModelCreating 메서드에 매핑됩니다 .

modelBuilder.Entity<TUserRole>()
            .HasKey(r => new {r.UserId, r.RoleId})
            .ToTable("AspNetUserRoles");

modelBuilder.Entity<TUserLogin>()
            .HasKey(l => new {l.LoginProvider, l.ProviderKey, l.UserId})
            .ToTable("AspNetUserLogins");

DXContext가 파생되지 않기 때문에 해당 키가 정의되지 않습니다.

당신이 파고 경우 소스Microsoft.AspNet.Identity.EntityFramework, 당신은 모든 것을 이해할 것이다.

나는 언젠가이 상황을 만났고 세 가지 가능한 해결책을 찾았습니다.

  1. 두 개의 다른 데이터베이스 또는 동일한 데이터베이스이지만 다른 테이블에 대해 별도의 DbContexts를 사용하십시오.
  2. DXContext를 ApplicationDbContext와 병합하고 하나의 데이터베이스를 사용합니다.
  3. 동일한 테이블에 대해 별도의 DbContext를 사용하고 그에 따라 마이그레이션을 관리합니다.

옵션 1 : 하단 업데이트를 참조하세요.

옵션 2 : 다음 과 같은 DbContext가 생성됩니다.

public class DXContext : IdentityDbContext<User, Role,
    int, UserLogin, UserRole, UserClaim>//: DbContext
{
    public DXContext()
        : base("name=DXContext")
    {
        Database.SetInitializer<DXContext>(null);// Remove default initializer
        Configuration.ProxyCreationEnabled = false;
        Configuration.LazyLoadingEnabled = false;
    }

    public static DXContext Create()
    {
        return new DXContext();
    }

    //Identity and Authorization
    public DbSet<UserLogin> UserLogins { get; set; }
    public DbSet<UserClaim> UserClaims { get; set; }
    public DbSet<UserRole> UserRoles { get; set; }
    
    // ... your custom DbSets
    public DbSet<RoleOperation> RoleOperations { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
        modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();

        // Configure Asp Net Identity Tables
        modelBuilder.Entity<User>().ToTable("User");
        modelBuilder.Entity<User>().Property(u => u.PasswordHash).HasMaxLength(500);
        modelBuilder.Entity<User>().Property(u => u.Stamp).HasMaxLength(500);
        modelBuilder.Entity<User>().Property(u => u.PhoneNumber).HasMaxLength(50);

        modelBuilder.Entity<Role>().ToTable("Role");
        modelBuilder.Entity<UserRole>().ToTable("UserRole");
        modelBuilder.Entity<UserLogin>().ToTable("UserLogin");
        modelBuilder.Entity<UserClaim>().ToTable("UserClaim");
        modelBuilder.Entity<UserClaim>().Property(u => u.ClaimType).HasMaxLength(150);
        modelBuilder.Entity<UserClaim>().Property(u => u.ClaimValue).HasMaxLength(500);
    }
}

옵션 3 : 옵션 2와 동일한 DbContext 하나를 갖게됩니다. 이름을 IdentityContext로 지정하겠습니다. 그리고 DXContext라는 또 다른 DbContext가 있습니다.

public class DXContext : DbContext
{        
    public DXContext()
        : base("name=DXContext") // connection string in the application configuration file.
    {
        Database.SetInitializer<DXContext>(null); // Remove default initializer
        Configuration.LazyLoadingEnabled = false;
        Configuration.ProxyCreationEnabled = false;
    }

    // Domain Model
    public DbSet<User> Users { get; set; }
    // ... other custom DbSets
    
    public static DXContext Create()
    {
        return new DXContext();
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();

        // IMPORTANT: we are mapping the entity User to the same table as the entity ApplicationUser
        modelBuilder.Entity<User>().ToTable("User"); 
    }

    public DbQuery<T> Query<T>() where T : class
    {
        return Set<T>().AsNoTracking();
    }
}

사용자는 :

public class User
{
    public int Id { get; set; }

    [Required, StringLength(100)]
    public string Name { get; set; }

    [Required, StringLength(128)]
    public string SomeOtherColumn { get; set; }
}

이 솔루션을 사용하여 엔터티 User를 엔터티 ApplicationUser와 동일한 테이블에 매핑합니다.

그런 다음, 코드 첫 번째 마이그레이션을 사용하면 IdentityContext에 대한 마이그레이션을 생성해야합니다 THEN 쉐일 렌드 라 차우에서 위대한 게시물이 다음의 DXContext을 위해 : 여러 데이터 컨텍스트와 코드 첫 번째 마이그레이션을

DXContext에 대해 생성 된 마이그레이션을 수정해야합니다. ApplicationUser와 User간에 공유되는 속성에 따라 다음과 같이됩니다.

        //CreateTable(
        //    "dbo.User",
        //    c => new
        //        {
        //            Id = c.Int(nullable: false, identity: true),
        //            Name = c.String(nullable: false, maxLength: 100),
        //            SomeOtherColumn = c.String(nullable: false, maxLength: 128),
        //        })
        //    .PrimaryKey(t => t.Id);
        AddColumn("dbo.User", "SomeOtherColumn", c => c.String(nullable: false, maxLength: 128));

그런 다음이 사용자 정의 클래스를 사용하여 global.asax 또는 애플리케이션의 다른 위치에서 순서대로 마이그레이션을 실행합니다 (먼저 ID 마이그레이션).

public static class DXDatabaseMigrator
{
    public static string ExecuteMigrations()
    {
        return string.Format("Identity migrations: {0}. DX migrations: {1}.", ExecuteIdentityMigrations(),
            ExecuteDXMigrations());
    }

    private static string ExecuteIdentityMigrations()
    {
        IdentityMigrationConfiguration configuration = new IdentityMigrationConfiguration();
        return RunMigrations(configuration);
    }

    private static string ExecuteDXMigrations()
    {
        DXMigrationConfiguration configuration = new DXMigrationConfiguration();
        return RunMigrations(configuration);
    }

    private static string RunMigrations(DbMigrationsConfiguration configuration)
    {
        List<string> pendingMigrations;
        try
        {
            DbMigrator migrator = new DbMigrator(configuration);
            pendingMigrations = migrator.GetPendingMigrations().ToList(); // Just to be able to log which migrations were executed

            if (pendingMigrations.Any())                
                    migrator.Update();     
        }
        catch (Exception e)
        {
            ExceptionManager.LogException(e);
            return e.Message;
        }
        return !pendingMigrations.Any() ? "None" : string.Join(", ", pendingMigrations);
    }
}

이렇게하면 n 계층 교차 절단 엔터티가 AspNetIdentity 클래스에서 상속되지 않기 때문에 사용하는 모든 프로젝트에서이 프레임 워크를 가져올 필요가 없습니다.

광범위한 게시물에 대해 죄송합니다. 이에 대한 지침을 제공 할 수 있기를 바랍니다. 프로덕션 환경에서 이미 옵션 2와 3을 사용했습니다.

업데이트 : 옵션 1 확장

마지막 두 프로젝트의 경우 첫 번째 옵션을 사용했습니다. IdentityUser에서 파생되는 AspNetUser 클래스와 AppUser라는 별도의 사용자 지정 클래스가 있습니다. 필자의 경우 DbContext는 각각 IdentityContext와 DomainContext입니다. 그리고 다음과 같이 AppUser의 ID를 정의했습니다.

public class AppUser : TrackableEntity
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
    // This Id is equal to the Id in the AspNetUser table and it's manually set.
    public override int Id { get; set; }

(TrackableEntity는 내 DomainContext 컨텍스트의 재정의 된 SaveChanges 메서드에서 사용하는 사용자 지정 추상 기본 클래스입니다.)

먼저 AspNetUser를 만든 다음 AppUser를 만듭니다. 이 접근 방식의 단점은 "CreateUser"기능이 트랜잭션 적이라는 것입니다 (SaveChanges를 개별적으로 호출하는 두 개의 DbContext가 있음을 기억하십시오). TransactionScope를 사용하는 것이 어떤 이유로 저에게 효과가 없었기 때문에 결국 추악한 일을했지만 그것은 저에게 효과적입니다.

        IdentityResult identityResult = UserManager.Create(aspNetUser, model.Password);

        if (!identityResult.Succeeded)
            throw new TechnicalException("User creation didn't succeed", new LogObjectException(result));

        AppUser appUser;
        try
        {
            appUser = RegisterInAppUserTable(model, aspNetUser);
        }
        catch (Exception)
        {
            // Roll back
            UserManager.Delete(aspNetUser);
            throw;
        }

(누군가이 부분을 수행하는 더 나은 방법을 제공한다면이 답변에 대한 의견을 보내거나 편집을 제안하는 것에 감사드립니다)

이점은 마이그레이션을 수정할 필요가 없으며 AspNetUser를 엉망으로 만들지 않고도 AppUser에 대해 미친 상속 계층을 사용할있다는 것 입니다. 그리고 실제로 내 IdentityContext (IdentityDbContext에서 파생 된 컨텍스트)에 대해 자동 마이그레이션을 사용합니다.

public sealed class IdentityMigrationConfiguration : DbMigrationsConfiguration<IdentityContext>
{
    public IdentityMigrationConfiguration()
    {
        AutomaticMigrationsEnabled = true;
        AutomaticMigrationDataLossAllowed = false;
    }

    protected override void Seed(IdentityContext context)
    {
    }
}

이 접근 방식은 AspNetIdentity 클래스에서 상속되는 n 계층 교차 절단 엔터티를 피할 수 있다는 이점도 있습니다.


광범위한 게시물에 대해 @Augusto에게 감사드립니다. 옵션 3이 작동하려면 마이그레이션 사용해야 합니까 ? 내가 아는 한 EF 마이그레이션은 변경 사항을 롤백하기위한 것입니까? 데이터베이스를 삭제 한 다음 다시 생성하고 새 빌드마다 시드하는 경우 모든 마이그레이션 작업을 수행해야합니까?
J86

마이그레이션을 사용하지 않고 시도하지 않았습니다. 나는 당신이 그것들을 사용하지 않고 그것을 달성 할 수 있을지 모르겠습니다. 아마도 가능할 것입니다. 데이터베이스에 삽입 된 사용자 지정 데이터를 유지하려면 항상 마이그레이션을 사용해야했습니다.
Augusto Barreto 2015

한 가지 지적 할 점은 마이그레이션을 사용하는 경우 ... AddOrUpdate(new EntityObject { shoes = green})"upsert"라고도하는를 사용해야합니다 . 컨텍스트에 추가하는 것이 아니라 중복 / 중복 엔티티 컨텍스트 정보를 생성하게됩니다.
Chef_Code

세 번째 옵션으로 작업하고 싶지만 이해가되지 않습니다. 누군가 IdentityContext가 어떻게 생겼는지 말해 줄 수 있습니까? 옵션 2와 똑같을 수는 없으니까요! @AugustoBarreto 도와 줄 수 있나요? 나는 비슷한에 대한 스레드를 만들었습니다, 어쩌면 당신이 나를 도울 수
Arianit

'TrackableEntity'는 어떻게 생겼습니까?
Ciaran Gallagher

224

제 경우에는 IdentityDbContext에서 올바르게 상속되었지만 (내 사용자 지정 형식과 키가 정의되어 있음) 실수로 기본 클래스의 OnModelCreating에 대한 호출을 제거했습니다.

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder); // I had removed this
    /// Rest of on model creating here.
}

그런 다음 ID 클래스에서 누락 된 인덱스를 수정 한 다음 마이그레이션을 생성하고 적절하게 마이그레이션을 활성화 할 수 있습니다.


같은 문제가 "줄을 제거했습니다". 솔루션이 작동했습니다. :) 타이.
개발자 마리우스 Žilėnas

2
이것은 복잡한 엔터티 관계에 대한 유창한 API를 사용하는 사용자 지정 매핑을 포함하도록 OnModelCreating 메서드를 재정의해야하는 문제를 해결했습니다. Identity와 동일한 컨텍스트를 사용하고 있으므로 매핑을 선언하기 전에 대답에 줄을 추가하는 것을 잊었습니다. 건배.

'override void OnModelCreating'이 없으면 작동하지만 재정의하면 'base.OnModelCreating (modelBuilder);'를 추가해야합니다. 재정의에. 내 문제를 해결했습니다.

13

ASP.NET Identity 2.1을 사용하고 기본 키를 기본값 string에서 int또는로 변경 한 Guid사용자를 위해

EntityType 'xxxxUserLogin'에 정의 된 키가 없습니다. 이 EntityType에 대한 키를 정의하십시오.

EntityType 'xxxxUserRole'에 정의 된 키가 없습니다. 이 EntityType에 대한 키를 정의하십시오.

에 새 키 유형을 지정하는 것을 잊었을 것입니다 IdentityDbContext.

public class AppIdentityDbContext : IdentityDbContext<
    AppUser, AppRole, int, AppUserLogin, AppUserRole, AppUserClaim>
{
    public AppIdentityDbContext()
        : base("MY_CONNECTION_STRING")
    {
    }
    ......
}

당신이 가지고 있다면

public class AppIdentityDbContext : IdentityDbContext
{
    ......
}

또는

public class AppIdentityDbContext : IdentityDbContext<AppUser>
{
    ......
}

마이그레이션을 추가하거나 데이터베이스를 업데이트하려고 할 때 '정의 된 키 없음'오류가 발생합니다.


또한 ID를 Int로 변경하려고하는데이 문제가 있지만 새 키 유형을 지정하기 위해 DbContext를 변경했습니다. 확인해야 할 다른 곳이 있습니까? 나는 지시를 매우 조심스럽게 따르고 있다고 생각했습니다.
Kyle

1
@Kyle : 모든 엔티티의 ID를 int (예 : AppRole, AppUser, AppUserClaim, AppUserLogin 및 AppUserRole)로 변경하려고합니까? 그렇다면 해당 클래스에 대해 새 키 유형을 지정했는지 확인해야 할 수도 있습니다. 'public class AppUserLogin : IdentityUserLogin <int> {}'처럼
David Liang

1
이것은 기본 키가 어떤 데이터 형식을 사용자 정의에 대한 공식 문서입니다 : docs.microsoft.com/en-us/aspnet/core/security/authentication/...
AdrienTorris

1
예, 내 문제는 IdentityDbContext <AppUser> 대신 일반 DbContext 클래스에서 상속되었습니다. 덕분에,이 많은 도움이
yibe

13

아래와 같이 DbContext를 변경하여;

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
        modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
    }

OnModelCreatingbase.OnModelCreating (modelBuilder); 메서드 호출을 추가 하기 만하면됩니다. 그리고 괜찮아집니다. EF6을 사용하고 있습니다.

#The Senator에게 특별히 감사드립니다.


1
 protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            //foreach (var relationship in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
            //    relationship.DeleteBehavior = DeleteBehavior.Restrict;

            modelBuilder.Entity<User>().ToTable("Users");

            modelBuilder.Entity<IdentityRole<string>>().ToTable("Roles");
            modelBuilder.Entity<IdentityUserToken<string>>().ToTable("UserTokens");
            modelBuilder.Entity<IdentityUserClaim<string>>().ToTable("UserClaims");
            modelBuilder.Entity<IdentityUserLogin<string>>().ToTable("UserLogins");
            modelBuilder.Entity<IdentityRoleClaim<string>>().ToTable("RoleClaims");
            modelBuilder.Entity<IdentityUserRole<string>>().ToTable("UserRoles");

        }
    }

0

내 문제는 비슷했습니다. ID 사용자와 연결하기 위해 ahd를 만드는 새 테이블이있었습니다. 위의 답변을 읽은 후 IsdentityUser 및 상속 된 속성과 관련이 있음을 깨달았습니다. 이미 Identity를 자체 컨텍스트로 설정 했으므로 관련 사용자 테이블을 실제 EF 속성으로 사용하는 대신 본질적으로 두 가지를 함께 묶는 것을 방지하기 위해 관련 엔터티를 가져 오기 위해 쿼리와 함께 매핑되지 않은 속성을 설정했습니다. (DataManager는 OtherEntity가 존재하는 현재 컨텍스트를 검색하도록 설정됩니다.)

    [Table("UserOtherEntity")]
        public partial class UserOtherEntity
        {
            public Guid UserOtherEntityId { get; set; }
            [Required]
            [StringLength(128)]
            public string UserId { get; set; }
            [Required]
            public Guid OtherEntityId { get; set; }
            public virtual OtherEntity OtherEntity { get; set; }
        }

    public partial class UserOtherEntity : DataManager
        {
            public static IEnumerable<OtherEntity> GetOtherEntitiesByUserId(string userId)
            {
                return Connect2Context.UserOtherEntities.Where(ue => ue.UserId == userId).Select(ue => ue.OtherEntity);
            }
        }

public partial class ApplicationUser : IdentityUser
    {
        public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
        {
            // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
            var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
            // Add custom user claims here
            return userIdentity;
        }

        [NotMapped]
        public IEnumerable<OtherEntity> OtherEntities
        {
            get
            {
                return UserOtherEntities.GetOtherEntitiesByUserId(this.Id);
            }
        }
    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.