Search code examples
c#asp.netasp.net-identity

Error "The INSERT statement conflicted with the FOREIGN KEY constraint " using Identity with custom property and relationship



I'm very new to asp.net MVC and I'm practicing building this application.

I'm trying to add a custom property to the default IdentityModel.cs generated by visual studio. This customproperty will held the university library that an user belongs to.

I'm sorry if I made this post too large, but I really don't know what is causing the problem and what should I omit.

What I have made so far: Created the University library entity:

 public class UniversityLibrary
{
    [Key]
    public int UniversityLibraryId { get; set; }
    public string UniversityLibraryName { get; set; }

    public virtual ICollection<ApplicationUser> Users {get;set;}

}

Added the custom property to IdentityModel.cs:

public class ApplicationUser : IdentityUser
{

public virtual UniversityLibrary UniversityLibraries { get; set; }  
public int UniversityLibraryId { get; set; }

    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
    {
        // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
        // Add custom user claims here
        return userIdentity;
    }
}

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

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

    public static ApplicationDbContext Create()
    {
        return new ApplicationDbContext();
    }


}

Added the custom property in the RegisterViewModel:

public class RegisterViewModel
{


    [Required]
    [EmailAddress]
    [Display(Name = "Email")]
    public string Email { get; set; }

    [Required]
    [DisplayName("University Library ID")]
    public int UniversityLibraryId { get; set; }

    public virtual ICollection<UniversityLibrary> Libraries { get; set; }

    [Required]
    [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
    [DataType(DataType.Password)]
    [Display(Name = "Password")]
    public string Password { get; set; }

    [DataType(DataType.Password)]
    [Display(Name = "Confirm password")]
    [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
    public string ConfirmPassword { get; set; }
}

Created a list of University libraries to be passed to my view using actionfilter:

public class ViewbagListOfUniversityLibraries : ActionFilterAttribute
{

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        IncludeRoleListIntoViewBag(filterContext.Controller.ViewBag);
        base.OnActionExecuted(filterContext);
    }

    private void IncludeRoleListIntoViewBag(dynamic viewBag)
    {
        using (var db = new LibraryManagementWebAppContext())
        {
            viewBag.UniversityLibraryList = db.UniversityLibraries
                .OrderBy(x => x.UniversityLibraryName)
                .Select(unversityLibrary => new SelectListItem()
                {
                    Text = unversityLibrary.UniversityLibraryName,
                    Value = unversityLibrary.UniversityLibraryId.ToString()
                })
                .ToList();

        }
    }
}

Modified the Register view to add the dropdown of university libraries:

@model LibraryManagementWebApp.Models.RegisterViewModel
@{
    ViewBag.Title = "Register";
    IEnumerable<SelectListItem> universityLibraryList = ViewBag.UniversityLibraryList;
}
...
<div class="form-group">
    @Html.LabelFor(m => m.UniversityLibraryId, new { @class = "col-md-2 control-label" })
    <div class="col-md-10">
        @Html.DropDownListFor(m => m.UniversityLibraryId, universityLibraryList,"Select One", new { @class = "form-control" })
    </div>
</div>

Added modelbuilders to my other context (the context for university Libraries), because it was causing erros like "EntityType 'IdentityUserLogin' has no key defined. Define the key for this EntityType"

public class LibraryManagementWebAppContext : DbContext
{
   public LibraryManagementWebAppContext() : base("name=LibraryManagementWebAppContext")
    {
    }

    public DbSet<UniversityLibrary> UniversityLibraries { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {

        modelBuilder.Entity<IdentityUserLogin>().HasKey(l => l.UserId);
        modelBuilder.Entity<IdentityRole>().HasKey(r => r.Id);
        modelBuilder.Entity<IdentityUserRole>().HasKey(r => new { r.RoleId, r.UserId });
        modelBuilder.Entity<UniversityLibrary>().HasKey(r => r.UniversityLibraryId);

        base.OnModelCreating(modelBuilder);
    }

}

Modified the controller to get the selected value before creating the new user in my database:

 [HttpPost]
    [AllowAnonymous]
    [ViewbagListOfUniversityLibraries]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Register(RegisterViewModel model)
    {
        if (ModelState.IsValid)
        {
            var user = new ApplicationUser { UserName = model.Email, Email = model.Email, UniversityLibraryId = Convert.ToInt32(model.UniversityLibraryId) };
            var result = await UserManager.CreateAsync(user, model.Password);
            if (result.Succeeded)
            {
                await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);

                // For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=320771
                // Send an email with this link
                // string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
                // var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
                // await UserManager.SendEmailAsync(user.Id, "Confirm your account", "Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>");

                return RedirectToAction("Index", "Home");
            }
            AddErrors(result);
        }

        // If we got this far, something failed, redisplay form
        return View(model);
    }

The problem is: When i try to register a new user, a get this error:

The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.AspNetUsers_dbo.UniversityLibraries_UniversityLibraryId". The conflict occurred in database "ASPNET_LIBRARYMANAGEMENTWEBAPP_aa38b17e20d0416d8c53a3a5d0faa59e", table "dbo.UniversityLibraries", column 'UniversityLibraryId'.

And I dont know how to solve it. Debugging my code I can see my user variable have a value for UniversityLibraryId. Debugging: user variable that is passed to CreateAsync

Also already verified in the database and UniversityLibrary table have the item with this same id. checked the database to see if the data is there


Solution

  • Ok, so I managed to find what I was doing wrong...

    As I have two contexts (One auto generated "IdentityModel.cs" and the second that I generated following this tutorial creating-an-entity-framework-data-model-for-an-asp-net-mvc-application)

    Each context was pointing to a different database, both with the same tables.

    So I was sucesfully creting "university libraries" to be used in the register page, but they we being created in the wrong database.

    Then I made some changes to make both contexts point to the same database and made my LibraryManagementWebAppContext inherit from IdentityDbContext

    My context for University libraries:

    public class LibraryManagementWebAppContext : IdentityDbContext<ApplicationUser>
    {
       public LibraryManagementWebAppContext() : base("name=DefaultConnection")
        {
        }
    
        public DbSet<UniversityLibrary> UniversityLibraries { get; set; }
    
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
     
            modelBuilder.Entity<IdentityUserLogin>().HasKey(l => l.UserId);
            modelBuilder.Entity<IdentityRole>().HasKey(r => r.Id);
            modelBuilder.Entity<IdentityUserRole>().HasKey(r => new { r.RoleId, r.UserId });
            modelBuilder.Entity<UniversityLibrary>().HasKey(r => r.UniversityLibraryId);
    
            base.OnModelCreating(modelBuilder);
        }
        
    }
    

    And the default IdentityModel generated by visual studio:

     public ApplicationDbContext()
            : base("name=DefaultConnection", throwIfV1Schema: false)
        {
        }
    
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
        }
    
        public static ApplicationDbContext Create()
        {
            return new ApplicationDbContext();
        }
    
       
    }
    

    After that I droped the databases and could sucesfully: 1 - Create University libraries 2 - Register user using my list of University Libraries

    As I said before, I am very new to asp.NET, so probably I did something that even working isn't the best aproach. Please feel free to complement my answer or sugest something that could be done.

    Thank you.