user6347904
user6347904

Reputation:

How do I add a foreign key to Identity user in EF Core?

Let's say I have a Todo model with a corresponding Todo table (DbSet<Todo>) in an EF-generated database. The table will store a todo item per row. Each user (in an ASP.NET Core Identity + IdentityServer app) will be associated with multiple todo items (one-to-many, a user can have multiple todo items).

The way I'd do it is to add a UserId foreign key to the Todo model that represents the user that owns the todo item .

How would I do that with EF Core while utilizing the classes proved by ASP.NET Core Identity? My intuition was to add the following to the Todo class model:

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

This uses ApplicationUser (the ASP.NET Core Identity default user class model) as discovery to populate UserId with a foreign key.

Here's the problem: when I run Update-Database I get an error! It says The entity type 'IdentityUserLogin<string>' requires a primary key to be defined..

Browsing through the code for IdentityUserLogin, I can see it has a property UserId which should be considered a primary key, right?

Anyway, to make sure its registered as a primary key, I added this code to ApplicationUser's OnModelCreating method: builder.Entity<ApplicationUser>().HasKey(u => u.Id);. I still get the same error! Not sure what I'm doing wrong here :(. Edit: Yes, I called the base class so that's not the problem.

Note: I know I can add a field to each user that carries a collection of todo item Ids instead of having a foreign key, but I'm not sure if that's the good way of doing this. Guidance on this issue appreciated.

Upvotes: 16

Views: 19911

Answers (2)

Vincent Cohen
Vincent Cohen

Reputation: 61

Dave, your solution worked fine !

Just some details for readers, adding the class to ApplicationDbContext as a DbSet means adding to the file ApplicationDbContext.cs Something like :

public DbSet<MyClass> MyClass { get; set; }

and when it's needed (in Package Manager Console) : PM> Add-Migration MyClass PM> Update-Database

Upvotes: 3

Dave
Dave

Reputation: 1593

You haven't shared the code for this, but I'm guessing you have made an override of OnModelCreating in your db context, but have not called base.OnModelCreating(modelBuilder);.

Without the base call, your context isn't applying the Identity related schema.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    // Add Identity related model configuration
    base.OnModelCreating(modelBuilder);

    // Your fluent modeling here
}

Edit: Felt like giving this a whirl so I just performed the below tasks. The TLDR would be that it worked out for me. I believe the issue you are having has nothing to do with the key you are making and something unrelated is causing your failure, because the failure you are getting is on a completely different table than the one you are building schema for.

I created a new .net core web application using Identity. First thing I did was call update-database to build up the DB from the initial migration that the project came with. This worked.

Next I added a Todo class. No properties, just an Id and the User field. Didn't even mark the foreign key.

public class Todo
{
    public int Id { get; set; }
    public string UserId { get; set; }
    public ApplicationUser User { get; set; }
}

I added it to the ApplicationDbContext as a DbSet. Only alteration to the context was to add the ToDo DbSet

Then I called add-migration todo in the package manager console to build the todo migration. It generated as expected with the FK in place.

public partial class Todo : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.CreateTable(
            name: "Todos",
            columns: table => new
            {
                Id = table.Column<int>(nullable: false)
                    .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                UserId = table.Column<string>(nullable: true)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_Todos", x => x.Id);
                table.ForeignKey(
                    name: "FK_Todos_AspNetUsers_UserId",
                    column: x => x.UserId,
                    principalTable: "AspNetUsers",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.Restrict);
            });

        migrationBuilder.CreateIndex(
            name: "IX_Todos_UserId",
            table: "Todos",
            column: "UserId");
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropTable(
            name: "Todos");
    }
}

I then added some code to my About controller action to just stick a record into the table to make sure the FK was working. It was.

public async Task<IActionResult> About()
{
    var user = await _userManager.GetUserAsync(HttpContext.User);
    var todo = new Todo
    {
        User = user
    };
    _context.Todos.Add(todo);
    await _context.SaveChangesAsync();

    return View();
}

Screenshot of the record in the DB along with tables and keys: enter image description here

Other than the obvious that you have more fields on your Todo class is there something drastically different that you've done differently that may lead us to the problem?

Upvotes: 14

Related Questions