AspNetCore.Identity UserManager CreateAsync cannot save relationships

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

Question

I have a model like this:

public class UserIdentity : IdentityUser
{
    public User User { get; set; }
}

public class User : Entity
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Surname { get; set; }
    public DateTime DateOfBirth { get; set; }
    public Occupation Occupation { get; set; }
    public Country Country { get; set; }
    public City City { get; set; }
    public Ethnicity Ethnicity { get; set; }
    public GenderEnum Gender { get; set; }
}

the IdentityUser is in AspNetCore.Identity namespace. Also, I'm using Urf.Core for my repository and service layer, and the Entity is for specifying trackable entities in that library.

in my Application layer I have a service for registering users and I use UserManager<UserIdentity> to save users.

public async Task Register(UserRegistrationDto dto)
    {
        var newUser = new User();

        var country = GetCountry(dto.CountryName);
        var city = GetCity(country, dto.CityName, dto.CityId.Value);

        var ethnicity = GetEthnicity(dto.EthnicityId.Value);
        var occupation = GetOccupation(dto.OccupationId.Value);

        newUser.Country = country;
        newUser.City = city;
        newUser.Occupation = occupation;
        newUser.Ethnicity = ethnicity;

        newUser.DateOfBirth = dto.DateOfBirth;

        newUser.Name = dto.Name;
        newUser.Surname = dto.Surname;
        newUser.Gender = (GenderEnum)dto.Gender.Value;

        _userService.Insert(newUser);

        var newIdentity = new UserIdentity {UserName = dto.Email, Email = dto.Email, User = newUser};
        var result = await _userManager.CreateAsync(newIdentity, dto.Password);

        //await _unitOfWork.SaveChangesAsync();
    }

Code explanation:

the GetCountry and GetCity, both check if the city and country exists or not, and if not it creates them. I insert the user and all its relations with Urf service in _userService.Insert(newUser) and save it all together in single transaction with again, Urf unit of work, await _unitOfWork.SaveChangesAsync()

The problem:

placing SaveChangesAsync() before _userManager.CreateAsync(), creates the user, city, country and all without any problem. But if I call _userManager.CreateAsync(), ef throws exception 'Cannot insert explicit value for identity column...' for id column in country, city, and user tables.

I want to save the UserIdentity and rest of the relationships in one call of SaveChanges(), but looks like _userManager.CreateAsync() cannot save relations like the way _unitOfWork.SaveChangesAsync() can.

Temoporary solution:

to get pass for now, I reversed the relations in UserIdentity and User, I create the identity first and if that succeeds I insert rest of them:

    public async Task Register(UserRegistrationDto dto)
    {
        var newIdentity = UserIdentity.CreateIdentity(dto.Email, dto.Email, dto.Phone);
        var result = await _userManager.CreateAsync(newIdentity, dto.Password);
        if (result.Succeeded)
        {
            var newUser = new User();

            var country = GetCountry(dto.CountryName);
            var city = GetCity(country, dto.CityName, dto.CityId.Value);

            var ethnicity = GetEthnicity(dto.EthnicityId.Value);
            var occupation = GetOccupation(dto.OccupationId.Value);

            newUser.Identity = newIdentity;
            newUser.Country = country;
            newUser.City = city;
            newUser.Occupation = occupation;
            newUser.Ethnicity = ethnicity;

            newUser.DateOfBirth = dto.DateOfBirth;

            newUser.Name = dto.Name;
            newUser.Surname = dto.Surname;
            newUser.Gender = (GenderEnum)dto.Gender.Value;

            _userService.Insert(newUser);

            await _unitOfWork.SaveChangesAsync();
        }
    }

but as you can see, it saves them in two seperate transactions, if _unitOfWork.SaveChangesAsync(); fails for any reason, the identity is created and I can't rollback (it is possible but dirty)

does anyone know why the problem I explained, happens?

1
0
2/11/2019 2:49:01 PM

Popular Answer

You neglected to post your full controller, particularly the services that are being injected, but based on your issue, I'm guessing you're injecting UserManager<IdentityUser>.

The type param here is what is actually going to be retrieved, persisted, etc. So, if you attempt to save a UserIdentity instance, it will be upcast to IdentityUser (which doesn't have your User property), and that is what's going to be created. If you need to work with UserIdentity, then you need to inject UserManager<UserIdentity>.

However, your design here is completely wrong. The whole point of the user type being generic is to allow extensibility. You should create a class that inherits from IdentityUser and specify any additional properties for your user directly there. You do not need and should not have a separate profile-style class added in via composition. In other words:

public class User : IdentityUser
{
    public string Name { get; set; }
    public string Surname { get; set; }
    public DateTime DateOfBirth { get; set; }
    public Occupation Occupation { get; set; }
    public Country Country { get; set; }
    public City City { get; set; }
    public Ethnicity Ethnicity { get; set; }
    public GenderEnum Gender { get; set; }
}

Then, in Startup.cs:

services.AddDefaultIdentity<User>();

And in your controller, inject UserManager<User> and work with User directly. UserIdentity goes in the trash.

0
2/11/2019 3:10:06 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