We recently enabled Entity History in our project. For most entities this seems to work properly aas intended. For (our subclassed entity of) AbpUser this logs duplicates of the audited properties (CreatorUserId, LastModifierUserId, ...) with faulty 'OriginalValue' data.
Row 3 shows the correct update from Id 663 to Id 13493 for the LastModifierUser. Rows 5-9 show faulty entries for the LastModifierUserId, where for some reason the Original Value is a string instead of a long, and does not correspond to the actual original value.
In order to debug this, I tried to subclass the EntityHistoryHelper and override CreateEntityChangeSet, and was able to verify that the returned changeset is correct. So these properties are added later in the flow, either somewhere between the call to CreateEntityChangeSet and the actual save to the db table, or during the actual save.
8 Answer(s)
-
0
Hi @dirkvr
Is it possible to share your User class and the code which creates or modifies the user entity ?
Thanks,
-
0
[Audited] [AuditExpansion(nameof(TenantId), typeof(Tenant), nameof(RmoniWeb.MultiTenancy.Tenant.Name))] public class User : AbpUser<User> { public virtual Guid? ProfilePictureId { get; set; } public virtual bool ShouldChangePasswordOnNextLogin { get; set; } public DateTime? SignInTokenExpireTimeUtc { get; set; } public string SignInToken { get; set; } public string GoogleAuthenticatorKey { get; set; } public List<ExtendedUserOrganizationUnit> OrganizationUnits { get; set; } public ICollection<ReportUserRecipient> ReportUserRecipients { get; set; } public ICollection<AlarmUserRecipient> AlarmUserRecipients { get; set; } public ICollection<Device> MobileDevices { get; set; } public bool IsRecipient { get; set; } public Tenant Tenant { get; set; } [ForeignKey("ApplicationLanguage")] public int? LanguageId { get; set; } public ApplicationLanguage ApplicationLanguage { get; set; } //Can add application specific user properties here public User() { IsLockoutEnabled = true; IsTwoFactorEnabled = true; } public static User CreateTenantAdminUser(int tenantId, string emailAddress, string phoneNumber = default) { var user = new User { TenantId = tenantId, UserName = AdminUserName, Name = AdminUserName, Surname = AdminUserName, EmailAddress = emailAddress, PhoneNumber = phoneNumber, Roles = new List<UserRole>(), OrganizationUnits = new List<ExtendedUserOrganizationUnit>() }; user.SetNormalizedNames(); return user; } public override void SetNewPasswordResetCode() { PasswordResetCode = Guid.NewGuid().ToString("N").Truncate(10).ToUpperInvariant(); } public void Unlock() { AccessFailedCount = 0; LockoutEndDateUtc = null; } public void SetSignInToken() { SignInToken = Guid.NewGuid().ToString(); SignInTokenExpireTimeUtc = Clock.Now.AddMinutes(1).ToUniversalTime(); } }
-
0
[AbpAuthorize(AppPermissions.Pages_Administration_Users_Edit)] protected virtual async Task UpdateUserAsync(CreateOrUpdateUserInput input) { var user = await UserManager.FindByIdAsync(input.User.Id.Value.ToString()); //Update user properties ObjectMapper.Map(input.User, user); //Passwords is not mapped (see mapping configuration) CheckErrors(await UserManager.UpdateAsync(user)); if (input.SetRandomPassword) { var randomPassword = await _userManager.CreateRandomPassword(); user.Password = _passwordHasher.HashPassword(user, randomPassword); input.User.Password = randomPassword; } else if (!input.User.Password.IsNullOrEmpty()) { await UserManager.InitializeOptionsAsync(AbpSession.TenantId); CheckErrors(await UserManager.ChangePasswordAsync(user, input.User.Password)); } //Update roles CheckErrors(await UserManager.SetRolesAsync(user, input.AssignedRoleNames)); //update organization units await UserManager.SetOrganizationUnitsAsync(user, input.OrganizationUnits.ToArray()); if (AbpSession.TenantId.HasValue) { await _cacheManager.GetUserOrganizationUnitIdsRecursiveCache(AbpSession.GetTenantId()).ClearAsync(); await _cacheManager.GetUserOrganizationUnitIdsCache(AbpSession.GetTenantId()).ClearAsync(); await _cacheManager.GetUserOrganizationUnitsRecursiveCache(AbpSession.GetTenantId()).ClearAsync(); } if (input.SendActivationEmail) { user.SetNewEmailConfirmationCode(); await _userEmailer.SendEmailActivationLinkAsync( user, AppUrlService.CreateEmailActivationUrlFormat(AbpSession.TenantId), input.User.Password ); } }
UserManager custom implementations:
public override Task<IdentityResult> SetRolesAsync(User user, string[] roleNames) { if (user.Name == "admin" && !roleNames.Contains(StaticRoleNames.Host.Admin)) { throw new UserFriendlyException(L("AdminRoleCannotRemoveFromAdminUser")); } return base.SetRolesAsync(user, roleNames); }
-
0
Hi,
Thanks. What does
AuditExpansion
attribute do ? -
0
The AuditExpansion attribute is used to provide extra context for certain properties in the UI. On Application start-up, a dictionary is built based on these attributes, so the endpoint that fetches the data knows where to fetch context. So if an entity has a UserId with a value of 5 that would be displayed as
5 (UserName)
That being said, The issue is happening without the attribute as well, and the duplicate properties are inserted in the DB on save, before we even attempt to fetch it for the front-end. at this point, no action has been taken yet due to the attribute
-
0
Hi @dirkvr
In that case, is it possible to share your project via email ([email protected]) so we can reproduce this problem ?
Thanks,
-
0
The project should be in your inbox, Please let us know if you require additional information
-
0
Hi @dirkvr
Thanks, we got the project and will reply back via email.