using Mapster; using Microsoft.EntityFrameworkCore; using Persistence.Database.Model; using Persistence.Models; using Persistence.Models.Requests; using Persistence.Repositories; using UuidExtensions; namespace Persistence.Repository.Repositories; public class ChangeLogRepository : IChangeLogRepository { private DbContext db; public ChangeLogRepository(DbContext db) { this.db = db; } public async Task AddRange(Guid idAuthor, Guid idDiscriminator, IEnumerable dtos, CancellationToken token) { var entities = new List(); foreach (var dto in dtos) { var entity = CreateEntityFromDto(idAuthor, idDiscriminator, dto); entities.Add(entity); } db.Set().AddRange(entities); var result = await db.SaveChangesAsync(token); return result; } public async Task MarkAsDeleted(Guid idEditor, IEnumerable ids, CancellationToken token) { var query = db.Set() .Where(s => ids.Contains(s.Id)) .Where(s => s.Obsolete == null); if (query.Count() != ids.Count()) { throw new ArgumentException("Count of active items not equal count of ids", nameof(ids)); } var entities = await query.ToArrayAsync(token); var result = await MarkAsObsolete(idEditor, entities, token); return result; } public async Task MarkAsDeleted(Guid idEditor, Guid idDiscriminator, CancellationToken token) { var query = db.Set() .Where(s => s.IdDiscriminator == idDiscriminator) .Where(e => e.Obsolete == null); var entities = await query.ToArrayAsync(token); var result = await MarkAsObsolete(idEditor, entities, token); return result; } private async Task MarkAsObsolete(Guid idEditor, IEnumerable entities, CancellationToken token) { var updateTime = DateTimeOffset.UtcNow; foreach (var entity in entities) { entity.Obsolete = updateTime; entity.IdEditor = idEditor; } return await db.SaveChangesAsync(token); } public async Task ClearAndAddRange(Guid idAuthor, Guid idDiscriminator, IEnumerable dtos, CancellationToken token) { var result = 0; using var transaction = await db.Database.BeginTransactionAsync(token); result += await MarkAsDeleted(idAuthor, idDiscriminator, token); result += await AddRange(idAuthor, idDiscriminator, dtos, token); await transaction.CommitAsync(token); return result; } public async Task UpdateRange(Guid idEditor, IEnumerable dtos, CancellationToken token) { var dbSet = db.Set(); var updatedIds = dtos.Select(d => d.Id); var updatedEntities = dbSet .Where(s => updatedIds.Contains(s.Id)) .ToDictionary(s => s.Id); var result = 0; using var transaction = await db.Database.BeginTransactionAsync(token); try { foreach (var dto in dtos) { var updatedEntity = updatedEntities.GetValueOrDefault(dto.Id); if (updatedEntity is null) { throw new ArgumentException($"Entity with id = {dto.Id} doesn't exist in Db", nameof(dto)); } var newEntity = CreateEntityFromDto(idEditor, updatedEntity.IdDiscriminator, dto); dbSet.Add(newEntity); updatedEntity.IdNext = newEntity.Id; updatedEntity.Obsolete = DateTimeOffset.UtcNow; updatedEntity.IdEditor = idEditor; } result = await db.SaveChangesAsync(token); await transaction.CommitAsync(token); return result; } catch { await transaction.RollbackAsync(token); throw; } } public async Task> GetByDate( Guid idDiscriminator, DateTimeOffset momentUtc, SectionPartRequest filterRequest, PaginationRequest paginationRequest, CancellationToken token) { var query = CreateQuery(idDiscriminator); query = query.Apply(momentUtc); query = query.Apply(filterRequest); var result = await query.ApplyPagination(paginationRequest, Convert, token); return result; } private IQueryable CreateQuery(Guid idDiscriminator) { var query = db.Set().Where(e => e.IdDiscriminator == idDiscriminator); return query; } public async Task> GetChangeLogForInterval(Guid idDiscriminator, DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token) { var query = db.Set().Where(s => s.IdDiscriminator == idDiscriminator); var min = new DateTimeOffset(dateBegin.ToUniversalTime().Date, TimeSpan.Zero); var max = new DateTimeOffset(dateEnd.ToUniversalTime().Date, TimeSpan.Zero); var createdQuery = query.Where(e => e.Creation >= min && e.Creation <= max); var editedQuery = query.Where(e => e.Obsolete != null && e.Obsolete >= min && e.Obsolete <= max); query = createdQuery.Union(editedQuery); var entities = await query.ToArrayAsync(token); var dtos = entities.Select(e => e.Adapt()); return dtos; } public async Task> GetDatesChange(Guid idDiscriminator, CancellationToken token) { var query = db.Set().Where(e => e.IdDiscriminator == idDiscriminator); var datesCreateQuery = query .Select(e => e.Creation) .Distinct(); var datesCreate = await datesCreateQuery.ToArrayAsync(token); var datesUpdateQuery = query .Where(e => e.Obsolete != null) .Select(e => e.Obsolete!.Value) .Distinct(); var datesUpdate = await datesUpdateQuery.ToArrayAsync(token); var dates = Enumerable.Concat(datesCreate, datesUpdate); var datesOnly = dates .Select(d => new DateOnly(d.Year, d.Month, d.Day)) .Distinct() .OrderBy(d => d); return datesOnly; } private ChangeLog CreateEntityFromDto(Guid idAuthor, Guid idDiscriminator, DataWithWellDepthAndSectionDto dto) { var entity = new ChangeLog() { Id = Uuid7.Guid(), Creation = DateTimeOffset.UtcNow, IdAuthor = idAuthor, IdDiscriminator = idDiscriminator, IdEditor = idAuthor, Value = dto.Value, IdSection = dto.IdSection, DepthStart = dto.DepthStart, DepthEnd = dto.DepthEnd, }; return entity; } public async Task> GetGtDate(Guid idDiscriminator, DateTimeOffset dateBegin, CancellationToken token) { var date = dateBegin.ToUniversalTime(); var query = this.db.Set() .Where(e => e.IdDiscriminator == idDiscriminator) .Where(e => e.Creation >= date || e.Obsolete >= date); var entities = await query.ToArrayAsync(token); var dtos = entities.Select(Convert); return dtos; } public async Task GetDatesRange(Guid idDiscriminator, CancellationToken token) { var query = db.Set() .Where(e => e.IdDiscriminator == idDiscriminator) .GroupBy(e => 1) .Select(group => new { Min = group.Min(e => e.Creation), Max = group.Max(e => (e.Obsolete.HasValue && e.Obsolete > e.Creation) ? e.Obsolete.Value : e.Creation), }); var values = await query.FirstOrDefaultAsync(token); if (values is null) { return null; } return new DatesRangeDto { From = values.Min, To = values.Max, }; } private DataWithWellDepthAndSectionDto Convert(ChangeLog entity) => entity.Adapt(); }