EntityType“ IdentityUserLogin”没有定义密钥。请为此 EntityType 定义密钥

我的工作与实体框架代码第一和 MVC 5。当我使用 个人用户帐户认证创建应用程序时,我得到了一个 Account 控制器,以及获得 Indiv 用户帐户身份验证所需的所有类和代码。

Among the code already in place was this:

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' has no key defined. Define the key for this EntityType.

没有定义键。定义此 EntityType 的键。

我做错了什么? 是因为我有两个上下文吗?

更新

读完奥古斯托的回复后,我选择了 选择三。下面是我的 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();
}
}

我还添加了一个 User.cs和一个 Role.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上的一个整数属性

120880 次浏览

问题是你的 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; }

它们的主键映射在方法 OnModelCreating of the class IdentityDbContext中:

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.EntityFrameworksources,你就会明白一切。

前段时间我遇到过这种情况,我发现了三种可能的解决方案(也许还有更多) :

  1. 对两个不同的数据库或同一数据库但不同的表使用单独的 DbContext。
  2. 将 DXContext 与 ApplicationDbContext 合并并使用一个数据库。
  3. Use separate DbContexts against the same table and manage their migrations accordingly.

选择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 相同的表。

然后,按照 Shailendra Chauhan 的这篇文章: 具有多个数据上下文的代码优先迁移,使用代码优先迁移,您将需要为 IdentityContext 生成迁移,为 DXContext 生成 THEN

您必须修改为 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 或应用程序的任何其他位置按顺序运行迁移(首先是 Identity 迁移) :

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 类,因此我不必在每个使用它们的项目中导入这个框架。

Sorry for the extensive post. I hope it could offer some guidance on this. I have already used options 2 and 3 in production environments.

更新: 扩展选项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 方法中使用的自定义抽象基类)

I first create the AspNetUser and then the AppUser. The drawback with this approach is that you have ensured that your "CreateUser" functionality is transactional (remember that there will be two DbContexts calling SaveChanges separately). Using TransactionScope didn't work for me for some reason, so I ended up doing something ugly but that works for me:

        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;
}

(Please, if somebody comes with a better way of doing this part I appreciate commenting or proposing an edit to this answer)

这样做的好处是不必修改迁移,而且可以使用 在 AppUser 上使用任何疯狂的继承层次结构,而不会干扰 AspNetUser。实际上,我对 IdentityContext (派生自 IdentityDbContext 的上下文)使用了自动迁移:

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


protected override void Seed(IdentityContext context)
{
}
}

这种方法还有一个好处,就是可以避免从 AspNetIdentity 类继承 n 层横切实体。

For those who use ASP.NET Identity 2.1 and have changed the primary key from the default string to either int or Guid, if you're still getting

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>
{
......
}

当您试图添加迁移或更新数据库时,您将得到“无键定义”错误。

在我的例子中,我正确地继承了 IdentityDbContext (定义了自己的自定义类型和键) ,但是无意中删除了对基类的 OnModelCreate 的调用:

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

然后修复标识类中丢失的索引,然后就可以生成迁移并适当地启用迁移。

通过改变下面的 DbContext;

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

只要添加对 base.OnModelCreate (model Builder)的 OnModelCreating方法调用,就可以了。

Special Thanks To #The Senator

我的问题是相似的-我有一个新的表,我正在创建,并结合到身份用户。在阅读了上述答案之后,意识到它必须与 IsdentyUser 和继承的属性有关。我已经将 Identity 设置为它自己的 Context,因此为了避免固有地将两者绑定在一起,而不是使用相关用户表作为真正的 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);
}
}
}
 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");


}
}