如何在DbContext的SaveChanges中注入Facade避免循环依赖(遵循Clean Architecture)
问题解决思路与实现方案
核心问题拆解
- 循环依赖:
DataBaseContext依赖IUserActionLogFacade,而UserActionsLogFacade又依赖IDataBaseContext,形成构造注入循环。 - Clean Architecture 违反:持久层(
DataBaseContext)直接调用领域层的 Facade 服务,打破了"依赖倒置"和"分层隔离"原则。
解决方案:事件驱动解耦
通过领域事件机制将实体变更逻辑与日志记录逻辑解耦,既打破循环依赖,又符合 Clean Architecture 分层要求。
步骤1:定义领域事件(领域层)
在领域层封装实体变更的事件信息,作为上下文传递的载体:
// 领域事件基类 public abstract class DomainEvent { public DateTime OccurredOn { get; } = DateTime.UtcNow; } // 实体变更事件 public class EntityChangedEvent : DomainEvent { public string EntityName { get; } public string Action { get; } public string PrimaryKeyValue { get; } public long UserId { get; } public Dictionary<string, (object OldValue, object NewValue)> ChangedProperties { get; } public EntityChangedEvent(string entityName, string action, string primaryKeyValue, long userId, Dictionary<string, (object, object)> changedProps) { EntityName = entityName; Action = action; PrimaryKeyValue = primaryKeyValue; UserId = userId; ChangedProperties = changedProps; } } // 领域事件发布接口 public interface IDomainEventPublisher { void Publish(DomainEvent domainEvent); }
步骤2:修改DataBaseContext(持久层)
移除对 IUserActionLogFacade 的依赖,改为在 SaveChanges 中收集变更并发布领域事件:
public class DataBaseContext : DbContext, IDataBaseContext { private readonly IDomainEventPublisher _domainEventPublisher; public DataBaseContext(DbContextOptions options, IDomainEventPublisher domainEventPublisher) : base(options) { _domainEventPublisher = domainEventPublisher; } // DbSet定义保持不变... public override int SaveChanges() { // 排除日志实体,避免循环触发事件 var modifiedEntries = ChangeTracker.Entries() .Where(e => e.State is EntityState.Added or EntityState.Modified or EntityState.Deleted && e.Entity.GetType() != typeof(UsersActionsLog)); foreach (var entry in modifiedEntries) { // 处理时间字段逻辑保持不变 var entityType = entry.Context.Model.FindEntityType(entry.Entity.GetType()); var inserted = entityType.FindProperty("InsertDate"); var updated = entityType.FindProperty("UpdateDate"); var deleted = entityType.FindProperty("DeleteDate"); switch (entry.State) { case EntityState.Added: inserted?.PropertyInfo.SetValue(entry.Entity, DateTime.Now); break; case EntityState.Modified: updated?.PropertyInfo.SetValue(entry.Entity, DateTime.Now); break; case EntityState.Deleted: deleted?.PropertyInfo.SetValue(entry.Entity, DateTime.Now); entry.State = EntityState.Modified; break; } // 收集变更详情(仅针对修改操作) var changedProps = new Dictionary<string, (object OldValue, object NewValue)>(); if (entry.State == EntityState.Modified) { foreach (var prop in entry.Properties.Where(p => p.IsModified)) { changedProps.Add(prop.Metadata.Name, (prop.OriginalValue, prop.CurrentValue)); } } // 发布领域事件 var action = entry.State switch { EntityState.Added => "Added", EntityState.Modified => "Modified", EntityState.Deleted => "Deleted", _ => string.Empty }; // 获取主键值(通用逻辑,适配不同实体) var primaryKey = entry.Properties.First(p => p.Metadata.IsPrimaryKey()).CurrentValue?.ToString() ?? string.Empty; // 实际项目中从上下文获取当前用户ID,这里暂时硬编码 var userId = 1; _domainEventPublisher.Publish(new EntityChangedEvent( entry.Entity.GetType().Name, action, primaryKey, userId, changedProps )); } return base.SaveChanges(); } }
步骤3:实现事件处理器(应用层)
在应用层实现事件处理器,负责接收实体变更事件并调用日志服务记录操作:
// 事件处理器通用接口 public interface IDomainEventHandler<TEvent> where TEvent : DomainEvent { Task Handle(TEvent domainEvent); } // 实体变更事件处理器 public class EntityChangedEventHandler : IDomainEventHandler<EntityChangedEvent> { private readonly IPostUserActionLogService _logService; public EntityChangedEventHandler(IPostUserActionLogService logService) { _logService = logService; } public Task Handle(EntityChangedEvent domainEvent) { // 遍历变更属性,生成多条日志(如果需要) foreach (var prop in domainEvent.ChangedProperties) { var logDto = new RequestPostUserActionLogServiceDto { Entity = domainEvent.EntityName, Action = domainEvent.Action, PrimaryKeyValue = domainEvent.PrimaryKeyValue, UserId = domainEvent.UserId, Successful = true, PropertyName = prop.Key, OldValue = prop.Value.OldValue?.ToString() ?? string.Empty, NewValue = prop.Value.NewValue?.ToString() ?? string.Empty }; _logService.Execute(logDto); } // 新增/删除操作直接生成单条日志 if (domainEvent.Action is "Added" or "Deleted") { var logDto = new RequestPostUserActionLogServiceDto { Entity = domainEvent.EntityName, Action = domainEvent.Action, PrimaryKeyValue = domainEvent.PrimaryKeyValue, UserId = domainEvent.UserId, Successful = true, PropertyName = string.Empty, OldValue = string.Empty, NewValue = string.Empty }; _logService.Execute(logDto); } return Task.CompletedTask; } }
步骤4:实现事件发布器(基础设施层)
实现领域事件发布器,通过依赖注入容器查找并执行对应处理器:
public class DomainEventPublisher : IDomainEventPublisher { private readonly IServiceProvider _serviceProvider; public DomainEventPublisher(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public void Publish(DomainEvent domainEvent) { var handlerType = typeof(IDomainEventHandler<>).MakeGenericType(domainEvent.GetType()); dynamic handler = _serviceProvider.GetService(handlerType); handler?.Handle((dynamic)domainEvent).Wait(); } }
步骤5:调整依赖注入配置
在启动类中注册所有相关服务,同时简化原Facade避免循环:
// Program.cs 或 Startup.cs builder.Services.AddScoped<IDataBaseContext, DataBaseContext>(); builder.Services.AddScoped<IPostUserActionLogService, PostUserActionLogService>(); builder.Services.AddScoped<IDomainEventPublisher, DomainEventPublisher>(); builder.Services.AddScoped<IDomainEventHandler<EntityChangedEvent>, EntityChangedEventHandler>(); // 简化Facade,避免依赖Context public class UserActionsLogFacade : IUserActionLogFacade { private readonly IPostUserActionLogService _postService; public UserActionsLogFacade(IPostUserActionLogService postService) { _postService = postService; } public IPostUserActionLogService PostUserActionLogService => _postService; } builder.Services.AddScoped<IUserActionLogFacade, UserActionsLogFacade>();
方案优势
- 打破循环依赖:通过事件中间层,彻底切断
DataBaseContext与Facade/LogService的直接依赖。 - 符合Clean Architecture:
- 领域层定义事件和抽象接口,处于核心位置。
- 应用层实现事件处理逻辑,协调领域服务与基础设施。
- 持久层仅负责数据持久化和事件发布,不直接调用领域服务。
- 扩展性强:后续新增其他变更处理逻辑(如通知、审计),只需新增事件处理器即可,无需修改
SaveChanges代码。
内容的提问来源于stack exchange,提问作者Ali.M Eghbaldar
相关产品推荐
相关产品推荐

