diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityRoleRepository.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityRoleRepository.cs index 25b33936ba..d5f51ae71b 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityRoleRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityRoleRepository.cs @@ -14,6 +14,15 @@ public interface IIdentityRoleRepository : IBasicRepository CancellationToken cancellationToken = default ); + Task> GetListWithUserCountAsync( + string sorting = null, + int maxResultCount = int.MaxValue, + int skipCount = 0, + string filter = null, + bool includeDetails = false, + CancellationToken cancellationToken = default + ); + Task> GetListAsync( string sorting = null, int maxResultCount = int.MaxValue, diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityUserRepository.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityUserRepository.cs index 7aff7c5584..29d6af4e3f 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityUserRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityUserRepository.cs @@ -119,11 +119,6 @@ public interface IIdentityUserRepository : IBasicRepository CancellationToken cancellationToken = default ); - Task> GetCountAsync( - Guid[] roleIds, - CancellationToken cancellationToken = default - ); - Task FindByTenantIdAndUserNameAsync( [NotNull] string userName, Guid? tenantId, diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleWithUserCount.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleWithUserCount.cs new file mode 100644 index 0000000000..d706d4aa8e --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleWithUserCount.cs @@ -0,0 +1,16 @@ +using System; + +namespace Volo.Abp.Identity; + +public class IdentityRoleWithUserCount +{ + public IdentityRole Role { get; set; } + + public long UserCount { get; set; } + + public IdentityRoleWithUserCount(IdentityRole role, long userCount) + { + Role = role; + UserCount = userCount; + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/RoleWithUserCount.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/RoleWithUserCount.cs deleted file mode 100644 index 4030ce3fad..0000000000 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/RoleWithUserCount.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace Volo.Abp.Identity; - -public class RoleWithUserCount -{ - public Guid RoleId { get; set; } - - public long UserCount { get; set; } -} diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityRoleRepository.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityRoleRepository.cs index c659dff653..57843adad0 100644 --- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityRoleRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityRoleRepository.cs @@ -28,6 +28,30 @@ public class EfCoreIdentityRoleRepository : EfCoreRepository r.NormalizedName == normalizedRoleName, GetCancellationToken(cancellationToken)); } + public virtual async Task> GetListWithUserCountAsync( + string sorting = null, + int maxResultCount = int.MaxValue, + int skipCount = 0, + string filter = null, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + var roles = await GetListInternalAsync(sorting, maxResultCount, skipCount, filter, includeDetails, cancellationToken: cancellationToken); + + var roleIds = roles.Select(x => x.Id).ToList(); + var userCount = await (await GetDbContextAsync()).Set() + .Where(userRole => roleIds.Contains(userRole.RoleId)) + .GroupBy(userRole => userRole.RoleId) + .Select(x => new + { + RoleId = x.Key, + Count = x.Count() + }) + .ToListAsync(GetCancellationToken(cancellationToken)); + + return roles.Select(role => new IdentityRoleWithUserCount(role, userCount.FirstOrDefault(x => x.RoleId == role.Id)?.Count ?? 0)).ToList(); + } + public virtual async Task> GetListAsync( string sorting = null, int maxResultCount = int.MaxValue, @@ -36,14 +60,7 @@ public class EfCoreIdentityRoleRepository : EfCoreRepository x.Name.Contains(filter) || - x.NormalizedName.Contains(filter)) - .OrderBy(sorting.IsNullOrWhiteSpace() ? nameof(IdentityRole.Name) : sorting) - .PageBy(skipCount, maxResultCount) - .ToListAsync(GetCancellationToken(cancellationToken)); + return await GetListInternalAsync(sorting , maxResultCount, skipCount, filter, includeDetails, cancellationToken); } public virtual async Task> GetListAsync( @@ -75,6 +92,24 @@ public class EfCoreIdentityRoleRepository : EfCoreRepository> GetListInternalAsync( + string sorting = null, + int maxResultCount = int.MaxValue, + int skipCount = 0, + string filter = null, + bool includeDetails = true, + CancellationToken cancellationToken = default) + { + return await (await GetDbSetAsync()) + .IncludeDetails(includeDetails) + .WhereIf(!filter.IsNullOrWhiteSpace(), + x => x.Name.Contains(filter) || + x.NormalizedName.Contains(filter)) + .OrderBy(sorting.IsNullOrWhiteSpace() ? nameof(IdentityRole.Name) : sorting) + .PageBy(skipCount, maxResultCount) + .ToListAsync(GetCancellationToken(cancellationToken)); + } + [Obsolete("Use WithDetailsAsync")] public override IQueryable WithDetails() { diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs index 9aa6b133c5..8e6e9cba6c 100644 --- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs @@ -263,25 +263,6 @@ public class EfCoreIdentityUserRepository : EfCoreRepository> GetCountAsync(Guid[] roleIds, CancellationToken cancellationToken = default) - { - var users = await (await GetDbSetAsync()) - .AsNoTracking() - .Where(user => user.Roles.Any(role => roleIds.Contains(role.RoleId))).IncludeDetails().ToListAsync(GetCancellationToken(cancellationToken)); - - var result = new List(); - foreach (var roleId in roleIds) - { - result.Add(new RoleWithUserCount - { - RoleId = roleId, - UserCount = users.Count(t => t.Roles.Any(role => role.RoleId == roleId)) - }); - } - - return result; - } - public virtual async Task> GetOrganizationUnitsAsync( Guid id, bool includeDetails = false, diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityRoleRepository.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityRoleRepository.cs index dfffe41116..e94bacb92c 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityRoleRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityRoleRepository.cs @@ -28,7 +28,7 @@ public class MongoIdentityRoleRepository : MongoDbRepository r.NormalizedName == normalizedRoleName, GetCancellationToken(cancellationToken)); } - public virtual async Task> GetListAsync( + public virtual async Task> GetListWithUserCountAsync( string sorting = null, int maxResultCount = int.MaxValue, int skipCount = 0, @@ -36,14 +36,37 @@ public class MongoIdentityRoleRepository : MongoDbRepository x.Name.Contains(filter) || - x.NormalizedName.Contains(filter)) - .OrderBy(sorting.IsNullOrWhiteSpace() ? nameof(IdentityRole.Name) : sorting) - .As>() - .PageBy>(skipCount, maxResultCount) + var roles = await GetListInternalAsync(sorting, maxResultCount, skipCount, filter, includeDetails, cancellationToken: cancellationToken); + var roleIds = roles.Select(x => x.Id).ToList(); + var userCount = await (await GetMongoQueryableAsync(cancellationToken)) + .Where(user => user.Roles.Any(role => roleIds.Contains(role.RoleId))) + .SelectMany(user => user.Roles) + .GroupBy(userRole => userRole.RoleId) + .Select(x => new + { + RoleId = x.Key, + Count = x.Count() + }) .ToListAsync(GetCancellationToken(cancellationToken)); + + return roles.Select(role => new IdentityRoleWithUserCount(role, userCount.FirstOrDefault(x => x.RoleId == role.Id)?.Count ?? 0)).ToList(); + } + + public virtual async Task> GetListAsync( + string sorting = null, + int maxResultCount = int.MaxValue, + int skipCount = 0, + string filter = null, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + return await GetListInternalAsync( + sorting, + maxResultCount, + skipCount, + filter, + includeDetails, + cancellationToken); } public virtual async Task> GetListAsync( @@ -75,4 +98,22 @@ public class MongoIdentityRoleRepository : MongoDbRepository>() .LongCountAsync(GetCancellationToken(cancellationToken)); } + + protected virtual async Task> GetListInternalAsync( + string sorting = null, + int maxResultCount = int.MaxValue, + int skipCount = 0, + string filter = null, + bool includeDetails = true, + CancellationToken cancellationToken = default) + { + return await (await GetMongoQueryableAsync(cancellationToken)) + .WhereIf(!filter.IsNullOrWhiteSpace(), + x => x.Name.Contains(filter) || + x.NormalizedName.Contains(filter)) + .OrderBy(sorting.IsNullOrWhiteSpace() ? nameof(IdentityRole.Name) : sorting) + .As>() + .PageBy>(skipCount, maxResultCount) + .ToListAsync(GetCancellationToken(cancellationToken)); + } } diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs index b4a8d935fb..d478687f7b 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs @@ -268,26 +268,7 @@ public class MongoIdentityUserRepository : MongoDbRepository>(minModifitionTime != null, p => p.LastModificationTime >= minModifitionTime) .LongCountAsync(GetCancellationToken(cancellationToken)); } - - public async Task> GetCountAsync(Guid[] roleIds, CancellationToken cancellationToken = default) - { - var users = await (await GetMongoQueryableAsync(cancellationToken)) - .Where(user => user.Roles.Any(role => roleIds.Contains(role.RoleId))) - .ToListAsync(GetCancellationToken(cancellationToken)); - - var result = new List(); - foreach (var roleId in roleIds) - { - result.Add(new RoleWithUserCount - { - RoleId = roleId, - UserCount = users.Count(t => t.Roles.Any(role => role.RoleId == roleId)) - }); - } - - return result; - } - + public virtual async Task> GetUsersInOrganizationUnitAsync( Guid organizationUnitId, CancellationToken cancellationToken = default) @@ -318,7 +299,7 @@ public class MongoIdentityUserRepository : MongoDbRepository ou.Code.StartsWith(code)) .Select(ou => ou.Id) .ToListAsync(cancellationToken); - + return await (await GetMongoQueryableAsync(cancellationToken)) .Where(u => u.OrganizationUnits.Any(uou => organizationUnitIds.Contains(uou.OrganizationUnitId))) .ToListAsync(cancellationToken); diff --git a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityRoleRepository_Tests.cs b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityRoleRepository_Tests.cs index 795ba45f42..c34e9ad56c 100644 --- a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityRoleRepository_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityRoleRepository_Tests.cs @@ -66,4 +66,16 @@ public abstract class IdentityRoleRepository_Tests : AbpIdentity role.Claims.ShouldNotBeNull(); role.Claims.Any().ShouldBeTrue(); } + + [Fact] + public async Task GetListWithUserCountAsync() + { + var roles = await RoleRepository.GetListWithUserCountAsync(); + + roles.Count.ShouldBe(4); + roles.ShouldContain(r => r.Role.Name == "admin" && r.UserCount == 2); + roles.ShouldContain(r => r.Role.Name == "moderator" && r.UserCount == 1); + roles.ShouldContain(r => r.Role.Name == "supporter" && r.UserCount == 2); + roles.ShouldContain(r => r.Role.Name == "manager" && r.UserCount == 1); + } } diff --git a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityUserRepository_Tests.cs b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityUserRepository_Tests.cs index 453917b08f..cb08fad60d 100644 --- a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityUserRepository_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityUserRepository_Tests.cs @@ -135,16 +135,6 @@ public abstract class IdentityUserRepository_Tests : AbpIdentity (await UserRepository.GetCountAsync("n")).ShouldBeGreaterThan(1); (await UserRepository.GetCountAsync("undefined-username")).ShouldBe(0); } - - [Fact] - public async Task GetCountAsync_With_RoleIds() - { - var roleWithUserCounts = await UserRepository.GetCountAsync(roleIds: new []{ TestData.RoleModeratorId, TestData.RoleSupporterId }); - - roleWithUserCounts.Count.ShouldBe(2); - roleWithUserCounts.ShouldContain(e => e.RoleId == TestData.RoleModeratorId && e.UserCount == 1); - roleWithUserCounts.ShouldContain(e => e.RoleId == TestData.RoleSupporterId && e.UserCount == 2); - } [Fact] public async Task GetUsersInOrganizationUnitAsync()