Foreign Key To Microsoft.AspNet.Identity.EntityFra

2019-01-24 12:20发布

问题:

I'm in VS 2013 and have just created an MVC application.

I'm creating an object I intend to have a foreign key to the AspNetUsers table in the resulting database. The project does have an ApplicationUser (deriving from IdentityUser) that looks like a property-column match with the AspNetUsers table.

How do we properly declare a foreign key to this?

public MyObject
{
   public string UserId { get; set; }

   [ForeignKey("UserId")]
   public ApplicationUser User { get; set;}

   // other properties
}

Now, I modify ApplicationUser to have a collection of MyObjects:

public ApplicationUser : IdentityUser
{
     public virtual ICollection<MyObject> MyObjects { get; set; }    
}

This seems to be how to do one-to-many in EF Code First. However, when I update-database, I'm getting the errors that say Identity members (IdentityUserLogin, IdentityUserRole, etc.) have no keys defined. Perhaps those classes were not meant to participate in EF Code First Migrations?

I could go "to the back" and add the foreign key via SQL statements, but if I wanted to update again from Code First, I might get errors (that the database doesn't currently match the older migration or something like that).

How do we properly foreign-key reference those membership tables?

I also tried to create an AspNetUser class with matching properties of the AspNetUsers table. Instead of "public ApplicationUser" on the Client class, I declared "public AspNetUser". Doing this resulted in a migration failure - "Automatic migration was not applied because it would result in data loss."

So, what to do?

回答1:

It is easy to create a one-to-many relationship between ApplicationUser and MyObject and add a "UserId" foreign key in your MyObjects table. What I like about this solution is that it follows EF conventions and there is no need for [ForeignKey] attribute in your model:

public class ApplicationUser : IdentityUser
{
    public virtual ICollection<MyObject> MyObjects { get; set; }
}

public class MyObject
{
    public int MyObjectId { get; set; }

    public string MyObjectName { get; set; }

    // other properties

    public virtual ApplicationUser ApplicationUser { get; set; }
}

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

    public DbSet<MyObject> MyObjects { get; set; }

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

        modelBuilder.Entity<MyObject>()
            .HasRequired(c => c.ApplicationUser)
            .WithMany(t => t.MyObjects)
            .Map(m => m.MapKey("UserId"));
    }
}

Notice the use of Fluent API to create a "UserId" foreign key in your MyObjects table. This solution would still work without adding the Fluent API, but then your foreign key column would be named "ApplicationUser_Id" in your MyObjects table by convention.



回答2:

I would do the following: In the ApplicationUser class, add a ForeignKey attribute,

public ApplicationUser : IdentityUser {
    [ForeignKey("UserID")]
    public virtual ICollection<MyCustomUser> MyCustomUsers{ get; set; }    
}

and in your model where you want to track to which user it belongs,

public MyObject {
    public string UserId { get; set; }

    // other properties
}

You don't need to store the whole ApplicationUser instance in the MyObject class, and the UserID will be generated automatically. It is important that is is of type string, as is the ID of the ApplicationUser!



回答3:

I think you have it backwards. Maybe try something like:

public MyCustomUser : IUser
{
   public string Id { get; set; }

   public string FavoriteHairColor { get; set;}
}

public ApplicationUser : IdentityUser
{
     public virtual ICollection<MyCustomUser> MyCustomUsers{ get; set; }    
}

I'm going from memory here so I might be a little off. Anyway, the important things is to have your EF user class inherit from IUser.



回答4:

The ASP.NET Identity classes doesn't use attributes to define the relations, they expect the relations to be configured by the DbContext.

The default DbContext used with ASP.NET Identity, IdentityDbContext<TUser> includes the configuration. So if you make your DbContext class inherit from IdentityDbContext<ApplicationUser> you should be all set.

Update

If you still get error: "Automatic migration was not applied because it would result in data loss." then do:

get Update-Database -Force

If you still get error messages about IdentityUserLogin, IdentityUserRole, etc. have no keys defined then you most likely are overriding the OnModelCreating method in your DbContext without calling the base method first. Add a call to the base like this:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    base.OnModelCreating(modelbuilder);
    // your code here
}

End of Update

If you don't want to inherit from IdentityDbContext you need to add some configuration for the Identity classes. One way to do this is by overriding OnModelCreating. This is where IdentityDbContext<ApplicationUser> configure the entity framework mappings. If you add this to your DbContext class it should set up the mappings you need.

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<IdentityUserLogin>().HasKey<string>(l => l.UserId);
    modelBuilder.Entity<IdentityRole>().HasKey<string>(r => r.Id);
    modelBuilder.Entity<IdentityUserRole>().HasKey(r => new { r.RoleId, r.UserId });
}


回答5:

public MyObject
{
   .. other properties

   [MaxLength(128), ForeignKey("ApplicationUser")]
   public virtual string UserId { get; set; }

   public virtual ApplicationUser ApplicationUser { get; set;}
}