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:
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:
(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?
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.