9
votes

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?

6
I would make a suggestion, avoid using the attributes to define your Entityframework relations. If you ever plan on using these object outside of a database application the attributes make it difficult to do.BlackICE
I've got your same issue, have you solved it?Luther

6 Answers

4
votes

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
votes

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!

1
votes
public MyObject
{
   .. other properties

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

   public virtual ApplicationUser ApplicationUser { get; set;}
}
0
votes

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.

0
votes

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 });
}
0
votes

this is also an alternative way for @kimbaudi suggestion. With this, you don't need to define mapping key attribute instead you can use your own defined Model attribute.

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


   public ApplicationUser User { get; set; }

    // other properties
}

public class ApplicationUser : IdentityUser
{ 
    public virtual ICollection<MyObject> MyObjects { 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(po => po.User)
        .WithMany(a => a.MyObjects)
        .HasForeignKey(po => po.UserId);
    }
}