diff --git a/IRaCIS.Core.Domain/BaseModel/DomainEvent.cs b/IRaCIS.Core.Domain/BaseModel/DomainEvent.cs new file mode 100644 index 000000000..f645ebf3e --- /dev/null +++ b/IRaCIS.Core.Domain/BaseModel/DomainEvent.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace IRaCIS.Core.Domain.BaseModel +{ + /// + /// 领域实体事件基类 + /// + public abstract class DomainEvent + { + + } + + public class FailedDomainEvent + { + public Guid Id { get; set; } + public string EventType { get; set; } + public string EventData { get; set; } + public DateTime FailedAt { get; set; } + } +} diff --git a/IRaCIS.Core.Domain/BaseModel/Entity.cs b/IRaCIS.Core.Domain/BaseModel/Entity.cs index 8482a17d4..d6d45e8da 100644 --- a/IRaCIS.Core.Domain/BaseModel/Entity.cs +++ b/IRaCIS.Core.Domain/BaseModel/Entity.cs @@ -1,21 +1,57 @@ -using System; +using IRaCIS.Core.Domain.BaseModel; +using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using System.Security.Cryptography; namespace IRaCIS.Core.Domain.Models { + + + public interface IAggregateRoot; + public interface IEntity + { + abstract TKey Id { get; set; } + + } + public abstract class Entity : IEntity { [Key] [Required] [DatabaseGenerated(DatabaseGeneratedOption.None)] public Guid Id { get; set; } + + #region 领域事件 仅仅允许通过提供的方法进行操作 + + private readonly List _domainEvents = []; + + [NotMapped] + public IReadOnlyCollection DomainEvents => _domainEvents.AsReadOnly(); + + + public void AddDomainEvent(DomainEvent domainEvent) + { + _domainEvents.Add(domainEvent); + } + + public void RemoveDomainEvent(DomainEvent domainEvent) + { + _domainEvents.Remove(domainEvent); + } + + public void ClearDomainEvents() + { + _domainEvents.Clear(); + } + #endregion + + } - public interface IEntity - { - abstract TKey Id { get; set; } - } + + #region 减少实体属性,增加基类 diff --git a/IRaCIS.Core.Infra.EFCore/IRaCIS.Core.Infra.EFCore.csproj b/IRaCIS.Core.Infra.EFCore/IRaCIS.Core.Infra.EFCore.csproj index 9b2ef5d9c..bbedc4058 100644 --- a/IRaCIS.Core.Infra.EFCore/IRaCIS.Core.Infra.EFCore.csproj +++ b/IRaCIS.Core.Infra.EFCore/IRaCIS.Core.Infra.EFCore.csproj @@ -25,7 +25,6 @@ - diff --git a/IRaCIS.Core.Infra.EFCore/Interceptor/AuditEntityInterceptor.cs b/IRaCIS.Core.Infra.EFCore/Interceptor/AuditEntityInterceptor.cs index b2f99decd..b8235e0e8 100644 --- a/IRaCIS.Core.Infra.EFCore/Interceptor/AuditEntityInterceptor.cs +++ b/IRaCIS.Core.Infra.EFCore/Interceptor/AuditEntityInterceptor.cs @@ -12,13 +12,14 @@ namespace IRaCIS.Core.Infra.EFCore; public class AuditEntityInterceptor(IUserInfo _userInfo) : SaveChangesInterceptor { - public override InterceptionResult SavingChanges(DbContextEventData eventData, InterceptionResult result) - { - AuditEntities(eventData.Context); - - return base.SavingChanges(eventData, result); - } + /// + /// 在事务提交之前执行 + /// + /// + /// + /// + /// public override ValueTask> SavingChangesAsync(DbContextEventData eventData, InterceptionResult result, CancellationToken cancellationToken = default) { @@ -26,7 +27,12 @@ public class AuditEntityInterceptor(IUserInfo _userInfo) : SaveChangesIntercepto return base.SavingChangesAsync(eventData, result, cancellationToken); } + public override InterceptionResult SavingChanges(DbContextEventData eventData, InterceptionResult result) + { + AuditEntities(eventData.Context); + return base.SavingChanges(eventData, result); + } public void AuditEntities(DbContext? context) { if (context == null) return; diff --git a/IRaCIS.Core.Infra.EFCore/Interceptor/DispatchDomainEventsInterceptor.cs b/IRaCIS.Core.Infra.EFCore/Interceptor/DispatchDomainEventsInterceptor.cs new file mode 100644 index 000000000..e12a02704 --- /dev/null +++ b/IRaCIS.Core.Infra.EFCore/Interceptor/DispatchDomainEventsInterceptor.cs @@ -0,0 +1,59 @@ +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using IRaCIS.Core.Domain.Models; +using MassTransit; + +namespace IRaCIS.Core.Infra.EFCore.Interceptor +{ + public class DispatchDomainEventsInterceptor(IPublishEndpoint publishEndpoint) : SaveChangesInterceptor + { + + //领域事件通常与数据变更密切相关。如果在 SaveChanges 之前发布事件,有可能事件发布时的数据状态还没有被持久化到数据库。这可能导致事件消费者看到的是一个不一致的状态 + + /// + /// 在事务提交之后分发事件 + /// + /// + /// + /// + /// + public override async ValueTask SavedChangesAsync(SaveChangesCompletedEventData eventData, int result, + CancellationToken cancellationToken = default) + { + await DispatchDomainEvents(eventData.Context); + return await base.SavedChangesAsync(eventData, result, cancellationToken); + } + public override int SavedChanges(SaveChangesCompletedEventData eventData, int result) + { + DispatchDomainEvents(eventData.Context).GetAwaiter().GetResult(); + return base.SavedChanges(eventData, result); + } + private async Task DispatchDomainEvents(DbContext? context) + { + if (context == null) return; + + var entities = context.ChangeTracker + .Entries() + .Where(e => e.Entity.DomainEvents.Any()) + .Select(e => e.Entity) + .ToList(); + + var domainEvents = entities + .SelectMany(e => e.DomainEvents) + .ToList(); + + entities.ForEach(e => e.ClearDomainEvents()); + + foreach (var domainEvent in domainEvents) + { + await publishEndpoint.Publish(domainEvent); + } + } + } +} diff --git a/IRaCIS.Core.Infrastructure/IRaCIS.Core.Infrastructure.csproj b/IRaCIS.Core.Infrastructure/IRaCIS.Core.Infrastructure.csproj index 324ac91a0..39d3b1d66 100644 --- a/IRaCIS.Core.Infrastructure/IRaCIS.Core.Infrastructure.csproj +++ b/IRaCIS.Core.Infrastructure/IRaCIS.Core.Infrastructure.csproj @@ -11,6 +11,7 @@ +