Search code examples
c#asp.net-core-mvcentity-framework-coreasp.net-core-2.2asp.net-core-identity

How to add new users from a logged-in user?


I have this weird behaviour when it comes to creating a new users from scratch(registration) and creating new ones from a logged in user that doesn't depend on the DB underneath(in this case I'm using postgreSQL). But first here's the relationship: Tables relationships

Now both at registration time and logged-in time I do use UserManager for the creation of the users.

First I create a new UserSetting, SaveChanges() and follow by assigning to the AppUser property UserSettingId the value of the Id of the new created entity.

Here's where it fails depending if you are already a registered and logged-in user or you are a new registering user, the code doesn't change:

var userSettings = await _userService.UserSettings.AddAsync(new UserSettings
{
    LanguageId = langId,
    ThemeId = themeId
});
//wrapper to the Context.SaveChangesAsync()
//failure point!
if (!await _userService.SaveChangesAsync<bool>())

And the exception error(at the creation of a new user from an already logged-in user) talks about a PK_LanguageId already existing as if I was creating a new Language entity but, as you can see, I'm just assigning just a default langId to the new UserSetting. Here's the exception screenshot:

enter image description here

(sorry for the italian, it does auto-translate) Short translation: duplicated key for Languages, as if it was trying to insert also a new Language entity.

And at the Startup the registered service(_userService) is transient.

And here's the model classes and their configuration in OnModelCreating:

public class AppUser : IdentityUser
{
    public long UserSettingsId { get; set; }
    public virtual UserSettings UserSettings { get; set; }
    //...other properties
}
public class UserSettings
{
    public long Id { get; set; }

    public int LanguageId { get; set; }
    public virtual Language Language { get; set; }

    public int ThemeId { get; set; }
    public virtual Theme Theme { get; set; }

    public virtual AppUser AppUser { get; set; }
}
public class Theme
{
    [Key]
    public int Id { get; set; }
    [StringLength(10)]
    public string Type { get; set; }
}
public class Language
{
    [Key]
    public int Id { get; set; }
    public string Name { get; set; }
    [MaxLength(2)]
    public string Code { get; set; }
}


builder.Entity<AppUser>().HasKey(u => u.Id);
builder.Entity<AppUser>().HasOne(u => u.UserSettings).WithOne(us => us.AppUser);

builder.Entity<UserSettings>().HasKey(us => us.Id);
builder.Entity<UserSettings>().Property(us => us.Id).ValueGeneratedOnAdd();
builder.Entity<UserSettings>().HasOne(us => us.Language);
builder.Entity<UserSettings>().HasOne(us => us.Theme);

What I'm doing wrong? Why it does behave so differently? Is there a different way to create users from an already logged-in user?


Solution

  • Found the source of the behaviour, it's the damn UserManager.GetUserAsync(), which I user to get the currently logged-in user with added UserSettings later on to propagate the "Admin"'s settings to the new creating account.

    GetUserAsync(User) does return an entity that does keep being tracked(instead of, as I thought, being like AsNoTracking()). So consequently when I did fill it's UserSetting I did in fact added a new UserSetting with new sub classes properties!

    Sooo, never expect UserManager give you the user without tracking!

    Thanks for everyone involved with the effort.