Skip to content

Instantly share code, notes, and snippets.

@vlapenkov
Created August 29, 2025 06:23
Show Gist options
  • Select an option

  • Save vlapenkov/f8cc5601cb1e85dd51f2343c6c8ac637 to your computer and use it in GitHub Desktop.

Select an option

Save vlapenkov/f8cc5601cb1e85dd51f2343c6c8ac637 to your computer and use it in GitHub Desktop.
BaseContext sample
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