diff --git a/aspnet-core/Lion.AbpPro.sln b/aspnet-core/Lion.AbpPro.sln index 027e8fde..caaab4e4 100644 --- a/aspnet-core/Lion.AbpPro.sln +++ b/aspnet-core/Lion.AbpPro.sln @@ -240,6 +240,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lion.AbpPro.LanguageManagem EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lion.AbpPro.LanguageManagement.Application.Tests", "modules\LanguageManagement\test\Lion.AbpPro.LanguageManagement.Application.Tests\Lion.AbpPro.LanguageManagement.Application.Tests.csproj", "{15852D6F-4110-4B98-B89D-5747777E8908}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lion.AbpPro.CAP.EntityFrameworkCore", "frameworks\src\Lion.AbpPro.CAP.EntityFrameworkCore\Lion.AbpPro.CAP.EntityFrameworkCore.csproj", "{68C902A2-A604-4F3A-879D-37941C00C7A9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -582,6 +584,10 @@ Global {15852D6F-4110-4B98-B89D-5747777E8908}.Debug|Any CPU.Build.0 = Debug|Any CPU {15852D6F-4110-4B98-B89D-5747777E8908}.Release|Any CPU.ActiveCfg = Release|Any CPU {15852D6F-4110-4B98-B89D-5747777E8908}.Release|Any CPU.Build.0 = Release|Any CPU + {68C902A2-A604-4F3A-879D-37941C00C7A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {68C902A2-A604-4F3A-879D-37941C00C7A9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {68C902A2-A604-4F3A-879D-37941C00C7A9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {68C902A2-A604-4F3A-879D-37941C00C7A9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -696,6 +702,7 @@ Global {8872921C-5BF9-4B4C-B882-6AF8CC78D2CF} = {3FE23400-A323-46ED-A7F4-30BFF8916D64} {E5994C85-C1C2-44F3-BF10-3277CA6CF2C9} = {3FE23400-A323-46ED-A7F4-30BFF8916D64} {15852D6F-4110-4B98-B89D-5747777E8908} = {3FE23400-A323-46ED-A7F4-30BFF8916D64} + {68C902A2-A604-4F3A-879D-37941C00C7A9} = {7BE85EBC-99AD-4CDE-957E-4BDD087FC4E3} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {28315BFD-90E7-4E14-A2EA-F3D23AF4126F} diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/GlobalUsings.cs b/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/GlobalUsings.cs new file mode 100644 index 00000000..8b553ee8 --- /dev/null +++ b/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/GlobalUsings.cs @@ -0,0 +1,13 @@ +// Global using directives + +global using System.Collections.Concurrent; +global using DotNetCore.CAP; +global using Microsoft.EntityFrameworkCore; +global using Microsoft.EntityFrameworkCore.Storage; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Options; +global using Volo.Abp.DependencyInjection; +global using Volo.Abp.Modularity; +global using Volo.Abp.Threading; +global using Volo.Abp.Uow; +global using Volo.Abp.Uow.EntityFrameworkCore; \ No newline at end of file diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/Lion.AbpPro.CAP.EntityFrameworkCore.csproj b/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/Lion.AbpPro.CAP.EntityFrameworkCore.csproj new file mode 100644 index 00000000..dadd3608 --- /dev/null +++ b/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/Lion.AbpPro.CAP.EntityFrameworkCore.csproj @@ -0,0 +1,15 @@ + + + + net7.0 + enable + + Lion.AbpPro.CAP.EntityFrameworkCore + + + + + + + + diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/Lion/AbpPro/CAP/EntityFrameworkCore/EfCoreLionAbpProCapTransactionApiFactory.cs b/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/Lion/AbpPro/CAP/EntityFrameworkCore/EfCoreLionAbpProCapTransactionApiFactory.cs new file mode 100644 index 00000000..6fcefd26 --- /dev/null +++ b/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/Lion/AbpPro/CAP/EntityFrameworkCore/EfCoreLionAbpProCapTransactionApiFactory.cs @@ -0,0 +1,64 @@ +namespace Lion.AbpPro.CAP.EntityFrameworkCore; + +public class EfCoreLionAbpProCapTransactionApiFactory : ILionAbpProCapTransactionApiFactory, ITransientDependency +{ + public Type TransactionApiType { get; } = typeof(EfCoreTransactionApi); + + protected readonly ICapPublisher Publisher; + protected readonly LionAbpProEfCoreDbContextCapOptions Options; + protected readonly ILionAbpProCapDbProviderInfoProvider LionAbpProCapDbProviderInfoProvider; + protected readonly ICancellationTokenProvider CancellationTokenProvider; + + public EfCoreLionAbpProCapTransactionApiFactory( + ICapPublisher publisher, + IOptions options, + ILionAbpProCapDbProviderInfoProvider lionAbpProCapDbProviderInfoProvider, + ICancellationTokenProvider cancellationTokenProvider) + { + Publisher = publisher; + Options = options.Value; + LionAbpProCapDbProviderInfoProvider = lionAbpProCapDbProviderInfoProvider; + CancellationTokenProvider = cancellationTokenProvider; + } + + public virtual ITransactionApi Create(ITransactionApi originalApi) + { + var efApi = (EfCoreTransactionApi)originalApi; + + var capTrans = CreateCapTransactionOrNull(efApi); + + return capTrans is null + ? originalApi + : new EfCoreTransactionApi(capTrans, efApi.StarterDbContext, CancellationTokenProvider); + } + + protected virtual IDbContextTransaction CreateCapTransactionOrNull(EfCoreTransactionApi originalApi) + { + // TODO 通过数据库连接字符串判断数据库类型 + // if (Options.CapUsingDbConnectionString != originalApi.StarterDbContext.Database.GetConnectionString()) + // { + // return null; + // } + + var dbProviderInfo = LionAbpProCapDbProviderInfoProvider.GetOrNull(originalApi.StarterDbContext.Database.ProviderName); + + if (dbProviderInfo?.CapTransactionType is null || dbProviderInfo.CapEfDbTransactionType is null) + { + return null; + } + + var capTransactionType = dbProviderInfo.CapTransactionType; + + if (ActivatorUtilities.CreateInstance(Publisher.ServiceProvider, capTransactionType) is not CapTransactionBase capTransaction) + { + return null; + } + + capTransaction.DbTransaction = originalApi.DbContextTransaction; + capTransaction.AutoCommit = false; + + Publisher.Transaction.Value = capTransaction; + + return (IDbContextTransaction)Activator.CreateInstance(dbProviderInfo.CapEfDbTransactionType, capTransaction); + } +} \ No newline at end of file diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/Lion/AbpPro/CAP/EntityFrameworkCore/ILionAbpProCapDbProviderInfoProvider.cs b/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/Lion/AbpPro/CAP/EntityFrameworkCore/ILionAbpProCapDbProviderInfoProvider.cs new file mode 100644 index 00000000..f6aed69f --- /dev/null +++ b/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/Lion/AbpPro/CAP/EntityFrameworkCore/ILionAbpProCapDbProviderInfoProvider.cs @@ -0,0 +1,6 @@ +namespace Lion.AbpPro.CAP.EntityFrameworkCore; + +public interface ILionAbpProCapDbProviderInfoProvider +{ + LionAbpProCapDbProviderInfo GetOrNull(string dbProviderName); +} \ No newline at end of file diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/Lion/AbpPro/CAP/EntityFrameworkCore/LionAbpProCapDbProviderInfo.cs b/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/Lion/AbpPro/CAP/EntityFrameworkCore/LionAbpProCapDbProviderInfo.cs new file mode 100644 index 00000000..1cce7116 --- /dev/null +++ b/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/Lion/AbpPro/CAP/EntityFrameworkCore/LionAbpProCapDbProviderInfo.cs @@ -0,0 +1,14 @@ +namespace Lion.AbpPro.CAP.EntityFrameworkCore; + +public class LionAbpProCapDbProviderInfo +{ + public Type CapTransactionType { get; } + + public Type CapEfDbTransactionType { get; } + + public LionAbpProCapDbProviderInfo(string capTransactionTypeName, string capEfDbTransactionTypeName) + { + CapTransactionType = Type.GetType(capTransactionTypeName, false); + CapEfDbTransactionType = Type.GetType(capEfDbTransactionTypeName, false); + } +} \ No newline at end of file diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/Lion/AbpPro/CAP/EntityFrameworkCore/LionAbpProCapEntityFrameworkCoreModule.cs b/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/Lion/AbpPro/CAP/EntityFrameworkCore/LionAbpProCapEntityFrameworkCoreModule.cs new file mode 100644 index 00000000..787bba48 --- /dev/null +++ b/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/Lion/AbpPro/CAP/EntityFrameworkCore/LionAbpProCapEntityFrameworkCoreModule.cs @@ -0,0 +1,7 @@ +namespace Lion.AbpPro.CAP.EntityFrameworkCore +{ + [DependsOn(typeof(LionAbpProCapModule))] + public class LionAbpProCapEntityFrameworkCoreModule : AbpModule + { + } +} \ No newline at end of file diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/Lion/AbpPro/CAP/EntityFrameworkCore/LionAbpProCapOptionsExtensions.cs b/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/Lion/AbpPro/CAP/EntityFrameworkCore/LionAbpProCapOptionsExtensions.cs new file mode 100644 index 00000000..43f219f1 --- /dev/null +++ b/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/Lion/AbpPro/CAP/EntityFrameworkCore/LionAbpProCapOptionsExtensions.cs @@ -0,0 +1,18 @@ +using Lion.AbpPro.CAP.EntityFrameworkCore; + +// ReSharper disable once CheckNamespace +namespace Microsoft.Extensions.DependencyInjection +{ + public static class LionAbpProCapOptionsExtensions + { + public static CapOptions SetCapDbConnectionString(this CapOptions options, string dbConnectionString) + { + options.RegisterExtension(new LionAbpProEfCoreDbContextCapOptionsExtension + { + CapUsingDbConnectionString = dbConnectionString + }); + + return options; + } + } +} diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/Lion/AbpPro/CAP/EntityFrameworkCore/LionAbpProEfCoreDbContextCapOptions.cs b/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/Lion/AbpPro/CAP/EntityFrameworkCore/LionAbpProEfCoreDbContextCapOptions.cs new file mode 100644 index 00000000..bc30551d --- /dev/null +++ b/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/Lion/AbpPro/CAP/EntityFrameworkCore/LionAbpProEfCoreDbContextCapOptions.cs @@ -0,0 +1,6 @@ +namespace Lion.AbpPro.CAP.EntityFrameworkCore; + +public class LionAbpProEfCoreDbContextCapOptions +{ + public string CapUsingDbConnectionString { get; set; } +} \ No newline at end of file diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/Lion/AbpPro/CAP/EntityFrameworkCore/LionAbpProEfCoreDbContextCapOptionsExtension.cs b/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/Lion/AbpPro/CAP/EntityFrameworkCore/LionAbpProEfCoreDbContextCapOptionsExtension.cs new file mode 100644 index 00000000..b28a1f07 --- /dev/null +++ b/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/Lion/AbpPro/CAP/EntityFrameworkCore/LionAbpProEfCoreDbContextCapOptionsExtension.cs @@ -0,0 +1,14 @@ +namespace Lion.AbpPro.CAP.EntityFrameworkCore; + +public class LionAbpProEfCoreDbContextCapOptionsExtension : ICapOptionsExtension +{ + public string CapUsingDbConnectionString { get; init; } + + public void AddServices(IServiceCollection services) + { + services.Configure(options => + { + options.CapUsingDbConnectionString = CapUsingDbConnectionString; + }); + } +} \ No newline at end of file diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/Lion/AbpPro/CAP/EntityFrameworkCore/LionAbpProLionAbpProCapDbProviderInfoProvider.cs b/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/Lion/AbpPro/CAP/EntityFrameworkCore/LionAbpProLionAbpProCapDbProviderInfoProvider.cs new file mode 100644 index 00000000..ec610c22 --- /dev/null +++ b/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/Lion/AbpPro/CAP/EntityFrameworkCore/LionAbpProLionAbpProCapDbProviderInfoProvider.cs @@ -0,0 +1,45 @@ +namespace Lion.AbpPro.CAP.EntityFrameworkCore; + +public class LionAbpProLionAbpProCapDbProviderInfoProvider : ILionAbpProCapDbProviderInfoProvider, ITransientDependency +{ + protected ConcurrentDictionary CapDbProviderInfos { get; set; } = new(); + + public virtual LionAbpProCapDbProviderInfo GetOrNull(string dbProviderName) + { + return CapDbProviderInfos.GetOrAdd(dbProviderName, InternalGetOrNull); + } + + protected virtual LionAbpProCapDbProviderInfo InternalGetOrNull(string databaseProviderName) + { + switch (databaseProviderName) + { + case "Microsoft.EntityFrameworkCore.SqlServer": + return new LionAbpProCapDbProviderInfo( + "DotNetCore.CAP.SqlServerCapTransaction, DotNetCore.CAP.SqlServer", + "Microsoft.EntityFrameworkCore.Storage.CapEFDbTransaction, DotNetCore.CAP.SqlServer"); + case "Npgsql.EntityFrameworkCore.PostgreSQL": + return new LionAbpProCapDbProviderInfo( + "DotNetCore.CAP.PostgreSqlCapTransaction, DotNetCore.CAP.PostgreSql", + "Microsoft.EntityFrameworkCore.Storage.CapEFDbTransaction, DotNetCore.CAP.PostgreSQL"); + case "Pomelo.EntityFrameworkCore.MySql": + return new LionAbpProCapDbProviderInfo( + "DotNetCore.CAP.MySqlCapTransaction, DotNetCore.CAP.MySql", + "Microsoft.EntityFrameworkCore.Storage.CapEFDbTransaction, DotNetCore.CAP.MySql"); + case "Oracle.EntityFrameworkCore": + case "Devart.Data.Oracle.Entity.EFCore": + return new LionAbpProCapDbProviderInfo( + "DotNetCore.CAP.OracleCapTransaction, DotNetCore.CAP.Oracle", + "Microsoft.EntityFrameworkCore.Storage.CapEFDbTransaction, DotNetCore.CAP.Oracle"); + case "Microsoft.EntityFrameworkCore.Sqlite": + return new LionAbpProCapDbProviderInfo( + "DotNetCore.CAP.SqliteCapTransaction, DotNetCore.CAP.Sqlite", + "Microsoft.EntityFrameworkCore.Storage.CapEFDbTransaction, DotNetCore.CAP.Sqlite"); + case "Microsoft.EntityFrameworkCore.InMemory": + return new LionAbpProCapDbProviderInfo( + "DotNetCore.CAP.InMemoryCapTransaction, DotNetCore.CAP.InMemoryStorage", + "Microsoft.EntityFrameworkCore.Storage.CapEFDbTransaction, DotNetCore.CAP.InMemoryStorage"); + default: + return null; + } + } +} \ No newline at end of file diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/ILionAbpProCapTransactionApiFactory.cs b/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/ILionAbpProCapTransactionApiFactory.cs new file mode 100644 index 00000000..5f311061 --- /dev/null +++ b/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/ILionAbpProCapTransactionApiFactory.cs @@ -0,0 +1,8 @@ +namespace Lion.AbpPro.CAP; + +public interface ILionAbpProCapTransactionApiFactory +{ + Type TransactionApiType { get; } + + ITransactionApi Create(ITransactionApi originalApi); +} \ No newline at end of file diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/LionAbpProCapConsumerServiceSelector.cs b/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/LionAbpProCapConsumerServiceSelector.cs index ac4d7ece..652e055e 100644 --- a/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/LionAbpProCapConsumerServiceSelector.cs +++ b/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/LionAbpProCapConsumerServiceSelector.cs @@ -1,19 +1,14 @@ namespace Lion.AbpPro.CAP; -[Dependency(ServiceLifetime.Singleton, ReplaceServices = true)] -[ExposeServices(typeof(IConsumerServiceSelector))] -public sealed class LionAbpProCapConsumerServiceSelector : ConsumerServiceSelector +public class LionAbpProCapConsumerServiceSelector : ConsumerServiceSelector, ISingletonDependency { - private AbpDistributedEventBusOptions AbpDistributedEventBusOptions { get; } - private IServiceProvider ServiceProvider { get; } + protected AbpDistributedEventBusOptions AbpDistributedEventBusOptions { get; } + protected IServiceProvider ServiceProvider { get; } /// /// Creates a new . /// - public LionAbpProCapConsumerServiceSelector( - IServiceProvider serviceProvider, - IOptions distributedEventBusOptions) - : base(serviceProvider) + public LionAbpProCapConsumerServiceSelector(IServiceProvider serviceProvider, IOptions distributedEventBusOptions) : base(serviceProvider) { ServiceProvider = serviceProvider; AbpDistributedEventBusOptions = distributedEventBusOptions.Value; @@ -22,7 +17,7 @@ public sealed class LionAbpProCapConsumerServiceSelector : ConsumerServiceSelect protected override IEnumerable FindConsumersFromInterfaceTypes(IServiceProvider provider) { var executorDescriptorList = base.FindConsumersFromInterfaceTypes(provider).ToList(); - + //handlers var handlers = AbpDistributedEventBusOptions.Handlers; @@ -35,8 +30,9 @@ public sealed class LionAbpProCapConsumerServiceSelector : ConsumerServiceSelect { continue; } + var genericArgs = @interface.GetGenericArguments(); - + if (genericArgs.Length != 1) { continue; @@ -51,17 +47,18 @@ public sealed class LionAbpProCapConsumerServiceSelector : ConsumerServiceSelect descriptor.Attribute.Group = descriptor.Attribute.Group.Insert( descriptor.Attribute.Group.LastIndexOf(".", StringComparison.Ordinal), $".{count}"); - + executorDescriptorList.Add(descriptor); } - + //Subscribe(genericArgs[0], new IocEventHandlerFactory(ServiceScopeFactory, handler)); } } + return executorDescriptorList; } - private IEnumerable GetHandlerDescription(Type eventType,Type typeInfo) + protected virtual IEnumerable GetHandlerDescription(Type eventType, Type typeInfo) { var serviceTypeInfo = typeof(IDistributedEventHandler<>) .MakeGenericType(eventType); diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/LionAbpProCapDistributedEventBus.cs b/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/LionAbpProCapDistributedEventBus.cs index 11a310d4..337a9c3f 100644 --- a/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/LionAbpProCapDistributedEventBus.cs +++ b/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/LionAbpProCapDistributedEventBus.cs @@ -1,24 +1,21 @@ namespace Lion.AbpPro.CAP; -public class LionAbpProCapDistributedEventBus : - EventBusBase, - IDistributedEventBus, - ISingletonDependency +public class LionAbpProCapDistributedEventBus : EventBusBase, IDistributedEventBus, ISingletonDependency { - private AbpDistributedEventBusOptions AbpDistributedEventBusOptions { get; } - private ConcurrentDictionary> HandlerFactories { get; } - private ConcurrentDictionary EventTypes { get; } + protected AbpDistributedEventBusOptions AbpDistributedEventBusOptions { get; } + protected readonly ICapPublisher CapPublisher; - private readonly ICapPublisher CapPublisher; + //TODO: Accessing to the List may not be thread-safe! + protected ConcurrentDictionary> HandlerFactories { get; } + protected ConcurrentDictionary EventTypes { get; } - public LionAbpProCapDistributedEventBus(IServiceScopeFactory serviceScopeFactory, IOptions distributedEventBusOptions, ICapPublisher capPublisher, + IUnitOfWorkManager unitOfWorkManager, ICurrentTenant currentTenant, - UnitOfWorkManager unitOfWorkManager, IEventHandlerInvoker eventHandlerInvoker) - : base(serviceScopeFactory, currentTenant,unitOfWorkManager,eventHandlerInvoker) + : base(serviceScopeFactory, currentTenant, unitOfWorkManager, eventHandlerInvoker) { CapPublisher = capPublisher; AbpDistributedEventBusOptions = distributedEventBusOptions.Value; @@ -82,36 +79,63 @@ public class LionAbpProCapDistributedEventBus : GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Clear()); } - protected override Task PublishToEventBusAsync(Type eventType, object eventData) + public IDisposable Subscribe(IDistributedEventHandler handler) where TEvent : class { - throw new NotImplementedException(); + return Subscribe(typeof(TEvent), handler); } - protected override void AddToUnitOfWork(IUnitOfWork unitOfWork, UnitOfWorkEventRecord eventRecord) + public virtual Task PublishAsync(TEvent eventData, bool onUnitOfWorkComplete = true, + bool useOutbox = true) where TEvent : class { - throw new NotImplementedException(); + return PublishAsync(typeof(TEvent), eventData, onUnitOfWorkComplete, useOutbox); } - public IDisposable Subscribe(IDistributedEventHandler handler) where TEvent : class + public virtual async Task PublishAsync(Type eventType, object eventData, bool onUnitOfWorkComplete = true, + bool useOutbox = true) { - return Subscribe(typeof(TEvent), handler); - } + if (onUnitOfWorkComplete && UnitOfWorkManager.Current != null) + { + AddToUnitOfWork( + UnitOfWorkManager.Current, + new UnitOfWorkEventRecord(eventType, eventData, EventOrderGenerator.GetNext(), useOutbox) + ); + return; + } - public async Task PublishAsync(TEvent eventData, bool onUnitOfWorkComplete = true, - bool useOutbox = true) where TEvent : class - { - var eventName = EventNameAttribute.GetNameOrDefault(typeof(TEvent)); - await CapPublisher.PublishAsync(eventName, eventData); + if (useOutbox && UnitOfWorkManager.Current != null) + { + if (UnitOfWorkManager.Current is not LionAbpProCapUnitOfWork capUnitOfWork || capUnitOfWork.CapTransaction is null) + { + UnitOfWorkManager.Current.OnCompleted(async () => + { + await PublishToEventBusAsync(eventType, eventData); + }); + } + else + { + using (CapPublisher.UseTransaction(capUnitOfWork.CapTransaction)) + { + // Use CAP transactional outbox + await PublishToEventBusAsync(eventType, eventData); + } + } + return; + } + + await PublishToEventBusAsync(eventType, eventData); } - public async Task PublishAsync(Type eventType, object eventData, bool onUnitOfWorkComplete = true, - bool useOutbox = true) + protected override async Task PublishToEventBusAsync(Type eventType, object eventData) { var eventName = EventNameAttribute.GetNameOrDefault(eventType); await CapPublisher.PublishAsync(eventName, eventData); } - + + protected override void AddToUnitOfWork(IUnitOfWork unitOfWork, UnitOfWorkEventRecord eventRecord) + { + unitOfWork.AddOrReplaceDistributedEvent(eventRecord); + } protected override IEnumerable GetHandlerFactories(Type eventType) { diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/LionAbpProCapModule.cs b/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/LionAbpProCapModule.cs index a9c5e779..c530c2a3 100644 --- a/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/LionAbpProCapModule.cs +++ b/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/LionAbpProCapModule.cs @@ -2,7 +2,8 @@ namespace Lion.AbpPro.CAP; [DependsOn( typeof(AbpEventBusModule), - typeof(LionAbpProLocalizationModule))] + typeof(LionAbpProLocalizationModule), + typeof(AbpUnitOfWorkModule))] public class LionAbpProCapModule : AbpModule { } \ No newline at end of file diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/LionAbpProCapPublisherExtension.cs b/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/LionAbpProCapPublisherExtension.cs new file mode 100644 index 00000000..c3d68dcf --- /dev/null +++ b/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/LionAbpProCapPublisherExtension.cs @@ -0,0 +1,11 @@ +namespace Lion.AbpPro.CAP; + +public static class LionAbpProCapPublisherExtension +{ + public static IDisposable UseTransaction(this ICapPublisher capPublisher, ICapTransaction capTransaction) + { + var previousValue = capPublisher.Transaction.Value; + capPublisher.Transaction.Value = capTransaction; + return new DisposeAction(() => capPublisher.Transaction.Value = previousValue); + } +} \ No newline at end of file diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/LionAbpProCapServiceCollectionExtensions.cs b/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/LionAbpProCapServiceCollectionExtensions.cs index a85b754f..ea81c2ce 100644 --- a/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/LionAbpProCapServiceCollectionExtensions.cs +++ b/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/LionAbpProCapServiceCollectionExtensions.cs @@ -1,3 +1,6 @@ +using DotNetCore.CAP.Serialization; +using Microsoft.Extensions.DependencyInjection.Extensions; + namespace Lion.AbpPro.CAP; public static class LionAbpProCapServiceCollectionExtensions @@ -6,9 +9,15 @@ public static class LionAbpProCapServiceCollectionExtensions this ServiceConfigurationContext context, Action capAction) { + // context.Services.AddSingleton(); + // context.Services.AddSingleton(); + // context.Services.AddSingleton(); + + context.Services.Replace(ServiceDescriptor.Transient()); + context.Services.Replace(ServiceDescriptor.Transient()); + context.Services.AddTransient(); + context.Services.AddCap(capAction); - context.Services.AddSingleton(); - context.Services.AddSingleton(); return context; } } \ No newline at end of file diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/LionAbpProCapUnitOfWork.cs b/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/LionAbpProCapUnitOfWork.cs new file mode 100644 index 00000000..6e4bec2b --- /dev/null +++ b/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/LionAbpProCapUnitOfWork.cs @@ -0,0 +1,51 @@ +namespace Lion.AbpPro.CAP; + +[Dependency(TryRegister = true)] +public class LionAbpProCapUnitOfWork : UnitOfWork +{ + public ICapTransaction CapTransaction { get; protected set; } + + protected ICapPublisher CapPublisher { get; } + + public LionAbpProCapUnitOfWork( + IServiceProvider serviceProvider, + IUnitOfWorkEventPublisher unitOfWorkEventPublisher, + IOptions options, + ICapPublisher capPublisher) + : base(serviceProvider, unitOfWorkEventPublisher, options) + { + CapPublisher = capPublisher; + } + + public override void AddTransactionApi(string key, ITransactionApi api) + { + var factories = ServiceProvider.GetServices(); + + var factory = factories.FirstOrDefault(x => x.TransactionApiType == api.GetType()); + + if (factory is not null) + { + api = factory.Create(api); + CapTransaction = CapPublisher.Transaction.Value; + } + + base.AddTransactionApi(key, api); + } + + public override ITransactionApi GetOrAddTransactionApi(string key, Func factory) + { + Check.NotNull(key, nameof(key)); + Check.NotNull(factory, nameof(factory)); + + var transactionApi = FindTransactionApi(key); + + if (transactionApi is not null) + { + return transactionApi; + } + + AddTransactionApi(key, factory()); + + return FindTransactionApi(key); + } +} \ No newline at end of file diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/LionAbpProJsonSerializer.cs b/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/LionAbpProJsonSerializer.cs new file mode 100644 index 00000000..4d146194 --- /dev/null +++ b/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/LionAbpProJsonSerializer.cs @@ -0,0 +1,67 @@ +using System.Text; +using System.Text.Json; +using DotNetCore.CAP.Messages; +using DotNetCore.CAP.Serialization; +using Volo.Abp.Json; + +namespace Lion.AbpPro.CAP; + +public class LionAbpProJsonSerializer : ISerializer, ISingletonDependency +{ + private readonly IJsonSerializer _jsonSerializer; + + public LionAbpProJsonSerializer(IJsonSerializer jsonSerializer) + { + _jsonSerializer = jsonSerializer; + } + + public virtual string Serialize(Message message) + { + return _jsonSerializer.Serialize(message); + } + + public virtual ValueTask SerializeAsync(Message message) + { + if (message == null) + { + throw new ArgumentNullException(nameof(message)); + } + + if (message.Value == null) + { + return new ValueTask(new TransportMessage(message.Headers, null)); + } + + var json = _jsonSerializer.Serialize(message.Value); + + return new ValueTask(new TransportMessage(message.Headers, Encoding.UTF8.GetBytes(json))); + } + + public virtual Message Deserialize(string json) + { + return _jsonSerializer.Deserialize(json); + } + + public virtual ValueTask DeserializeAsync(TransportMessage transportMessage, Type valueType) + { + if (valueType == null || transportMessage.Body.IsEmpty) + { + return new ValueTask(new Message(transportMessage.Headers, null)); + } + + var json = Encoding.UTF8.GetString(transportMessage.Body.ToArray()); + + return new ValueTask(new Message(transportMessage.Headers, + _jsonSerializer.Deserialize(valueType, json))); + } + + public virtual object Deserialize(object value, Type valueType) + { + return _jsonSerializer.Deserialize(valueType, value.ToString()); + } + + public virtual bool IsJsonType(object jsonObject) + { + return jsonObject is JsonElement; + } +} \ No newline at end of file diff --git a/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Application/Users/UserAppService.cs b/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Application/Users/UserAppService.cs index 3496f171..f499c0be 100644 --- a/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Application/Users/UserAppService.cs +++ b/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Application/Users/UserAppService.cs @@ -4,6 +4,7 @@ using Magicodes.ExporterAndImporter.Excel.AspNetCore; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Volo.Abp.Account; +using Volo.Abp.Uow; using IdentityRole = Volo.Abp.Identity.IdentityRole; namespace Lion.AbpPro.BasicManagement.Users @@ -80,7 +81,12 @@ namespace Lion.AbpPro.BasicManagement.Users { // abp 5.0 之后新增字段,是否运行用户登录,默认设置为true input.IsActive = true; - return await _identityUserAppService.CreateAsync(input); + using (var uow = UnitOfWorkManager.Begin(new AbpUnitOfWorkOptions() { IsTransactional = true }, true)) + { + await _identityUserAppService.CreateAsync(input); + await uow.CompleteAsync(); + } + return null; } /// @@ -90,7 +96,12 @@ namespace Lion.AbpPro.BasicManagement.Users public virtual async Task UpdateAsync(UpdateUserInput input) { input.UserInfo.IsActive = true; - return await _identityUserAppService.UpdateAsync(input.UserId, input.UserInfo); + using (var uow = UnitOfWorkManager.Begin(new AbpUnitOfWorkOptions() { IsTransactional = true }, true)) + { + await _identityUserAppService.UpdateAsync(input.UserId, input.UserInfo); + } + + return null; } /// diff --git a/aspnet-core/services/host/Lion.AbpPro.HttpApi.Host/AbpProHttpApiHostModule.cs b/aspnet-core/services/host/Lion.AbpPro.HttpApi.Host/AbpProHttpApiHostModule.cs index 3400f2a5..42897c77 100644 --- a/aspnet-core/services/host/Lion.AbpPro.HttpApi.Host/AbpProHttpApiHostModule.cs +++ b/aspnet-core/services/host/Lion.AbpPro.HttpApi.Host/AbpProHttpApiHostModule.cs @@ -1,4 +1,5 @@ using Hangfire.Redis; +using Lion.AbpPro.CAP.EntityFrameworkCore; using Swagger; using Volo.Abp.BackgroundJobs.Hangfire; using Volo.Abp.Timing; @@ -15,6 +16,7 @@ namespace Lion.AbpPro typeof(AbpAccountWebModule), typeof(AbpProApplicationModule), typeof(LionAbpProCapModule), + typeof(LionAbpProCapEntityFrameworkCoreModule), typeof(AbpAspNetCoreMvcUiBasicThemeModule), typeof(AbpCachingStackExchangeRedisModule), typeof(AbpBackgroundJobsHangfireModule) @@ -302,6 +304,7 @@ namespace Lion.AbpPro { context.AddAbpCap(capOptions => { + capOptions.SetCapDbConnectionString(configuration["ConnectionStrings:Default"]); capOptions.UseEntityFramework(); capOptions.UseRabbitMQ(option => { @@ -326,7 +329,7 @@ namespace Lion.AbpPro capOptions.UseInMemoryStorage(); capOptions.UseInMemoryMessageQueue(); var hostingEnvironment = context.Services.GetHostingEnvironment(); - bool auth = !hostingEnvironment.IsDevelopment(); + var auth = !hostingEnvironment.IsDevelopment(); capOptions.UseDashboard(options => { options.UseAuth = auth; }); }); } diff --git a/aspnet-core/services/host/Lion.AbpPro.HttpApi.Host/Lion.AbpPro.HttpApi.Host.csproj b/aspnet-core/services/host/Lion.AbpPro.HttpApi.Host/Lion.AbpPro.HttpApi.Host.csproj index 9184032f..b0b08547 100644 --- a/aspnet-core/services/host/Lion.AbpPro.HttpApi.Host/Lion.AbpPro.HttpApi.Host.csproj +++ b/aspnet-core/services/host/Lion.AbpPro.HttpApi.Host/Lion.AbpPro.HttpApi.Host.csproj @@ -44,6 +44,7 @@ +