AppDbContext.cs 7.7 KB
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Rcs.Application;
using Rcs.Domain;
using Rcs.Domain.Entities;

namespace Rcs.Infrastructure.DB.MsSql
{
    public class AppDbContext : DbContext, IAppDbContext
    {
        private readonly IDomainEventDispatcher? _domainEventDispatcher;
        private readonly ILogger<AppDbContext>? _logger;

        public AppDbContext(DbContextOptions<AppDbContext> options)
            : base(options)
        {
        }

        public AppDbContext(
            DbContextOptions<AppDbContext> options,
            IDomainEventDispatcher domainEventDispatcher,
            ILogger<AppDbContext> logger)
            : base(options)
        {
            _domainEventDispatcher = domainEventDispatcher;
            _logger = logger;
        }
        
        // RCS领域
        public DbSet<Robot> Robots { get; set; }
        public DbSet<RobotCacheLocation> RobotCacheLocations { get; set; }
        
        // Map聚合
        public DbSet<Map> Maps { get; set; }
        public DbSet<MapNode> MapNodes { get; set; }
        public DbSet<MapEdge> MapEdges { get; set; }
        public DbSet<MapResource> MapResources { get; set; }
        public DbSet<MapFile> MapFiles { get; set; }
        
        // TaskTemplate聚合
        public DbSet<TaskTemplate> TaskTemplates { get; set; }
        public DbSet<TaskStep> TaskSteps { get; set; }
        public DbSet<StepProperty> StepProperties { get; set; }
        public DbSet<StepAction> StepActions { get; set; }
        public DbSet<NetActionPropertys> NetActionPropertys { get; set; }

        // StorageLocationType
        public DbSet<StorageLocationType> StorageLocationTypes { get; set; }

        // ActionConfiguration聚合
        public DbSet<ActionConfiguration> ActionConfigurations { get; set; }
        public DbSet<ActionParameterDefinition> ActionParameterDefinitions { get; set; }


        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly);
            base.OnModelCreating(modelBuilder);
        }

        /// <summary>
        /// 重写SaveChangesAsync以自动处理领域事件
        /// 修复:先收集事件,再保存数据库,确保已删除实体的事件也能被分发
        /// @author zzy
        /// </summary>
        public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
        {
            try
            {
                var domainEventsToDispatch = CollectDomainEvents();

                if (domainEventsToDispatch.Count == 0)
                {
                    return await base.SaveChangesAsync(cancellationToken);
                }

                var result = await base.SaveChangesAsync(cancellationToken);

                await DispatchCollectedEventsAsync(domainEventsToDispatch, cancellationToken);

                return result;
            }
            catch (Exception ex)
            {
                _logger?.LogError(ex, "保存数据库更改时发生错误");
                throw;
            }
        }

        /// <summary>
        /// 收集所有领域事件(在保存前调用,避免已删除实体丢失事件)
        /// @author zzy
        /// </summary>
        private List<(Entity Entity, IDomainEvent Event)> CollectDomainEvents()
        {
            var allEvents = new List<(Entity Entity, IDomainEvent Event)>();

            // 获取所有具有领域事件的实体(包括已删除的)
            var domainEntities = ChangeTracker.Entries<Entity>()
                .Where(e => e.Entity.DomainEvents != null && e.Entity.DomainEvents.Any())
                .Select(e => e.Entity)
                .ToList();

            // 收集所有事件
            foreach (var entity in domainEntities)
            {
                var entityEvents = entity.DomainEvents.Select(e => (Entity: entity, Event: e));
                allEvents.AddRange(entityEvents);
            }

            return allEvents;
        }

        /// <summary>
        /// 分发已收集的领域事件
        /// @author zzy
        /// </summary>
        private async Task DispatchCollectedEventsAsync(
            List<(Entity Entity, IDomainEvent Event)> collectedEvents,
            CancellationToken cancellationToken = default)
        {
            if (_domainEventDispatcher == null || collectedEvents.Count == 0)
            {
                return;
            }

            try
            {
                _logger?.LogInformation("开始分发已收集的 {EventCount} 个领域事件", collectedEvents.Count);

                // 按时间戳排序所有事件
                var sortedEvents = collectedEvents
                    .OrderBy(x => GetEventTimestamp(x.Event))
                    .ToList();

                // 按顺序处理事件
                for (int i = 0; i < sortedEvents.Count; i++)
                {
                    var (entity, domainEvent) = sortedEvents[i];

                    await _domainEventDispatcher.DispatchAsync(domainEvent, cancellationToken);
                }

                // 清除所有实体的事件
                foreach (var (entity, _) in sortedEvents.GroupBy(x => x.Entity).Select(g => g.First()))
                {
                    entity.ClearDomainEvents();
                }

                _logger?.LogInformation("所有 {EventCount} 个领域事件按序处理完成", sortedEvents.Count);
            }
            catch (Exception ex)
            {
                _logger?.LogError(ex, "分发领域事件时发生错误");
                throw;
            }
        }

        /// <summary>
        /// 重写SaveChanges以自动处理领域事件
        /// @author zzy
        /// </summary>
        public override int SaveChanges()
        {
            return SaveChangesAsync().GetAwaiter().GetResult();
        }

        /// <summary>
        /// 获取事件时间戳
        /// @author zzy
        /// </summary>
        private static DateTime GetEventTimestamp(IDomainEvent domainEvent)
        {
            var properties = domainEvent.GetType().GetProperties()
                .Where(p => p.Name.Contains("Time") || p.Name.Contains("Date") || p.Name.Contains("At"))
                .ToList();

            foreach (var property in properties)
            {
                if (property.PropertyType == typeof(DateTime) || property.PropertyType == typeof(DateTime?))
                {
                    var value = property.GetValue(domainEvent);
                    if (value is DateTime dateTime)
                    {
                        return dateTime;
                    }
                }
            }

            return DateTime.Now;
        }
    }

    public interface IAppDbContext
    {
        public DbSet<Robot> Robots { get; set; }
        public DbSet<RobotCacheLocation> RobotCacheLocations { get; set; }
        
        // Map聚合
        public DbSet<Map> Maps { get; set; }
        public DbSet<MapNode> MapNodes { get; set; }
        public DbSet<MapEdge> MapEdges { get; set; }
        public DbSet<MapResource> MapResources { get; set; }
        public DbSet<MapFile> MapFiles { get; set; }
        
        // TaskTemplate聚合
        public DbSet<TaskTemplate> TaskTemplates { get; set; }
        public DbSet<TaskStep> TaskSteps { get; set; }
        public DbSet<StepProperty> StepProperties { get; set; }
        public DbSet<StepAction> StepActions { get; set; }
        public DbSet<NetActionPropertys> NetActionPropertys { get; set; }

        // ActionConfiguration聚合
        public DbSet<ActionConfiguration> ActionConfigurations { get; set; }
        public DbSet<ActionParameterDefinition> ActionParameterDefinitions { get; set; }
    }
}