├── .gitignore ├── LICENSE ├── NTccTransactionCore.sln ├── README.md ├── TCC Architecture.png ├── samples ├── NTccTransaction.Http.Capital.WebApi │ ├── Controllers │ │ └── CapitalController.cs │ ├── NTccTransaction.Http.Capital.WebApi.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Services │ │ ├── CapitalService.cs │ │ └── ICapitalService.cs │ ├── Startup.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ └── log4net.config └── NTccTransaction.Http.Order.WebApi │ ├── Controllers │ └── OrderController.cs │ ├── ICapitalApi.cs │ ├── NTccTransaction.Http.Order.WebApi.csproj │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── Proxy │ └── CapitalProxy.cs │ ├── Services │ ├── IOrderService.cs │ ├── IOrderService1.cs │ ├── OrderService.cs │ └── OrderService1.cs │ ├── Startup.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ └── log4net.config ├── src ├── NTccTransaction.Abstractions │ ├── Check.cs │ ├── Exception │ │ ├── CancellingException.cs │ │ ├── ConcurrencyTransactionException.cs │ │ ├── ConfirmingException.cs │ │ ├── NoExistedTransactionException.cs │ │ ├── TransactionException.cs │ │ └── TryingException.cs │ ├── ICompensable.cs │ ├── INTccInterceptor.cs │ ├── INTccTransactionOptionsExtension.cs │ ├── IUniqueIdentity.cs │ ├── Internal │ │ ├── CacheExecutorDescriptor.cs │ │ ├── IBootstrapper.cs │ │ ├── IMethodInvocation.cs │ │ ├── INTccTransactionService.cs │ │ ├── IProcessingServer.cs │ │ ├── ITransactionContextEditor.cs │ │ ├── ITransactionMethodInvoker.cs │ │ ├── ITransactionRecoveryProcessingServer.cs │ │ ├── ITransactionServiceSelector.cs │ │ └── InvocationContext.cs │ ├── MethodRole.cs │ ├── NTccTransaction.Abstractions.csproj │ ├── Persistence │ │ ├── IAmbientTransaction.cs │ │ ├── IParticipant.cs │ │ ├── ITransaction.cs │ │ ├── ITransactionAccessor.cs │ │ ├── ITransactionManager.cs │ │ ├── ITransactionRecovery.cs │ │ └── ITransactionRepository.cs │ ├── Propagation.cs │ ├── Serialization │ │ └── ISerializer.cs │ ├── System │ │ ├── Collections │ │ │ └── Generic │ │ │ │ ├── HashSetExtensions.cs │ │ │ │ └── StackExtension.cs │ │ └── Reflection │ │ │ └── MethodBaseExtensions.cs │ ├── TransactionContext.cs │ ├── TransactionStatus.cs │ ├── TransactionType.cs │ └── TransactionXId.cs ├── NTccTransaction.Aop │ ├── AutofacRegistration.cs │ ├── CastleAsyncInterceptorAdapter.cs │ ├── CastleMethodInvocationAdapter.cs │ ├── CastleMethodInvocationAdapterBase.cs │ ├── CastleMethodInvocationAdapterWithReturnValue.cs │ ├── CompensableTransactionInterceptor.cs │ ├── FodyWeavers.xml │ ├── FodyWeavers.xsd │ ├── NTccTransaction.Aop.csproj │ ├── NTccTransaction.AopOptionsExtension.cs │ ├── NTccTransaction.Options.Extensions.cs │ ├── ResourceCoordinatorInterceptor.cs │ └── TccAsyncDeterminationInterceptor.cs ├── NTccTransaction.Oracle │ ├── DBScript │ │ └── NTccTransaction.sql │ ├── NTccTransaction.Oracle.csproj │ ├── NTccTransaction.cs │ ├── Options │ │ ├── DbContextOptionsFactory.cs │ │ ├── NTccTransaction.EFOptions.cs │ │ ├── NTccTransaction.Options.Extensions.cs │ │ ├── NTccTransaction.OracleOptions.cs │ │ └── NTccTransaction.OracleOptionsExtension.cs │ ├── TransactionDbContext.cs │ └── TransactionRepository.cs ├── NTccTransaction.SqlServer │ ├── DBScript │ │ └── NTccTransaction.sql │ ├── NTccTransaction.SqlServer.csproj │ ├── NTccTransaction.cs │ ├── Options │ │ ├── DbContextOptionsFactory.cs │ │ ├── NTccTransaction.EFOptions.cs │ │ ├── NTccTransaction.Options.Extensions.cs │ │ ├── NTccTransaction.SqlServerOptions.cs │ │ └── NTccTransaction.SqlServerOptionsExtension.cs │ ├── TransactionDbContext.cs │ └── TransactionRepository.cs └── NTccTransaction │ ├── CompensableAttribute.cs │ ├── CompensableMethodContext.cs │ ├── Internal │ ├── DefaultTransactionContextEditor.cs │ ├── IBootstrapper.Default.cs │ ├── ITransactionMethodInvoker.Default.cs │ ├── ITransactionRecoveryProcessingServer.Default.cs │ ├── ITransactionServiceSelector.Default.cs │ ├── MethodMatcherCache.cs │ └── ObjectMethodExecutor │ │ ├── AwaitableInfo.cs │ │ ├── CoercedAwaitableInfo.cs │ │ ├── ObjectMethodExecutor.cs │ │ ├── ObjectMethodExecutorAwaitable.cs │ │ └── ObjectMethodExecutorFSharpSupport.cs │ ├── NTccTransaction.Builder.cs │ ├── NTccTransaction.ServiceCollectionExtensions.cs │ ├── NTccTransaction.csproj │ ├── NTccTransactionOptions.cs │ ├── Persistence │ ├── AmbientTransaction.cs │ ├── CachableTransactionRepository.cs │ ├── Participant.cs │ ├── Transaction.cs │ ├── TransactionManager.cs │ └── TransactionRecovery.cs │ ├── Serialization │ └── StringSerializer.cs │ └── Utils │ ├── CompensableHelper.cs │ ├── NTccContractResolver.cs │ └── TransactionUtils.cs └── test └── NTccTransaction.Test ├── LocalService └── LocalServiceUnitTest.cs └── NTccTransaction.Test.csproj /.gitignore: -------------------------------------------------------------------------------- 1 | /.vs 2 | bin/ 3 | obj/ 4 | /packages 5 | *.user 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 wzl-bxg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NTccTransactionCore.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30717.126 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{816CAAED-2B80-47B7-85F4-A1222F9E1888}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{A1AD4F79-2D56-40B2-AF73-7C1AFDFD6D9E}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{F77EE4FB-4501-4CE4-A92B-72259171F206}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NTccTransaction", "src\NTccTransaction\NTccTransaction.csproj", "{8A46B1E4-1A6C-4F9D-BF2B-75BA574757E1}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NTccTransaction.Oracle", "src\NTccTransaction.Oracle\NTccTransaction.Oracle.csproj", "{2B83E6DA-EBF2-4067-BBB2-F5126BE6C1B7}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NTccTransaction.Aop", "src\NTccTransaction.Aop\NTccTransaction.Aop.csproj", "{61A42BA6-8DDB-4AD8-B053-5268BD9066FC}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NTccTransaction.Abstractions", "src\NTccTransaction.Abstractions\NTccTransaction.Abstractions.csproj", "{6827958D-2CC2-4F96-AB18-7579ED94D0D4}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NTccTransaction.Http.Order.WebApi", "samples\NTccTransaction.Http.Order.WebApi\NTccTransaction.Http.Order.WebApi.csproj", "{B64BFC0D-C524-4A44-B92C-119EF4A474BD}" 21 | EndProject 22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NTccTransaction.Test", "test\NTccTransaction.Test\NTccTransaction.Test.csproj", "{79A7395F-2665-4821-A587-07FD2A381DCB}" 23 | EndProject 24 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NTccTransaction.Http.Capital.WebApi", "samples\NTccTransaction.Http.Capital.WebApi\NTccTransaction.Http.Capital.WebApi.csproj", "{6241B896-5234-4684-9B42-0CAC8E8D5D00}" 25 | EndProject 26 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NTccTransaction.SqlServer", "src\NTccTransaction.SqlServer\NTccTransaction.SqlServer.csproj", "{8A4EEB06-08AB-41BF-904F-04135DC6F42A}" 27 | EndProject 28 | Global 29 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 30 | Debug|Any CPU = Debug|Any CPU 31 | Release|Any CPU = Release|Any CPU 32 | EndGlobalSection 33 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 34 | {8A46B1E4-1A6C-4F9D-BF2B-75BA574757E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {8A46B1E4-1A6C-4F9D-BF2B-75BA574757E1}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {8A46B1E4-1A6C-4F9D-BF2B-75BA574757E1}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {8A46B1E4-1A6C-4F9D-BF2B-75BA574757E1}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {2B83E6DA-EBF2-4067-BBB2-F5126BE6C1B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {2B83E6DA-EBF2-4067-BBB2-F5126BE6C1B7}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {2B83E6DA-EBF2-4067-BBB2-F5126BE6C1B7}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {2B83E6DA-EBF2-4067-BBB2-F5126BE6C1B7}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {61A42BA6-8DDB-4AD8-B053-5268BD9066FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {61A42BA6-8DDB-4AD8-B053-5268BD9066FC}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {61A42BA6-8DDB-4AD8-B053-5268BD9066FC}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {61A42BA6-8DDB-4AD8-B053-5268BD9066FC}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {6827958D-2CC2-4F96-AB18-7579ED94D0D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {6827958D-2CC2-4F96-AB18-7579ED94D0D4}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {6827958D-2CC2-4F96-AB18-7579ED94D0D4}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {6827958D-2CC2-4F96-AB18-7579ED94D0D4}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {B64BFC0D-C524-4A44-B92C-119EF4A474BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {B64BFC0D-C524-4A44-B92C-119EF4A474BD}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {B64BFC0D-C524-4A44-B92C-119EF4A474BD}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {B64BFC0D-C524-4A44-B92C-119EF4A474BD}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {79A7395F-2665-4821-A587-07FD2A381DCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {79A7395F-2665-4821-A587-07FD2A381DCB}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {79A7395F-2665-4821-A587-07FD2A381DCB}.Release|Any CPU.ActiveCfg = Release|Any CPU 57 | {79A7395F-2665-4821-A587-07FD2A381DCB}.Release|Any CPU.Build.0 = Release|Any CPU 58 | {6241B896-5234-4684-9B42-0CAC8E8D5D00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 59 | {6241B896-5234-4684-9B42-0CAC8E8D5D00}.Debug|Any CPU.Build.0 = Debug|Any CPU 60 | {6241B896-5234-4684-9B42-0CAC8E8D5D00}.Release|Any CPU.ActiveCfg = Release|Any CPU 61 | {6241B896-5234-4684-9B42-0CAC8E8D5D00}.Release|Any CPU.Build.0 = Release|Any CPU 62 | {8A4EEB06-08AB-41BF-904F-04135DC6F42A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 63 | {8A4EEB06-08AB-41BF-904F-04135DC6F42A}.Debug|Any CPU.Build.0 = Debug|Any CPU 64 | {8A4EEB06-08AB-41BF-904F-04135DC6F42A}.Release|Any CPU.ActiveCfg = Release|Any CPU 65 | {8A4EEB06-08AB-41BF-904F-04135DC6F42A}.Release|Any CPU.Build.0 = Release|Any CPU 66 | EndGlobalSection 67 | GlobalSection(SolutionProperties) = preSolution 68 | HideSolutionNode = FALSE 69 | EndGlobalSection 70 | GlobalSection(NestedProjects) = preSolution 71 | {8A46B1E4-1A6C-4F9D-BF2B-75BA574757E1} = {816CAAED-2B80-47B7-85F4-A1222F9E1888} 72 | {2B83E6DA-EBF2-4067-BBB2-F5126BE6C1B7} = {816CAAED-2B80-47B7-85F4-A1222F9E1888} 73 | {61A42BA6-8DDB-4AD8-B053-5268BD9066FC} = {816CAAED-2B80-47B7-85F4-A1222F9E1888} 74 | {6827958D-2CC2-4F96-AB18-7579ED94D0D4} = {816CAAED-2B80-47B7-85F4-A1222F9E1888} 75 | {B64BFC0D-C524-4A44-B92C-119EF4A474BD} = {F77EE4FB-4501-4CE4-A92B-72259171F206} 76 | {79A7395F-2665-4821-A587-07FD2A381DCB} = {A1AD4F79-2D56-40B2-AF73-7C1AFDFD6D9E} 77 | {6241B896-5234-4684-9B42-0CAC8E8D5D00} = {F77EE4FB-4501-4CE4-A92B-72259171F206} 78 | {8A4EEB06-08AB-41BF-904F-04135DC6F42A} = {816CAAED-2B80-47B7-85F4-A1222F9E1888} 79 | EndGlobalSection 80 | GlobalSection(ExtensibilityGlobals) = postSolution 81 | SolutionGuid = {59092443-14B5-4593-B1D3-6E102F01E4F4} 82 | EndGlobalSection 83 | EndGlobal 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NTcc-TransactionCore - The .NET Core port based on tcc-transaction 2 | 3 | 4 | ## Introduction 5 | 6 | This is the README file for NTcc-TransactionCore, .NET Core port of Java tcc-transaction. It supports .NET Core/netstandard 2.0 and later, not supported for .NET Framework. 7 | 8 | NTcc-TransactionCore is an opensource project aimed at creating a free-for-commercial use TCC transaction, with enterprise features. 9 | 10 | ## Architecture overview 11 | ![Architecture.png](https://github.com/wzl-bxg/NTcc-TransactionCore/blob/main/TCC%20Architecture.png) 12 | 13 | ## Getting Started 14 | 15 | ### NuGet 16 | 17 | NTcc-TransactionCore can be installed in your project with the following command. 18 | 19 | ~~~shell 20 | PM> Install-Package NTccTransactionCore 21 | PM> Install-Package NTccTransactionCore.Aop 22 | ~~~ 23 | 24 | NTcc-TransactionCore Currently supports Oracle, SqlServer as transaction log storage, following packages are available to install: 25 | 26 | ~~~shell 27 | PM> Install-Package NTccTransactionCore.Oracle 28 | PM> Install-Package NTccTransactionCore.SqlServer 29 | ~~~ 30 | 31 | ### Configuration 32 | 33 | First, you need to configure NTcc-TransactionCore in your `Startup.cs`: 34 | 35 | ~~~c# 36 | public ILifetimeScope AutofacContainer { get; private set; } 37 | 38 | public IServiceCollection Services { get; private set; } 39 | 40 | public void ConfigureServices(IServiceCollection services) 41 | { 42 | //...... 43 | 44 | services.AddNTccTransaction((option) => 45 | { 46 | option.UseOracle((oracleOption) => 47 | { 48 | oracleOption.ConnectionString = Configuration.GetConnectionString("Your ConnectionStrings");// configure db connectiong 49 | }); 50 | 51 | option.UseCastleInterceptor(); // use Castle Interceptor 52 | }); 53 | 54 | Services = services; 55 | } 56 | 57 | public void ConfigureContainer(ContainerBuilder containerBuilder) 58 | { 59 | containerBuilder.Register(Services); // register Castle Interceptor 60 | } 61 | 62 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 63 | { 64 | this.AutofacContainer = app.ApplicationServices.GetAutofacRoot(); 65 | } 66 | ~~~ 67 | 68 | ### DB Script 69 | 70 | Currently supports Oracle, SqlServer as transaction log storage, execute the following database script to create transaction table: 71 | 72 | #### Oracle 73 | 74 | ~~~sql 75 | CREATE TABLE NTCC_TRANSACTION 76 | ( 77 | TRANSACTION_ID VARCHAR2(128) NOT NULL 78 | , GLOBAL_TRANSACTION_ID VARCHAR2(128) 79 | , BRANCH_QUALIFIER VARCHAR2(128) 80 | , STATUS NUMBER(9, 0) NOT NULL 81 | , TRANSACTION_TYPE NUMBER(9, 0) NOT NULL 82 | , RETRIED_COUNT NUMBER(9, 0) NOT NULL 83 | , CREATE_UTC_TIME DATE NOT NULL 84 | , LAST_UPDATE_UTC_TIME DATE NOT NULL 85 | , VERSION NUMBER(9, 0) NOT NULL 86 | , CONTENT CLOB 87 | , CONSTRAINT PK_NTCC_TRANSACTION PRIMARY KEY 88 | ( 89 | TRANSACTION_ID 90 | ) 91 | ); 92 | ~~~ 93 | 94 | #### Sql Server 95 | 96 | ~~~sql 97 | CREATE TABLE [dbo].[NTCC_TRANSACTION] 98 | ( 99 | [TRANSACTION_ID] varchar(128) NOT NULL 100 | ,[GLOBAL_TRANSACTION_ID] varchar(128) NULL 101 | ,[BRANCH_QUALIFIER] varchar(128) NULL 102 | ,[STATUS] int NOT NULL 103 | ,[TRANSACTION_TYPE] int NOT NULL 104 | ,[RETRIED_COUNT] int NOT NULL 105 | ,[CREATE_UTC_TIME] datetime NOT NULL 106 | ,[LAST_UPDATE_UTC_TIME] datetime NOT NULL 107 | ,[VERSION] int NOT NULL 108 | ,[CONTENT] nvarchar(MAX) NULL 109 | ,PRIMARY KEY 110 | ( 111 | [TRANSACTION_ID] 112 | ) 113 | ) 114 | ~~~ 115 | 116 | 117 | 118 | ### In Business Logic Service 119 | 120 | In your business service, you need implement `INTccTransactionService`: 121 | 122 | ~~~c# 123 | public class OrderService : IOrderService, INTccTransactionService 124 | { 125 | private readonly ILogger _logger; 126 | private readonly ICapitalProxy _capitalProxy; 127 | 128 | public OrderService(ILogger logger, ICapitalProxy capitalProxy) 129 | { 130 | _logger = logger; 131 | _capitalProxy = capitalProxy; 132 | } 133 | 134 | [Compensable(CancelMethod = "CancelOrder", ConfirmMethod = "ConfirmOrder")] 135 | public async Task TryPostOrder(string input, TransactionContext transactionContext = null) 136 | { 137 | return await Task.FromResult(""); 138 | } 139 | 140 | public async Task ConfirmOrder(string input, TransactionContext transactionContext = null) 141 | { 142 | await Task.CompletedTask; 143 | } 144 | 145 | public async Task CancelOrder(string input, TransactionContext transactionContext = null) 146 | { 147 | await Task.CompletedTask; 148 | } 149 | } 150 | ~~~ 151 | 152 | And add the attribute `[Compensable(CancelMethod = "xxx", ConfirmMethod = "xxx")]` on the `Try` method: 153 | 154 | ~~~c# 155 | [Compensable(CancelMethod = "CancelOrder", ConfirmMethod = "ConfirmOrder")] 156 | public async Task TryPostOrder(string input, TransactionContext transactionContext = null) 157 | { 158 | return await Task.FromResult(""); 159 | } 160 | ~~~ 161 | 162 | The type of the last parameter of the `Try` method must be `TransactionContext`, it's used to propagate transactions, and you need add `Confirm` method and `Cancel` method in the business logic service,the parameters of the two methods must be same as `Try ` method. 163 | 164 | ~~~C# 165 | public async Task ConfirmOrder(string input, TransactionContext transactionContext = null) 166 | { 167 | await Task.CompletedTask; 168 | } 169 | 170 | public async Task CancelOrder(string input, TransactionContext transactionContext = null) 171 | { 172 | await Task.CompletedTask; 173 | } 174 | ~~~ 175 | 176 | Then register your class that implement `INTccTransactionService` in `Startup.cs` : 177 | 178 | ```c# 179 | public void ConfigureServices(IServiceCollection services) 180 | { 181 | services.AddTransient(); 182 | services.AddTransient(); 183 | } 184 | ``` 185 | 186 | ### Contribute 187 | 188 | One of the easiest ways to contribute is to participate in discussions and discuss issues. You can also contribute by submitting pull requests with code changes. -------------------------------------------------------------------------------- /TCC Architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wzl-bxg/NTcc-TransactionCore/6c58058a86d1fa28ac7d09d579c6215507b146d8/TCC Architecture.png -------------------------------------------------------------------------------- /samples/NTccTransaction.Http.Capital.WebApi/Controllers/CapitalController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.Extensions.Logging; 3 | using NTccTransaction.Abstractions; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | 9 | namespace NTccTransaction.Http.Capital.WebApi.Controllers 10 | { 11 | [ApiController] 12 | [Route("[controller]")] 13 | public class CapitalController : ControllerBase 14 | { 15 | 16 | private readonly ILogger _logger; 17 | private readonly ICapitalService _capitalService; 18 | 19 | public CapitalController(ILogger logger, ICapitalService capitalService) 20 | { 21 | _logger = logger; 22 | _capitalService = capitalService; 23 | } 24 | 25 | [HttpPost("[action]")] 26 | public async Task RecordAsync(TransactionContext transactionContext) 27 | { 28 | return await _capitalService.Record(transactionContext); 29 | } 30 | 31 | [HttpGet] 32 | public async Task get() 33 | { 34 | return await Task.FromResult("test"); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /samples/NTccTransaction.Http.Capital.WebApi/NTccTransaction.Http.Capital.WebApi.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /samples/NTccTransaction.Http.Capital.WebApi/Program.cs: -------------------------------------------------------------------------------- 1 | using Autofac.Extensions.DependencyInjection; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.Hosting; 5 | using Microsoft.Extensions.Logging; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Threading.Tasks; 10 | 11 | namespace NTccTransaction.Http.Capital.WebApi 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | CreateHostBuilder(args).Build().Run(); 18 | } 19 | 20 | public static IHostBuilder CreateHostBuilder(string[] args) => 21 | Host.CreateDefaultBuilder(args) 22 | .UseServiceProviderFactory(new AutofacServiceProviderFactory()) 23 | .ConfigureWebHostDefaults(webBuilder => 24 | { 25 | webBuilder.UseStartup(); 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /samples/NTccTransaction.Http.Capital.WebApi/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:62251", 8 | "sslPort": 0 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "swagger", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "NTccTransaction.Http.Capital.WebApi": { 21 | "commandName": "Project", 22 | "dotnetRunMessages": "true", 23 | "launchBrowser": true, 24 | "launchUrl": "swagger", 25 | "applicationUrl": "http://localhost:5001", 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /samples/NTccTransaction.Http.Capital.WebApi/Services/CapitalService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using NTccTransaction.Abstractions; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace NTccTransaction.Http.Capital.WebApi 9 | { 10 | public class CapitalService : ICapitalService, INTccTransactionService 11 | { 12 | private readonly ILogger _logger; 13 | 14 | public CapitalService(ILogger logger) 15 | { 16 | _logger = logger; 17 | } 18 | 19 | [Compensable(CancelMethod = "CancelRecord", ConfirmMethod = "ConfirmRecord")] 20 | public async Task Record(TransactionContext transactionContext) 21 | { 22 | _logger.LogInformation("CapitalService.Record"); 23 | 24 | return await Task.FromResult("Capital Return"); 25 | } 26 | 27 | public void ConfirmRecord(TransactionContext transactionContext = null) 28 | { 29 | _logger.LogInformation("CapitalService.ConfirmRecord"); 30 | } 31 | 32 | public void CancelRecord(TransactionContext transactionContext = null) 33 | { 34 | _logger.LogInformation("CapitalService.CancelRecord"); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /samples/NTccTransaction.Http.Capital.WebApi/Services/ICapitalService.cs: -------------------------------------------------------------------------------- 1 | using NTccTransaction.Abstractions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace NTccTransaction.Http.Capital.WebApi 8 | { 9 | public interface ICapitalService 10 | { 11 | Task Record(TransactionContext transactionContext); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /samples/NTccTransaction.Http.Capital.WebApi/Startup.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wzl-bxg/NTcc-TransactionCore/6c58058a86d1fa28ac7d09d579c6215507b146d8/samples/NTccTransaction.Http.Capital.WebApi/Startup.cs -------------------------------------------------------------------------------- /samples/NTccTransaction.Http.Capital.WebApi/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/NTccTransaction.Http.Capital.WebApi/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "capitalDb": "Server=127.0.0.1;Database=capitalDb;User Id=sa;Password=12345678;" 4 | }, 5 | "Logging": { 6 | "LogLevel": { 7 | "Default": "Information", 8 | "Microsoft": "Warning", 9 | "Microsoft.Hosting.Lifetime": "Information" 10 | } 11 | }, 12 | "AllowedHosts": "*" 13 | } 14 | -------------------------------------------------------------------------------- /samples/NTccTransaction.Http.Capital.WebApi/log4net.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /samples/NTccTransaction.Http.Order.WebApi/Controllers/OrderController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.Extensions.Logging; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace NTccTransaction.Http.Order.WebApi.Controllers 9 | { 10 | [ApiController] 11 | [Route("[controller]")] 12 | public class OrderController : ControllerBase 13 | { 14 | 15 | 16 | private readonly ILogger _logger; 17 | 18 | private readonly IOrderService _orderService; 19 | 20 | public OrderController(ILogger logger, IOrderService orderService) 21 | { 22 | _logger = logger; 23 | _orderService = orderService; 24 | } 25 | 26 | [HttpGet] 27 | public async Task PostOrder(string input) 28 | { 29 | return await _orderService.PostOrder(input); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /samples/NTccTransaction.Http.Order.WebApi/ICapitalApi.cs: -------------------------------------------------------------------------------- 1 | using NTccTransaction.Abstractions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using WebApiClientCore; 7 | using WebApiClientCore.Attributes; 8 | 9 | namespace NTccTransaction.Http.Order.WebApi 10 | { 11 | public interface ICapitalApi : IHttpApi 12 | { 13 | [HttpPost("Capital/Record")] 14 | Task Record([JsonContent] TransactionContext transactionContext = null); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/NTccTransaction.Http.Order.WebApi/NTccTransaction.Http.Order.WebApi.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /samples/NTccTransaction.Http.Order.WebApi/Program.cs: -------------------------------------------------------------------------------- 1 | using Autofac.Extensions.DependencyInjection; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.Hosting; 5 | using Microsoft.Extensions.Logging; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Threading.Tasks; 10 | 11 | namespace NTccTransaction.Http.Order.WebApi 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | CreateHostBuilder(args).Build().Run(); 18 | } 19 | 20 | public static IHostBuilder CreateHostBuilder(string[] args) => 21 | Host.CreateDefaultBuilder(args) 22 | .UseServiceProviderFactory(new AutofacServiceProviderFactory()) 23 | .ConfigureWebHostDefaults(webBuilder => 24 | { 25 | webBuilder.UseStartup(); 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /samples/NTccTransaction.Http.Order.WebApi/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:61695", 8 | "sslPort": 0 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "swagger", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "NTccTransaction.Http.Order.WebApi": { 21 | "commandName": "Project", 22 | "dotnetRunMessages": "true", 23 | "launchBrowser": true, 24 | "launchUrl": "swagger", 25 | "applicationUrl": "http://localhost:5000", 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /samples/NTccTransaction.Http.Order.WebApi/Proxy/CapitalProxy.cs: -------------------------------------------------------------------------------- 1 | using NTccTransaction.Abstractions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using WebApiClientCore; 7 | 8 | namespace NTccTransaction.Http.Order.WebApi 9 | { 10 | public interface ICapitalProxy 11 | { 12 | Task Record(TransactionContext transactionContext); 13 | 14 | } 15 | 16 | public class CapitalProxy : ICapitalProxy, INTccTransactionService 17 | { 18 | private readonly ICapitalApi _capitalApi; 19 | public CapitalProxy(ICapitalApi capitalApi) 20 | { 21 | _capitalApi = capitalApi; 22 | } 23 | 24 | [Compensable(CancelMethod = "Record", ConfirmMethod = "Record", Propagation = Propagation.SUPPORTS)] 25 | public async Task Record(TransactionContext transactionContext = null) 26 | { 27 | return await _capitalApi.Record(transactionContext); 28 | 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /samples/NTccTransaction.Http.Order.WebApi/Services/IOrderService.cs: -------------------------------------------------------------------------------- 1 | using NTccTransaction.Abstractions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace NTccTransaction.Http.Order.WebApi 8 | { 9 | public interface IOrderService 10 | { 11 | Task PostOrder(string input, TransactionContext transactionContext = null); 12 | 13 | Task ConfirmOrder(string input, TransactionContext transactionContext = null); 14 | 15 | Task CancelOrder(string input, TransactionContext transactionContext = null); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /samples/NTccTransaction.Http.Order.WebApi/Services/IOrderService1.cs: -------------------------------------------------------------------------------- 1 | using NTccTransaction.Abstractions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace NTccTransaction.Http.Order.WebApi 8 | { 9 | public interface IOrderService1 10 | { 11 | Task PostOrder(string input, TransactionContext transactionContext = null); 12 | 13 | Task ConfirmOrder(string input, TransactionContext transactionContext = null); 14 | 15 | Task CancelOrder(string input, TransactionContext transactionContext = null); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /samples/NTccTransaction.Http.Order.WebApi/Services/OrderService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using NTccTransaction.Abstractions; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace NTccTransaction.Http.Order.WebApi 9 | { 10 | public class OrderService : IOrderService, INTccTransactionService 11 | { 12 | private readonly ILogger _logger; 13 | private readonly ICapitalProxy _capitalProxy; 14 | private readonly IOrderService1 _orderService1; 15 | 16 | public OrderService(ILogger logger, ICapitalProxy capitalProxy, IOrderService1 orderService1) 17 | { 18 | _logger = logger; 19 | _capitalProxy = capitalProxy; 20 | _orderService1 = orderService1; 21 | } 22 | 23 | [Compensable(CancelMethod = "CancelOrder", ConfirmMethod = "ConfirmOrder")] 24 | public async Task PostOrder(string input, TransactionContext transactionContext = null) 25 | { 26 | _logger.LogInformation("OrderService.PostOrder:" + input); 27 | 28 | var result = await _capitalProxy.Record(null); 29 | //var result = await _orderService1.PostOrder("vvv"); 30 | 31 | if (input.StartsWith("Exception")) 32 | { 33 | throw new Exception("Exception Test"); 34 | } 35 | return await Task.FromResult(""); 36 | // return await Task.FromResult(result); 37 | } 38 | 39 | public async Task ConfirmOrder(string input, TransactionContext transactionContext = null) 40 | { 41 | _logger.LogInformation("OrderService.ConfirmOrder:" + input); 42 | await Task.CompletedTask; 43 | } 44 | 45 | public async Task CancelOrder(string input, TransactionContext transactionContext = null) 46 | { 47 | _logger.LogInformation("OrderService.CancelOrder:" + input); 48 | await Task.CompletedTask; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /samples/NTccTransaction.Http.Order.WebApi/Services/OrderService1.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using NTccTransaction.Abstractions; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace NTccTransaction.Http.Order.WebApi 9 | { 10 | public class OrderService1 : IOrderService1, INTccTransactionService 11 | { 12 | private readonly ILogger _logger; 13 | private readonly ICapitalProxy _capitalProxy; 14 | 15 | public OrderService1(ILogger logger, ICapitalProxy capitalProxy) 16 | { 17 | _logger = logger; 18 | _capitalProxy = capitalProxy; 19 | } 20 | 21 | [Compensable(CancelMethod = "CancelOrder", ConfirmMethod = "ConfirmOrder")] 22 | public async Task PostOrder(string input, TransactionContext transactionContext = null) 23 | { 24 | _logger.LogInformation("OrderService1.PostOrder:" + input); 25 | return await Task.FromResult("1"); 26 | } 27 | 28 | public async Task ConfirmOrder(string input, TransactionContext transactionContext = null) 29 | { 30 | _logger.LogInformation("OrderService1.ConfirmOrder:" + input); 31 | await Task.CompletedTask; 32 | } 33 | 34 | public async Task CancelOrder(string input, TransactionContext transactionContext = null) 35 | { 36 | _logger.LogInformation("OrderService1.CancelOrder:" + input); 37 | await Task.CompletedTask; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/NTccTransaction.Http.Order.WebApi/Startup.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using Autofac.Extensions.DependencyInjection; 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.Hosting; 9 | using Microsoft.Extensions.Logging; 10 | using Microsoft.OpenApi.Models; 11 | using NTccTransaction.SqlServer; 12 | using NTccTransaction.Aop; 13 | using System; 14 | using System.Collections.Generic; 15 | using System.Linq; 16 | using System.Threading.Tasks; 17 | 18 | namespace NTccTransaction.Http.Order.WebApi 19 | { 20 | public class Startup 21 | { 22 | public Startup(IConfiguration configuration) 23 | { 24 | Configuration = configuration; 25 | } 26 | 27 | public IConfiguration Configuration { get; } 28 | 29 | public ILifetimeScope AutofacContainer { get; private set; } 30 | 31 | public IServiceCollection Services { get; private set; } 32 | 33 | 34 | 35 | // This method gets called by the runtime. Use this method to add services to the container. 36 | public void ConfigureServices(IServiceCollection services) 37 | { 38 | 39 | services.AddControllers(); 40 | services.AddSwaggerGen(c => 41 | { 42 | c.SwaggerDoc("v1", new OpenApiInfo { Title = "NTccTransaction.Http.Order.WebApi", Version = "v1" }); 43 | }); 44 | 45 | services.AddTransient(); 46 | services.AddTransient(); 47 | 48 | services.AddTransient(); 49 | services.AddTransient(); 50 | 51 | services.AddTransient(); 52 | services.AddTransient(); 53 | 54 | services.AddNTccTransaction((option) => 55 | { 56 | option.UseSqlServer((sqlOption) => 57 | { 58 | sqlOption.ConnectionString = Configuration.GetConnectionString("orderDb"); 59 | 60 | }); 61 | 62 | option.UseCastleInterceptor(); 63 | }); 64 | 65 | services.AddHttpApi() 66 | .ConfigureHttpApi(Configuration.GetSection(nameof(ICapitalApi))); 67 | 68 | Services = services; 69 | } 70 | 71 | public void ConfigureContainer(ContainerBuilder containerBuilder) 72 | { 73 | containerBuilder.Register(Services); 74 | } 75 | 76 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 77 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) 78 | { 79 | loggerFactory.AddLog4Net(); 80 | 81 | this.AutofacContainer = app.ApplicationServices.GetAutofacRoot(); 82 | 83 | if (env.IsDevelopment()) 84 | { 85 | app.UseDeveloperExceptionPage(); 86 | app.UseSwagger(); 87 | app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "NTccTransaction.Http.Order.WebApi v1")); 88 | } 89 | 90 | app.UseRouting(); 91 | 92 | app.UseAuthorization(); 93 | 94 | app.UseEndpoints(endpoints => 95 | { 96 | endpoints.MapControllers(); 97 | }); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /samples/NTccTransaction.Http.Order.WebApi/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/NTccTransaction.Http.Order.WebApi/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "orderDb": "Server=127.0.0.1;Database=orderDb;User Id=sa;Password=12345678;" 4 | }, 5 | "ICapitalApi": { 6 | "HttpHost": "http://localhost:5001", 7 | "UseParameterPropertyValidate": false, 8 | "UseReturnValuePropertyValidate": false, 9 | "JsonSerializeOptions": { 10 | "IgnoreNullValues": true, 11 | "WriteIndented": false 12 | } 13 | }, 14 | 15 | "Logging": { 16 | "LogLevel": { 17 | "Default": "Information", 18 | "Microsoft": "Warning", 19 | "Microsoft.Hosting.Lifetime": "Information" 20 | } 21 | }, 22 | "AllowedHosts": "*" 23 | } 24 | -------------------------------------------------------------------------------- /samples/NTccTransaction.Http.Order.WebApi/log4net.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/Check.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NTccTransaction.Abstractions 4 | { 5 | public static class Check 6 | { 7 | public static void NotNull(object value, string name) 8 | { 9 | if(value == null) 10 | { 11 | throw new ArgumentNullException(name); 12 | } 13 | } 14 | 15 | public static void NotNullOrEmpty(string value, string name) 16 | { 17 | if (string.IsNullOrEmpty(value)) 18 | { 19 | throw new ArgumentNullException(name); 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/Exception/CancellingException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace NTccTransaction.Abstractions 5 | { 6 | public class CancellingException : TransactionException 7 | { 8 | public override string Message => $"NTccTransaction,Cancelling exception:{ base.Message}"; 9 | 10 | public CancellingException() 11 | : base() 12 | { 13 | 14 | } 15 | 16 | public CancellingException(string message) 17 | : base(message) 18 | { 19 | 20 | } 21 | 22 | public CancellingException(string message, Exception innerException) 23 | : base(message, innerException) 24 | { 25 | 26 | } 27 | 28 | public CancellingException(SerializationInfo info, StreamingContext context) 29 | : base(info, context) 30 | { 31 | 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/Exception/ConcurrencyTransactionException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NTccTransaction.Abstractions 4 | { 5 | public class ConcurrencyTransactionException : Exception 6 | { 7 | public ConcurrencyTransactionException() 8 | { 9 | 10 | } 11 | 12 | public ConcurrencyTransactionException(string message) 13 | : base(message) 14 | { 15 | 16 | } 17 | 18 | public ConcurrencyTransactionException(string message, Exception innerException) 19 | : base(message, innerException) 20 | { 21 | 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/Exception/ConfirmingException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace NTccTransaction.Abstractions 5 | { 6 | public class ConfirmingException: TransactionException 7 | { 8 | public override string Message => $"NTccTransaction,Confirming exception:{ base.Message}"; 9 | 10 | public ConfirmingException() 11 | : base() 12 | { 13 | 14 | } 15 | 16 | public ConfirmingException(string message) 17 | : base(message) 18 | { 19 | 20 | } 21 | 22 | public ConfirmingException(string message, Exception innerException) 23 | : base(message, innerException) 24 | { 25 | 26 | } 27 | 28 | public ConfirmingException(SerializationInfo info, StreamingContext context) 29 | : base(info, context) 30 | { 31 | 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/Exception/NoExistedTransactionException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace NTccTransaction.Abstractions 5 | { 6 | public class NoExistedTransactionException : TransactionException 7 | { 8 | public override string Message => $"NTccTransaction,transaction not existed exception:{ base.Message}"; 9 | 10 | public NoExistedTransactionException() 11 | : base() 12 | { 13 | 14 | } 15 | 16 | public NoExistedTransactionException(string message) 17 | : base(message) 18 | { 19 | 20 | } 21 | 22 | public NoExistedTransactionException(string message, Exception innerException) 23 | : base(message, innerException) 24 | { 25 | 26 | } 27 | 28 | public NoExistedTransactionException(SerializationInfo info, StreamingContext context) 29 | : base(info, context) 30 | { 31 | 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/Exception/TransactionException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace NTccTransaction.Abstractions 5 | { 6 | public class TransactionException: Exception 7 | { 8 | public override string Message => $"NTccTransaction:{ base.Message}"; 9 | 10 | public TransactionException() 11 | :base() 12 | { 13 | 14 | } 15 | 16 | public TransactionException(string message) 17 | : base(message) 18 | { 19 | 20 | } 21 | 22 | public TransactionException(string message, Exception innerException) 23 | : base(message, innerException) 24 | { 25 | 26 | } 27 | 28 | public TransactionException(SerializationInfo info, StreamingContext context) 29 | : base(info, context) 30 | { 31 | 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/Exception/TryingException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace NTccTransaction.Abstractions 5 | { 6 | public class TryingException : TransactionException 7 | { 8 | public override string Message => $"NTccTransaction,Trying exception:{ base.Message}"; 9 | 10 | public TryingException() 11 | : base() 12 | { 13 | 14 | } 15 | 16 | public TryingException(string message) 17 | : base(message) 18 | { 19 | 20 | } 21 | 22 | public TryingException(string message, Exception innerException) 23 | : base(message, innerException) 24 | { 25 | 26 | } 27 | 28 | public TryingException(SerializationInfo info, StreamingContext context) 29 | : base(info, context) 30 | { 31 | 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/ICompensable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NTccTransaction.Abstractions 4 | { 5 | public interface ICompensable 6 | { 7 | string ConfirmMethod { get; } 8 | 9 | string CancelMethod { get; } 10 | 11 | Propagation Propagation { get; } 12 | 13 | Type[] DelayCancelExceptionTypes { get; } 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/INTccInterceptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace NTccTransaction.Abstractions 7 | { 8 | public interface INTccInterceptor 9 | { 10 | Task InterceptAsync(IMethodInvocation invocation); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/INTccTransactionOptionsExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace NTccTransaction.Abstractions 4 | { 5 | /// 6 | /// NTccTransaction options extension 7 | /// 8 | public interface INTccTransactionOptionsExtension 9 | { 10 | /// 11 | /// Registered child service. 12 | /// 13 | /// add service to the 14 | void AddServices(IServiceCollection services); 15 | } 16 | } -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/IUniqueIdentity.cs: -------------------------------------------------------------------------------- 1 | namespace NTccTransaction.Abstractions 2 | { 3 | public interface IUniqueIdentity 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/Internal/CacheExecutorDescriptor.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Reflection; 6 | 7 | namespace NTccTransaction.Abstractions 8 | { 9 | 10 | /// 11 | /// A descriptor of user definition method. 12 | /// 13 | public class CacheExecutorDescriptor 14 | { 15 | public TypeInfo ServiceTypeInfo { get; set; } 16 | 17 | public MethodInfo MethodInfo { get; set; } 18 | 19 | public TypeInfo ImplTypeInfo { get; set; } 20 | 21 | public ICompensable Attribute { get; set; } 22 | 23 | public IList Parameters { get; set; } 24 | } 25 | 26 | public class ParameterDescriptor 27 | { 28 | public string Name { get; set; } 29 | 30 | public Type ParameterType { get; set; } 31 | 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/Internal/IBootstrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace NTccTransaction.Abstractions 8 | { 9 | public interface IBootstrapper 10 | { 11 | Task BootstrapAsync(CancellationToken stoppingToken); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/Internal/IMethodInvocation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace NTccTransaction.Abstractions 8 | { 9 | public interface IMethodInvocation 10 | { 11 | object[] Arguments { get; } 12 | 13 | IReadOnlyDictionary ArgumentsDictionary { get; } 14 | 15 | Type[] GenericArguments { get; } 16 | 17 | object TargetObject { get; } 18 | 19 | MethodInfo Method { get; } 20 | 21 | object ReturnValue { get; set; } 22 | 23 | Task ProceedAsync(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/Internal/INTccTransactionService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace NTccTransaction.Abstractions 6 | { 7 | public interface INTccTransactionService 8 | { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/Internal/IProcessingServer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace NTccTransaction.Abstractions 6 | { 7 | public interface IProcessingServer : IDisposable 8 | { 9 | void Pulse(); 10 | 11 | void Start(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/Internal/ITransactionContextEditor.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace NTccTransaction.Abstractions 4 | { 5 | public interface ITransactionContextEditor 6 | { 7 | TransactionContext GetContext(object target, MethodBase methodBase, object[] args); 8 | 9 | void SetContext(TransactionContext transactionContext, object target, MethodBase methodBase, object[] args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/Internal/ITransactionMethodInvoker.cs: -------------------------------------------------------------------------------- 1 | using NTccTransaction.Abstractions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace NTccTransaction.Abstractions 9 | { 10 | 11 | /// 12 | /// Perform user definition method of tcc. 13 | /// 14 | public interface ITransactionMethodInvoker 15 | { 16 | /// 17 | /// Invoke tcc transaction method with TransactionContext and InvocationContext. 18 | /// 19 | /// The transaction context 20 | /// The invocation context 21 | /// The object of . 22 | Task InvokeAsync(TransactionContext transactionContext, InvocationContext invocationContext, CancellationToken cancellationToken = default); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/Internal/ITransactionRecoveryProcessingServer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace NTccTransaction.Abstractions 6 | { 7 | public interface ITransactionRecoveryProcessingServer : IProcessingServer 8 | { 9 | bool IsHealthy(); 10 | 11 | void ReStart(bool force = false); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/Internal/ITransactionServiceSelector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace NTccTransaction.Abstractions 6 | { 7 | /// 8 | /// Defines an interface for selecting an consumer service method to invoke for the current message. 9 | /// 10 | public interface ITransactionServiceSelector 11 | { 12 | /// 13 | /// Selects a set of candidates for the current message associated with 14 | /// 15 | /// A set of candidates or null. 16 | IReadOnlyList SelectCandidates(); 17 | 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/Internal/InvocationContext.cs: -------------------------------------------------------------------------------- 1 | using NTccTransaction.Abstractions; 2 | using System; 3 | 4 | namespace NTccTransaction.Abstractions 5 | { 6 | /// 7 | /// Invocation context:Used to handle Participant's Confirm and Cancel metadata 8 | /// 9 | [Serializable] 10 | public class InvocationContext 11 | { 12 | public Type TargetType { get; } 13 | 14 | public string MethodName { get; } 15 | 16 | public Type[] Parameters { get; } 17 | 18 | public object[] Arguments { get; } 19 | 20 | public InvocationContext(Type targetType, string methodName, Type[] parameters, object[] arguments) 21 | { 22 | Check.NotNull(targetType, nameof(targetType)); 23 | Check.NotNull(methodName, nameof(methodName)); 24 | 25 | TargetType = targetType; 26 | MethodName = methodName; 27 | Parameters = parameters ?? new Type[0]; 28 | Arguments = arguments ?? new object[0]; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/MethodRole.cs: -------------------------------------------------------------------------------- 1 | namespace NTccTransaction.Abstractions 2 | { 3 | public enum MethodRole : int 4 | { 5 | ROOT = 0, 6 | CONSUMER = 1, 7 | PROVIDER = 2, 8 | NORMAL = 3 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/NTccTransaction.Abstractions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | bin\$(Configuration)\netstandard2.0\NTccTransaction.Abstractions.xml 9 | 1701;1702;1705;CS1591 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/Persistence/IAmbientTransaction.cs: -------------------------------------------------------------------------------- 1 | namespace NTccTransaction.Abstractions 2 | { 3 | /// 4 | /// Ambient transaction 5 | /// 6 | public interface IAmbientTransaction : ITransactionAccessor 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/Persistence/IParticipant.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace NTccTransaction.Abstractions 9 | { 10 | public interface IParticipant 11 | { 12 | TransactionXid Xid { get; set; } 13 | 14 | InvocationContext ConfirmInvocationContext { get; set; } 15 | 16 | InvocationContext CancelInvocationContext { get; set; } 17 | 18 | 19 | Task CommitAsync(IServiceScopeFactory serviceScopeFactory); 20 | Task RollbackAsync(IServiceScopeFactory serviceScopeFactory); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/Persistence/ITransaction.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | 6 | namespace NTccTransaction.Abstractions 7 | { 8 | public interface ITransaction : IDisposable 9 | { 10 | event EventHandler Disoped; 11 | 12 | TransactionXid Xid { get; } 13 | TransactionStatus Status { get; set; } 14 | TransactionType TransactionType { get; } 15 | int RetriedCount { get; set; } 16 | DateTime CreateUtcTime { get; } 17 | DateTime LastUpdateUtcTime { get; set; } 18 | int Version { get; set; } 19 | 20 | Task CommitAsync(IServiceScopeFactory serviceScopeFactory); 21 | 22 | Task RollbackAsync(IServiceScopeFactory serviceScopeFactory); 23 | 24 | void AddRetriedCount(); 25 | 26 | void AddVersion(); 27 | 28 | void ChangeStatus(TransactionStatus status); 29 | 30 | 31 | 32 | #region Participant 33 | 34 | void AddParticipant(IParticipant participant); 35 | 36 | IReadOnlyList FindAllParticipantAsReadOnly(); 37 | 38 | #endregion 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/Persistence/ITransactionAccessor.cs: -------------------------------------------------------------------------------- 1 | namespace NTccTransaction.Abstractions 2 | { 3 | /// 4 | /// Transaction accessor 5 | /// 6 | public interface ITransactionAccessor 7 | { 8 | ITransaction Transaction { get; } 9 | 10 | /// 11 | /// Register transaction 12 | /// 13 | /// The transaction need to be register 14 | void RegisterTransaction(ITransaction transaction); 15 | 16 | /// 17 | /// Unregister transaction 18 | /// 19 | /// The transaction that has been unregister 20 | ITransaction UnRegisterTransaction(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/Persistence/ITransactionManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using System.Threading.Tasks; 5 | 6 | namespace NTccTransaction.Abstractions 7 | { 8 | /// 9 | /// Transaction manager 10 | /// Start root transaction 11 | /// Propagate branch transaction 12 | /// Manage transaction lifetime 13 | /// 14 | public interface ITransactionManager 15 | { 16 | /// 17 | /// Get current transaction 18 | /// 19 | ITransaction Current { get; } 20 | 21 | /// 22 | /// Begin transaction 23 | /// 24 | /// 25 | ITransaction Begin(); 26 | 27 | /// 28 | /// Begin transaction 29 | /// 30 | /// 31 | /// 32 | ITransaction Begin(object uniqueIdentity); 33 | 34 | /// 35 | /// Propagate branch transaction 36 | /// 37 | /// 38 | /// 39 | ITransaction PropagationNewBegin(TransactionContext transactionContext); 40 | 41 | /// 42 | /// Propagate existing branch transaction 43 | /// 44 | /// 45 | /// 46 | ITransaction PropagationExistBegin(TransactionContext transactionContext); 47 | 48 | /// 49 | /// Has already has transaction 50 | /// 51 | /// 52 | bool IsTransactionActive(); 53 | 54 | /// 55 | /// Commit transaction 56 | /// 57 | Task CommitAsync(); 58 | 59 | /// 60 | /// Rollback transaction 61 | /// 62 | Task RollbackAsync(); 63 | 64 | /// 65 | /// Add participant 66 | /// 67 | /// 68 | void AddParticipant(IParticipant participant); 69 | 70 | /// 71 | /// 72 | /// 73 | /// 74 | /// 75 | /// 76 | bool IsDelayCancelException(Exception tryingException, HashSet allDelayCancelExceptionTypes); 77 | 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/Persistence/ITransactionRecovery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace NTccTransaction.Abstractions 7 | { 8 | public interface ITransactionRecovery 9 | { 10 | Task StartRecoverAsync(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/Persistence/ITransactionRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace NTccTransaction.Abstractions 5 | { 6 | public interface ITransactionRepository 7 | { 8 | void Create(ITransaction transaction); 9 | 10 | void Update(ITransaction transaction); 11 | 12 | void Delete(ITransaction transaction); 13 | 14 | ITransaction FindByXid(TransactionXid xid); 15 | 16 | IEnumerable FindAllUnmodifiedSince(DateTime dateTime); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/Propagation.cs: -------------------------------------------------------------------------------- 1 | namespace NTccTransaction.Abstractions 2 | { 3 | public enum Propagation:int 4 | { 5 | REQUIRED = 0, 6 | 7 | SUPPORTS = 1, 8 | 9 | MANDATORY = 2, 10 | 11 | REQUIRES_NEW = 3 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/Serialization/ISerializer.cs: -------------------------------------------------------------------------------- 1 | namespace NTccTransaction.Abstractions 2 | { 3 | public interface ISerializer 4 | { 5 | T Deserialize(string content); 6 | 7 | string Serialize(T obj); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/System/Collections/Generic/HashSetExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace System.Collections.Generic 2 | { 3 | public static class HashSetExtensions 4 | { 5 | public static void AddRange(this HashSet set, ICollection items) 6 | { 7 | if(set==null) 8 | { 9 | set = new HashSet(); 10 | } 11 | 12 | if(items!=null && items.Count > 0) 13 | { 14 | foreach(var item in items) 15 | { 16 | set.Add(item); 17 | } 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/System/Collections/Generic/StackExtension.cs: -------------------------------------------------------------------------------- 1 | namespace System.Collections.Generic 2 | { 3 | public static class StackExtension 4 | { 5 | public static bool IsNull(this Stack stack) 6 | { 7 | return stack == null ; 8 | } 9 | 10 | public static bool IsNullOrEmpty(this Stack stack) 11 | { 12 | return stack == null || stack.Count <= 0; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/System/Reflection/MethodBaseExtensions.cs: -------------------------------------------------------------------------------- 1 | using NTccTransaction.Abstractions; 2 | using System.Linq; 3 | 4 | namespace System.Reflection 5 | { 6 | public static class MethodBaseExtensions 7 | { 8 | public static Type[] GetParameterTypes(this MethodBase method) 9 | { 10 | Check.NotNull(method, nameof(method)); 11 | 12 | return method.GetParameters().Select(p => p.ParameterType).ToArray(); 13 | } 14 | 15 | public static int GetParameterPosition(this MethodBase methodInfo) 16 | { 17 | var parameterTypes = methodInfo.GetParameters().Select(p => p.ParameterType).ToArray(); 18 | for (int i = 0; i < parameterTypes.Length; i++) 19 | { 20 | if (parameterTypes[i] == typeof(T)) 21 | { 22 | return i; 23 | } 24 | } 25 | 26 | return -1; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/TransactionContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NTccTransaction.Abstractions 4 | { 5 | [Serializable] 6 | public class TransactionContext 7 | { 8 | public TransactionXid Xid { get; set; } 9 | 10 | public TransactionStatus Status { get; set; } 11 | 12 | public TransactionContext() 13 | { 14 | 15 | } 16 | 17 | public TransactionContext(TransactionXid xid, TransactionStatus status) 18 | { 19 | Xid = xid?? throw new ArgumentNullException(nameof(xid)); 20 | Status = status; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/TransactionStatus.cs: -------------------------------------------------------------------------------- 1 | namespace NTccTransaction.Abstractions 2 | { 3 | /// 4 | /// Transaction status 5 | /// 6 | public enum TransactionStatus: int 7 | { 8 | TRYING=0, 9 | 10 | CONFIRMING=1, 11 | 12 | CANCELLING = 2 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/TransactionType.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace NTccTransaction.Abstractions 4 | { 5 | /// 6 | /// Transaction type 7 | /// 8 | public enum TransactionType:int 9 | { 10 | [Description("Root transaction")] 11 | ROOT=1, 12 | 13 | [Description("Branch transaction")] 14 | BRANCH = 2 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/NTccTransaction.Abstractions/TransactionXId.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | 5 | namespace NTccTransaction.Abstractions 6 | { 7 | [Serializable] 8 | public class TransactionXid 9 | { 10 | [field: NonSerialized] 11 | private static string CUSTOMIZED_TRANSACTION_ID = "UniqueIdentity"; 12 | 13 | public int FormatId { get; set; } = 1; 14 | public string GlobalTransactionId { get; set; } 15 | public string BranchQualifier { get; set; } 16 | 17 | public TransactionXid() 18 | { 19 | GlobalTransactionId = Guid.NewGuid().ToString(); 20 | BranchQualifier = Guid.NewGuid().ToString(); 21 | } 22 | 23 | public TransactionXid(object uniqueIdentity) 24 | { 25 | if (uniqueIdentity == null) 26 | { 27 | 28 | GlobalTransactionId = Guid.NewGuid().ToString(); 29 | BranchQualifier = Guid.NewGuid().ToString(); 30 | } 31 | else 32 | { 33 | this.GlobalTransactionId = CUSTOMIZED_TRANSACTION_ID; 34 | this.BranchQualifier = uniqueIdentity.ToString(); 35 | } 36 | } 37 | 38 | 39 | public TransactionXid(string globalTransactionId) 40 | { 41 | this.GlobalTransactionId = globalTransactionId; 42 | this.BranchQualifier = Guid.NewGuid().ToString(); 43 | } 44 | 45 | 46 | public TransactionXid(string globalTransactionId, string branchQualifier) 47 | { 48 | this.GlobalTransactionId = globalTransactionId; 49 | this.BranchQualifier = branchQualifier; 50 | } 51 | 52 | public TransactionXid Clone() 53 | { 54 | string cloneGlobalTransactionId = null; 55 | string cloneBranchQualifier = null; 56 | 57 | if (!string.IsNullOrEmpty(this.GlobalTransactionId)) 58 | { 59 | cloneGlobalTransactionId = this.GlobalTransactionId.Clone().ToString(); 60 | } 61 | 62 | if (!string.IsNullOrEmpty(this.BranchQualifier)) 63 | { 64 | cloneBranchQualifier = this.BranchQualifier.Clone().ToString(); 65 | } 66 | 67 | return new TransactionXid(cloneGlobalTransactionId, cloneBranchQualifier); 68 | } 69 | 70 | public override string ToString() 71 | { 72 | var stringBuilder = new StringBuilder(); 73 | 74 | if (CUSTOMIZED_TRANSACTION_ID.SequenceEqual(this.GlobalTransactionId)) 75 | { 76 | // format:UniqueIdentity:xxxx-xxxx-xxxx-xxxx 77 | stringBuilder.Append(this.GlobalTransactionId.ToString()); 78 | stringBuilder.Append(":").Append(this.BranchQualifier); 79 | 80 | } 81 | else 82 | { 83 | // format:xxxx-xxxx-xxxx-xxxx:xxxx-xxxx-xxxx-xxxx 84 | stringBuilder.Append(new Guid(this.GlobalTransactionId)); 85 | stringBuilder.Append(":").Append(new Guid(this.BranchQualifier)); 86 | } 87 | 88 | return stringBuilder.ToString(); 89 | } 90 | 91 | public override int GetHashCode() 92 | { 93 | const int prime = 31; 94 | int result = 1; 95 | result = prime * result + this.FormatId; 96 | result = prime * result + this.BranchQualifier.GetHashCode(); 97 | result = prime * result + this.GlobalTransactionId.GetHashCode(); 98 | return result; 99 | } 100 | 101 | public override bool Equals(object obj) 102 | { 103 | if (object.ReferenceEquals(this, obj)) 104 | { 105 | return true; 106 | } 107 | else if (obj == null) 108 | { 109 | return false; 110 | } 111 | else if (this.GetType() != obj.GetType()) 112 | { 113 | return false; 114 | } 115 | TransactionXid other = (TransactionXid)obj; 116 | if (this.FormatId != other.FormatId) 117 | { 118 | return false; 119 | } 120 | else if (!this.BranchQualifier.SequenceEqual(other.BranchQualifier)) 121 | { 122 | return false; 123 | } 124 | else if (!this.GlobalTransactionId.SequenceEqual(other.GlobalTransactionId)) 125 | { 126 | return false; 127 | } 128 | return true; 129 | } 130 | 131 | public static bool operator ==(TransactionXid left, TransactionXid right) 132 | { 133 | return left.Equals(right); 134 | } 135 | 136 | public static bool operator !=(TransactionXid left, TransactionXid right) 137 | { 138 | return !(left == right); 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/NTccTransaction.Aop/AutofacRegistration.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using Autofac.Builder; 3 | using Autofac.Core; 4 | using Autofac.Extras.DynamicProxy; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using NTccTransaction.Abstractions; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Reflection; 11 | using System.Text; 12 | 13 | namespace NTccTransaction.Aop 14 | { 15 | public static class AutofacRegistration 16 | { 17 | public static void Register(this ContainerBuilder builder, IServiceCollection services) 18 | { 19 | 20 | foreach (var service in services) 21 | { 22 | if (service.ImplementationType != null && typeof(INTccTransactionService).IsAssignableFrom(service.ImplementationType)) 23 | { 24 | // Test if the an open generic type is being registered 25 | var serviceTypeInfo = service.ServiceType.GetTypeInfo(); 26 | if (serviceTypeInfo.IsGenericTypeDefinition) 27 | { 28 | builder 29 | .RegisterGeneric(service.ImplementationType) 30 | .As(service.ServiceType) 31 | .ConfigureLifecycle(service.Lifetime) 32 | .ConfigureConventions(); 33 | } 34 | else 35 | { 36 | builder 37 | .RegisterType(service.ImplementationType) 38 | .As(service.ServiceType) 39 | .ConfigureLifecycle(service.Lifetime) 40 | .ConfigureConventions(); 41 | } 42 | } 43 | 44 | } 45 | } 46 | 47 | public static IRegistrationBuilder ConfigureConventions( 48 | this IRegistrationBuilder registrationBuilder) 49 | where TActivatorData : ReflectionActivatorData 50 | { 51 | var serviceType = registrationBuilder.RegistrationData.Services.OfType().FirstOrDefault()?.ServiceType; 52 | if (serviceType == null) 53 | { 54 | return registrationBuilder; 55 | } 56 | 57 | var implementationType = registrationBuilder.ActivatorData.ImplementationType; 58 | if (implementationType == null) 59 | { 60 | return registrationBuilder; 61 | } 62 | 63 | if (serviceType.IsInterface) 64 | { 65 | registrationBuilder = registrationBuilder.EnableInterfaceInterceptors(); 66 | } 67 | 68 | registrationBuilder.InterceptedBy(typeof(TccAsyncDeterminationInterceptor<>).MakeGenericType(typeof(CompensableTransactionInterceptor))); 69 | registrationBuilder.InterceptedBy(typeof(TccAsyncDeterminationInterceptor<>).MakeGenericType(typeof(ResourceCoordinatorInterceptor))); 70 | 71 | return registrationBuilder; 72 | } 73 | 74 | /// 75 | /// Configures the lifecycle on a service registration. 76 | /// 77 | /// The activator data type. 78 | /// The object registration style. 79 | /// The registration being built. 80 | /// The lifecycle specified on the service registration. 81 | /// 82 | /// The , configured with the proper lifetime scope, 83 | /// and available for additional configuration. 84 | /// 85 | private static IRegistrationBuilder ConfigureLifecycle( 86 | this IRegistrationBuilder registrationBuilder, 87 | ServiceLifetime lifecycleKind) 88 | { 89 | switch (lifecycleKind) 90 | { 91 | case ServiceLifetime.Singleton: 92 | registrationBuilder.SingleInstance(); 93 | break; 94 | case ServiceLifetime.Scoped: 95 | registrationBuilder.InstancePerLifetimeScope(); 96 | break; 97 | case ServiceLifetime.Transient: 98 | registrationBuilder.InstancePerDependency(); 99 | break; 100 | } 101 | 102 | return registrationBuilder; 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/NTccTransaction.Aop/CastleAsyncInterceptorAdapter.cs: -------------------------------------------------------------------------------- 1 | using Castle.DynamicProxy; 2 | using NTccTransaction.Abstractions; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace NTccTransaction.Aop 9 | { 10 | public class CastleAsyncInterceptorAdapter : AsyncInterceptorBase 11 | where TInterceptor : INTccInterceptor 12 | { 13 | private readonly TInterceptor _interceptor; 14 | 15 | public CastleAsyncInterceptorAdapter(TInterceptor interceptor) 16 | { 17 | _interceptor = interceptor; 18 | } 19 | 20 | protected override async Task InterceptAsync(IInvocation invocation, IInvocationProceedInfo proceedInfo, Func proceed) 21 | { 22 | await _interceptor.InterceptAsync( 23 | new CastleMethodInvocationAdapter(invocation, proceedInfo, proceed) 24 | ); 25 | } 26 | 27 | protected override async Task InterceptAsync(IInvocation invocation, IInvocationProceedInfo proceedInfo, Func> proceed) 28 | { 29 | var adapter = new CastleMethodInvocationAdapterWithReturnValue(invocation, proceedInfo, proceed); 30 | 31 | await _interceptor.InterceptAsync( 32 | adapter 33 | ); 34 | 35 | return (TResult)adapter.ReturnValue; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/NTccTransaction.Aop/CastleMethodInvocationAdapter.cs: -------------------------------------------------------------------------------- 1 | using Castle.DynamicProxy; 2 | using NTccTransaction.Abstractions; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace NTccTransaction.Aop 9 | { 10 | public class CastleMethodInvocationAdapter : CastleMethodInvocationAdapterBase, IMethodInvocation 11 | { 12 | protected IInvocationProceedInfo ProceedInfo { get; } 13 | protected Func Proceed { get; } 14 | 15 | public CastleMethodInvocationAdapter(IInvocation invocation, IInvocationProceedInfo proceedInfo, 16 | Func proceed) 17 | : base(invocation) 18 | { 19 | ProceedInfo = proceedInfo; 20 | Proceed = proceed; 21 | } 22 | 23 | public override async Task ProceedAsync() 24 | { 25 | await Proceed(Invocation, ProceedInfo); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/NTccTransaction.Aop/CastleMethodInvocationAdapterBase.cs: -------------------------------------------------------------------------------- 1 | using Castle.DynamicProxy; 2 | using NTccTransaction.Abstractions; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace NTccTransaction.Aop 10 | { 11 | public abstract class CastleMethodInvocationAdapterBase : IMethodInvocation 12 | { 13 | public object[] Arguments => Invocation.Arguments; 14 | 15 | public IReadOnlyDictionary ArgumentsDictionary => _lazyArgumentsDictionary.Value; 16 | private readonly Lazy> _lazyArgumentsDictionary; 17 | 18 | public Type[] GenericArguments => Invocation.GenericArguments; 19 | 20 | public object TargetObject => Invocation.InvocationTarget ?? Invocation.MethodInvocationTarget; 21 | 22 | public MethodInfo Method => Invocation.MethodInvocationTarget ?? Invocation.Method; 23 | 24 | public object ReturnValue { get; set; } 25 | 26 | protected IInvocation Invocation { get; } 27 | 28 | protected CastleMethodInvocationAdapterBase(IInvocation invocation) 29 | { 30 | Invocation = invocation; 31 | _lazyArgumentsDictionary = new Lazy>(GetArgumentsDictionary); 32 | } 33 | 34 | public abstract Task ProceedAsync(); 35 | 36 | private IReadOnlyDictionary GetArgumentsDictionary() 37 | { 38 | var dict = new Dictionary(); 39 | 40 | var methodParameters = Method.GetParameters(); 41 | for (int i = 0; i < methodParameters.Length; i++) 42 | { 43 | dict[methodParameters[i].Name] = Invocation.Arguments[i]; 44 | } 45 | 46 | return dict; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/NTccTransaction.Aop/CastleMethodInvocationAdapterWithReturnValue.cs: -------------------------------------------------------------------------------- 1 | using Castle.DynamicProxy; 2 | using NTccTransaction.Abstractions; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace NTccTransaction.Aop 9 | { 10 | public class CastleMethodInvocationAdapterWithReturnValue : CastleMethodInvocationAdapterBase, IMethodInvocation 11 | { 12 | protected IInvocationProceedInfo ProceedInfo { get; } 13 | protected Func> Proceed { get; } 14 | 15 | public CastleMethodInvocationAdapterWithReturnValue(IInvocation invocation, 16 | IInvocationProceedInfo proceedInfo, 17 | Func> proceed) 18 | : base(invocation) 19 | { 20 | ProceedInfo = proceedInfo; 21 | Proceed = proceed; 22 | } 23 | 24 | public override async Task ProceedAsync() 25 | { 26 | ReturnValue = await Proceed(Invocation, ProceedInfo); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/NTccTransaction.Aop/CompensableTransactionInterceptor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using Newtonsoft.Json; 3 | using NTccTransaction.Abstractions; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace NTccTransaction.Aop 10 | { 11 | public class CompensableTransactionInterceptor : INTccInterceptor 12 | { 13 | private readonly IServiceProvider _serviceProvider; 14 | private readonly ITransactionManager _transactionManager; 15 | private readonly ILogger _logger; 16 | private HashSet _delayCancelExceptionTypes; 17 | 18 | public CompensableTransactionInterceptor(IServiceProvider serviceProvider, ITransactionManager transactionManager 19 | , ILogger logger, NTccTransactionOptions nTccTransactionOptions) 20 | { 21 | _serviceProvider = serviceProvider; 22 | _transactionManager = transactionManager; 23 | _logger = logger; 24 | _delayCancelExceptionTypes = nTccTransactionOptions.DelayCancelExceptionTypes; 25 | } 26 | public async Task InterceptAsync(IMethodInvocation invocation) 27 | { 28 | if (!CompensableHelper.IsCompensableMethod(invocation.Method)) 29 | { 30 | await invocation.ProceedAsync(); 31 | return; 32 | } 33 | 34 | CompensableMethodContext compensableMethodContext = new CompensableMethodContext(invocation, _serviceProvider); 35 | var isTransactionActive = _transactionManager.IsTransactionActive(); 36 | 37 | if (!TransactionUtils.IsLegalTransactionContext(isTransactionActive, compensableMethodContext)) 38 | { 39 | throw new TransactionException($"no available compensable transaction, the method {compensableMethodContext.MethodInfo.Name} is illegal"); 40 | } 41 | 42 | switch (compensableMethodContext.GetMethodRole(isTransactionActive)) 43 | { 44 | case MethodRole.ROOT: 45 | await RootMethodProceed(compensableMethodContext); 46 | break; 47 | case MethodRole.PROVIDER: 48 | await ProviderMethodProceed(compensableMethodContext); 49 | break; 50 | default: 51 | await invocation.ProceedAsync(); 52 | break; 53 | } 54 | 55 | } 56 | 57 | private async Task RootMethodProceed(CompensableMethodContext compensableMethodContext) 58 | { 59 | var allDelayCancelExceptionTypes = new HashSet(); 60 | 61 | allDelayCancelExceptionTypes.AddRange(this._delayCancelExceptionTypes); 62 | allDelayCancelExceptionTypes.AddRange(compensableMethodContext.Compensable.DelayCancelExceptionTypes); 63 | 64 | 65 | using (var transaction = _transactionManager.Begin(compensableMethodContext.GetUniqueIdentity())) 66 | { 67 | try 68 | { 69 | await compensableMethodContext.MethodInvocation.ProceedAsync(); 70 | } 71 | catch (Exception ex) 72 | { 73 | if (!_transactionManager.IsDelayCancelException(ex, allDelayCancelExceptionTypes)) 74 | { 75 | _logger.LogError(string.Format("compensable transaction trying failed. transaction content:{0} ", JsonConvert.SerializeObject(transaction))); 76 | await _transactionManager.RollbackAsync(); 77 | } 78 | throw ex; 79 | } 80 | 81 | await _transactionManager.CommitAsync(); 82 | } 83 | } 84 | 85 | private async Task ProviderMethodProceed(CompensableMethodContext compensableMethodContext) 86 | { 87 | 88 | //It designed to reuse API by calling difference method base on incoming transaction status from remote calling 89 | switch (compensableMethodContext.TransactionContext.Status) 90 | { 91 | case TransactionStatus.TRYING: 92 | _transactionManager.PropagationNewBegin(compensableMethodContext.TransactionContext); 93 | 94 | await compensableMethodContext.MethodInvocation.ProceedAsync(); 95 | break; 96 | case TransactionStatus.CONFIRMING: 97 | try 98 | { 99 | using (_transactionManager.PropagationExistBegin(compensableMethodContext.TransactionContext)) 100 | { 101 | await _transactionManager.CommitAsync(); 102 | } 103 | } 104 | catch (Exception ex) 105 | { 106 | _logger.LogWarning(ex, "the transaction has been committed"); 107 | //the transaction has been commit, ignore it. 108 | } 109 | break; 110 | case TransactionStatus.CANCELLING: 111 | try 112 | { 113 | using (_transactionManager.PropagationExistBegin(compensableMethodContext.TransactionContext)) 114 | { 115 | await _transactionManager.RollbackAsync(); 116 | } 117 | } 118 | catch (Exception ex) 119 | { 120 | _logger.LogWarning(ex, "the transaction has been cancelled"); 121 | //the transaction has been rollback, ignore it. 122 | } 123 | break; 124 | } 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/NTccTransaction.Aop/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /src/NTccTransaction.Aop/FodyWeavers.xsd: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. 16 | 17 | 18 | 19 | 20 | A comma-separated list of error codes that can be safely ignored in assembly verification. 21 | 22 | 23 | 24 | 25 | 'false' to turn off automatic generation of the XML Schema file. 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/NTccTransaction.Aop/NTccTransaction.Aop.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | bin\$(Configuration)\netstandard2.0\NTccTransaction.Aop.xml 9 | 1701;1702;1705;CS1591 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/NTccTransaction.Aop/NTccTransaction.AopOptionsExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using NTccTransaction.Abstractions; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace NTccTransaction.Aop 8 | { 9 | public class AopNTccTransactionOptionsExtension : INTccTransactionOptionsExtension 10 | { 11 | 12 | public void AddServices(IServiceCollection services) 13 | { 14 | services.AddTransient(); 15 | services.AddTransient(); 16 | 17 | services.AddTransient(); 18 | services.AddTransient(); 19 | 20 | services.AddTransient(typeof(TccAsyncDeterminationInterceptor<>)); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/NTccTransaction.Aop/NTccTransaction.Options.Extensions.cs: -------------------------------------------------------------------------------- 1 | using NTccTransaction; 2 | using NTccTransaction.Aop; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace Microsoft.Extensions.DependencyInjection 8 | { 9 | public static class NTccTransactionOptionsExtensions 10 | { 11 | public static NTccTransactionOptions UseCastleInterceptor(this NTccTransactionOptions options) 12 | { 13 | 14 | options.RegisterExtension(new AopNTccTransactionOptionsExtension()); 15 | 16 | return options; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/NTccTransaction.Aop/ResourceCoordinatorInterceptor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using NTccTransaction.Abstractions; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace NTccTransaction.Aop 11 | { 12 | public class ResourceCoordinatorInterceptor : INTccInterceptor 13 | { 14 | private IServiceProvider _serviceProvider; 15 | private readonly ITransactionManager _transactionManager; 16 | 17 | public ResourceCoordinatorInterceptor(IServiceProvider serviceProvider, ITransactionManager transactionManager) 18 | { 19 | _serviceProvider = serviceProvider; 20 | _transactionManager = transactionManager; 21 | } 22 | 23 | public async Task InterceptAsync(IMethodInvocation invocation) 24 | { 25 | if (!CompensableHelper.IsCompensableMethod(invocation.Method)) 26 | { 27 | await invocation.ProceedAsync(); 28 | return; 29 | } 30 | 31 | var transaction = _transactionManager.Current; 32 | if (transaction != null) 33 | { 34 | switch (transaction.Status) 35 | { 36 | case TransactionStatus.TRYING: 37 | EnlistParticipant(invocation); 38 | break; 39 | case TransactionStatus.CONFIRMING: 40 | break; 41 | case TransactionStatus.CANCELLING: 42 | break; 43 | } 44 | } 45 | await invocation.ProceedAsync(); 46 | } 47 | 48 | private void EnlistParticipant(IMethodInvocation invocation) 49 | { 50 | var methodInfo = invocation.Method; 51 | var target = invocation.TargetObject; 52 | var targetType = invocation.TargetObject.GetType(); 53 | var arguments = invocation.Arguments; 54 | 55 | var compensable = methodInfo.GetCustomAttributes(false).OfType().FirstOrDefault(); 56 | 57 | if (compensable == null) 58 | { 59 | throw new TransactionException($"Method {methodInfo.Name} is not marked as Compensable"); 60 | } 61 | 62 | var confirmMethodName = compensable.ConfirmMethod; 63 | var cancelMethodName = compensable.CancelMethod; 64 | 65 | var transaction = _transactionManager.Current; 66 | var xid = new TransactionXid(transaction.Xid.GlobalTransactionId); // random branch transaction ID 67 | 68 | // Set the value of the parameter [TransactionContext] in the [Try] method 69 | var transactionContextEditor = _serviceProvider.GetRequiredService(); 70 | 71 | if (transactionContextEditor.GetContext(target, methodInfo, arguments) == null) 72 | { 73 | transactionContextEditor.SetContext(new TransactionContext(xid, TransactionStatus.TRYING), target, methodInfo, arguments); 74 | } 75 | 76 | var parameterTypes = methodInfo.GetParameterTypes(); 77 | var confirmInvocation = new InvocationContext(targetType, confirmMethodName, parameterTypes, arguments); 78 | var cancelInvocation = new InvocationContext(targetType, cancelMethodName, parameterTypes, arguments); 79 | 80 | var participant = _serviceProvider.GetRequiredService(); 81 | participant.Xid = xid; 82 | participant.ConfirmInvocationContext = confirmInvocation; 83 | participant.CancelInvocationContext = cancelInvocation; 84 | 85 | _transactionManager.AddParticipant(participant); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/NTccTransaction.Aop/TccAsyncDeterminationInterceptor.cs: -------------------------------------------------------------------------------- 1 | using Castle.DynamicProxy; 2 | using NTccTransaction.Abstractions; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace NTccTransaction.Aop 8 | { 9 | public class TccAsyncDeterminationInterceptor : AsyncDeterminationInterceptor 10 | where TInterceptor : INTccInterceptor 11 | { 12 | public TccAsyncDeterminationInterceptor(TInterceptor tccInterceptor) 13 | : base(new CastleAsyncInterceptorAdapter(tccInterceptor)) 14 | { 15 | 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/NTccTransaction.Oracle/DBScript/NTccTransaction.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE NTCC_TRANSACTION 2 | ( 3 | TRANSACTION_ID VARCHAR2(128) NOT NULL 4 | , GLOBAL_TRANSACTION_ID VARCHAR2(128) 5 | , BRANCH_QUALIFIER VARCHAR2(128) 6 | , STATUS NUMBER(9, 0) NOT NULL 7 | , TRANSACTION_TYPE NUMBER(9, 0) NOT NULL 8 | , RETRIED_COUNT NUMBER(9, 0) NOT NULL 9 | , CREATE_UTC_TIME DATE NOT NULL 10 | , LAST_UPDATE_UTC_TIME DATE NOT NULL 11 | , VERSION NUMBER(9, 0) NOT NULL 12 | , CONTENT CLOB 13 | , CONSTRAINT PK_NTCC_TRANSACTION PRIMARY KEY 14 | ( 15 | TRANSACTION_ID 16 | ) 17 | ); 18 | -------------------------------------------------------------------------------- /src/NTccTransaction.Oracle/NTccTransaction.Oracle.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | bin\$(Configuration)\netstandard2.0\NTccTransaction.Oracle.xml 9 | 1701;1702;1705;CS1591 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/NTccTransaction.Oracle/NTccTransaction.cs: -------------------------------------------------------------------------------- 1 | using NTccTransaction.Abstractions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.ComponentModel.DataAnnotations; 5 | using System.ComponentModel.DataAnnotations.Schema; 6 | 7 | namespace NTccTransaction.Oracle 8 | { 9 | [Table("NTCC_TRANSACTION")] 10 | public class NTccTransaction 11 | { 12 | [Key] 13 | [DatabaseGenerated(DatabaseGeneratedOption.None)] 14 | [Column("TRANSACTION_ID")] 15 | public string TransactionId { get; set; } 16 | 17 | [Column("GLOBAL_TRANSACTION_ID")] 18 | //public byte[] GlobalTransactionId { get; set; } 19 | public string GlobalTransactionId { get; set; } 20 | 21 | [Column("BRANCH_QUALIFIER")] 22 | //public byte[] BranchQualifier { get; set; } 23 | public string BranchQualifier { get; set; } 24 | 25 | [Column("STATUS")] 26 | public TransactionStatus Status { get; set; } 27 | 28 | [Column("TRANSACTION_TYPE")] 29 | public TransactionType TransactionType { get; set; } 30 | 31 | [Column("RETRIED_COUNT")] 32 | public int RetriedCount { get; set; } 33 | 34 | [Column("CREATE_UTC_TIME")] 35 | public DateTime CreateUtcTime { get; set; } 36 | 37 | [Column("LAST_UPDATE_UTC_TIME")] 38 | public DateTime LastUpdateUtcTime { get; set; } 39 | 40 | [Column("VERSION")] 41 | public int Version { get; set; } 42 | 43 | [Column("CONTENT")] 44 | public string Content { get; set; } 45 | 46 | public override bool Equals(object obj) 47 | { 48 | if (ReferenceEquals(this, obj)) 49 | return true; 50 | 51 | var transactionEntity = obj as NTccTransaction; 52 | if (transactionEntity == null) 53 | return false; 54 | 55 | if (TransactionId == null || 56 | transactionEntity.TransactionId == null) 57 | return false; 58 | 59 | if (TransactionId == transactionEntity.TransactionId) 60 | return true; 61 | 62 | return false; 63 | } 64 | 65 | public override int GetHashCode() 66 | { 67 | if (TransactionId == null) 68 | { 69 | return base.GetHashCode(); 70 | } 71 | 72 | return TransactionId.GetHashCode() ^ 31 + 1000; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/NTccTransaction.Oracle/Options/DbContextOptionsFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Logging; 4 | using Microsoft.Extensions.Options; 5 | using NTccTransaction.Oracle; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Text; 9 | 10 | namespace NTccTransaction.Oracle 11 | { 12 | public class DbContextOptionsFactory 13 | { 14 | public static DbContextOptions Create(IServiceProvider serviceProvider) 15 | where TDbContext : DbContext 16 | { 17 | var dbContextOptions = new DbContextOptionsBuilder() 18 | .UseLoggerFactory(serviceProvider.GetRequiredService()); 19 | 20 | var options = serviceProvider.GetRequiredService>().Value; 21 | 22 | dbContextOptions.UseOracle(options.ConnectionString, (builder) => 23 | { 24 | if (!string.IsNullOrEmpty(options.Version)) 25 | { 26 | builder.UseOracleSQLCompatibility(options.Version); 27 | } 28 | }); 29 | 30 | return dbContextOptions.Options; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/NTccTransaction.Oracle/Options/NTccTransaction.EFOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NTccTransaction.Oracle 4 | { 5 | public class EFOptions 6 | { 7 | public const string DefaultSchema = ""; 8 | 9 | /// 10 | /// Gets or sets the schema to use when creating database objects. 11 | /// Default is . 12 | /// 13 | public string Schema { get; set; } = DefaultSchema; 14 | 15 | 16 | /// 17 | /// Data version 18 | /// 19 | public string Version { get; set; } 20 | } 21 | } -------------------------------------------------------------------------------- /src/NTccTransaction.Oracle/Options/NTccTransaction.Options.Extensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using NTccTransaction.Oracle; 3 | using NTccTransaction; 4 | using System; 5 | 6 | namespace Microsoft.Extensions.DependencyInjection 7 | { 8 | public static class NTccTransactionOptionsExtensions 9 | { 10 | 11 | public static NTccTransactionOptions UseOracle(this NTccTransactionOptions options, Action configure) 12 | { 13 | if (configure == null) 14 | { 15 | throw new ArgumentNullException(nameof(configure)); 16 | } 17 | 18 | options.RegisterExtension(new OracleNTccTransactionOptionsExtension(x => 19 | { 20 | configure(x); 21 | })); 22 | 23 | return options; 24 | } 25 | 26 | } 27 | } -------------------------------------------------------------------------------- /src/NTccTransaction.Oracle/Options/NTccTransaction.OracleOptions.cs: -------------------------------------------------------------------------------- 1 | namespace NTccTransaction.Oracle 2 | { 3 | public class OracleOptions : EFOptions 4 | { 5 | /// 6 | /// Gets or sets the database's connection string that will be used to store database entities. 7 | /// 8 | public string ConnectionString { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /src/NTccTransaction.Oracle/Options/NTccTransaction.OracleOptionsExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.DependencyInjection.Extensions; 3 | using NTccTransaction; 4 | using NTccTransaction.Abstractions; 5 | using NTccTransaction.Oracle; 6 | using System; 7 | 8 | namespace NTccTransaction.Oracle 9 | { 10 | internal class OracleNTccTransactionOptionsExtension : INTccTransactionOptionsExtension 11 | { 12 | private readonly Action _configure; 13 | 14 | public OracleNTccTransactionOptionsExtension(Action configure) 15 | { 16 | _configure = configure; 17 | } 18 | 19 | public void AddServices(IServiceCollection services) 20 | { 21 | services.AddTransient(); 22 | 23 | 24 | services.Configure(_configure); 25 | 26 | services.AddTransient(); 27 | services.TryAddTransient(DbContextOptionsFactory.Create); 28 | } 29 | 30 | 31 | } 32 | } -------------------------------------------------------------------------------- /src/NTccTransaction.Oracle/TransactionDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace NTccTransaction.Oracle 5 | { 6 | public class TransactionDbContext : DbContext 7 | { 8 | private ILoggerFactory _loggerFactory { get; set; } 9 | 10 | public TransactionDbContext(DbContextOptions options, ILoggerFactory loggerFactory) 11 | : base(options) 12 | { 13 | _loggerFactory = loggerFactory; 14 | } 15 | 16 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 17 | { 18 | optionsBuilder.UseLoggerFactory(_loggerFactory); 19 | base.OnConfiguring(optionsBuilder); 20 | } 21 | 22 | protected override void OnModelCreating(ModelBuilder modelBuilder) 23 | { 24 | modelBuilder.Entity().Property(x => x.Version).IsConcurrencyToken(); 25 | 26 | base.OnModelCreating(modelBuilder); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/NTccTransaction.Oracle/TransactionRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using NTccTransaction; 3 | using NTccTransaction.Abstractions; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | using System.Linq; 8 | 9 | namespace NTccTransaction.Oracle 10 | { 11 | public class TransactionRepository : CachableTransactionRepository 12 | { 13 | private readonly TransactionDbContext _dbContext; 14 | private readonly DbSet _dbSet; 15 | private readonly ISerializer _serializer; 16 | 17 | public TransactionRepository(TransactionDbContext dbContext, ISerializer serializer) 18 | { 19 | _dbContext = dbContext; 20 | _dbSet = dbContext.Set(); 21 | 22 | _serializer = serializer; 23 | } 24 | 25 | protected override int DoCreate(ITransaction transaction) 26 | { 27 | Check.NotNull(transaction, nameof(transaction)); 28 | var currentVersion = transaction.Version; 29 | var entity = NewEntity(transaction); 30 | _dbSet.Add(entity); 31 | return _dbContext.SaveChanges(); 32 | } 33 | 34 | protected override int DoUpdate(ITransaction transaction) 35 | { 36 | Check.NotNull(transaction, nameof(transaction)); 37 | var transactionId = transaction.Xid.ToString(); 38 | var entity = _dbSet.FirstOrDefault(x => x.TransactionId == transactionId); 39 | UpdateEntity(transaction, entity); 40 | return _dbContext.SaveChanges(); 41 | } 42 | 43 | protected override int DoDelete(ITransaction transaction) 44 | { 45 | Check.NotNull(transaction, nameof(transaction)); 46 | 47 | var id = transaction.Xid.ToString(); 48 | var entity = _dbSet.FirstOrDefault(x => x.TransactionId == id); 49 | if (entity != null) 50 | { 51 | _dbContext.Entry(entity).State = EntityState.Deleted; 52 | return _dbContext.SaveChanges(); 53 | } 54 | return 0; 55 | } 56 | 57 | protected override ITransaction DoFindOne(TransactionXid xid) 58 | { 59 | var id = xid.ToString(); 60 | 61 | // be careful the cache data. 62 | var entity = _dbSet.FirstOrDefault(x => x.TransactionId == id); 63 | 64 | return ToTransaction(entity); 65 | } 66 | 67 | private NTccTransaction NewEntity(ITransaction transaction) 68 | { 69 | Check.NotNull(transaction, nameof(transaction)); 70 | return new NTccTransaction 71 | { 72 | TransactionId = transaction.Xid.ToString(), 73 | GlobalTransactionId = transaction.Xid.GlobalTransactionId, 74 | BranchQualifier = transaction.Xid.BranchQualifier, 75 | CreateUtcTime = transaction.CreateUtcTime, 76 | LastUpdateUtcTime = transaction.LastUpdateUtcTime, 77 | RetriedCount = transaction.RetriedCount, 78 | TransactionType = transaction.TransactionType, 79 | Status = transaction.Status, 80 | Version = transaction.Version, 81 | 82 | Content = _serializer.Serialize(transaction), 83 | }; 84 | } 85 | 86 | private void UpdateEntity(ITransaction transaction, NTccTransaction entity) 87 | { 88 | Check.NotNull(transaction, nameof(transaction)); 89 | Check.NotNull(entity, nameof(entity)); 90 | 91 | var currentVersion = transaction.Version; 92 | if (entity.Version != currentVersion) 93 | { 94 | throw new ConcurrencyTransactionException(); 95 | } 96 | 97 | transaction.AddVersion(); 98 | entity.CreateUtcTime = transaction.CreateUtcTime; 99 | entity.LastUpdateUtcTime = transaction.LastUpdateUtcTime; 100 | entity.RetriedCount = transaction.RetriedCount; 101 | entity.TransactionType = transaction.TransactionType; 102 | entity.Status = transaction.Status; 103 | entity.Version = transaction.Version; 104 | entity.Content = _serializer.Serialize(transaction); 105 | } 106 | 107 | protected override IEnumerable DoFindAllUnmodifiedSince(DateTime dateTime) 108 | { 109 | var transactions = _dbSet.Where(a => a.LastUpdateUtcTime < dateTime).Take(100).ToList(); 110 | foreach (var transaction in transactions) 111 | { 112 | yield return ToTransaction(transaction); 113 | } 114 | } 115 | 116 | private ITransaction ToTransaction(NTccTransaction transaction) 117 | { 118 | 119 | if (transaction == null) 120 | { 121 | return null; 122 | } 123 | 124 | return _serializer.Deserialize(transaction.Content); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/NTccTransaction.SqlServer/DBScript/NTccTransaction.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE [dbo].[NTCC_TRANSACTION] 2 | ( 3 | [TRANSACTION_ID] varchar(128) NOT NULL 4 | ,[GLOBAL_TRANSACTION_ID] varchar(128) NULL 5 | ,[BRANCH_QUALIFIER] varchar(128) NULL 6 | ,[STATUS] int NOT NULL 7 | ,[TRANSACTION_TYPE] int NOT NULL 8 | ,[RETRIED_COUNT] int NOT NULL 9 | ,[CREATE_UTC_TIME] datetime NOT NULL 10 | ,[LAST_UPDATE_UTC_TIME] datetime NOT NULL 11 | ,[VERSION] int NOT NULL 12 | ,[CONTENT] nvarchar(MAX) NULL 13 | ,PRIMARY KEY 14 | ( 15 | [TRANSACTION_ID] 16 | ) 17 | ) 18 | 19 | GO 20 | 21 | -------------------------------------------------------------------------------- /src/NTccTransaction.SqlServer/NTccTransaction.SqlServer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | bin\$(Configuration)\netstandard2.0\NTccTransaction.SqlServer.xml 9 | 1701;1702;1705;CS1591 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/NTccTransaction.SqlServer/NTccTransaction.cs: -------------------------------------------------------------------------------- 1 | using NTccTransaction.Abstractions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.ComponentModel.DataAnnotations; 5 | using System.ComponentModel.DataAnnotations.Schema; 6 | 7 | namespace NTccTransaction.SqlServer 8 | { 9 | [Table("NTCC_TRANSACTION")] 10 | public class NTccTransaction 11 | { 12 | [Key] 13 | [DatabaseGenerated(DatabaseGeneratedOption.None)] 14 | [Column("TRANSACTION_ID")] 15 | public string TransactionId { get; set; } 16 | 17 | [Column("GLOBAL_TRANSACTION_ID")] 18 | //public byte[] GlobalTransactionId { get; set; } 19 | public string GlobalTransactionId { get; set; } 20 | 21 | [Column("BRANCH_QUALIFIER")] 22 | //public byte[] BranchQualifier { get; set; } 23 | public string BranchQualifier { get; set; } 24 | 25 | [Column("STATUS")] 26 | public TransactionStatus Status { get; set; } 27 | 28 | [Column("TRANSACTION_TYPE")] 29 | public TransactionType TransactionType { get; set; } 30 | 31 | [Column("RETRIED_COUNT")] 32 | public int RetriedCount { get; set; } 33 | 34 | [Column("CREATE_UTC_TIME")] 35 | public DateTime CreateUtcTime { get; set; } 36 | 37 | [Column("LAST_UPDATE_UTC_TIME")] 38 | public DateTime LastUpdateUtcTime { get; set; } 39 | 40 | [Column("VERSION")] 41 | public int Version { get; set; } 42 | 43 | [Column("CONTENT")] 44 | public string Content { get; set; } 45 | 46 | public override bool Equals(object obj) 47 | { 48 | if (ReferenceEquals(this, obj)) 49 | return true; 50 | 51 | var transactionEntity = obj as NTccTransaction; 52 | if (transactionEntity == null) 53 | return false; 54 | 55 | if (TransactionId == null || 56 | transactionEntity.TransactionId == null) 57 | return false; 58 | 59 | if (TransactionId == transactionEntity.TransactionId) 60 | return true; 61 | 62 | return false; 63 | } 64 | 65 | public override int GetHashCode() 66 | { 67 | if (TransactionId == null) 68 | { 69 | return base.GetHashCode(); 70 | } 71 | 72 | return TransactionId.GetHashCode() ^ 31 + 1000; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/NTccTransaction.SqlServer/Options/DbContextOptionsFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Logging; 4 | using Microsoft.Extensions.Options; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Text; 8 | 9 | namespace NTccTransaction.SqlServer 10 | { 11 | public class DbContextOptionsFactory 12 | { 13 | public static DbContextOptions Create(IServiceProvider serviceProvider) 14 | where TDbContext : DbContext 15 | { 16 | var dbContextOptions = new DbContextOptionsBuilder() 17 | .UseLoggerFactory(serviceProvider.GetRequiredService()); 18 | 19 | var options = serviceProvider.GetRequiredService>().Value; 20 | 21 | dbContextOptions.UseSqlServer(options.ConnectionString); 22 | 23 | return dbContextOptions.Options; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/NTccTransaction.SqlServer/Options/NTccTransaction.EFOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NTccTransaction.SqlServer 4 | { 5 | public class EFOptions 6 | { 7 | public const string DefaultSchema = ""; 8 | 9 | /// 10 | /// Gets or sets the schema to use when creating database objects. 11 | /// Default is . 12 | /// 13 | public string Schema { get; set; } = DefaultSchema; 14 | 15 | 16 | /// 17 | /// Data version 18 | /// 19 | public string Version { get; set; } 20 | } 21 | } -------------------------------------------------------------------------------- /src/NTccTransaction.SqlServer/Options/NTccTransaction.Options.Extensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using NTccTransaction.SqlServer; 3 | using NTccTransaction; 4 | using System; 5 | 6 | namespace Microsoft.Extensions.DependencyInjection 7 | { 8 | public static class NTccTransactionOptionsExtensions 9 | { 10 | 11 | public static NTccTransactionOptions UseSqlServer(this NTccTransactionOptions options, Action configure) 12 | { 13 | if (configure == null) 14 | { 15 | throw new ArgumentNullException(nameof(configure)); 16 | } 17 | 18 | options.RegisterExtension(new SqlServerNTccTransactionOptionsExtension(x => 19 | { 20 | configure(x); 21 | })); 22 | 23 | return options; 24 | } 25 | 26 | } 27 | } -------------------------------------------------------------------------------- /src/NTccTransaction.SqlServer/Options/NTccTransaction.SqlServerOptions.cs: -------------------------------------------------------------------------------- 1 | namespace NTccTransaction.SqlServer 2 | { 3 | public class SqlServerOptions : EFOptions 4 | { 5 | /// 6 | /// Gets or sets the database's connection string that will be used to store database entities. 7 | /// 8 | public string ConnectionString { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /src/NTccTransaction.SqlServer/Options/NTccTransaction.SqlServerOptionsExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.DependencyInjection.Extensions; 3 | using NTccTransaction; 4 | using NTccTransaction.Abstractions; 5 | using System; 6 | 7 | namespace NTccTransaction.SqlServer 8 | { 9 | internal class SqlServerNTccTransactionOptionsExtension : INTccTransactionOptionsExtension 10 | { 11 | private readonly Action _configure; 12 | 13 | public SqlServerNTccTransactionOptionsExtension(Action configure) 14 | { 15 | _configure = configure; 16 | } 17 | 18 | public void AddServices(IServiceCollection services) 19 | { 20 | services.AddTransient(); 21 | 22 | services.Configure(_configure); 23 | 24 | services.AddTransient(); 25 | services.TryAddTransient(DbContextOptionsFactory.Create); 26 | } 27 | 28 | 29 | } 30 | } -------------------------------------------------------------------------------- /src/NTccTransaction.SqlServer/TransactionDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace NTccTransaction.SqlServer 5 | { 6 | public class TransactionDbContext : DbContext 7 | { 8 | private ILoggerFactory _loggerFactory { get; set; } 9 | 10 | public TransactionDbContext(DbContextOptions options, ILoggerFactory loggerFactory) 11 | : base(options) 12 | { 13 | _loggerFactory = loggerFactory; 14 | } 15 | 16 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 17 | { 18 | optionsBuilder.UseLoggerFactory(_loggerFactory); 19 | base.OnConfiguring(optionsBuilder); 20 | } 21 | 22 | protected override void OnModelCreating(ModelBuilder modelBuilder) 23 | { 24 | modelBuilder.Entity().Property(x => x.Version).IsConcurrencyToken(); 25 | 26 | base.OnModelCreating(modelBuilder); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/NTccTransaction.SqlServer/TransactionRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using NTccTransaction; 3 | using NTccTransaction.Abstractions; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | using System.Linq; 8 | 9 | namespace NTccTransaction.SqlServer 10 | { 11 | public class TransactionRepository : CachableTransactionRepository 12 | { 13 | private readonly TransactionDbContext _dbContext; 14 | private readonly DbSet _dbSet; 15 | private readonly ISerializer _serializer; 16 | 17 | public TransactionRepository(TransactionDbContext dbContext, ISerializer serializer) 18 | { 19 | _dbContext = dbContext; 20 | _dbSet = dbContext.Set(); 21 | 22 | _serializer = serializer; 23 | } 24 | 25 | protected override int DoCreate(ITransaction transaction) 26 | { 27 | Check.NotNull(transaction, nameof(transaction)); 28 | var currentVersion = transaction.Version; 29 | var entity = NewEntity(transaction); 30 | _dbSet.Add(entity); 31 | return _dbContext.SaveChanges(); 32 | } 33 | 34 | protected override int DoUpdate(ITransaction transaction) 35 | { 36 | Check.NotNull(transaction, nameof(transaction)); 37 | var transactionId = transaction.Xid.ToString(); 38 | var entity = _dbSet.FirstOrDefault(x => x.TransactionId == transactionId); 39 | UpdateEntity(transaction, entity); 40 | return _dbContext.SaveChanges(); 41 | } 42 | 43 | protected override int DoDelete(ITransaction transaction) 44 | { 45 | Check.NotNull(transaction, nameof(transaction)); 46 | 47 | var id = transaction.Xid.ToString(); 48 | var entity = _dbSet.FirstOrDefault(x => x.TransactionId == id); 49 | if (entity != null) 50 | { 51 | _dbContext.Entry(entity).State = EntityState.Deleted; 52 | return _dbContext.SaveChanges(); 53 | } 54 | return 0; 55 | } 56 | 57 | protected override ITransaction DoFindOne(TransactionXid xid) 58 | { 59 | var id = xid.ToString(); 60 | 61 | // be careful the cache data. 62 | var entity = _dbSet.FirstOrDefault(x => x.TransactionId == id); 63 | 64 | return ToTransaction(entity); 65 | } 66 | 67 | private NTccTransaction NewEntity(ITransaction transaction) 68 | { 69 | Check.NotNull(transaction, nameof(transaction)); 70 | return new NTccTransaction 71 | { 72 | TransactionId = transaction.Xid.ToString(), 73 | GlobalTransactionId = transaction.Xid.GlobalTransactionId, 74 | BranchQualifier = transaction.Xid.BranchQualifier, 75 | CreateUtcTime = transaction.CreateUtcTime, 76 | LastUpdateUtcTime = transaction.LastUpdateUtcTime, 77 | RetriedCount = transaction.RetriedCount, 78 | TransactionType = transaction.TransactionType, 79 | Status = transaction.Status, 80 | Version = transaction.Version, 81 | 82 | Content = _serializer.Serialize(transaction), 83 | }; 84 | } 85 | 86 | private void UpdateEntity(ITransaction transaction, NTccTransaction entity) 87 | { 88 | Check.NotNull(transaction, nameof(transaction)); 89 | Check.NotNull(entity, nameof(entity)); 90 | 91 | var currentVersion = transaction.Version; 92 | if (entity.Version != currentVersion) 93 | { 94 | throw new ConcurrencyTransactionException(); 95 | } 96 | 97 | transaction.AddVersion(); 98 | entity.CreateUtcTime = transaction.CreateUtcTime; 99 | entity.LastUpdateUtcTime = transaction.LastUpdateUtcTime; 100 | entity.RetriedCount = transaction.RetriedCount; 101 | entity.TransactionType = transaction.TransactionType; 102 | entity.Status = transaction.Status; 103 | entity.Version = transaction.Version; 104 | entity.Content = _serializer.Serialize(transaction); 105 | } 106 | 107 | protected override IEnumerable DoFindAllUnmodifiedSince(DateTime dateTime) 108 | { 109 | var transactions = _dbSet.Where(a => a.LastUpdateUtcTime < dateTime).Take(100).ToList(); 110 | foreach (var transaction in transactions) 111 | { 112 | yield return ToTransaction(transaction); 113 | } 114 | } 115 | 116 | private ITransaction ToTransaction(NTccTransaction transaction) 117 | { 118 | 119 | if (transaction == null) 120 | { 121 | return null; 122 | } 123 | 124 | return _serializer.Deserialize(transaction.Content); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/NTccTransaction/CompensableAttribute.cs: -------------------------------------------------------------------------------- 1 | using NTccTransaction.Abstractions; 2 | using System; 3 | using System.Reflection; 4 | 5 | namespace NTccTransaction 6 | { 7 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] 8 | public class CompensableAttribute : Attribute, ICompensable 9 | { 10 | 11 | public Propagation Propagation { get; set; } 12 | 13 | public string ConfirmMethod { get; set; } 14 | 15 | public string CancelMethod { get; set; } 16 | 17 | public Type[] DelayCancelExceptionTypes { get; set; } 18 | 19 | /// 20 | /// Used to prevent starting a compensable transaction for the method. 21 | /// Default: false. 22 | /// 23 | public bool IsDisabled { get; set; } 24 | 25 | public CompensableAttribute() 26 | { 27 | DelayCancelExceptionTypes = new Type[0]; 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/NTccTransaction/CompensableMethodContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using NTccTransaction.Abstractions; 3 | using System; 4 | using System.Collections.Concurrent; 5 | using System.Linq; 6 | using System.Reflection; 7 | 8 | namespace NTccTransaction 9 | { 10 | public class CompensableMethodContext 11 | { 12 | public object[] Arguments { get; } 13 | public MethodBase MethodInfo { get; } 14 | 15 | public object Target { get; } 16 | 17 | public ICompensable Compensable { get; } 18 | public Propagation Propagation { get; } 19 | public TransactionContext TransactionContext { get; } 20 | 21 | public IMethodInvocation MethodInvocation { get; set; } 22 | 23 | public CompensableMethodContext(IMethodInvocation methodInvocation, IServiceProvider serviceProvider) 24 | { 25 | ICompensable compensable; 26 | ValidateMethodInfo(methodInvocation.Method, out compensable); 27 | 28 | this.Target = methodInvocation.TargetObject; 29 | this.Arguments = methodInvocation.Arguments; 30 | this.MethodInfo = methodInvocation.Method; 31 | this.Compensable = compensable; 32 | this.Propagation = this.Compensable.Propagation; 33 | this.MethodInvocation = methodInvocation; 34 | 35 | var transactionContextEditor = serviceProvider.GetRequiredService(); 36 | 37 | this.TransactionContext = transactionContextEditor.GetContext(this.Target, this.MethodInfo, this.Arguments); 38 | } 39 | 40 | /// 41 | /// Get method role 42 | /// 43 | /// whether exist transaction 44 | /// 45 | public MethodRole GetMethodRole(bool isTransactionActive) 46 | { 47 | // Root transaction 48 | if ((Propagation == Propagation.REQUIRED && !isTransactionActive && TransactionContext == null) || 49 | Propagation == Propagation.REQUIRES_NEW) 50 | { 51 | return MethodRole.ROOT; 52 | } 53 | // Propagation transaction 54 | else if ((Propagation == Propagation.REQUIRED || Propagation == Propagation.MANDATORY) && 55 | !isTransactionActive && 56 | TransactionContext != null) 57 | { 58 | return MethodRole.PROVIDER; 59 | } 60 | // Normal 61 | else 62 | { 63 | return MethodRole.NORMAL; 64 | } 65 | } 66 | 67 | /// 68 | /// Get the transaction 69 | /// 70 | /// 71 | public object GetUniqueIdentity() 72 | { 73 | var parameters = this.MethodInfo.GetParameters(); 74 | for (int i = 0; i < parameters.Length; i++) 75 | { 76 | var parameter = parameters[i]; 77 | var parameterType = parameter.ParameterType; 78 | if (typeof(IUniqueIdentity).IsAssignableFrom(parameterType)) 79 | { 80 | return Arguments; 81 | } 82 | } 83 | 84 | return null; 85 | } 86 | 87 | private static ConcurrentDictionary _methodLegalDic = new ConcurrentDictionary(); 88 | 89 | private void ValidateMethodInfo(MethodBase methodBase, out ICompensable compensable) 90 | { 91 | compensable = _methodLegalDic.GetOrAdd(methodBase, m => 92 | { 93 | var targetType = m.DeclaringType; 94 | var parameterTypes = m.GetParameterTypes(); 95 | 96 | if (m.GetParameterPosition() < 0) 97 | { 98 | throw new TransactionException($"Method: {m.Name}, has no parameter that the type is TransactionContext"); 99 | } 100 | 101 | var c = methodBase.GetCustomAttributes(false).OfType().FirstOrDefault(); 102 | if (c == null) 103 | { 104 | throw new TransactionException($"Method: {m.Name}, not marked with Compensable attribute"); 105 | } 106 | 107 | if (targetType.GetMethod(c.ConfirmMethod, parameterTypes) == null) 108 | { 109 | throw new TransactionException($"Method: {m.Name}, can not found the corresponding Confirm method"); 110 | } 111 | 112 | if (targetType.GetMethod(c.CancelMethod, parameterTypes) == null) 113 | { 114 | throw new TransactionException($"Method: {m.Name}, can not found the corresponding Cancel method"); 115 | } 116 | 117 | return c; 118 | }); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/NTccTransaction/Internal/DefaultTransactionContextEditor.cs: -------------------------------------------------------------------------------- 1 | using NTccTransaction.Abstractions; 2 | using System.Linq; 3 | using System.Reflection; 4 | 5 | namespace NTccTransaction 6 | { 7 | public class DefaultTransactionContextEditor : ITransactionContextEditor 8 | { 9 | public TransactionContext GetContext(object target, MethodBase methodBase, object[] args) 10 | { 11 | int position = GetTransactionContextParamPosition(methodBase); 12 | if (position >= 0) 13 | { 14 | return (TransactionContext)args[position]; 15 | } 16 | 17 | return null; 18 | } 19 | 20 | public void SetContext(TransactionContext transactionContext, object target, MethodBase methodBase, object[] args) 21 | { 22 | int position = GetTransactionContextParamPosition(methodBase); 23 | if (position >= 0) 24 | { 25 | args[position] = transactionContext; 26 | } 27 | } 28 | 29 | private int GetTransactionContextParamPosition(MethodBase methodBase) 30 | { 31 | var parameterTypes = methodBase.GetParameters().Select(p => p.ParameterType).ToArray(); 32 | for (int i = 0; i < parameterTypes.Length; i++) 33 | { 34 | if (parameterTypes[i] == typeof(TransactionContext)) 35 | { 36 | return i; 37 | } 38 | } 39 | 40 | return -1; 41 | } 42 | 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/NTccTransaction/Internal/IBootstrapper.Default.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Hosting; 2 | using Microsoft.Extensions.Logging; 3 | using NTccTransaction.Abstractions; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace NTccTransaction 11 | { 12 | public class Bootstrapper : BackgroundService, IBootstrapper 13 | { 14 | private readonly ILogger _logger; 15 | 16 | private IEnumerable _processors { get; } 17 | 18 | public Bootstrapper( 19 | ILogger logger, 20 | IEnumerable processors) 21 | { 22 | _logger = logger; 23 | _processors = processors; 24 | } 25 | 26 | public async Task BootstrapAsync(CancellationToken stoppingToken) 27 | { 28 | await BootstrapCoreAsync(); 29 | } 30 | 31 | protected virtual Task BootstrapCoreAsync() 32 | { 33 | foreach (var item in _processors) 34 | { 35 | try 36 | { 37 | item.Start(); 38 | } 39 | catch (Exception ex) 40 | { 41 | _logger.LogError(ex.Message); 42 | } 43 | } 44 | 45 | return Task.CompletedTask; 46 | } 47 | 48 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 49 | { 50 | await BootstrapAsync(stoppingToken); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/NTccTransaction/Internal/ITransactionMethodInvoker.Default.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Logging; 3 | using NTccTransaction.Abstractions; 4 | using System; 5 | using System.Collections.Concurrent; 6 | using System.Collections.Generic; 7 | using System.ComponentModel; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | 13 | namespace NTccTransaction 14 | { 15 | public class TransactionMethodInvoker : ITransactionMethodInvoker 16 | { 17 | private readonly ILogger _logger; 18 | private readonly IServiceProvider _serviceProvider; 19 | private readonly ConcurrentDictionary _executors; 20 | private readonly MethodMatcherCache _selector; 21 | 22 | public TransactionMethodInvoker(ILoggerFactory loggerFactory, IServiceProvider serviceProvider, MethodMatcherCache selector) 23 | { 24 | _serviceProvider = serviceProvider; 25 | _logger = loggerFactory.CreateLogger(); 26 | _executors = new ConcurrentDictionary(); 27 | _selector = selector; 28 | } 29 | 30 | public async Task InvokeAsync(TransactionContext transactionContext, InvocationContext invocationContext, CancellationToken cancellationToken = default) 31 | { 32 | CacheExecutorDescriptor matchExcutor; 33 | _selector.TryGetTccTransactionExecutor(invocationContext.TargetType.FullName + "." + invocationContext.MethodName, out matchExcutor); 34 | 35 | cancellationToken.ThrowIfCancellationRequested(); 36 | 37 | var methodInfo = matchExcutor.MethodInfo; 38 | var executor = _executors.GetOrAdd(methodInfo.MetadataToken, x => ObjectMethodExecutor.Create(methodInfo, matchExcutor.ImplTypeInfo)); 39 | using (var scope = _serviceProvider.CreateScope()) 40 | { 41 | var provider = scope.ServiceProvider; 42 | var obj = GetInstance(provider, matchExcutor); 43 | 44 | var parameterDescriptors = matchExcutor.Parameters; 45 | 46 | for (var i = 0; i < parameterDescriptors.Count; i++) 47 | { 48 | 49 | if (parameterDescriptors[i].ParameterType == typeof(TransactionContext)) 50 | { 51 | invocationContext.Arguments[i] = transactionContext; 52 | } 53 | else if (parameterDescriptors[i].ParameterType == typeof(CancellationToken)) 54 | { 55 | invocationContext.Arguments[i] = cancellationToken; 56 | } 57 | } 58 | var resultObj = await ExecuteWithParameterAsync(executor, obj, invocationContext.Arguments); 59 | } 60 | } 61 | 62 | protected virtual object GetInstance(IServiceProvider provider, CacheExecutorDescriptor descriptor) 63 | { 64 | var srvType = descriptor.ServiceTypeInfo?.AsType(); 65 | var implType = descriptor.ImplTypeInfo.AsType(); 66 | 67 | object obj = null; 68 | if (srvType != null) 69 | { 70 | obj = provider.GetServices(srvType).FirstOrDefault(o => o.GetType() == implType); 71 | } 72 | 73 | if (obj == null) 74 | { 75 | obj = ActivatorUtilities.GetServiceOrCreateInstance(provider, implType); 76 | } 77 | 78 | return obj; 79 | } 80 | 81 | private async Task ExecuteWithParameterAsync(ObjectMethodExecutor executor, object @class, object[] parameter) 82 | { 83 | if (executor.IsMethodAsync) 84 | { 85 | return await executor.ExecuteAsync(@class, parameter); 86 | } 87 | 88 | return executor.Execute(@class, parameter); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/NTccTransaction/Internal/ITransactionRecoveryProcessingServer.Default.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using NTccTransaction.Abstractions; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace NTccTransaction 11 | { 12 | public class TransactionRecoveryProcessingServer : ITransactionRecoveryProcessingServer 13 | { 14 | private readonly ILogger _logger; 15 | private readonly IServiceProvider _serviceProvider; 16 | private readonly MethodMatcherCache _selector; 17 | private readonly NTccTransactionOptions _options; 18 | private readonly ITransactionRecovery _transactionRecovery; 19 | 20 | private CancellationTokenSource _cts; 21 | private static bool _isHealthy = true; 22 | private bool _disposed; 23 | private Task _compositeTask; 24 | 25 | 26 | 27 | public TransactionRecoveryProcessingServer(ILogger logger, IServiceProvider serviceProvider 28 | , NTccTransactionOptions nTccTransactionOptions, ITransactionRecovery transactionRecovery) 29 | { 30 | _logger = logger; 31 | _serviceProvider = serviceProvider; 32 | _selector = serviceProvider.GetService(); 33 | _options = nTccTransactionOptions; 34 | _transactionRecovery = transactionRecovery; 35 | 36 | _cts = new CancellationTokenSource(); 37 | } 38 | 39 | public bool IsHealthy() 40 | { 41 | return _isHealthy; 42 | } 43 | 44 | public void Start() 45 | { 46 | _selector.GetTccCandidatesMethods(); //collect tcc candidates methods 47 | 48 | Task.Factory.StartNew(async () => 49 | { 50 | try 51 | { 52 | while (true) 53 | { 54 | await _transactionRecovery.StartRecoverAsync(); 55 | 56 | _cts.Token.ThrowIfCancellationRequested(); 57 | _cts.Token.WaitHandle.WaitOne(_options.FailedRetryInterval * 1000); 58 | } 59 | } 60 | catch (OperationCanceledException ex) 61 | { 62 | //ignore 63 | } 64 | }, _cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); 65 | } 66 | 67 | public void ReStart(bool force = false) 68 | { 69 | if (!IsHealthy() || force) 70 | { 71 | Pulse(); 72 | 73 | _cts = new CancellationTokenSource(); 74 | _isHealthy = true; 75 | 76 | Start(); 77 | } 78 | } 79 | 80 | public void Dispose() 81 | { 82 | if (_disposed) 83 | { 84 | return; 85 | } 86 | 87 | _disposed = true; 88 | 89 | try 90 | { 91 | Pulse(); 92 | 93 | _compositeTask?.Wait(TimeSpan.FromSeconds(2)); 94 | } 95 | catch (AggregateException ex) 96 | { 97 | var innerEx = ex.InnerExceptions[0]; 98 | if (!(innerEx is OperationCanceledException)) 99 | { 100 | _logger.LogError(innerEx.Message); 101 | } 102 | } 103 | } 104 | 105 | public void Pulse() 106 | { 107 | _cts?.Cancel(); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/NTccTransaction/Internal/ITransactionServiceSelector.Default.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using NTccTransaction.Abstractions; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Text; 8 | 9 | namespace NTccTransaction 10 | { 11 | public class TransactionServiceSelector : ITransactionServiceSelector 12 | { 13 | private readonly ILifetimeScope _serviceProvider; 14 | 15 | public TransactionServiceSelector(ILifetimeScope serviceProvider) 16 | { 17 | _serviceProvider = serviceProvider; 18 | } 19 | 20 | public IReadOnlyList SelectCandidates() 21 | { 22 | var executorDescriptorList = new List(); 23 | 24 | executorDescriptorList.AddRange(FindMatchExecutorFromInterfaceTypes(_serviceProvider)); 25 | 26 | return executorDescriptorList; 27 | } 28 | 29 | protected virtual IEnumerable FindMatchExecutorFromInterfaceTypes( 30 | ILifetimeScope provider) 31 | { 32 | var executorDescriptorList = new List(); 33 | 34 | var types = provider.ComponentRegistry.Registrations 35 | .Where(r => typeof(INTccTransactionService).IsAssignableFrom(r.Activator.LimitType)) 36 | .Select(r => r.Activator.LimitType).ToList(); 37 | 38 | foreach (var type in types) 39 | { 40 | var serviceTypes = type.GetInterfaces().ToList(); 41 | foreach (var serviceType in serviceTypes) 42 | { 43 | if (serviceType == typeof(INTccTransactionService)) 44 | { 45 | continue; 46 | } 47 | var serviceTypeInfo = serviceType.GetTypeInfo(); 48 | executorDescriptorList.AddRange(GetCompensableAttributesDescription(type.GetTypeInfo(), serviceTypeInfo)); 49 | break; 50 | } 51 | } 52 | 53 | return executorDescriptorList; 54 | } 55 | 56 | protected IEnumerable GetCompensableAttributesDescription(TypeInfo typeInfo, TypeInfo serviceTypeInfo = null) 57 | { 58 | IList listDescriptor = new List(); 59 | 60 | foreach (var method in typeInfo.DeclaredMethods) 61 | { 62 | var compensableAttr = method.GetCustomAttributes(true); 63 | var compensableAttributes = compensableAttr as IList ?? compensableAttr.ToList(); 64 | 65 | if (!compensableAttributes.Any()) 66 | { 67 | continue; 68 | } 69 | 70 | foreach (var attr in compensableAttributes) 71 | { 72 | 73 | var parameters = method.GetParameters() 74 | .Select(parameter => new ParameterDescriptor 75 | { 76 | Name = parameter.Name, 77 | ParameterType = parameter.ParameterType 78 | }).ToList(); 79 | 80 | listDescriptor.Add(InitDescriptor(attr, method, typeInfo, serviceTypeInfo, parameters)); 81 | } 82 | } 83 | 84 | IList listCompensableDescriptor = new List(); 85 | foreach (var descriptor in listDescriptor) 86 | { 87 | var cancelMethod = descriptor.Attribute.CancelMethod; 88 | var confirmMethod = descriptor.Attribute.ConfirmMethod; 89 | 90 | MethodInfo cancelMethodInfo = typeInfo.GetDeclaredMethod(cancelMethod); 91 | var cancelParameters = cancelMethodInfo.GetParameters() 92 | .Select(parameter => new ParameterDescriptor 93 | { 94 | Name = parameter.Name, 95 | ParameterType = parameter.ParameterType 96 | }).ToList(); 97 | 98 | listCompensableDescriptor.Add(InitDescriptor(null, cancelMethodInfo, typeInfo, serviceTypeInfo, cancelParameters)); 99 | 100 | MethodInfo confirmMethodInfo = typeInfo.GetDeclaredMethod(confirmMethod); 101 | var confirmParameters = cancelMethodInfo.GetParameters() 102 | .Select(parameter => new ParameterDescriptor 103 | { 104 | Name = parameter.Name, 105 | ParameterType = parameter.ParameterType 106 | }).ToList(); 107 | 108 | listCompensableDescriptor.Add(InitDescriptor(null, confirmMethodInfo, typeInfo, serviceTypeInfo, confirmParameters)); 109 | 110 | } 111 | 112 | 113 | 114 | return listDescriptor.Concat(listCompensableDescriptor); 115 | } 116 | 117 | 118 | private static CacheExecutorDescriptor InitDescriptor( 119 | CompensableAttribute attr, 120 | MethodInfo methodInfo, 121 | TypeInfo implType, 122 | TypeInfo serviceTypeInfo, 123 | IList parameters) 124 | { 125 | var descriptor = new CacheExecutorDescriptor 126 | { 127 | Attribute = attr, 128 | MethodInfo = methodInfo, 129 | ImplTypeInfo = implType, 130 | ServiceTypeInfo = serviceTypeInfo, 131 | Parameters = parameters 132 | }; 133 | 134 | return descriptor; 135 | } 136 | 137 | 138 | 139 | private class RegexExecuteDescriptor 140 | { 141 | public string Name { get; set; } 142 | 143 | public T Descriptor { get; set; } 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/NTccTransaction/Internal/MethodMatcherCache.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Core Community. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using NTccTransaction.Abstractions; 5 | using System; 6 | using System.Collections.Concurrent; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | 10 | namespace NTccTransaction 11 | { 12 | public class MethodMatcherCache 13 | { 14 | private readonly ITransactionServiceSelector _selector; 15 | 16 | public MethodMatcherCache(ITransactionServiceSelector selector) 17 | { 18 | _selector = selector; 19 | Entries = new ConcurrentDictionary(); 20 | } 21 | 22 | private ConcurrentDictionary Entries { get; } 23 | 24 | 25 | /// 26 | /// Get a dictionary of candidates.In the dictionary, 27 | /// the Key is the HybridCacheAttribute Group, the Value for the current Group of candidates 28 | /// 29 | public ConcurrentDictionary GetTccCandidatesMethods() 30 | { 31 | if (Entries.Count != 0) 32 | { 33 | return Entries; 34 | } 35 | 36 | var executorCollection = _selector.SelectCandidates(); 37 | 38 | foreach (var item in executorCollection) 39 | { 40 | Entries.TryAdd(item.ImplTypeInfo.FullName + "." + item.MethodInfo.Name, item); 41 | } 42 | 43 | return Entries; 44 | } 45 | 46 | 47 | public bool TryGetTccTransactionExecutor(string key, 48 | out CacheExecutorDescriptor matchExcutor) 49 | { 50 | if (Entries == null) 51 | { 52 | throw new ArgumentNullException(nameof(Entries)); 53 | } 54 | 55 | matchExcutor = null; 56 | 57 | if (Entries.TryGetValue(key, out matchExcutor)) 58 | { 59 | return matchExcutor != null; 60 | } 61 | 62 | return false; 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /src/NTccTransaction/Internal/ObjectMethodExecutor/AwaitableInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Core Community. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Runtime.CompilerServices; 8 | 9 | namespace NTccTransaction 10 | { 11 | internal struct AwaitableInfo 12 | { 13 | public Type AwaiterType { get; } 14 | public PropertyInfo AwaiterIsCompletedProperty { get; } 15 | public MethodInfo AwaiterGetResultMethod { get; } 16 | public MethodInfo AwaiterOnCompletedMethod { get; } 17 | public MethodInfo AwaiterUnsafeOnCompletedMethod { get; } 18 | public Type ResultType { get; } 19 | public MethodInfo GetAwaiterMethod { get; } 20 | 21 | public AwaitableInfo( 22 | Type awaiterType, 23 | PropertyInfo awaiterIsCompletedProperty, 24 | MethodInfo awaiterGetResultMethod, 25 | MethodInfo awaiterOnCompletedMethod, 26 | MethodInfo awaiterUnsafeOnCompletedMethod, 27 | Type resultType, 28 | MethodInfo getAwaiterMethod) 29 | { 30 | AwaiterType = awaiterType; 31 | AwaiterIsCompletedProperty = awaiterIsCompletedProperty; 32 | AwaiterGetResultMethod = awaiterGetResultMethod; 33 | AwaiterOnCompletedMethod = awaiterOnCompletedMethod; 34 | AwaiterUnsafeOnCompletedMethod = awaiterUnsafeOnCompletedMethod; 35 | ResultType = resultType; 36 | GetAwaiterMethod = getAwaiterMethod; 37 | } 38 | 39 | public static bool IsTypeAwaitable(Type type, out AwaitableInfo awaitableInfo) 40 | { 41 | // Based on Roslyn code: http://source.roslyn.io/#Microsoft.CodeAnalysis.Workspaces/Shared/Extensions/ISymbolExtensions.cs,db4d48ba694b9347 42 | 43 | // Awaitable must have method matching "object GetAwaiter()" 44 | var getAwaiterMethod = type.GetRuntimeMethods().FirstOrDefault(m => 45 | m.Name.Equals("GetAwaiter", StringComparison.OrdinalIgnoreCase) 46 | && m.GetParameters().Length == 0 47 | && m.ReturnType != null); 48 | if (getAwaiterMethod == null) 49 | { 50 | awaitableInfo = default(AwaitableInfo); 51 | return false; 52 | } 53 | 54 | var awaiterType = getAwaiterMethod.ReturnType; 55 | 56 | // Awaiter must have property matching "bool IsCompleted { get; }" 57 | var isCompletedProperty = awaiterType.GetRuntimeProperties().FirstOrDefault(p => 58 | p.Name.Equals("IsCompleted", StringComparison.OrdinalIgnoreCase) 59 | && p.PropertyType == typeof(bool) 60 | && p.GetMethod != null); 61 | if (isCompletedProperty == null) 62 | { 63 | awaitableInfo = default(AwaitableInfo); 64 | return false; 65 | } 66 | 67 | // Awaiter must implement INotifyCompletion 68 | var awaiterInterfaces = awaiterType.GetInterfaces(); 69 | var implementsINotifyCompletion = awaiterInterfaces.Any(t => t == typeof(INotifyCompletion)); 70 | if (!implementsINotifyCompletion) 71 | { 72 | awaitableInfo = default(AwaitableInfo); 73 | return false; 74 | } 75 | 76 | // INotifyCompletion supplies a method matching "void OnCompleted(Action action)" 77 | var iNotifyCompletionMap = awaiterType 78 | .GetTypeInfo() 79 | .GetRuntimeInterfaceMap(typeof(INotifyCompletion)); 80 | var onCompletedMethod = iNotifyCompletionMap.InterfaceMethods.Single(m => 81 | m.Name.Equals("OnCompleted", StringComparison.OrdinalIgnoreCase) 82 | && m.ReturnType == typeof(void) 83 | && m.GetParameters().Length == 1 84 | && m.GetParameters()[0].ParameterType == typeof(Action)); 85 | 86 | // Awaiter optionally implements ICriticalNotifyCompletion 87 | var implementsICriticalNotifyCompletion = 88 | awaiterInterfaces.Any(t => t == typeof(ICriticalNotifyCompletion)); 89 | MethodInfo unsafeOnCompletedMethod; 90 | if (implementsICriticalNotifyCompletion) 91 | { 92 | // ICriticalNotifyCompletion supplies a method matching "void UnsafeOnCompleted(Action action)" 93 | var iCriticalNotifyCompletionMap = awaiterType 94 | .GetTypeInfo() 95 | .GetRuntimeInterfaceMap(typeof(ICriticalNotifyCompletion)); 96 | unsafeOnCompletedMethod = iCriticalNotifyCompletionMap.InterfaceMethods.Single(m => 97 | m.Name.Equals("UnsafeOnCompleted", StringComparison.OrdinalIgnoreCase) 98 | && m.ReturnType == typeof(void) 99 | && m.GetParameters().Length == 1 100 | && m.GetParameters()[0].ParameterType == typeof(Action)); 101 | } 102 | else 103 | { 104 | unsafeOnCompletedMethod = null; 105 | } 106 | 107 | // Awaiter must have method matching "void GetResult" or "T GetResult()" 108 | var getResultMethod = awaiterType.GetRuntimeMethods().FirstOrDefault(m => 109 | m.Name.Equals("GetResult") 110 | && m.GetParameters().Length == 0); 111 | if (getResultMethod == null) 112 | { 113 | awaitableInfo = default(AwaitableInfo); 114 | return false; 115 | } 116 | 117 | awaitableInfo = new AwaitableInfo( 118 | awaiterType, 119 | isCompletedProperty, 120 | getResultMethod, 121 | onCompletedMethod, 122 | unsafeOnCompletedMethod, 123 | getResultMethod.ReturnType, 124 | getAwaiterMethod); 125 | return true; 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /src/NTccTransaction/Internal/ObjectMethodExecutor/CoercedAwaitableInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Core Community. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Linq.Expressions; 6 | 7 | namespace NTccTransaction 8 | { 9 | internal struct CoercedAwaitableInfo 10 | { 11 | public AwaitableInfo AwaitableInfo { get; } 12 | public Expression CoercerExpression { get; } 13 | public Type CoercerResultType { get; } 14 | public bool RequiresCoercion => CoercerExpression != null; 15 | 16 | public CoercedAwaitableInfo(AwaitableInfo awaitableInfo) 17 | { 18 | AwaitableInfo = awaitableInfo; 19 | CoercerExpression = null; 20 | CoercerResultType = null; 21 | } 22 | 23 | public CoercedAwaitableInfo(Expression coercerExpression, Type coercerResultType, 24 | AwaitableInfo coercedAwaitableInfo) 25 | { 26 | CoercerExpression = coercerExpression; 27 | CoercerResultType = coercerResultType; 28 | AwaitableInfo = coercedAwaitableInfo; 29 | } 30 | 31 | public static bool IsTypeAwaitable(Type type, out CoercedAwaitableInfo info) 32 | { 33 | if (AwaitableInfo.IsTypeAwaitable(type, out var directlyAwaitableInfo)) 34 | { 35 | info = new CoercedAwaitableInfo(directlyAwaitableInfo); 36 | return true; 37 | } 38 | 39 | // It's not directly awaitable, but maybe we can coerce it. 40 | // Currently we support coercing FSharpAsync. 41 | if (ObjectMethodExecutorFSharpSupport.TryBuildCoercerFromFSharpAsyncToAwaitable(type, 42 | out var coercerExpression, 43 | out var coercerResultType)) 44 | { 45 | if (AwaitableInfo.IsTypeAwaitable(coercerResultType, out var coercedAwaitableInfo)) 46 | { 47 | info = new CoercedAwaitableInfo(coercerExpression, coercerResultType, coercedAwaitableInfo); 48 | return true; 49 | } 50 | } 51 | 52 | info = default(CoercedAwaitableInfo); 53 | return false; 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/NTccTransaction/Internal/ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Core Community. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Runtime.CompilerServices; 6 | 7 | namespace NTccTransaction 8 | { 9 | /// 10 | /// Provides a common awaitable structure that can 11 | /// return, regardless of whether the underlying value is a System.Task, an FSharpAsync, or an 12 | /// application-defined custom awaitable. 13 | /// 14 | internal struct ObjectMethodExecutorAwaitable 15 | { 16 | private readonly object _customAwaitable; 17 | private readonly Func _getAwaiterMethod; 18 | private readonly Func _isCompletedMethod; 19 | private readonly Func _getResultMethod; 20 | private readonly Action _onCompletedMethod; 21 | private readonly Action _unsafeOnCompletedMethod; 22 | 23 | // Perf note: since we're requiring the customAwaitable to be supplied here as an object, 24 | // this will trigger a further allocation if it was a value type (i.e., to box it). We can't 25 | // fix this by making the customAwaitable type generic, because the calling code typically 26 | // does not know the type of the awaitable/awaiter at compile-time anyway. 27 | // 28 | // However, we could fix it by not passing the customAwaitable here at all, and instead 29 | // passing a func that maps directly from the target object (e.g., controller instance), 30 | // target method (e.g., action method info), and params array to the custom awaiter in the 31 | // GetAwaiter() method below. In effect, by delaying the actual method call until the 32 | // upstream code calls GetAwaiter on this ObjectMethodExecutorAwaitable instance. 33 | // This optimization is not currently implemented because: 34 | // [1] It would make no difference when the awaitable was an object type, which is 35 | // by far the most common scenario (e.g., System.Task). 36 | // [2] It would be complex - we'd need some kind of object pool to track all the parameter 37 | // arrays until we needed to use them in GetAwaiter(). 38 | // We can reconsider this in the future if there's a need to optimize for ValueTask 39 | // or other value-typed awaitables. 40 | 41 | public ObjectMethodExecutorAwaitable( 42 | object customAwaitable, 43 | Func getAwaiterMethod, 44 | Func isCompletedMethod, 45 | Func getResultMethod, 46 | Action onCompletedMethod, 47 | Action unsafeOnCompletedMethod) 48 | { 49 | _customAwaitable = customAwaitable; 50 | _getAwaiterMethod = getAwaiterMethod; 51 | _isCompletedMethod = isCompletedMethod; 52 | _getResultMethod = getResultMethod; 53 | _onCompletedMethod = onCompletedMethod; 54 | _unsafeOnCompletedMethod = unsafeOnCompletedMethod; 55 | } 56 | 57 | public Awaiter GetAwaiter() 58 | { 59 | var customAwaiter = _getAwaiterMethod(_customAwaitable); 60 | return new Awaiter(customAwaiter, _isCompletedMethod, _getResultMethod, _onCompletedMethod, 61 | _unsafeOnCompletedMethod); 62 | } 63 | 64 | public struct Awaiter : ICriticalNotifyCompletion 65 | { 66 | private readonly object _customAwaiter; 67 | private readonly Func _isCompletedMethod; 68 | private readonly Func _getResultMethod; 69 | private readonly Action _onCompletedMethod; 70 | private readonly Action _unsafeOnCompletedMethod; 71 | 72 | public Awaiter( 73 | object customAwaiter, 74 | Func isCompletedMethod, 75 | Func getResultMethod, 76 | Action onCompletedMethod, 77 | Action unsafeOnCompletedMethod) 78 | { 79 | _customAwaiter = customAwaiter; 80 | _isCompletedMethod = isCompletedMethod; 81 | _getResultMethod = getResultMethod; 82 | _onCompletedMethod = onCompletedMethod; 83 | _unsafeOnCompletedMethod = unsafeOnCompletedMethod; 84 | } 85 | 86 | public bool IsCompleted => _isCompletedMethod(_customAwaiter); 87 | 88 | public object GetResult() 89 | { 90 | return _getResultMethod(_customAwaiter); 91 | } 92 | 93 | public void OnCompleted(Action continuation) 94 | { 95 | _onCompletedMethod(_customAwaiter, continuation); 96 | } 97 | 98 | public void UnsafeOnCompleted(Action continuation) 99 | { 100 | // If the underlying awaitable implements ICriticalNotifyCompletion, use its UnsafeOnCompleted. 101 | // If not, fall back on using its OnCompleted. 102 | // 103 | // Why this is safe: 104 | // - Implementing ICriticalNotifyCompletion is a way of saying the caller can choose whether it 105 | // needs the execution context to be preserved (which it signals by calling OnCompleted), or 106 | // that it doesn't (which it signals by calling UnsafeOnCompleted). Obviously it's faster *not* 107 | // to preserve and restore the context, so we prefer that where possible. 108 | // - If a caller doesn't need the execution context to be preserved and hence calls UnsafeOnCompleted, 109 | // there's no harm in preserving it anyway - it's just a bit of wasted cost. That's what will happen 110 | // if a caller sees that the proxy implements ICriticalNotifyCompletion but the proxy chooses to 111 | // pass the call on to the underlying awaitable's OnCompleted method. 112 | 113 | var underlyingMethodToUse = _unsafeOnCompletedMethod ?? _onCompletedMethod; 114 | underlyingMethodToUse(_customAwaiter, continuation); 115 | } 116 | } 117 | } 118 | } -------------------------------------------------------------------------------- /src/NTccTransaction/Internal/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Core Community. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Linq; 6 | using System.Linq.Expressions; 7 | using System.Reflection; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | 11 | namespace NTccTransaction 12 | { 13 | /// 14 | /// Helper for detecting whether a given type is FSharpAsync`1, and if so, supplying 15 | /// an for mapping instances of that type to a C# awaitable. 16 | /// 17 | /// 18 | /// The main design goal here is to avoid taking a compile-time dependency on 19 | /// FSharp.Core.dll, because non-F# applications wouldn't use it. So all the references 20 | /// to FSharp types have to be constructed dynamically at runtime. 21 | /// 22 | internal static class ObjectMethodExecutorFSharpSupport 23 | { 24 | private static readonly object _fsharpValuesCacheLock = new object(); 25 | private static Assembly _fsharpCoreAssembly; 26 | private static MethodInfo _fsharpAsyncStartAsTaskGenericMethod; 27 | private static PropertyInfo _fsharpOptionOfTaskCreationOptionsNoneProperty; 28 | private static PropertyInfo _fsharpOptionOfCancellationTokenNoneProperty; 29 | 30 | public static bool TryBuildCoercerFromFSharpAsyncToAwaitable( 31 | Type possibleFSharpAsyncType, 32 | out Expression coerceToAwaitableExpression, 33 | out Type awaitableType) 34 | { 35 | var methodReturnGenericType = possibleFSharpAsyncType.IsGenericType 36 | ? possibleFSharpAsyncType.GetGenericTypeDefinition() 37 | : null; 38 | 39 | if (!IsFSharpAsyncOpenGenericType(methodReturnGenericType)) 40 | { 41 | coerceToAwaitableExpression = null; 42 | awaitableType = null; 43 | return false; 44 | } 45 | 46 | var awaiterResultType = possibleFSharpAsyncType.GetGenericArguments().Single(); 47 | awaitableType = typeof(Task<>).MakeGenericType(awaiterResultType); 48 | 49 | // coerceToAwaitableExpression = (object fsharpAsync) => 50 | // { 51 | // return (object)FSharpAsync.StartAsTask( 52 | // (Microsoft.FSharp.Control.FSharpAsync)fsharpAsync, 53 | // FSharpOption.None, 54 | // FSharpOption.None); 55 | // }; 56 | var startAsTaskClosedMethod = _fsharpAsyncStartAsTaskGenericMethod 57 | .MakeGenericMethod(awaiterResultType); 58 | var coerceToAwaitableParam = Expression.Parameter(typeof(object)); 59 | coerceToAwaitableExpression = Expression.Lambda( 60 | Expression.Convert( 61 | Expression.Call( 62 | startAsTaskClosedMethod, 63 | Expression.Convert(coerceToAwaitableParam, possibleFSharpAsyncType), 64 | Expression.MakeMemberAccess(null, _fsharpOptionOfTaskCreationOptionsNoneProperty), 65 | Expression.MakeMemberAccess(null, _fsharpOptionOfCancellationTokenNoneProperty)), 66 | typeof(object)), 67 | coerceToAwaitableParam); 68 | 69 | return true; 70 | } 71 | 72 | private static bool IsFSharpAsyncOpenGenericType(Type possibleFSharpAsyncGenericType) 73 | { 74 | var typeFullName = possibleFSharpAsyncGenericType?.FullName; 75 | if (!string.Equals(typeFullName, "Microsoft.FSharp.Control.FSharpAsync`1", StringComparison.Ordinal)) 76 | { 77 | return false; 78 | } 79 | 80 | lock (_fsharpValuesCacheLock) 81 | { 82 | if (_fsharpCoreAssembly != null) 83 | { 84 | return possibleFSharpAsyncGenericType.Assembly == _fsharpCoreAssembly; 85 | } 86 | 87 | return TryPopulateFSharpValueCaches(possibleFSharpAsyncGenericType); 88 | } 89 | } 90 | 91 | private static bool TryPopulateFSharpValueCaches(Type possibleFSharpAsyncGenericType) 92 | { 93 | var assembly = possibleFSharpAsyncGenericType.Assembly; 94 | var fsharpOptionType = assembly.GetType("Microsoft.FSharp.Core.FSharpOption`1"); 95 | var fsharpAsyncType = assembly.GetType("Microsoft.FSharp.Control.FSharpAsync"); 96 | 97 | if (fsharpOptionType == null || fsharpAsyncType == null) 98 | { 99 | return false; 100 | } 101 | 102 | // Get a reference to FSharpOption.None 103 | var fsharpOptionOfTaskCreationOptionsType = fsharpOptionType 104 | .MakeGenericType(typeof(TaskCreationOptions)); 105 | _fsharpOptionOfTaskCreationOptionsNoneProperty = fsharpOptionOfTaskCreationOptionsType 106 | .GetTypeInfo() 107 | .GetRuntimeProperty("None"); 108 | 109 | // Get a reference to FSharpOption.None 110 | var fsharpOptionOfCancellationTokenType = fsharpOptionType 111 | .MakeGenericType(typeof(CancellationToken)); 112 | _fsharpOptionOfCancellationTokenNoneProperty = fsharpOptionOfCancellationTokenType 113 | .GetTypeInfo() 114 | .GetRuntimeProperty("None"); 115 | 116 | // Get a reference to FSharpAsync.StartAsTask<> 117 | var fsharpAsyncMethods = fsharpAsyncType 118 | .GetRuntimeMethods() 119 | .Where(m => m.Name.Equals("StartAsTask", StringComparison.Ordinal)); 120 | foreach (var candidateMethodInfo in fsharpAsyncMethods) 121 | { 122 | var parameters = candidateMethodInfo.GetParameters(); 123 | if (parameters.Length == 3 124 | && TypesHaveSameIdentity(parameters[0].ParameterType, possibleFSharpAsyncGenericType) 125 | && parameters[1].ParameterType == fsharpOptionOfTaskCreationOptionsType 126 | && parameters[2].ParameterType == fsharpOptionOfCancellationTokenType) 127 | { 128 | // This really does look like the correct method (and hence assembly). 129 | _fsharpAsyncStartAsTaskGenericMethod = candidateMethodInfo; 130 | _fsharpCoreAssembly = assembly; 131 | break; 132 | } 133 | } 134 | 135 | return _fsharpCoreAssembly != null; 136 | } 137 | 138 | private static bool TypesHaveSameIdentity(Type type1, Type type2) 139 | { 140 | return type1.Assembly == type2.Assembly 141 | && string.Equals(type1.Namespace, type2.Namespace, StringComparison.Ordinal) 142 | && string.Equals(type1.Name, type2.Name, StringComparison.Ordinal); 143 | } 144 | } 145 | } -------------------------------------------------------------------------------- /src/NTccTransaction/NTccTransaction.Builder.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | 4 | namespace NTccTransaction 5 | { 6 | /// 7 | /// Allows fine grained configuration of NTccTransaction services. 8 | /// 9 | public sealed class NTccTransactionBuilder 10 | { 11 | public NTccTransactionBuilder(IServiceCollection services) 12 | { 13 | Services = services; 14 | } 15 | 16 | /// 17 | /// Gets the where services are configured. 18 | /// 19 | public IServiceCollection Services { get; } 20 | 21 | /// 22 | /// Adds a scoped service of the type specified in serviceType with an implementation 23 | /// 24 | private NTccTransactionBuilder AddScoped(Type serviceType, Type concreteType) 25 | { 26 | Services.AddScoped(serviceType, concreteType); 27 | return this; 28 | } 29 | 30 | /// 31 | /// Adds a singleton service of the type specified in serviceType with an implementation 32 | /// 33 | private NTccTransactionBuilder AddSingleton(Type serviceType, Type concreteType) 34 | { 35 | Services.AddSingleton(serviceType, concreteType); 36 | return this; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/NTccTransaction/NTccTransaction.ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using NTccTransaction; 2 | using NTccTransaction.Abstractions; 3 | using System; 4 | using Microsoft.Extensions.DependencyInjection.Extensions; 5 | 6 | namespace Microsoft.Extensions.DependencyInjection 7 | { 8 | public static class NTccTransactionConfigExtension 9 | { 10 | internal static IServiceCollection ServiceCollection; 11 | 12 | /// 13 | /// Adds and configures the consistence services for the consistency. 14 | /// 15 | /// The services available in the application. 16 | /// An action to configure the . 17 | /// An for application services. 18 | public static NTccTransactionBuilder AddNTccTransaction(this IServiceCollection services, Action setupAction) 19 | { 20 | if (setupAction == null) 21 | { 22 | throw new ArgumentNullException(nameof(setupAction)); 23 | } 24 | 25 | ServiceCollection = services; 26 | 27 | services.AddSingleton(); 28 | services.AddSingleton(); 29 | 30 | services.AddTransient(); 31 | services.AddTransient(); 32 | 33 | services.AddSingleton(); 34 | 35 | services.TryAddEnumerable(ServiceDescriptor.Singleton()); 36 | services.TryAddSingleton(); 37 | 38 | services.TryAddSingleton(); 39 | 40 | services.TryAddSingleton(); 41 | services.TryAddSingleton(); 42 | services.TryAddSingleton(); 43 | 44 | 45 | //Options and extension service 46 | var options = new NTccTransactionOptions(); 47 | setupAction(options); 48 | 49 | foreach (var serviceExtension in options.Extensions) 50 | { 51 | serviceExtension.AddServices(services); 52 | } 53 | 54 | services.AddSingleton(options); 55 | 56 | //Startup and Hosted 57 | services.AddSingleton(); 58 | services.AddHostedService(); 59 | 60 | return new NTccTransactionBuilder(services); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/NTccTransaction/NTccTransaction.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | bin\$(Configuration)\netstandard2.0\NTccTransaction.xml 9 | 1701;1702;1705;CS1591 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/NTccTransaction/NTccTransactionOptions.cs: -------------------------------------------------------------------------------- 1 | using NTccTransaction.Abstractions; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace NTccTransaction 6 | { 7 | public class NTccTransactionOptions 8 | { 9 | public IList Extensions { get; } 10 | 11 | /// 12 | /// Default succeeded message expiration time span, in seconds. 13 | /// 14 | public const int DefaultSucceedMessageExpirationAfter = 24 * 3600; 15 | 16 | /// 17 | /// Failed message retry waiting interval. 18 | /// 19 | public const int DefaultFailedMessageWaitingInterval = 60; 20 | 21 | /// 22 | /// Failed message retry count. 23 | /// 24 | public const int DefaultFailedRetryCount = 30;//30 seconds 25 | 26 | /// 27 | /// recover duration 28 | /// 29 | public const int DefaultRecoverDuration = 120;//120 seconds 30 | 31 | public NTccTransactionOptions() 32 | { 33 | SucceedMessageExpiredAfter = DefaultSucceedMessageExpirationAfter; 34 | FailedRetryInterval = DefaultFailedMessageWaitingInterval; 35 | FailedRetryCount = DefaultFailedRetryCount; 36 | RecoverDuration = DefaultRecoverDuration; 37 | Extensions = new List(); 38 | DelayCancelExceptionTypes = new HashSet() { typeof(ConcurrencyTransactionException) }; 39 | } 40 | 41 | /// 42 | /// Sent or received succeed message after time span of due, then the message will be deleted at due time. 43 | /// Default is 24*3600 seconds. 44 | /// 45 | public int SucceedMessageExpiredAfter { get; set; } 46 | 47 | /// 48 | /// Failed messages polling delay time. 49 | /// Default is 60 seconds. 50 | /// 51 | public int FailedRetryInterval { get; set; } 52 | 53 | 54 | /// 55 | /// recover duration 56 | /// Default is 120 seconds. 57 | /// 58 | public int RecoverDuration { get; set; } 59 | 60 | /// 61 | /// The number of message retries, the retry will stop when the threshold is reached. 62 | /// Default is 50 times. 63 | /// 64 | public int FailedRetryCount { get; set; } 65 | 66 | /// 67 | /// Delay Cancel Exception Types collection 68 | /// 69 | public HashSet DelayCancelExceptionTypes { get; set; } = new HashSet(); 70 | 71 | /// 72 | /// Registers an extension that will be executed when building services. 73 | /// 74 | /// 75 | public void RegisterExtension(INTccTransactionOptionsExtension extension) 76 | { 77 | if (extension == null) 78 | { 79 | throw new ArgumentNullException(nameof(extension)); 80 | } 81 | 82 | Extensions.Add(extension); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/NTccTransaction/Persistence/AmbientTransaction.cs: -------------------------------------------------------------------------------- 1 | using NTccTransaction.Abstractions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Threading; 6 | 7 | namespace NTccTransaction 8 | { 9 | /// 10 | /// Handle the nested transactions and guarantee the thread safe 11 | /// 12 | public class AmbientTransaction : IAmbientTransaction 13 | { 14 | public ITransaction Transaction => GetCurrentTransaction(); 15 | private readonly AsyncLocal> _transactionStack = new AsyncLocal>(); 16 | 17 | public void RegisterTransaction(ITransaction transaction) 18 | { 19 | Check.NotNull(transaction,nameof(transaction)); 20 | 21 | if (_transactionStack.Value.IsNull()) 22 | { 23 | _transactionStack.Value = new Stack(); 24 | } 25 | 26 | _transactionStack.Value.Push(transaction); 27 | transaction.Disoped += UnRegisterSelfAfterDisoped; 28 | } 29 | 30 | public ITransaction UnRegisterTransaction() 31 | { 32 | if (!_transactionStack.Value.IsNullOrEmpty()) 33 | { 34 | var transaction= _transactionStack.Value.Pop(); 35 | transaction.Disoped -= UnRegisterSelfAfterDisoped; 36 | return transaction; 37 | } 38 | return null; 39 | } 40 | 41 | private ITransaction GetCurrentTransaction() 42 | { 43 | if (!_transactionStack.Value.IsNullOrEmpty()) 44 | { 45 | return _transactionStack.Value.Peek(); 46 | } 47 | return null; 48 | } 49 | 50 | private void UnRegisterSelfAfterDisoped(object sender, EventArgs args) 51 | { 52 | this.UnRegisterTransaction(); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/NTccTransaction/Persistence/CachableTransactionRepository.cs: -------------------------------------------------------------------------------- 1 | using NTccTransaction.Abstractions; 2 | using NTccTransaction; 3 | using System; 4 | using System.Runtime.Caching; 5 | using System.Collections.Generic; 6 | 7 | namespace NTccTransaction 8 | { 9 | public abstract class CachableTransactionRepository : ITransactionRepository 10 | { 11 | private static readonly ObjectCache Cache = MemoryCache.Default; 12 | 13 | public virtual void Create(ITransaction transaction) 14 | { 15 | Check.NotNull(transaction, nameof(transaction)); 16 | 17 | try 18 | { 19 | int result = DoCreate(transaction); 20 | if (result > 0) 21 | { 22 | PutToCache(transaction); 23 | return; 24 | } 25 | } 26 | catch (Exception ex) 27 | { 28 | throw new TransactionException($"Exception occurred while persisting the transaction, xid:{transaction.Xid}", ex); 29 | } 30 | } 31 | 32 | public void Update(ITransaction transaction) 33 | { 34 | int result = 0; 35 | 36 | try 37 | { 38 | result = DoUpdate(transaction); 39 | if (result > 0) 40 | { 41 | PutToCache(transaction); 42 | return; 43 | } 44 | else 45 | { 46 | throw new ConcurrencyTransactionException(); 47 | } 48 | } 49 | 50 | finally 51 | { 52 | if (result <= 0) 53 | { 54 | RemoveFromCache(transaction); 55 | } 56 | } 57 | } 58 | 59 | public void Delete(ITransaction transaction) 60 | { 61 | int result = 0; 62 | try 63 | { 64 | result = DoDelete(transaction); 65 | } 66 | finally 67 | { 68 | RemoveFromCache(transaction); 69 | } 70 | } 71 | 72 | public ITransaction FindByXid(TransactionXid xid) 73 | { 74 | var transaction = FindFromCache(xid); 75 | 76 | if (transaction == null) 77 | { 78 | transaction = DoFindOne(xid); 79 | 80 | if (transaction != null) 81 | { 82 | PutToCache(transaction); 83 | } 84 | } 85 | 86 | return transaction; 87 | } 88 | 89 | public IEnumerable FindAllUnmodifiedSince(DateTime dateTime) 90 | { 91 | 92 | IEnumerable transactions = DoFindAllUnmodifiedSince(dateTime); 93 | 94 | foreach (ITransaction transaction in transactions) 95 | { 96 | PutToCache(transaction); 97 | } 98 | 99 | return transactions; 100 | } 101 | 102 | protected abstract IEnumerable DoFindAllUnmodifiedSince(DateTime dateTime); 103 | 104 | protected abstract int DoCreate(ITransaction transaction); 105 | 106 | protected abstract int DoUpdate(ITransaction transaction); 107 | 108 | protected abstract int DoDelete(ITransaction transaction); 109 | 110 | protected abstract ITransaction DoFindOne(TransactionXid xid); 111 | 112 | #region Cache 113 | private void PutToCache(ITransaction transaction) 114 | { 115 | Check.NotNull(transaction, nameof(transaction)); 116 | 117 | var policy = new CacheItemPolicy 118 | { 119 | SlidingExpiration = TimeSpan.FromSeconds(30) 120 | }; 121 | Cache.Set(transaction.Xid.ToString(), transaction, policy); 122 | } 123 | 124 | private ITransaction FindFromCache(TransactionXid transactionXid) 125 | { 126 | Check.NotNull(transactionXid, nameof(transactionXid)); 127 | return (ITransaction)Cache.Get(transactionXid.ToString()); 128 | } 129 | 130 | private void RemoveFromCache(ITransaction transaction) 131 | { 132 | Check.NotNull(transaction, nameof(transaction)); 133 | Cache.Remove(transaction.Xid.ToString()); 134 | } 135 | 136 | #endregion 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/NTccTransaction/Persistence/Participant.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using NTccTransaction.Abstractions; 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | namespace NTccTransaction 7 | { 8 | [Serializable] 9 | public class Participant : IParticipant 10 | { 11 | public TransactionXid Xid { get; set; } 12 | 13 | public InvocationContext ConfirmInvocationContext { get; set; } 14 | 15 | public InvocationContext CancelInvocationContext { get; set; } 16 | 17 | 18 | public async Task CommitAsync(IServiceScopeFactory serviceScopeFactory) 19 | { 20 | using (var scope = serviceScopeFactory.CreateScope()) 21 | { 22 | ITransactionMethodInvoker tccTransactionMethodInvoker = scope.ServiceProvider.GetRequiredService(); 23 | await tccTransactionMethodInvoker.InvokeAsync(new TransactionContext(Xid, TransactionStatus.CONFIRMING), ConfirmInvocationContext); 24 | } 25 | } 26 | 27 | public async Task RollbackAsync(IServiceScopeFactory serviceScopeFactory) 28 | { 29 | using (var scope = serviceScopeFactory.CreateScope()) 30 | { 31 | ITransactionMethodInvoker tccTransactionMethodInvoker = scope.ServiceProvider.GetRequiredService(); 32 | await tccTransactionMethodInvoker.InvokeAsync(new TransactionContext(Xid, TransactionStatus.CANCELLING), CancelInvocationContext); 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/NTccTransaction/Persistence/Transaction.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Newtonsoft.Json; 3 | using NTccTransaction.Abstractions; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Collections.Immutable; 7 | using System.Threading.Tasks; 8 | 9 | namespace NTccTransaction 10 | { 11 | [Serializable] 12 | public class Transaction : ITransaction, IDisposable 13 | { 14 | [field: NonSerialized] 15 | public event EventHandler Disoped; 16 | 17 | public TransactionXid Xid { get; set; } 18 | public TransactionStatus Status { get; set; } 19 | public TransactionType TransactionType { get; set; } 20 | public int RetriedCount { get; set; } 21 | public DateTime CreateUtcTime { get; set; } 22 | public DateTime LastUpdateUtcTime { get; set; } 23 | public int Version { get; set; } 24 | 25 | private Transaction() 26 | { 27 | CreateUtcTime = DateTime.Now.ToUniversalTime(); 28 | LastUpdateUtcTime = DateTime.Now.ToUniversalTime(); 29 | Version = 1; 30 | } 31 | 32 | public Transaction(TransactionType transactionType) 33 | : this() 34 | { 35 | Xid = new TransactionXid(); 36 | Status = TransactionStatus.TRYING; 37 | TransactionType = transactionType; 38 | } 39 | 40 | public Transaction(string uniqueIdentity, TransactionType transactionType) 41 | : this() 42 | { 43 | Xid = new TransactionXid(uniqueIdentity); 44 | Status = TransactionStatus.TRYING; 45 | TransactionType = transactionType; 46 | } 47 | 48 | public Transaction(TransactionContext transactionContext) 49 | : this() 50 | { 51 | this.Xid = transactionContext.Xid; 52 | this.Status = TransactionStatus.TRYING; 53 | this.TransactionType = TransactionType.BRANCH; 54 | } 55 | 56 | public async Task CommitAsync(IServiceScopeFactory serviceScopeFactory) 57 | { 58 | foreach (var participant in FindAllParticipantAsReadOnly()) 59 | { 60 | await participant.CommitAsync(serviceScopeFactory); 61 | } 62 | } 63 | 64 | public async Task RollbackAsync(IServiceScopeFactory serviceScopeFactory) 65 | { 66 | foreach (var participant in FindAllParticipantAsReadOnly()) 67 | { 68 | await participant.RollbackAsync(serviceScopeFactory); 69 | } 70 | } 71 | 72 | public void AddRetriedCount() 73 | { 74 | this.RetriedCount++; 75 | this.LastUpdateUtcTime = DateTime.Now.ToUniversalTime(); 76 | } 77 | 78 | public void AddVersion() 79 | { 80 | this.Version++; 81 | this.LastUpdateUtcTime = DateTime.Now.ToUniversalTime(); 82 | } 83 | 84 | public void ChangeStatus(TransactionStatus status) 85 | { 86 | this.Status = status; 87 | this.LastUpdateUtcTime = DateTime.Now.ToUniversalTime(); 88 | } 89 | 90 | 91 | public List Participants { get; private set; } = new List(); 92 | 93 | public void AddParticipant(IParticipant participant) 94 | { 95 | Check.NotNull(participant, nameof(participant)); 96 | Participants.Add(participant); 97 | } 98 | 99 | public IReadOnlyList FindAllParticipantAsReadOnly() 100 | { 101 | return Participants.ToImmutableList(); 102 | } 103 | 104 | public void Dispose() 105 | { 106 | Disoped?.Invoke(this, new EventArgs()); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/NTccTransaction/Persistence/TransactionManager.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using NTccTransaction.Abstractions; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Threading.Tasks; 8 | 9 | namespace NTccTransaction 10 | { 11 | public class TransactionManager : ITransactionManager 12 | { 13 | private readonly IAmbientTransaction _ambientTransaction; 14 | 15 | private readonly IServiceScopeFactory _serviceScopeFactory; 16 | 17 | public ITransaction Current => _ambientTransaction.Transaction; 18 | 19 | public TransactionManager( 20 | IAmbientTransaction ambientTransaction, 21 | IServiceScopeFactory serviceScopeFactory) 22 | { 23 | _ambientTransaction = ambientTransaction; 24 | _serviceScopeFactory = serviceScopeFactory; 25 | } 26 | 27 | /// 28 | /// 29 | /// 30 | /// 31 | public ITransaction Begin() 32 | { 33 | var transaction = new Transaction(TransactionType.ROOT); 34 | using (var scope = _serviceScopeFactory.CreateScope()) 35 | { 36 | var transactionRepository = scope.ServiceProvider.GetRequiredService(); 37 | transactionRepository.Create(transaction); 38 | 39 | RegisterTransaction(transaction); 40 | return transaction; 41 | } 42 | } 43 | 44 | /// 45 | /// 46 | /// 47 | /// 48 | /// 49 | public ITransaction Begin(object uniqueIdentity) 50 | { 51 | var transaction = new Transaction(TransactionType.ROOT); 52 | using (var scope = _serviceScopeFactory.CreateScope()) 53 | { 54 | var transactionRepository = scope.ServiceProvider.GetRequiredService(); 55 | transactionRepository.Create(transaction); 56 | 57 | RegisterTransaction(transaction); 58 | 59 | return transaction; 60 | } 61 | } 62 | 63 | /// 64 | /// 65 | /// 66 | /// 67 | /// 68 | public ITransaction PropagationNewBegin(TransactionContext transactionContext) 69 | { 70 | var transaction = new Transaction(transactionContext); 71 | 72 | using (var scope = _serviceScopeFactory.CreateScope()) 73 | { 74 | var transactionRepository = scope.ServiceProvider.GetRequiredService(); 75 | transactionRepository.Create(transaction); 76 | 77 | RegisterTransaction(transaction); 78 | return transaction; 79 | } 80 | } 81 | 82 | /// 83 | /// 84 | /// 85 | /// 86 | /// 87 | public ITransaction PropagationExistBegin(TransactionContext transactionContext) 88 | { 89 | using (var scope = _serviceScopeFactory.CreateScope()) 90 | { 91 | var transactionRepository = scope.ServiceProvider.GetRequiredService(); 92 | var transaction = transactionRepository.FindByXid(transactionContext.Xid); 93 | if (transaction == null) 94 | { 95 | throw new NoExistedTransactionException(); 96 | } 97 | 98 | transaction.ChangeStatus(transactionContext.Status); 99 | RegisterTransaction(transaction); 100 | return transaction; 101 | } 102 | } 103 | 104 | /// 105 | /// Commit 106 | /// 107 | public async Task CommitAsync() 108 | { 109 | var transaction = this.Current; 110 | 111 | // When confirm successfully, delete the NTccTransaction from database 112 | // The confirm action will be call from the top of stack, so delete the transaction data will not impact the excution under it 113 | using (var scope = _serviceScopeFactory.CreateScope()) 114 | { 115 | var transactionRepository = scope.ServiceProvider.GetRequiredService(); 116 | transaction.ChangeStatus(TransactionStatus.CONFIRMING); 117 | transactionRepository.Update(transaction); 118 | 119 | try 120 | { 121 | await transaction.CommitAsync(_serviceScopeFactory); 122 | transactionRepository.Delete(transaction); 123 | } 124 | catch (Exception commitException) 125 | { 126 | throw new ConfirmingException("Confirm failed", commitException); 127 | } 128 | } 129 | } 130 | 131 | /// 132 | /// Rollback 133 | /// 134 | public async Task RollbackAsync() 135 | { 136 | var transaction = this.Current; 137 | 138 | using (var scope = _serviceScopeFactory.CreateScope()) 139 | { 140 | var transactionRepository = scope.ServiceProvider.GetRequiredService(); 141 | 142 | transaction.ChangeStatus(TransactionStatus.CANCELLING); 143 | transactionRepository.Update(transaction); 144 | 145 | try 146 | { 147 | await transaction.RollbackAsync(_serviceScopeFactory); 148 | transactionRepository.Delete(transaction); 149 | } 150 | catch (Exception rollbackException) 151 | { 152 | throw new CancellingException("Rollback failed", rollbackException); 153 | } 154 | } 155 | 156 | } 157 | 158 | /// 159 | /// 160 | /// 161 | /// 162 | public void AddParticipant(IParticipant participant) 163 | { 164 | var transaction = this.Current; 165 | transaction.AddParticipant(participant); 166 | 167 | using (var scope = _serviceScopeFactory.CreateScope()) 168 | { 169 | var transactionRepository = scope.ServiceProvider.GetRequiredService(); 170 | transactionRepository.Update(transaction); 171 | } 172 | } 173 | 174 | /// 175 | /// 176 | /// 177 | /// 178 | public bool IsTransactionActive() 179 | { 180 | return Current != null; 181 | } 182 | 183 | 184 | private void RegisterTransaction(ITransaction transaction) 185 | { 186 | _ambientTransaction.RegisterTransaction(transaction); 187 | } 188 | 189 | public bool IsDelayCancelException(Exception tryingException, HashSet allDelayCancelExceptionTypes) 190 | { 191 | if (null == allDelayCancelExceptionTypes || !allDelayCancelExceptionTypes.Any()) 192 | { 193 | return false; 194 | } 195 | 196 | var tryingExceptionType = tryingException.GetType(); 197 | 198 | return allDelayCancelExceptionTypes.Any(t => t.IsAssignableFrom(tryingExceptionType)); 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/NTccTransaction/Persistence/TransactionRecovery.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Logging; 3 | using Newtonsoft.Json; 4 | using NTccTransaction.Abstractions; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace NTccTransaction 11 | { 12 | public class TransactionRecovery : ITransactionRecovery 13 | { 14 | private readonly NTccTransactionOptions _options; 15 | private readonly ITransactionRepository _transactionRepository; 16 | private readonly ILogger _logger; 17 | private readonly IServiceScopeFactory _serviceScopeFactory; 18 | 19 | public TransactionRecovery(NTccTransactionOptions nTccTransactionOptions, ITransactionRepository transactionRepository 20 | , IServiceScopeFactory serviceScopeFactory, ILogger logger) 21 | { 22 | _options = nTccTransactionOptions; 23 | _transactionRepository = transactionRepository; 24 | _serviceScopeFactory = serviceScopeFactory; 25 | _logger = logger; 26 | } 27 | 28 | public async Task StartRecoverAsync() 29 | { 30 | var date = DateTime.UtcNow.AddSeconds(-_options.RecoverDuration); 31 | var transactions = _transactionRepository.FindAllUnmodifiedSince(date); 32 | 33 | await RecoverErrorTransactionsAsync(transactions); 34 | } 35 | 36 | private async Task RecoverErrorTransactionsAsync(IEnumerable transactions) 37 | { 38 | using (var scope = _serviceScopeFactory.CreateScope()) 39 | { 40 | foreach (var transaction in transactions) 41 | { 42 | if (transaction.RetriedCount > _options.FailedRetryCount) 43 | { 44 | _logger.LogError(String.Format("recover failed with max retry count,will not try again. txid:{0}, status:{1},retried count:{2},transaction content:{3}", transaction.Xid, (int)transaction.Status, transaction.RetriedCount, JsonConvert.SerializeObject(transaction))); 45 | continue; 46 | } 47 | if (transaction.TransactionType.Equals(TransactionType.BRANCH) 48 | && transaction.CreateUtcTime.AddSeconds(_options.RecoverDuration * _options.FailedRetryCount) > DateTime.UtcNow) 49 | { 50 | continue; 51 | } 52 | 53 | try 54 | { 55 | transaction.AddRetriedCount(); 56 | 57 | if (transaction.Status == TransactionStatus.CONFIRMING) 58 | { 59 | transaction.ChangeStatus(TransactionStatus.CONFIRMING); 60 | _transactionRepository.Update(transaction); 61 | 62 | await transaction.CommitAsync(_serviceScopeFactory); 63 | _transactionRepository.Delete(transaction); 64 | 65 | } 66 | else if (transaction.Status == TransactionStatus.CANCELLING || transaction.TransactionType == TransactionType.ROOT) 67 | { 68 | transaction.ChangeStatus(TransactionStatus.CANCELLING); 69 | _transactionRepository.Update(transaction); 70 | 71 | await transaction.RollbackAsync(_serviceScopeFactory); 72 | _transactionRepository.Delete(transaction); 73 | 74 | } 75 | } 76 | catch (Exception ex) 77 | { 78 | if (typeof(ConcurrencyTransactionException).IsInstanceOfType(ex) 79 | || ex.GetType().IsAssignableFrom(typeof(ConcurrencyTransactionException)) 80 | || ex.GetType().Name.Contains("Concurrency")) 81 | { 82 | _logger.LogWarning(ex, String.Format("optimisticLockException happened while recover. txid:{0}, status:{1},retried count:{2},transaction content:{3}", transaction.Xid, (int)transaction.Status, transaction.RetriedCount, JsonConvert.SerializeObject(transaction))); 83 | } 84 | else 85 | { 86 | _logger.LogError(ex, String.Format("recover failed, txid:{0}, status:{1},retried count:{2},transaction content:{3}", transaction.Xid, (int)transaction.Status, transaction.RetriedCount, JsonConvert.SerializeObject(transaction))); 87 | } 88 | } 89 | } 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/NTccTransaction/Serialization/StringSerializer.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using NTccTransaction.Abstractions; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace NTccTransaction 8 | { 9 | public class StringSerializer : ISerializer 10 | { 11 | public T Deserialize(string content) 12 | { 13 | var settings = new JsonSerializerSettings() 14 | { 15 | ContractResolver = new NTccContractResolver(), 16 | TypeNameHandling = TypeNameHandling.All, 17 | NullValueHandling = NullValueHandling.Ignore 18 | }; 19 | 20 | return JsonConvert.DeserializeObject(content, settings); 21 | } 22 | 23 | public string Serialize(T obj) 24 | { 25 | var settings = new JsonSerializerSettings() 26 | { 27 | ContractResolver = new NTccContractResolver(), 28 | TypeNameHandling = TypeNameHandling.All, 29 | NullValueHandling = NullValueHandling.Ignore 30 | }; 31 | return JsonConvert.SerializeObject(obj, settings); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/NTccTransaction/Utils/CompensableHelper.cs: -------------------------------------------------------------------------------- 1 | using NTccTransaction.Abstractions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | 8 | namespace NTccTransaction 9 | { 10 | public class CompensableHelper 11 | { 12 | public static bool IsCompensableMethod(MethodInfo methodInfo) 13 | { 14 | CompensableAttribute compensableAttribute; 15 | Check.NotNull(methodInfo, nameof(methodInfo)); 16 | 17 | //Method declaration 18 | var attrs = methodInfo.GetCustomAttributes(true).OfType().ToArray(); 19 | if (attrs.Any()) 20 | { 21 | compensableAttribute = attrs.First(); 22 | return !compensableAttribute.IsDisabled; 23 | } 24 | 25 | //compensableAttribute = null; 26 | return false; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/NTccTransaction/Utils/NTccContractResolver.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Serialization; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Text; 8 | 9 | namespace NTccTransaction 10 | { 11 | public class NTccContractResolver: DefaultContractResolver 12 | { 13 | protected override IList CreateProperties(Type type, MemberSerialization memberSerialization) 14 | { 15 | var props = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) 16 | .Select(p => base.CreateProperty(p, memberSerialization)) 17 | .ToList(); 18 | 19 | props.ForEach(p => { p.Writable = true; p.Readable = true; }); 20 | 21 | return props; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/NTccTransaction/Utils/TransactionUtils.cs: -------------------------------------------------------------------------------- 1 | using NTccTransaction.Abstractions; 2 | 3 | namespace NTccTransaction 4 | { 5 | public class TransactionUtils 6 | { 7 | /// 8 | /// Validate transaction context 9 | /// 10 | /// 11 | /// 12 | /// 13 | public static bool IsLegalTransactionContext(bool isTransactionActive, CompensableMethodContext compensableMethodContext) 14 | { 15 | if (compensableMethodContext.Propagation==Propagation.MANDATORY && 16 | !isTransactionActive && 17 | compensableMethodContext.TransactionContext == null) 18 | { 19 | return false; 20 | } 21 | 22 | return true; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/NTccTransaction.Test/LocalService/LocalServiceUnitTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | namespace NTccTransaction.Test 4 | { 5 | [TestClass] 6 | public class LocalServiceUnitTest 7 | { 8 | [TestMethod] 9 | public void TestLocalService() 10 | { 11 | 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/NTccTransaction.Test/NTccTransaction.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | --------------------------------------------------------------------------------