Prerequisites
Please answer the following questions before submitting an issue. YOU MAY DELETE THE PREREQUISITES SECTION.
- What is your product version?
- 10.3
- What is your product type (Angular or MVC)?
- Angular
- What is product framework type (.net framework or .net core)?
- .Net core
If issue related with ABP Framework
- What is ABP Framework version?
- 6.3
Multitenancy ENABLED
Question
I have a strange issue in the UserAppService class.
I have added a new custom property to the User class that needs to get set during update and creation of users. The issue is that in order to assign the right values for the custom property I need to get some data from the database created by the host. To avoid too much polution of the create and update user methods I have extracted the functionality to a private async method inside the UserAppService class.
While entering UpdateUserAsync() _unitOfWorkManager.Current.GetTenantId()
returns the correct tenantId but after my private method is called the same statement returns null.
This is what I have done inside the private method: Attempt #1 Assuming tenant ID will be reset to AbpSession.GetTenantId() value after using is complete (it is not)
private async Task DoCustomStuffAsync(CreateOrUpdateUserInput input, User user)
{
if(user.CustomProp == null)
return;
using (_unitOfWorkManager.Current.SetTenantId(null))
{
// Getting host data *** WORKS
}
}
Attempt #2 forcing new UOW
private async Task DoCustomStuffAsync(CreateOrUpdateUserInput input, User user)
{
if(user.CustomProp == null)
return;
using(var uow = _unitOfWorkManager.Begin())
using (_unitOfWorkManager.Current.SetTenantId(null))
{
// Getting host data *** WORKS
uow.Complete();
}
}
Attempt #2 Forcing new transaction scope
private async Task DoCustomStuffAsync(CreateOrUpdateUserInput input, User user)
{
if(user.CustomProp == null)
return;
using(var uow = _unitOfWorkManager.Begin(RequiresNew))
using (_unitOfWorkManager.Current.SetTenantId(null))
{
// Getting host data *** WORKS
uow.Complete();
}
}
Attempt #3 Manual dispose of uow scope
private async Task DoCustomStuffAsync(CreateOrUpdateUserInput input, User user)
{
if(user.CustomProp == null)
return;
using(_unitOfWorkManager.Begin())
using (var uow = _unitOfWorkManager.Current.SetTenantId(null))
{
// Getting host data *** WORKS
uow.Dispose(); //Should not be nessesary
}
}
Attempt #4 Marking methods (and class) with UOW
[UnitOfWork]
public async Task UpdateUserAsync(....)
[UnitOfWork]
private async Task DoCustomStuffAsync(CreateOrUpdateUserInput input, User user)
{
if(user.CustomProp == null)
return;
using (var uow = _unitOfWorkManager.Current.SetTenantId(null))
{
// Getting host data *** WORKS
}
}
What am I doing wrong here? I can obviously set the tenantID again in the UpdateUserAsync method but that is not a good way IMO.
Best regards
4 Answer(s)
-
0
Hi @SorenRomer
Yes, this shouldn't work like that. Could you share your main method which calls DoCustomStuffAsync as well ? We can try to reproduce this on our side.
Thanks,
-
0
Hi @ismcagdas, Sure thing, I kept the
DoCustomStuffAsync
naming:[UnitOfWork] [AbpAuthorize(AppPermissions.Pages_Administration_Users_Edit)] protected virtual async Task UpdateUserAsync(CreateOrUpdateUserInput input) { Debug.Assert(input.User.Id != null, "input.User.Id should be set."); var user = await UserManager.GetUserByIdAsync(input.User.Id.Value); //Update user properties ObjectMapper.Map(input.User, user); //Passwords is not mapped (see mapping configuration) // CUSTOM CODE // _unitOfWorkManager.Current.GetTenantId() is **1** at this point. if (input.AssignedCourseId != null && AbpSession.MultiTenancySide != Abp.MultiTenancy.MultiTenancySides.Host) { await DoCustomStuffAsync(input, user); } // _unitOfWorkManager.Current.GetTenantId() is **null** at this point. 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 (input.SendActivationEmail) { user.SetNewEmailConfirmationCode(); await _userEmailer.SendEmailActivationLinkAsync( user, AppUrlService.CreateEmailActivationUrlFormat(AbpSession.TenantId), input.User.Password ); } }
Here is a simplified version of the custom method:
[UnitOfWork] private async Task DoCustomStuffAsync(CreateOrUpdateUserInput input, User user) { if (input.CustomEnityId != null) { using (_unitOfWorkManager.Current.SetTenantId(null)) { // GETTING GENERAL DATA FROM HOST var _customEntities = _customEntityRepository.GetAllIncluding(c => c.CourseFlows).FirstOrDefault(c => c.Id == (int)input.AssignedCourseId); // CODE REMOVED FOR CLARITY } EventBus.Trigger(new CustomEvent { Id = user.Id}); } else { Logger.Info($"User {user.UserName} is not assigned to a custom entity"); } }
Thanks in advance
-
0
I found issue - sort of.... If I avoid calling
EventBus.Trigger(new CustomEvent { Id = user.Id});
the tenantId is reset to the correct value after the using is disposed. Callingawait EventBus.TriggerAsync(new CustomEvent { Id = user.Id});
results in the same behaviour.I don't understand this behaviour of the EventBus? Why does it stick to tenantid null when it is being called after the using is disposed?
Wrapping the call to the eventbus inside its own using solves the problem:
using (_unitOfWorkManager.Current.SetTenantId(AbpSession.GetTenantId())) { await EventBus.TriggerAsync(new CustomEvent { Id = user.Id}); }
-
0
@SorenRomer
I think it is becasue you are mixing async and sync code. If using
EventBus.TriggerAsync
works, I suggest you to use it.