My class Subcontractor has a property ApplicationUser CreatedUser. When a subcontractor is created and added to the database I want to store the current IdentityUser as the CreatedUser. I used EF Core Migrations to create my database. The CreatedUser was mapped to CreatedUserId in the database as a foreign key by default when I added the migration.
When I run my app, the first subcontractor record that is added by a logged in user works fine and the CreatedUserId field is updated. My issue is that once a user tries to create a second subcontractor record I get an uncaught exception. If I remove updating the CreatedUser field it works fine and I can add multiple subcontractor records.
This leads me to believe that I need to add my own relationship configuration in OnModelCreating to override the default EF migration configuration? I am not sure though what I need to specify so that the Subcontractor database table will accept multiple records with the same CreatedUserId field? Or, is the default relationship correct, and do I need to change something in my create method to fix?
By default, this is what EF Core Migrations creates:
modelBuilder.Entity("SubTracker.Data.Models.Subcontractor", b =>
{
b.HasOne("SubTracker.Data.Models.ApplicationUser", "Approver")
.WithMany()
.HasForeignKey("ApproverId");
b.HasOne("SubTracker.Data.Models.ApplicationUser", "CreatedUser")
.WithMany()
.HasForeignKey("CreatedUserId");
});
Paired down, my class is:
public class Subcontractor
{
public long Id { get; set; }
public string Name { get; set; }
public string AddressLineOne { get; set; }
public string AddressLineTwo { get; set; }
public string City { get; set; }
public string State { get; set; }
public ApplicationUser CreatedUser { get; set; }
}
In my controller my create method is:
private Task<ApplicationUser> GetCurrentUserAsync() => _userManager.GetUserAsync(HttpContext.User);
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Name,AddressLineOne,AddressLineTwo,City,State,Zipcode,ContactName,ContactEmail,ContactPhone")] Subcontractor subcontractor)
{
if (ModelState.IsValid)
{
var user = await GetCurrentUserAsync();
subcontractor.CreatedTimestamp = DateTime.Now;
subcontractor.UpdatedTimestamp = subcontractor.CreatedTimestamp;
subcontractor.AnnualSafetyPrequalApproved = false;
subcontractor.AnnualInsuranceApproved = false;
subcontractor.Status = "In Process";
subcontractor.CreatedUser = user;
_context.Add(subcontractor);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(subcontractor);
}
My ApplicationUser class:
public class ApplicationUser : IdentityUser
{
public string Branch { get; set; }
public string Area { get; set; }
public string Title{ get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Role { get; set; }
}
I have a separate DB context for Identity and for my application data. My paired down DBContext (removed other model classes but have nothing for ApplicationUser) for my application data is:
public class SubTrackerDbContext : DbContext
{
public DbSet<Subcontractor> Subcontractors { get; set; }
public SubTrackerDbContext(DbContextOptions<SubTrackerDbContext> options) : base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.ConfigureWarnings(warnings => warnings.Ignore(RelationalEventId.QueryClientEvaluationWarning));
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Subcontractor>().ToTable("Subcontractor");
}
}
My DB context for Identity is:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
}
I think the problem is part of how you set the classes.
First you need a foreign key for the ApplicationUser' in the
Subcontractor` class:
public int CreatedUserId { get; set; }
public virtual ApplicationUser CreatedUser { get; set; }
Then (and this is optionally) your ApplicationUser
must have a collection of Subcontractors
:
public virtual ICollection<Subcontractor> Subcontractors { get; set; }
On your create method just specify the user id not the entire user.