Hi Aaron,
Do you mean "A host user in the admin role"?
Yes.
What does it mean to apply access ... for users in the tenant admin role?
A host user in the "Admin" role has functionality to allow users in the host "Tenant Admin" role to "Login as this tenant" for selected tenants.
Who approves the application?
A host user in the "Admin" role. Hope this makes sense.
Hi Aaron, This can be closed. The DisplayName property is updated from a data provider import and set back to original state so the duplicate read is actually correct but because it is in the same unit of work it had not been persisted to the database and the database query was reading the previous state. If that makes sense. Thanks for your help, your pointers put me in the right direction.
Are you modifying and reading in the same unit of work?
That's something to work with. Let me change the position of this line:
_unitOfWorkManager.Current.SaveChanges();
and see where it gets us.
The idea of a repro project is fairly painful but that's where I'll go next....
Ok, let's do it your way. That code is as follows:
exec sp_executesql N'SELECT [n].[Id], [n].[CreationTime], [n].[CreatorUserId], [n].[DeleterUserId], [n].[DeletionTime], [n].[DisplayName], [n].[ExtensionData], [n].[IsDeleted], [n].[LastModificationTime], [n].[LastModifierUserId], [n].[OrganizationUnitId], [n].[TenantId]
FROM [NcEntity] AS [n]
WHERE ((([n].[IsDeleted] = 0) OR ([n].[IsDeleted] <> @__IsSoftDeleteFilterEnabled_0)) AND (([n].[TenantId] = @__CurrentTenantId_1) OR (CASE
WHEN [n].[TenantId] = @__CurrentTenantId_2
THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT)
END = @__IsMustHaveTenantFilterEnabled_3))) AND ([n].[OrganizationUnitId] = @__organizationUnit_Id_0)',N'@__IsSoftDeleteFilterEnabled_0 bit,@__CurrentTenantId_1 int,@__CurrentTenantId_2 int,@__IsMustHaveTenantFilterEnabled_3 bit,@__organizationUnit_Id_0 bigint',@__IsSoftDeleteFilterEnabled_0=1,@__CurrentTenantId_1=3,@__CurrentTenantId_2=3,@__IsMustHaveTenantFilterEnabled_3=1,@__organizationUnit_Id_0=63
Which is a bit pointless because the below shows us what we need: On the left hand side the value in duplicates shows as "Elizabeth" whereas a simultaneous read on the database, to the right hand side, shows there are no duplicates because the database has "Elizabeth P" and "Elizabeth W". Now do you see the problem?
Aaron, I'm an SQL guy, don't try put me off with your SQL profiler nonsense or I will send you some logs and ask you to analyse them. I put a breakpoint in my code and then run a query on my database; I then put another breakpoint to capture the values of duplicates and it contains "Elizabeth", I updated the entity on the last run (an hour ago) but the call to;
var entities = _ncEntityRepository.GetAllList(e => e.OrganizationUnitId == organizationUnit.Id);
is not reading the updated DisplayName from the last run. My priority is above average, can you help me?
Cheers, Bob
Aaron, I have to reopen this issue. The logic is ok and the updates are persisted to the database. But when this runs, one hour later;
var duplicates = entities.GroupBy(x => x.DisplayName)
.Where(g => g.Count() > 1)
.Select(y => y.Key)
.ToList();
it does not pick up the persisted changes. Is there any way I can force a read of the database?
Hi Aaron, Jeepers, creepers. That's geriatric scenility setting in too early. If you ever need a job let me know and you can hold my zimmer frame as I shuffle my way to the toilet in embarrassment. Again, thanks. My mistake, sorry for troubling you!
Thanks for your help Aaron, here's what I now have. LastModifierUserId is persisted as null even though userId value = 4. Can you see anything I'm doing wrong?
[UnitOfWork]
public virtual void MakeNcEntityDisplayNameUnique(int tenantId)
{
using (_unitOfWorkManager.Current.SetTenantId(tenantId))
{
var adminUsers = _userManager.GetUsersInRoleAsync("Admin").GetAwaiter().GetResult();
var userId = adminUsers.Select(m => m.Id).FirstOrDefault();
using (_abpSession.Use(tenantId, userId))
{
var organizationUnits = _organizationUnitRepository.GetAllList();
foreach (var organizationUnit in organizationUnits)
{
var entities = _ncEntityRepository.GetAllList(e => e.OrganizationUnitId == organizationUnit.Id);
var duplicates = entities.GroupBy(x => x.DisplayName)
.Where(g => g.Count() > 1)
.Select(y => y.Key)
.ToList();
foreach (var duplicate in duplicates)
{
foreach (var entity in _ncEntityRepository.GetAllList(e => e.DisplayName == duplicate && e.OrganizationUnitId == organizationUnit.Id))
{
var surname = entity.GetData<AttributeDataDto>("Surname").Value;
var newDisplayName = entity.DisplayName + " " + surname.Substring(0, 1);
var entityExists = _ncEntityRepository.FirstOrDefault(e => e.DisplayName == newDisplayName && e.OrganizationUnitId == organizationUnit.Id);
if (entityExists != null)
{
entity.DisplayName = entity.DisplayName + " " + surname;
}
else
{
entity.DisplayName = newDisplayName;
}
entity.LastModifierUserId = userId;
_ncEntityRepository.Update(entity);
}
}
}
}
_unitOfWorkManager.Current.SaveChanges();
return;
}
}
Hi Aaron, My Hangfire job is in the Web.Core project. Would this be the reason the unitOfWork.Current is null?
Thanks Aaron, I'm not the best with unit of work. I add code as follows:
_unitOfWorkManager.Current.SaveChanges();
return;
and get 'System.NullReferenceException: 'Object reference not set to an instance of an object.' unitOfWorkManager has been successfuly injected but there is no Current property.
But I'm sure you're right and I'll persevere with unit of work and post back when and if I can get a solution. But before I go, how do I set the userId in the unit of work?
public void MakeNcEntityDisplayNameUnique(int tenantId)
{
using (_unitOfWorkManager.Current.SetTenantId(tenantId))
{
var adminUsers = _userManager.GetUsersInRoleAsync("Admin").GetAwaiter().GetResult();
var userId = adminUsers.Select(m => m.Id).FirstOrDefault();
using (_abpSession.Use(tenantId, userId))
{
Thanks again for the pointer.