Unable to save changes because a circular dependency was detected in the data to be saved

asp.net-mvc c# entity-framework-core

Question

I am trying to add both a ApplicationUser and Person entity to my Context

My ApplicationUser class

public class ApplicationUser : IdentityUser<Guid>
{
    public int? PersonID { get; set; }

    [ForeignKey("PersonID")]
    public Person Person { get; set; }

    [InverseProperty("User")]
    public ICollection<UserSource> UserSources { get; set; }

    public virtual ICollection<IdentityUserClaim<Guid>> Claims { get; set; }
    public virtual ICollection<IdentityUserLogin<Guid>> Logins { get; set; }
    public virtual ICollection<IdentityUserToken<Guid>> Tokens { get; set; }
    public virtual ICollection<ApplicationUserRole> UserRoles { get; set; }
}

My Person class

public class Person : BaseEntity
{
    [Required]
    [DataType(DataType.Text)]
    public string FirstName { get; set; }

    [Required]
    [DataType(DataType.Text)]
    public string LastName { get; set; }

    [Required]
    [DataType(DataType.Text)]
    public string FullName { get; set; }

    public int HeightInches { get; set; }

    public int WeightPounds { get; set; }

    [DataType(DataType.Text)]
    public string BatHand { get; set; }

    [DataType(DataType.Text)]
    public string ThrowHand { get; set; }

    public DimDate BirthDate { get; set; }

    public DimDate DeathDate { get; set; }

    public DimDate DebutDate { get; set; }
    public DimDate FinalDate { get; set; }
    public Guid? UserID { get; set; }

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

    [InverseProperty("LeagueOwner")]
    public ICollection<League> LeaguesOwned { get; set; }

    public override int GetHashCode() => Tuple.Create(this.FirstName, this.LastName, this.FullName).GetHashCode();
}

I am trying to create both of these entities, and then tie them together in the RegisterUser method of my UserService

Public Class UserService : IUserService
{
    private MyDbContext context;
    private ILogger logger;
    private UserManager<ApplicationUser> userManager;

    public async Task<ApplicationUser> RegisterUser(RegistrationModel registrationModel)
    {
        // Create the User first
        var user = new ApplicationUser
        {
            UserName = registrationModel.UserName,
            Email = registrationModel.Email
        };

        // Create the User with a password
        var result = await this.userManager.CreateAsync(user, registrationModel.Password);

        // Make sure the user is successfully created
        if (result.Succeeded)
        {
            try
            {
                this.logger.LogInformation($"User {user.UserName} successfully created and added to the database");

                // create a person for the User (this is causing me to have a headache...)
                // I originally had this is separate methods... moving into one so I can make more sense
                var fullName = (registrationModel.FirstName == registrationModel.LastName) ? registrationModel.FirstName : registrationModel.FirstName + " " + registrationModel.LastName;
                var newPerson = new Person
                {
                    FirstName = registrationModel.FirstName,
                    LastName = registrationModel.LastName,
                    FullName = fullName
                };

                // Add the person to the DB
                await this.context.People.AddAsync(newPerson);

                // Add the User to the Person and vice versa
                user.Person = newPerson;
                newPerson.User = user;

                // Save the changes
                await this.context.SaveChangesAsync();
                this.logger.LogInformation($"Person for {user.NormalizedUserName} created");

                // Add source to the user
                var userSource = new UserSource
                {
                    MySource = registrationModel.WhereDidYouHear,
                    User = user
                };

                await this.context.UserSources.AddAsync(userSource);
                this.logger.LogInformation($"Source added to UserSources for {user.NormalizedUserName}");

                return user;
            }
            catch (Exception e)
            {
                this.logger.LogError(e);
                return null;
            }
        }
        foreach (var error in result.Errors)
        {
            this.logger.LogError(error.Description);
        }

        return null;
    }
}

However, when this method executes, I am getting the following error in my log:

System.InvalidOperationException: Unable to save changes because a circular dependency was detected in the data to be saved: 'Person [Added] <- Person { 'PersonID' } ApplicationUser [Added] <- User { 'UserID' } Person [Added]'

As far as I'm aware, I think I have the relationship set up correctly between ApplicationUser and Person, so I'm not sure why it's not allowing me to attach Person to ApplicationUser and vice versa.

I will include the RegistrationModel for reference, as data in this is used in the RegisterUser Method:

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

    [RegularExpression(@"^[a-zA-Z0-9_]{5,255}$")]
    [Required]
    [StringLength(256)]
    [Display(Name = "UserName")]
    public string UserName { get; set; }

    [Required]
    [StringLength(256)]
    public string FirstName { get; set; }

    [Required]
    [StringLength(256)]
    public string LastName { get; set; }

    [Required]
    [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} 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; }

    [DataType(DataType.Text)]
    [Display(Name = "Where did you hear about us?")]
    public string WhereDidYouHear { get; set; }
}
1
0
3/8/2020 2:34:50 PM

Accepted Answer

I was able to fix this by deleting PersonID property in my AspNetUser class, and based my new setup from This Tutorial

Here is my adjusted AspNetUser class:

public class ApplicationUser : IdentityUser<Guid>
{
    [InverseProperty("User")]
    public Person Person { get; set; }

    [InverseProperty("User")]
    public ICollection<UserSource> UserSources { get; set; }

    [InverseProperty("Owner")]
    public ICollection<League> LeaguesOwned { get; set; }

    public virtual ICollection<IdentityUserClaim<Guid>> Claims { get; set; }
    public virtual ICollection<IdentityUserLogin<Guid>> Logins { get; set; }
    public virtual ICollection<IdentityUserToken<Guid>> Tokens { get; set; }
    public virtual ICollection<ApplicationUserRole> UserRoles { get; set; }
}

My Person class didn't change, so I made adjusted the RegisterUser method in my UserService to look as such:

public class UserService : IUserService
{
    private StatPeekContext context;
    private ILogger logger;
    private UserManager<ApplicationUser> userManager;

    /// <summary>
    /// This will register a new user
    /// </summary>
    /// <param name="registrationModel"></param>
    /// <returns></returns>
    public async Task<ApplicationUser> RegisterUser(RegistrationModel registrationModel)
    {
        // Create the User first
        var user = new ApplicationUser
        {
            UserName = registrationModel.UserName,
            Email = registrationModel.Email
        };

        // Create the User with a password
        var result = await this.userManager.CreateAsync(user, registrationModel.Password);

        // Make sure the user is successfully created
        if (result.Succeeded)
        {
            try
            {
                this.logger.LogInformation($"User {user.UserName} successfully created and added to the database");

                var fullName = (registrationModel.FirstName == registrationModel.LastName) ? registrationModel.FirstName : registrationModel.FirstName + " " + registrationModel.LastName;
                var newPerson = new Person
                {
                    FirstName = registrationModel.FirstName,
                    LastName = registrationModel.LastName,
                    FullName = fullName,
                    UserID = user.Id
                };

                // Add the person to the DB
                await this.context.People.AddAsync(newPerson);

                this.logger.LogInformation($"Person for {user.NormalizedUserName} created");

                // Add source to the user
                var userSource = new UserSource
                {
                    MySource = registrationModel.WhereDidYouHear,
                    UserID = user.Id
                };

                await this.context.UserSources.AddAsync(userSource);
                await this.context.SaveChangesAsync();
                this.logger.LogInformation($"Source added to UserSources for {user.NormalizedUserName}");

                return user;
            }
            catch (Exception e)
            {
                this.logger.LogError(e);
                return null;
            }
        }
        foreach (var error in result.Errors)
        {
            this.logger.LogError(error.Description);
        }

        return null;
    }
}

This looks like it solved it.

1
3/8/2020 7:25:23 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow