Reputation:
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
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
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:
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