diff --git a/AsbCloudDb/Model/AsbCloudDbContext.cs b/AsbCloudDb/Model/AsbCloudDbContext.cs index afce3792..a43f55c1 100644 --- a/AsbCloudDb/Model/AsbCloudDbContext.cs +++ b/AsbCloudDb/Model/AsbCloudDbContext.cs @@ -361,6 +361,6 @@ namespace AsbCloudDb.Model { var sql = $"REFRESH MATERIALIZED VIEW {materializedViewName};"; return Database.ExecuteSqlRawAsync(sql, token); - } + } } } diff --git a/AsbCloudDb/Model/IAsbCloudDbContext.cs b/AsbCloudDb/Model/IAsbCloudDbContext.cs index 4eac95f9..446a7656 100644 --- a/AsbCloudDb/Model/IAsbCloudDbContext.cs +++ b/AsbCloudDb/Model/IAsbCloudDbContext.cs @@ -67,9 +67,7 @@ namespace AsbCloudDb.Model Task RefreshMaterializedViewAsync(CancellationToken token) where TEntity : class; int SaveChanges(); int SaveChanges(bool acceptAllChangesOnSuccess); - Task SaveChangesAsync(CancellationToken cancellationToken); - DbSet Set(string name) where TEntity : class; - DbSet Set() where TEntity : class; - + Task SaveChangesAsync(CancellationToken cancellationToken); + DbSet Set() where TEntity : class; } } \ No newline at end of file diff --git a/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj b/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj index 1a8e2432..bf643cc0 100644 --- a/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj +++ b/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj @@ -42,7 +42,7 @@ - + diff --git a/AsbCloudInfrastructure/Repository/UserRepository.cs b/AsbCloudInfrastructure/Repository/UserRepository.cs index efc0db16..8ddbc5f3 100644 --- a/AsbCloudInfrastructure/Repository/UserRepository.cs +++ b/AsbCloudInfrastructure/Repository/UserRepository.cs @@ -1,9 +1,7 @@ using AsbCloudApp.Data; using AsbCloudApp.Exceptions; using AsbCloudApp.Repositories; -using AsbCloudDb; using AsbCloudDb.Model; -using AsbCloudInfrastructure.EfCache; using Mapster; using Microsoft.EntityFrameworkCore; using System; @@ -11,6 +9,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Memory; namespace AsbCloudInfrastructure.Repository { @@ -29,10 +28,12 @@ namespace AsbCloudInfrastructure.Repository dst => dst.Files, dst => dst.RelationUsersUserRoles) .Config; - - public UserRepository(IAsbCloudDbContext dbContext, IUserRoleRepository userRoleRepository) { + private readonly IMemoryCache memoryCache; + public UserRepository(IAsbCloudDbContext dbContext, IUserRoleRepository userRoleRepository, IMemoryCache memoryCache) + { this.dbContext = dbContext; this.userRoleRepository = userRoleRepository; + this.memoryCache = memoryCache; } public async Task InsertAsync(UserExtendedDto dto, CancellationToken token) @@ -58,31 +59,36 @@ namespace AsbCloudInfrastructure.Repository public async Task> GetAllAsync(CancellationToken token) { - var dtos = (await GetCacheUserAsync(token)).ToList(); - if (dtos is null) + var users = await GetCacheUserAsync(token); + if (users is null) return Enumerable.Empty(); - - for (var i = 0; i < dtos.Count; i++) - dtos[i].RoleNames = GetRolesNamesByIdUser(dtos[i].Id); + var dtos = new List(); + foreach(var user in users) + { + var dto = Convert(user); + dto.RoleNames = GetRolesNamesByIdUser(user.Id); + dtos.Add(dto); + }; return dtos; } + public UserExtendedDto? GetOrDefault(int id) { - var dto = GetCacheUser().FirstOrDefault(u => u.Id == id); - if (dto is null) + var user = GetCacheUser().FirstOrDefault(u => u.Id == id); + if (user is null) return null; - var entity = Convert(dto); + var dto = Convert(user); dto.RoleNames = GetRolesNamesByIdUser(dto.Id); return dto; } public async Task GetOrDefaultAsync(int id, CancellationToken token) - { - var dto = (await GetCacheUserAsync(token)).FirstOrDefault(u => u.Id == id); - if (dto is null) + { + var user = (await GetCacheUserAsync(token)).FirstOrDefault(u => u.Id == id); + if (user is null) return null; - + var dto = Convert(user); dto.RoleNames = GetRolesNamesByIdUser(dto.Id); return dto; } @@ -90,11 +96,13 @@ namespace AsbCloudInfrastructure.Repository public async Task UpdateAsync(UserExtendedDto dto, CancellationToken token) { if (dto.Id <= 1) - throw new ArgumentInvalidException($"Invalid id {dto.Id}. You can't edit this user.", nameof(dto)); + throw new ArgumentInvalidException + ($"Invalid id {dto.Id}. You can't edit this user.", nameof(dto)); var oldUser = (await GetCacheUserAsync(token)).FirstOrDefault(u => u.Id == dto.Id); if (oldUser is null) - return 0; + throw new ArgumentInvalidException + ($"Invalid id {dto.Id}. You can't edit this user.", nameof(dto)); if (oldUser.Login != dto.Login) await AssertLoginIsBusyAsync(dto.Login, token); @@ -102,25 +110,43 @@ namespace AsbCloudInfrastructure.Repository var userRoles = await userRoleRepository.GetByNamesAsync(dto.RoleNames, token).ConfigureAwait(false); await UpdateRolesCacheForUserAsync(dto.Id, userRoles, token); - var entity = Convert(dto); - - var result = dbContext.Users.Upsert(entity); + var entity = dbContext.Users.FirstOrDefault(u => u.Id == dto.Id); + if (entity is null) + throw new ArgumentInvalidException + ($"Invalid id {dto.Id}. You can't edit this user.", nameof(dto)); + entity.Id = dto.Id; + entity.Login = dto.Login; + entity.Name = dto.Name; + entity.Email = dto.Email; + entity.Phone = dto.Phone; + entity.Surname = dto.Surname; + entity.Patronymic = dto.Patronymic; + entity.Position = dto.Position; + entity.IdCompany = dto.IdCompany; + entity.IdState = dto.IdState; await dbContext.SaveChangesAsync(token); DropCacheUsers(); - return result.Entity.Id; + return entity.Id; } public async Task DeleteAsync(int id, CancellationToken token) { - var dto = (await GetCacheUserAsync(token)).FirstOrDefault(u => u.Id == id); - if (dto is null) - return 0; - - var entity = Convert(dto); - var result = dbContext.Users.Remove(entity); - await dbContext.SaveChangesAsync(token); - DropCacheUsers(); - return result.Entity.Id; + var user = (await GetCacheUserAsync(token)).FirstOrDefault(u => u.Id == id); + if (user is null) + throw new ArgumentInvalidException + ($"Invalid id {id}. You can't edit this user.", nameof(id)); + var query = dbContext + .Users + .Where(u => u.Id == id); + if (query.Any()) + { + var result = dbContext.Users.Remove(query.First()); + await dbContext.SaveChangesAsync(token); + DropCacheUsers(); + return result.Entity.Id; + } + throw new ArgumentInvalidException + ($"Invalid id {id}. You can't edit this user.", nameof(id)); } public IEnumerable GetRolesByIdUser(int idUser, int nestedLevel = 0) @@ -156,9 +182,14 @@ namespace AsbCloudInfrastructure.Repository } private IEnumerable GetRolesNamesByIdUser(int idUser) - => GetRolesByIdUser(idUser, 7) - .Select(r => r.Caption) - .Distinct(); + { + var userRoles = GetRolesByIdUser(idUser, 7) + .Select(r => r.Caption) + .Distinct(); + if (userRoles.Any()) + return userRoles; + return Enumerable.Empty(); + } private async Task AssertLoginIsBusyAsync(string login, CancellationToken token) { @@ -167,63 +198,87 @@ namespace AsbCloudInfrastructure.Repository if (existingUserDto is not null) throw new ArgumentInvalidException($"Login {login} is busy by {existingUserDto.MakeDisplayName()}, id{existingUserDto.Id}", nameof(login)); + } + + + private IEnumerable GetCachRelationUserUserRoleCacheTag() + { + var cache = memoryCache.GetOrCreate(relationUserUserRoleCacheTag, cacheEntry => + { + cacheEntry.AbsoluteExpirationRelativeToNow = cacheObsolence; + cacheEntry.SlidingExpiration = cacheObsolence; + var query = dbContext.RelationUserUserRoles + .Include(r => r.UserRole) + .Include(r => r.User); + var entities = query.ToArray(); + return entities; + }); + return cache!; + } + private void DropCacheRelationUserUserRoleCacheTag() + { + memoryCache.Remove(relationUserUserRoleCacheTag); } - private Task> GetCacheUserAsync(CancellationToken token) - => dbContext.Users - .Include(r => r.Company) - .Include(r => r.RelationUsersUserRoles) - .FromCacheAsync(userCacheTag, cacheObsolence, Convert, token); - private IEnumerable GetCacheUser() - => dbContext.Users - .Include(r => r.Company) - .Include(r => r.RelationUsersUserRoles) - .FromCache(userCacheTag, cacheObsolence, Convert); - private void DropCacheUsers() - => dbContext.Users.DropCache(userCacheTag); - - private Task> GetCacheRelationUserUserRoleAsync(CancellationToken token) - => dbContext.RelationUserUserRoles - .Include(r => r.UserRole) - .Include(r => r.User) - .FromCacheAsync(relationUserUserRoleCacheTag, cacheObsolence, token); - private IEnumerable GetCachRelationUserUserRoleCacheTag() - => dbContext.RelationUserUserRoles - .Include(r => r.UserRole) - .Include(r => r.User) - .FromCache(relationUserUserRoleCacheTag, cacheObsolence); - private void DropCacheRelationUserUserRoleCacheTag() - => dbContext.RelationUserUserRoles.DropCache(relationUserUserRoleCacheTag); - - private async Task UpdateRolesCacheForUserAsync(int idUser, IEnumerable newRoles, CancellationToken token) + private async Task UpdateRolesCacheForUserAsync(int idUser, IEnumerable roleDtos, CancellationToken token) { - var relations = (await GetCacheRelationUserUserRoleAsync(token)).Where(r => r.IdUser == idUser); + var relations = dbContext.RelationUserUserRoles.Where(r => r.IdUser == idUser); dbContext.RelationUserUserRoles.RemoveRange(relations); - - if (newRoles?.Any() == true) - await dbContext.RelationUserUserRoles.AddRangeAsync(newRoles.Select(role => new RelationUserUserRole - { - IdUser = idUser, - IdUserRole = role.Id - }), token).ConfigureAwait(false); - + var entityRoles = roleDtos.Select(role => new RelationUserUserRole + { + IdUser = idUser, + IdUserRole = role.Id + }); + dbContext.RelationUserUserRoles.AddRange(entityRoles); await dbContext.SaveChangesAsync(token); DropCacheRelationUserUserRoleCacheTag(); } - protected virtual User Convert(UserExtendedDto dto) + private void DropCacheUsers() + => memoryCache.Remove(userCacheTag); + + private IEnumerable GetCacheUser() + { + var cache = memoryCache.GetOrCreate(userCacheTag, cacheEntry => + { + cacheEntry.AbsoluteExpirationRelativeToNow = cacheObsolence; + cacheEntry.SlidingExpiration = cacheObsolence; + var query = dbContext.Users + .Include(r => r.Company) + .Include(r => r.RelationUsersUserRoles); + var entities = query.ToArray(); + return entities; + }); + return cache!; + } + + private Task> GetCacheUserAsync(CancellationToken token) + { + var cache = memoryCache.GetOrCreateAsync(userCacheTag, async (cacheEntry) => + { + cacheEntry.AbsoluteExpirationRelativeToNow = cacheObsolence; + cacheEntry.SlidingExpiration = cacheObsolence; + var query = dbContext.Users + .Include(r => r.Company) + .Include(r => r.RelationUsersUserRoles); + var entities = await query.ToArrayAsync(token); + return entities.AsEnumerable(); + }); + return cache!; + } + + protected User Convert(UserExtendedDto dto) { var entity = dto.Adapt(userTypeAdapterConfig); if (string.IsNullOrEmpty(entity.PasswordHash)) entity.PasswordHash = dbContext.Users.FirstOrDefault(u => u.Id == dto.Id)?.PasswordHash; return entity; } - protected virtual UserExtendedDto Convert(User entity) { var dto = entity.Adapt(); return dto; - } + } } #nullable disable }