Created
August 29, 2025 06:23
-
-
Save vlapenkov/f8cc5601cb1e85dd51f2343c6c8ac637 to your computer and use it in GitHub Desktop.
BaseContext sample
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| public abstract class BaseContext : DbContext | |
| { | |
| public BaseContext(DbContextOptions options) : base(options) | |
| { | |
| } | |
| public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = new CancellationToken()) | |
| { | |
| var trackedEntities = await OnBeforeSaving(); | |
| var currentTransaction = Database.CurrentTransaction; | |
| if (currentTransaction != null) | |
| { | |
| try | |
| { | |
| var result = await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken); | |
| await OnAfterSaving(trackedEntities); | |
| return result; | |
| } | |
| catch (DbUpdateConcurrencyException ex) | |
| { | |
| throw new AppException("Сущность была изменена с момента последнего чтения"); | |
| } | |
| } | |
| else | |
| { | |
| using var transaction = await Database.BeginTransactionAsync(); | |
| try | |
| { | |
| var result = await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken); | |
| await OnAfterSaving(trackedEntities); | |
| await transaction.CommitAsync(); | |
| return result; | |
| } | |
| catch (DbUpdateConcurrencyException ex) | |
| { | |
| await transaction.RollbackAsync(); | |
| throw new AppException("Сущность была изменена с момента последнего чтения"); | |
| } | |
| catch | |
| { | |
| await transaction.RollbackAsync(); | |
| throw; | |
| } | |
| } | |
| } | |
| protected virtual Task<List<EventEntry>> OnBeforeSaving(IEntityFilterContext? entityFilter = null) | |
| { | |
| var transactionId = Guid.NewGuid(); | |
| var entities = ChangeTracker.Entries<BaseEntity>().Where(p => p.State != EntityState.Unchanged).ToList(); | |
| foreach (var entity in entities) | |
| { | |
| SetEventType(entity); | |
| DateTime modifiedDate = DateTime.UtcNow; | |
| if (entity.Entity is ISoftDeleted softDeleted) | |
| { | |
| if (entity.State == EntityState.Deleted) | |
| { | |
| entity.State = EntityState.Modified; | |
| if (softDeleted.EndDate is null) | |
| softDeleted.EndDate = modifiedDate; | |
| } | |
| } | |
| //Audit | |
| if (entity.Entity is BaseAuditEntity auditEntity) | |
| { | |
| switch (entity.State) | |
| { | |
| case EntityState.Added: | |
| entity.Property(x => ((BaseAuditEntity)x).Modified).IsModified = false; | |
| auditEntity.Created = modifiedDate; | |
| auditEntity.CreatedUserId = entityFilter?.GetUser() ?? Guid.Empty; | |
| break; | |
| case EntityState.Modified: | |
| entity.Property(x => ((BaseAuditEntity)x).Created).IsModified = false; | |
| auditEntity.Modified = modifiedDate; | |
| auditEntity.ModifiedUserId = entityFilter?.GetUser() ?? Guid.Empty; | |
| break; | |
| } | |
| if (entity.State != EntityState.Added) | |
| { | |
| auditEntity.VersionHistory.History.Add(auditEntity.RowVersion); | |
| } | |
| //1. Выставляем идентификатор текущей транзакции | |
| auditEntity.RowVersion = transactionId; | |
| } | |
| } | |
| return Task.FromResult(entities.Select(x => CreateEventEntry(x)).ToList()); | |
| } | |
| protected virtual Task OnAfterSaving(IEnumerable<EventEntry> entityEntries) | |
| { | |
| return Task.CompletedTask; | |
| } | |
| public EntityEntry<TEntity> Archive<TEntity>(TEntity entity) where TEntity : BaseEntity, ISoftDeleted | |
| { | |
| var entry = Entry<TEntity>(entity); | |
| if (entry.State != EntityState.Added) | |
| { | |
| entry.State = EntityState.Modified; | |
| } | |
| entry.Entity.EndDate = DateTime.UtcNow; | |
| entry.Entity.EventType = EventType.Archive; | |
| return entry; | |
| } | |
| public void DetachEventTracking<TEntity>(TEntity entity) where TEntity : BaseEntity | |
| { | |
| entity.EventType = EventType.None; | |
| } | |
| private void SetEventType<TEntity>(EntityEntry<TEntity> entity) where TEntity : BaseEntity | |
| { | |
| if (entity.Entity.EventType != EventType.Unchanged) | |
| return; | |
| if (entity.State == EntityState.Deleted) | |
| { | |
| if (entity.Entity is ISoftDeleted) | |
| { | |
| entity.Entity.EventType = EventType.SoftDelete; | |
| return; | |
| } | |
| } | |
| entity.Entity.EventType = (EventType)entity.State; | |
| } | |
| protected async Task ResolveEventsAsync<TEntity>(IEventServiceDescriptor eventServiceProvider, IEnumerable<EventEntry> entityEntries) where TEntity : BaseEntity, new() | |
| { | |
| foreach (var processor in eventServiceProvider.GetProcessors<EventEntry<TEntity>>()) | |
| { | |
| foreach (var entry in entityEntries.OfType<EventEntry<TEntity>>().Where(x => x.Value.EventType != EventType.None)) | |
| { | |
| await processor.Process(entry); | |
| entry.Value.EventType = EventType.Unchanged; | |
| } | |
| } | |
| } | |
| private EventEntry CreateEventEntry(EntityEntry entityEntry) | |
| { | |
| var type = entityEntry.Entity.GetType(); | |
| object? original = null; | |
| if (entityEntry.State != EntityState.Added) | |
| { | |
| original = Activator.CreateInstance(type); | |
| foreach (var property in entityEntry.Properties) | |
| { | |
| var prop = type.GetProperty(property.Metadata.Name); | |
| prop.SetValue(original, property.OriginalValue); | |
| } | |
| } | |
| var res = (EventEntry)Activator.CreateInstance(typeof(EventEntry<>).MakeGenericType(type), | |
| args: new object[] { original, entityEntry.Entity }); | |
| return res; | |
| } | |
| public List<Guid> GetFilter(IEntityFilterContext? context, RightEntities entity) | |
| { | |
| return context == null ? new List<Guid>() : context.GetFilter(entity); | |
| } | |
| protected Guid? GetUser(IEntityFilterContext? context) | |
| { | |
| return context == null ? null : context.GetUser(); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment