From 5e15749aa8ae8578a6b8b3c365d6ab25802aa0e1 Mon Sep 17 00:00:00 2001 From: 28810 <28810@YEXIANGQIN> Date: Fri, 24 Apr 2020 01:47:48 +0800 Subject: [PATCH] =?UTF-8?q?-=20=E5=A2=9E=E5=8A=A0=20UnitOfWorkManager=20?= =?UTF-8?q?=E5=B7=A5=E4=BD=9C=E5=8D=95=E5=85=83=E7=AE=A1=E7=90=86=E5=99=A8?= =?UTF-8?q?=EF=BC=8C=E5=AE=9E=E7=8E=B0=E5=A4=9A=E7=A7=8D=E4=BC=A0=E6=92=AD?= =?UTF-8?q?=E4=BA=8B=E5=8A=A1=EF=BC=9B#289?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/HomeController.cs | 70 +++++ Examples/aspnetcore_transaction/Program.cs | 27 ++ .../Properties/launchSettings.json | 27 ++ Examples/aspnetcore_transaction/Startup.cs | 58 ++++ .../TransactionalAttribute.cs | 51 ++++ .../aspnetcore_transaction.csproj | 17 ++ .../DbContext/DbContextScopedFreeSql.cs | 8 +- FreeSql.DbContext/FreeSql.DbContext.csproj | 2 +- FreeSql.DbContext/FreeSql.DbContext.xml | 94 +++--- .../ContextSet/RepositoryUnitOfWorkManager.cs | 201 ------------- .../Repository/DefaultRepository.cs | 8 + .../UnitOfWork/UnitOfWorkManager.cs | 274 ++++++++++++++++++ FreeSql.sln | 15 + FreeSql/FreeSql.xml | 143 --------- .../AdoProvider/AdoProviderTransaction.cs | 31 +- 15 files changed, 621 insertions(+), 405 deletions(-) create mode 100644 Examples/aspnetcore_transaction/Controllers/HomeController.cs create mode 100644 Examples/aspnetcore_transaction/Program.cs create mode 100644 Examples/aspnetcore_transaction/Properties/launchSettings.json create mode 100644 Examples/aspnetcore_transaction/Startup.cs create mode 100644 Examples/aspnetcore_transaction/TransactionalAttribute.cs create mode 100644 Examples/aspnetcore_transaction/aspnetcore_transaction.csproj delete mode 100644 FreeSql.DbContext/Repository/ContextSet/RepositoryUnitOfWorkManager.cs create mode 100644 FreeSql.DbContext/UnitOfWork/UnitOfWorkManager.cs diff --git a/Examples/aspnetcore_transaction/Controllers/HomeController.cs b/Examples/aspnetcore_transaction/Controllers/HomeController.cs new file mode 100644 index 00000000..34707033 --- /dev/null +++ b/Examples/aspnetcore_transaction/Controllers/HomeController.cs @@ -0,0 +1,70 @@ +using FreeSql; +using FreeSql.DataAnnotations; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace aspnetcore_transaction.Controllers +{ + [ApiController] + [Route("")] + public class HomeController : ControllerBase + { + private readonly ILogger _logger; + + public HomeController(ILogger logger) + { + _logger = logger; + } + + [HttpGet] + //[Transactional] + virtual public object Get([FromServices] BaseRepository repoSong, [FromServices] BaseRepository repoDetail, [FromServices] SongRepository repoSong2, + [FromServices] SongService serviceSong) + { + serviceSong.Test(); + return "111"; + } + } + + public class SongService + { + BaseRepository _repoSong; + BaseRepository _repoDetail; + SongRepository _repoSong2; + + public SongService(BaseRepository repoSong, BaseRepository repoDetail, SongRepository repoSong2) + { + _repoSong = repoSong; + _repoDetail = repoDetail; + _repoSong2 = repoSong2; + } + + [Transactional] + public virtual void Test() + { + _repoSong.Insert(new Song()); + _repoDetail.Insert(new Detail()); + _repoSong2.Insert(new Song()); + } + } + + public class SongRepository : DefaultRepository + { + public SongRepository(UnitOfWorkManager uowm) : base(uowm?.Orm, uowm) { } + } + + public class Song + { + [Column(IsIdentity = true)] + public int Id { get; set; } + public string Title { get; set; } + } + public class Detail + { + [Column(IsIdentity = true)] + public int Id { get; set; } + + public int SongId { get; set; } + public string Title { get; set; } + } +} diff --git a/Examples/aspnetcore_transaction/Program.cs b/Examples/aspnetcore_transaction/Program.cs new file mode 100644 index 00000000..24fcc6bf --- /dev/null +++ b/Examples/aspnetcore_transaction/Program.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace aspnetcore_transaction +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }) + .UseServiceProviderFactory(new FreeSql.DynamicProxyServiceProviderFactory()); + } +} diff --git a/Examples/aspnetcore_transaction/Properties/launchSettings.json b/Examples/aspnetcore_transaction/Properties/launchSettings.json new file mode 100644 index 00000000..a2e23312 --- /dev/null +++ b/Examples/aspnetcore_transaction/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:35350/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "dbcontext_01": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:35351/" + } + } +} \ No newline at end of file diff --git a/Examples/aspnetcore_transaction/Startup.cs b/Examples/aspnetcore_transaction/Startup.cs new file mode 100644 index 00000000..67727f65 --- /dev/null +++ b/Examples/aspnetcore_transaction/Startup.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using aspnetcore_transaction.Controllers; +using FreeSql; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.HttpsPolicy; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace aspnetcore_transaction +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + Fsql = new FreeSql.FreeSqlBuilder() + .UseConnectionString(FreeSql.DataType.Sqlite, @"Data Source=|DataDirectory|\test_trans.db") + .UseAutoSyncStructure(true) + .UseMonitorCommand(cmd => Trace.WriteLine(cmd.CommandText)) + .UseNoneCommandParameter(true) + .Build(); + } + + public IConfiguration Configuration { get; } + public static IFreeSql Fsql { get; private set; } + + public void ConfigureServices(IServiceCollection services) + { + services.AddControllersWithViews(); + + services.AddSingleton(Fsql); + services.AddScoped(); + services.AddFreeRepository(null, typeof(Startup).Assembly); + services.AddScoped(); + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + Console.OutputEncoding = Encoding.GetEncoding("GB2312"); + Console.InputEncoding = Encoding.GetEncoding("GB2312"); + + app.UseHttpMethodOverride(new HttpMethodOverrideOptions { FormFieldName = "X-Http-Method-Override" }); + app.UseDeveloperExceptionPage(); + app.UseRouting(); + app.UseEndpoints(a => a.MapControllers()); + } + } +} diff --git a/Examples/aspnetcore_transaction/TransactionalAttribute.cs b/Examples/aspnetcore_transaction/TransactionalAttribute.cs new file mode 100644 index 00000000..049575c1 --- /dev/null +++ b/Examples/aspnetcore_transaction/TransactionalAttribute.cs @@ -0,0 +1,51 @@ +using FreeSql; +using Microsoft.AspNetCore.Mvc.Filters; +using System; +using System.Collections.Generic; +using System.Data; +using System.Text; +using System.Threading.Tasks; + +namespace FreeSql +{ + /// + /// 使用事务执行,请查看 Program.cs 代码开启动态代理 + /// + [AttributeUsage(AttributeTargets.Method)] + public class TransactionalAttribute : DynamicProxyAttribute, IActionFilter + { + public Propagation Propagation { get; set; } = Propagation.Requierd; + public IsolationLevel? IsolationLevel { get; set; } + + [DynamicProxyFromServices] + UnitOfWorkManager _uowManager; + IUnitOfWork _uow; + + public override Task Before(DynamicProxyBeforeArguments args) => OnBefore(_uowManager); + public override Task After(DynamicProxyAfterArguments args) => OnAfter(args.Exception); + + //这里是为了 controller + public void OnActionExecuting(ActionExecutingContext context) => OnBefore(context.HttpContext.RequestServices.GetService(typeof(UnitOfWorkManager)) as UnitOfWorkManager); + public void OnActionExecuted(ActionExecutedContext context) => OnAfter(context.Exception); + + + Task OnBefore(UnitOfWorkManager uowm) + { + _uow = uowm.Begin(this.Propagation, this.IsolationLevel); + return Task.FromResult(false); + } + Task OnAfter(Exception ex) + { + try + { + if (ex == null) _uow.Commit(); + else _uow.Rollback(); + } + finally + { + _uow.Dispose(); + } + return Task.FromResult(false); + } + } +} diff --git a/Examples/aspnetcore_transaction/aspnetcore_transaction.csproj b/Examples/aspnetcore_transaction/aspnetcore_transaction.csproj new file mode 100644 index 00000000..1ff5ec53 --- /dev/null +++ b/Examples/aspnetcore_transaction/aspnetcore_transaction.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp3.1 + + + + + + + + + + + + + diff --git a/FreeSql.DbContext/DbContext/DbContextScopedFreeSql.cs b/FreeSql.DbContext/DbContext/DbContextScopedFreeSql.cs index e1b0b1ef..66310a51 100644 --- a/FreeSql.DbContext/DbContext/DbContextScopedFreeSql.cs +++ b/FreeSql.DbContext/DbContext/DbContextScopedFreeSql.cs @@ -35,21 +35,21 @@ namespace FreeSql public ISelect Select() where T1 : class { - _resolveDbContext()?.FlushCommand(); + _resolveDbContext?.Invoke()?.FlushCommand(); return _originalFsql.Select().WithTransaction(_resolveUnitOfWork()?.GetOrBeginTransaction(false)); } public ISelect Select(object dywhere) where T1 : class => Select().WhereDynamic(dywhere); public IDelete Delete() where T1 : class { - _resolveDbContext()?.FlushCommand(); + _resolveDbContext?.Invoke()?.FlushCommand(); return _originalFsql.Delete().WithTransaction(_resolveUnitOfWork()?.GetOrBeginTransaction()); } public IDelete Delete(object dywhere) where T1 : class => Delete().WhereDynamic(dywhere); public IUpdate Update() where T1 : class { - var db = _resolveDbContext(); + var db = _resolveDbContext?.Invoke(); db?.FlushCommand(); var update = _originalFsql.Update().WithTransaction(_resolveUnitOfWork()?.GetOrBeginTransaction()); if (db?.Options.NoneParameter != null) update.NoneParameter(db.Options.NoneParameter.Value); @@ -59,7 +59,7 @@ namespace FreeSql public IInsert Insert() where T1 : class { - var db = _resolveDbContext(); + var db = _resolveDbContext?.Invoke(); db?.FlushCommand(); var insert = _originalFsql.Insert().WithTransaction(_resolveUnitOfWork()?.GetOrBeginTransaction()); if (db?.Options.NoneParameter != null) insert.NoneParameter(db.Options.NoneParameter.Value); diff --git a/FreeSql.DbContext/FreeSql.DbContext.csproj b/FreeSql.DbContext/FreeSql.DbContext.csproj index 9cf7f4a8..75e7ecf8 100644 --- a/FreeSql.DbContext/FreeSql.DbContext.csproj +++ b/FreeSql.DbContext/FreeSql.DbContext.csproj @@ -6,7 +6,7 @@ true YeXiangQin FreeSql is the ORM in .NetCore, .NetFramework, And Xamarin. It supports Mysql, Postgresql, SqlServer, Oracle, Sqlite, Odbc, 达梦, And Access - https://github.com/2881099/FreeSql.DbContext + https://github.com/2881099/FreeSql/wiki/DbContext FreeSql ORM DbContext git MIT diff --git a/FreeSql.DbContext/FreeSql.DbContext.xml b/FreeSql.DbContext/FreeSql.DbContext.xml index 9f17feac..650dae1f 100644 --- a/FreeSql.DbContext/FreeSql.DbContext.xml +++ b/FreeSql.DbContext/FreeSql.DbContext.xml @@ -227,41 +227,6 @@ 分表规则,参数:旧表名;返回:新表名 https://github.com/2881099/FreeSql/wiki/Repository - - - 仓储的工作单元管理器 - - - - - 如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,默认的选择。 - - - - - 支持当前事务,如果没有当前事务,就以非事务方法执行。 - - - - - 使用当前事务,如果没有当前事务,就抛出异常。 - - - - - 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 - - - - - 以非事务方式执行操作,如果当前事务存在则抛出异常。 - - - - - 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,就新建一个事务。 - - 开启过滤器,若使用 using 则使用完后,恢复为原有状态 @@ -381,6 +346,65 @@ 例如:20191121_214504_1 + + + 工作单元管理器 + + + + + 当前的工作单元 + + + + + 将仓储的事务交给我管理 + + + + + + 创建工作单元 + + 事务传播方式 + 事务隔离级别 + + + + + 事务传播方式 + + + + + 如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,默认的选择。 + + + + + 支持当前事务,如果没有当前事务,就以非事务方法执行。 + + + + + 使用当前事务,如果没有当前事务,就抛出异常。 + + + + + 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 + + + + + 以非事务方式执行操作,如果当前事务存在则抛出异常。 + + + + + 以嵌套事务方式执行。 + + EFCore 95% 相似的 FluentApi 扩展方法 diff --git a/FreeSql.DbContext/Repository/ContextSet/RepositoryUnitOfWorkManager.cs b/FreeSql.DbContext/Repository/ContextSet/RepositoryUnitOfWorkManager.cs deleted file mode 100644 index a3524b59..00000000 --- a/FreeSql.DbContext/Repository/ContextSet/RepositoryUnitOfWorkManager.cs +++ /dev/null @@ -1,201 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; -using System.Linq; -using System.Linq.Expressions; -using System.Threading; - -namespace FreeSql -{ - /// - /// 仓储的工作单元管理器 - /// - public class RepositoryUnitOfWorkManager : IDisposable - { - IFreeSql _fsql; - List _uows = new List(); - bool _isNotSupported = false; - - public RepositoryUnitOfWorkManager(IFreeSql fsql) - { - _fsql = fsql ?? throw new ArgumentNullException($"{nameof(RepositoryUnitOfWorkManager)} 构造参数 {nameof(fsql)} 不能为 null"); - } - - ~RepositoryUnitOfWorkManager() => this.Dispose(); - int _disposeCounter; - public void Dispose() - { - if (Interlocked.Increment(ref _disposeCounter) != 1) return; - try - { - Exception exception = null; - for (var a = _uows.Count - 1; a >= 0; a--) - { - try - { - if (exception == null) _uows[a].Commit(); - else _uows[a].Rollback(); - } - catch (Exception ex) - { - if (exception == null) exception = ex; - } - } - if (exception != null) throw exception; - } - finally - { - _uows.Clear(); - GC.SuppressFinalize(this); - } - } - - public enum Propagation - { - /// - /// 如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,默认的选择。 - /// - Requierd, - /// - /// 支持当前事务,如果没有当前事务,就以非事务方法执行。 - /// - Supports, - /// - /// 使用当前事务,如果没有当前事务,就抛出异常。 - /// - Mandatory, - /// - /// 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 - /// - NotSupported, - /// - /// 以非事务方式执行操作,如果当前事务存在则抛出异常。 - /// - Never, - /// - /// 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,就新建一个事务。 - /// - Nested - } - - public IRepositoryUnitOfWork Begin(Propagation propagation, IsolationLevel? isolationLevel = null) - { - if (propagation == Propagation.Requierd) - { - if (_isNotSupported == false) - { - for (var a = _uows.Count - 1; a >= 0; a--) - if (_uows[a].GetOrBeginTransaction(false) != null) - return new UnitOfWorkProxy(_uows[a]); - } - var uow = new RepositoryUnitOfWork(_fsql); - if (isolationLevel != null) uow.IsolationLevel = isolationLevel.Value; - try { uow.GetOrBeginTransaction(); } - catch { uow.Dispose(); throw; } - _uows.Add(uow); - return uow; - } - if (propagation == Propagation.Supports) - { - if (_isNotSupported == false) - { - for (var a = _uows.Count - 1; a >= 0; a--) - if (_uows[a].GetOrBeginTransaction(false) != null) - return new UnitOfWorkProxy(_uows[a]); - } - return new UnitOfWorkNothing(_fsql); - } - if (propagation == Propagation.Mandatory) - { - if (_isNotSupported == false) - { - for (var a = _uows.Count - 1; a >= 0; a--) - if (_uows[a].GetOrBeginTransaction(false) != null) - return new UnitOfWorkProxy(_uows[a]); - throw new Exception("Propagation_Mandatory: 使用当前事务,如果没有当前事务,就抛出异常"); - } - throw new Exception("Propagation_Mandatory: 使用当前事务,如果没有当前事务,就抛出异常(NotSupported 事务挂起中)"); - } - if (propagation == Propagation.NotSupported) - { - if (_isNotSupported == false) - { - _isNotSupported = true; - return new UnitOfWorkNothing(_fsql) { OnDispose = () => _isNotSupported = false }; - } - return new UnitOfWorkNothing(_fsql); - } - if (propagation == Propagation.Never) - { - if (_isNotSupported == false) - { - for (var a = _uows.Count - 1; a >= 0; a--) - if (_uows[a].GetOrBeginTransaction(false) != null) - throw new Exception("Propagation_Never: 以非事务方式执行操作,如果当前事务存在则抛出异常"); - } - return new UnitOfWorkNothing(_fsql); - } - if (propagation == Propagation.Nested) - { - var uow = new RepositoryUnitOfWork(_fsql); - if (isolationLevel != null) uow.IsolationLevel = isolationLevel.Value; - try { uow.GetOrBeginTransaction(); } - catch { uow.Dispose(); throw; } - _uows.Add(uow); - return uow; - } - throw new NotImplementedException(); - } - - class UnitOfWorkProxy : IRepositoryUnitOfWork - { - IRepositoryUnitOfWork _baseUow; - public UnitOfWorkProxy(IRepositoryUnitOfWork baseUow) => _baseUow = baseUow; - public IsolationLevel? IsolationLevel { get => _baseUow.IsolationLevel; set { } } - public DbContext.EntityChangeReport EntityChangeReport => _baseUow.EntityChangeReport; - - public bool Enable => _baseUow.Enable; - public void Close() => _baseUow.Close(); - public void Open() => _baseUow.Open(); - - public DbTransaction GetOrBeginTransaction(bool isCreate = true) => _baseUow.GetOrBeginTransaction(isCreate); - public void Commit() => this.Dispose(); - public void Rollback() => _baseUow.Rollback(); - public void Dispose() { } - - public IBaseRepository GetRepository(Expression> filter = null) where TEntity : class => _baseUow.GetRepository(filter); - public IBaseRepository GetRepository(Expression> filter = null) where TEntity : class => _baseUow.GetRepository(filter); - public IBaseRepository GetGuidRepository(Expression> filter = null, Func asTable = null) where TEntity : class => _baseUow.GetGuidRepository(filter); - } - class UnitOfWorkNothing : IRepositoryUnitOfWork - { - internal IFreeSql _fsql; - internal Action OnDispose; - public UnitOfWorkNothing(IFreeSql fsql) => _fsql = fsql; - public IsolationLevel? IsolationLevel { get; set; } - public DbContext.EntityChangeReport EntityChangeReport { get; } = new DbContext.EntityChangeReport(); - - public bool Enable { get; } - public void Close() { } - public void Open() { } - - public DbTransaction GetOrBeginTransaction(bool isCreate = true) => null; - public void Commit() - { - if (EntityChangeReport != null && EntityChangeReport.OnChange != null && EntityChangeReport.Report.Any() == true) - EntityChangeReport.OnChange.Invoke(EntityChangeReport.Report); - this.Dispose(); - } - public void Rollback() => this.Dispose(); - public void Dispose() { - EntityChangeReport?.Report.Clear(); - OnDispose?.Invoke(); - } - - public IBaseRepository GetRepository(Expression> filter = null) where TEntity : class => new DefaultRepository(_fsql, filter); - public IBaseRepository GetRepository(Expression> filter = null) where TEntity : class => new DefaultRepository(_fsql, filter); - public IBaseRepository GetGuidRepository(Expression> filter = null, Func asTable = null) where TEntity : class => new GuidRepository(_fsql, filter, asTable); - } - } -} diff --git a/FreeSql.DbContext/Repository/Repository/DefaultRepository.cs b/FreeSql.DbContext/Repository/Repository/DefaultRepository.cs index 6f36a3dc..86b43806 100644 --- a/FreeSql.DbContext/Repository/Repository/DefaultRepository.cs +++ b/FreeSql.DbContext/Repository/Repository/DefaultRepository.cs @@ -7,11 +7,19 @@ namespace FreeSql { public DefaultRepository(IFreeSql fsql) : base(fsql, null, null) { } public DefaultRepository(IFreeSql fsql, Expression> filter) : base(fsql, filter, null) { } + public DefaultRepository(IFreeSql fsql, UnitOfWorkManager uowManger) : base(uowManger?.Orm ?? fsql, null, null) + { + uowManger?.Binding(this); + } } public class GuidRepository : BaseRepository where TEntity : class { public GuidRepository(IFreeSql fsql) : this(fsql, null, null) { } public GuidRepository(IFreeSql fsql, Expression> filter, Func asTable) : base(fsql, filter, asTable) { } + public GuidRepository(IFreeSql fsql, UnitOfWorkManager uowManger) : base(uowManger?.Orm ?? fsql, null, null) + { + uowManger?.Binding(this); + } } } diff --git a/FreeSql.DbContext/UnitOfWork/UnitOfWorkManager.cs b/FreeSql.DbContext/UnitOfWork/UnitOfWorkManager.cs new file mode 100644 index 00000000..428fd7fb --- /dev/null +++ b/FreeSql.DbContext/UnitOfWork/UnitOfWorkManager.cs @@ -0,0 +1,274 @@ + +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Linq; +using System.Threading; + +namespace FreeSql +{ + /// + /// 工作单元管理器 + /// + public class UnitOfWorkManager : IDisposable + { + DbContextScopedFreeSql _ormScoped; + public IFreeSql Orm => _ormScoped; + List _rawUows = new List(); + List _allUows = new List(); + List _repos = new List(); + + public UnitOfWorkManager(IFreeSql fsql) + { + if (fsql == null) throw new ArgumentNullException($"{nameof(UnitOfWorkManager)} 构造参数 {nameof(fsql)} 不能为 null"); + _ormScoped = DbContextScopedFreeSql.Create(fsql, null, () => this.Current); + } + + #region Dispose + ~UnitOfWorkManager() => this.Dispose(); + int _disposeCounter; + public void Dispose() + { + if (Interlocked.Increment(ref _disposeCounter) != 1) return; + try + { + Exception exception = null; + for (var a = _rawUows.Count - 1; a >= 0; a--) + { + try + { + if (exception == null) _rawUows[a].Uow.Commit(); + else _rawUows[a].Uow.Rollback(); + } + catch (Exception ex) + { + if (exception == null) exception = ex; + } + } + if (exception != null) throw exception; + } + finally + { + _rawUows.Clear(); + _allUows.Clear(); + _repos.Clear(); + GC.SuppressFinalize(this); + } + } + #endregion + + /// + /// 当前的工作单元 + /// + public IUnitOfWork Current => _allUows.LastOrDefault()?.Uow; + + /// + /// 将仓储的事务交给我管理 + /// + /// + public void Binding(IBaseRepository repository) + { + var repoInfo = new RepoInfo(repository); + repository.UnitOfWork = Current; + _repos.Add(repoInfo); + } + void SetAllRepositoryUow() + { + foreach (var repo in _repos) + repo.Repository.UnitOfWork = Current ?? repo.OrginalUow; + } + + /// + /// 创建工作单元 + /// + /// 事务传播方式 + /// 事务隔离级别 + /// + public IUnitOfWork Begin(Propagation propagation = Propagation.Requierd, IsolationLevel? isolationLevel = null) + { + switch (propagation) + { + case Propagation.Requierd: return FindedUowCreateVirtual() ?? CreateUow(isolationLevel); + case Propagation.Supports: return FindedUowCreateVirtual() ?? CreateUowNothing(_allUows.LastOrDefault()?.IsNotSupported ?? false); + case Propagation.Mandatory: return FindedUowCreateVirtual() ?? throw new Exception("Propagation_Mandatory: 使用当前事务,如果没有当前事务,就抛出异常"); + case Propagation.NotSupported: return CreateUowNothing(true); + case Propagation.Never: + var isNotSupported = _allUows.LastOrDefault()?.IsNotSupported ?? false; + if (isNotSupported == false) + { + for (var a = _rawUows.Count - 1; a >= 0; a--) + if (_rawUows[a].Uow.GetOrBeginTransaction(false) != null) + throw new Exception("Propagation_Never: 以非事务方式执行操作,如果当前事务存在则抛出异常"); + } + return CreateUowNothing(isNotSupported); + case Propagation.Nested: return CreateUow(isolationLevel); + default: throw new NotImplementedException(); + } + } + + IUnitOfWork FindedUowCreateVirtual() + { + var isNotSupported = _allUows.LastOrDefault()?.IsNotSupported ?? false; + if (isNotSupported == false) + { + for (var a = _rawUows.Count - 1; a >= 0; a--) + if (_rawUows[a].Uow.GetOrBeginTransaction(false) != null) + { + var uow = new UnitOfWorkVirtual(_rawUows[a].Uow); + var uowInfo = new UowInfo(uow, UowInfo.UowType.Virtual, isNotSupported); + uow.OnDispose = () => _allUows.Remove(uowInfo); + _allUows.Add(uowInfo); + SetAllRepositoryUow(); + return uow; + } + } + return null; + } + IUnitOfWork CreateUowNothing(bool isNotSupported) + { + var uow = new UnitOfWorkNothing(Orm); + var uowInfo = new UowInfo(uow, UowInfo.UowType.Nothing, isNotSupported); + uow.OnDispose = () => _allUows.Remove(uowInfo); + _allUows.Add(uowInfo); + SetAllRepositoryUow(); + return uow; + } + IUnitOfWork CreateUow(IsolationLevel? isolationLevel) + { + var uow = new UnitOfWorkOrginal(new UnitOfWork(Orm)); + var uowInfo = new UowInfo(uow, UowInfo.UowType.Orginal, false); + if (isolationLevel != null) uow.IsolationLevel = isolationLevel.Value; + try { uow.GetOrBeginTransaction(); } + catch { uow.Dispose(); throw; } + + uow.OnDispose = () => + { + _rawUows.Remove(uowInfo); + _allUows.Remove(uowInfo); + SetAllRepositoryUow(); + }; + _rawUows.Add(uowInfo); + _allUows.Add(uowInfo); + SetAllRepositoryUow(); + return uow; + } + + class RepoInfo + { + public IBaseRepository Repository; + public IUnitOfWork OrginalUow; + + public RepoInfo(IBaseRepository repository) + { + this.Repository = repository; + this.OrginalUow = repository.UnitOfWork; + } + } + class UowInfo + { + public IUnitOfWork Uow; + public UowType Type; + public bool IsNotSupported; + public enum UowType { Orginal, Virtual, Nothing } + + public UowInfo(IUnitOfWork uow, UowType type, bool isNotSupported) + { + this.Uow = uow; + this.Type = type; + this.IsNotSupported = isNotSupported; + } + } + class UnitOfWorkOrginal : IUnitOfWork + { + IUnitOfWork _baseUow; + internal Action OnDispose; + public UnitOfWorkOrginal(IUnitOfWork baseUow) => _baseUow = baseUow; + public IsolationLevel? IsolationLevel { get => _baseUow.IsolationLevel; set => _baseUow.IsolationLevel = value; } + public DbContext.EntityChangeReport EntityChangeReport => _baseUow.EntityChangeReport; + + public bool Enable => _baseUow.Enable; + public void Close() => _baseUow.Close(); + public void Open() => _baseUow.Open(); + + public DbTransaction GetOrBeginTransaction(bool isCreate = true) => _baseUow.GetOrBeginTransaction(isCreate); + public void Commit() => _baseUow.Commit(); + public void Rollback() => _baseUow.Rollback(); + public void Dispose() + { + _baseUow.Dispose(); + OnDispose?.Invoke(); + } + } + class UnitOfWorkVirtual : IUnitOfWork + { + IUnitOfWork _baseUow; + internal Action OnDispose; + public UnitOfWorkVirtual(IUnitOfWork baseUow) => _baseUow = baseUow; + public IsolationLevel? IsolationLevel { get => _baseUow.IsolationLevel; set { } } + public DbContext.EntityChangeReport EntityChangeReport => _baseUow.EntityChangeReport; + + public bool Enable => _baseUow.Enable; + public void Close() => _baseUow.Close(); + public void Open() => _baseUow.Open(); + + public DbTransaction GetOrBeginTransaction(bool isCreate = true) => _baseUow.GetOrBeginTransaction(isCreate); + public void Commit() { } + public void Rollback() => _baseUow.Rollback(); + public void Dispose() => OnDispose?.Invoke(); + } + class UnitOfWorkNothing : IUnitOfWork + { + internal IFreeSql _fsql; + internal Action OnDispose; + public UnitOfWorkNothing(IFreeSql fsql) => _fsql = fsql; + public IsolationLevel? IsolationLevel { get; set; } + public DbContext.EntityChangeReport EntityChangeReport { get; } = new DbContext.EntityChangeReport(); + + public bool Enable { get; } + public void Close() { } + public void Open() { } + + public DbTransaction GetOrBeginTransaction(bool isCreate = true) => null; + public void Commit() + { + if (EntityChangeReport != null && EntityChangeReport.OnChange != null && EntityChangeReport.Report.Any() == true) + EntityChangeReport.OnChange.Invoke(EntityChangeReport.Report); + EntityChangeReport?.Report.Clear(); + } + public void Rollback() => EntityChangeReport?.Report.Clear(); + public void Dispose() => OnDispose?.Invoke(); + } + } + + /// + /// 事务传播方式 + /// + public enum Propagation + { + /// + /// 如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,默认的选择。 + /// + Requierd, + /// + /// 支持当前事务,如果没有当前事务,就以非事务方法执行。 + /// + Supports, + /// + /// 使用当前事务,如果没有当前事务,就抛出异常。 + /// + Mandatory, + /// + /// 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 + /// + NotSupported, + /// + /// 以非事务方式执行操作,如果当前事务存在则抛出异常。 + /// + Never, + /// + /// 以嵌套事务方式执行。 + /// + Nested + } +} diff --git a/FreeSql.sln b/FreeSql.sln index 7cf8a130..0f8259b1 100644 --- a/FreeSql.sln +++ b/FreeSql.sln @@ -78,6 +78,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FreeSql.Extensions.Linq", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FreeSql.Provider.Dameng", "Providers\FreeSql.Provider.Dameng\FreeSql.Provider.Dameng.csproj", "{E74D90E8-1CBC-4677-817B-1CA05AB97937}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "aspnetcore_transaction", "Examples\aspnetcore_transaction\aspnetcore_transaction.csproj", "{07AB0B37-A8B1-4FB1-9259-7B804E369E36}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -472,6 +474,18 @@ Global {E74D90E8-1CBC-4677-817B-1CA05AB97937}.Release|x64.Build.0 = Release|Any CPU {E74D90E8-1CBC-4677-817B-1CA05AB97937}.Release|x86.ActiveCfg = Release|Any CPU {E74D90E8-1CBC-4677-817B-1CA05AB97937}.Release|x86.Build.0 = Release|Any CPU + {07AB0B37-A8B1-4FB1-9259-7B804E369E36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07AB0B37-A8B1-4FB1-9259-7B804E369E36}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07AB0B37-A8B1-4FB1-9259-7B804E369E36}.Debug|x64.ActiveCfg = Debug|Any CPU + {07AB0B37-A8B1-4FB1-9259-7B804E369E36}.Debug|x64.Build.0 = Debug|Any CPU + {07AB0B37-A8B1-4FB1-9259-7B804E369E36}.Debug|x86.ActiveCfg = Debug|Any CPU + {07AB0B37-A8B1-4FB1-9259-7B804E369E36}.Debug|x86.Build.0 = Debug|Any CPU + {07AB0B37-A8B1-4FB1-9259-7B804E369E36}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07AB0B37-A8B1-4FB1-9259-7B804E369E36}.Release|Any CPU.Build.0 = Release|Any CPU + {07AB0B37-A8B1-4FB1-9259-7B804E369E36}.Release|x64.ActiveCfg = Release|Any CPU + {07AB0B37-A8B1-4FB1-9259-7B804E369E36}.Release|x64.Build.0 = Release|Any CPU + {07AB0B37-A8B1-4FB1-9259-7B804E369E36}.Release|x86.ActiveCfg = Release|Any CPU + {07AB0B37-A8B1-4FB1-9259-7B804E369E36}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -499,6 +513,7 @@ Global {B397A761-F646-41CF-A160-AB6C05DAF2FB} = {2A381C57-2697-427B-9F10-55DA11FD02E4} {57B3F5B0-D46A-4442-8EC6-9A9A784404B7} = {4A92E8A6-9A6D-41A1-9CDA-DE10899648AA} {E74D90E8-1CBC-4677-817B-1CA05AB97937} = {2A381C57-2697-427B-9F10-55DA11FD02E4} + {07AB0B37-A8B1-4FB1-9259-7B804E369E36} = {94C8A78D-AA15-47B2-A348-530CD86BFC1B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {089687FD-5D25-40AB-BA8A-A10D1E137F98} diff --git a/FreeSql/FreeSql.xml b/FreeSql/FreeSql.xml index d6b6f1a8..48b7b879 100644 --- a/FreeSql/FreeSql.xml +++ b/FreeSql/FreeSql.xml @@ -2310,137 +2310,6 @@ - - - 查询,若使用读写分离,查询【从库】条件cmdText.StartsWith("SELECT "),否则查询【主库】 - - - - - - - - - 查询,ExecuteReaderAsync(dr => {}, "select * from user where age > ?age", new { age = 25 }) - - - - - - - 查询 - - - - - - - 查询,ExecuteArrayAsync("select * from user where age > ?age", new { age = 25 }) - - - - - - - - 查询 - - - - - - - 查询,ExecuteDataSetAsync("select * from user where age > ?age; select 2", new { age = 25 }) - - - - - - - - 查询 - - - - - - - 查询,ExecuteDataTableAsync("select * from user where age > ?age", new { age = 25 }) - - - - - - - - 在【主库】执行 - - - - - - - - 在【主库】执行,ExecuteNonQueryAsync("delete from user where age > ?age", new { age = 25 }) - - - - - - - - 在【主库】执行 - - - - - - - - 在【主库】执行,ExecuteScalarAsync("select 1 from user where age > ?age", new { age = 25 }) - - - - - - - - 执行SQL返回对象集合,QueryAsync<User>("select * from user where age > ?age", new SqlParameter { ParameterName = "age", Value = 25 }) - - - - - - - - - - 执行SQL返回对象集合,QueryAsync<User>("select * from user where age > ?age", new { age = 25 }) - - - - - - - - - 执行SQL返回对象集合,Query<User>("select * from user where age > ?age; select * from address", new SqlParameter { ParameterName = "age", Value = 25 }) - - - - - - - - - - 执行SQL返回对象集合,Query<User>("select * from user where age > ?age; select * from address", new { age = 25 }) - - - - - - 可自定义解析表达式 @@ -2961,12 +2830,6 @@ 超时 - - - 获取资源 - - - 使用完毕后,归还资源 @@ -3037,12 +2900,6 @@ 资源对象 - - - 从对象池获取对象成功的时候触发,通过该方法统计或初始化对象 - - 资源对象 - 归还对象给对象池的时候触发 diff --git a/FreeSql/Internal/CommonProvider/AdoProvider/AdoProviderTransaction.cs b/FreeSql/Internal/CommonProvider/AdoProvider/AdoProviderTransaction.cs index cd7356a5..bed124cb 100644 --- a/FreeSql/Internal/CommonProvider/AdoProvider/AdoProviderTransaction.cs +++ b/FreeSql/Internal/CommonProvider/AdoProvider/AdoProviderTransaction.cs @@ -16,14 +16,14 @@ namespace FreeSql.Internal.CommonProvider class Transaction2 { internal Aop.TraceBeforeEventArgs AopBefore; - internal Object Conn; + internal Object Connection; internal DbTransaction Transaction; internal DateTime RunTime; internal TimeSpan Timeout; public Transaction2(Object conn, DbTransaction tran, TimeSpan timeout) { - Conn = conn; + Connection = conn; Transaction = tran; RunTime = DateTime.Now; Timeout = timeout; @@ -31,7 +31,6 @@ namespace FreeSql.Internal.CommonProvider } private ConcurrentDictionary _trans = new ConcurrentDictionary(); - private object _trans_lock = new object(); public DbTransaction TransactionCurrentThread => _trans.TryGetValue(Thread.CurrentThread.ManagedThreadId, out var conn) && conn.Transaction?.Connection != null ? conn.Transaction : null; public Aop.TraceBeforeEventArgs TransactionCurrentThreadAopBefore => _trans.TryGetValue(Thread.CurrentThread.ManagedThreadId, out var conn) && conn.Transaction?.Connection != null ? conn.AopBefore : null; @@ -61,35 +60,27 @@ namespace FreeSql.Internal.CommonProvider throw ex; } if (_trans.ContainsKey(tid)) CommitTransaction(); - - lock (_trans_lock) - _trans.TryAdd(tid, tran); + _trans.TryAdd(tid, tran); } private void CommitTimeoutTransaction() { if (_trans.Count > 0) { - Transaction2[] trans = null; - lock (_trans_lock) - trans = _trans.Values.Where(st2 => DateTime.Now.Subtract(st2.RunTime) > st2.Timeout).ToArray(); - foreach (Transaction2 tran in trans) CommitTransaction(true, tran, null, "Timeout自动提交"); + var trans = _trans.Values.Where(st2 => DateTime.Now.Subtract(st2.RunTime) > st2.Timeout).ToArray(); + foreach (var tran in trans) CommitTransaction(true, tran, null, "Timeout自动提交"); } } private void CommitTransaction(bool isCommit, Transaction2 tran, Exception rollbackException, string remark = null) { if (tran == null || tran.Transaction == null || tran.Transaction.Connection == null) return; - - if (_trans.ContainsKey(tran.Conn.LastGetThreadId)) - lock (_trans_lock) - if (_trans.ContainsKey(tran.Conn.LastGetThreadId)) - _trans.TryRemove(tran.Conn.LastGetThreadId, out var oldtran); + _trans.TryRemove(tran.Connection.LastGetThreadId, out var oldtran); Exception ex = null; if (string.IsNullOrEmpty(remark)) remark = isCommit ? "提交" : "回滚"; try { - Trace.WriteLine($"线程{tran.Conn.LastGetThreadId}事务{remark}"); + Trace.WriteLine($"线程{tran.Connection.LastGetThreadId}事务{remark}"); if (isCommit) tran.Transaction.Commit(); else tran.Transaction.Rollback(); } @@ -100,7 +91,7 @@ namespace FreeSql.Internal.CommonProvider } finally { - ReturnConnection(MasterPool, tran.Conn, ex); //MasterPool.Return(tran.Conn, ex); + ReturnConnection(MasterPool, tran.Connection, ex); //MasterPool.Return(tran.Conn, ex); var after = new Aop.TraceAfterEventArgs(tran.AopBefore, remark, ex ?? rollbackException); _util?._orm?.Aop.TraceAfterHandler?.Invoke(this, after); @@ -141,10 +132,8 @@ namespace FreeSql.Internal.CommonProvider if (Interlocked.Increment(ref _disposeCounter) != 1) return; try { - Transaction2[] trans = null; - lock (_trans_lock) - trans = _trans.Values.ToArray(); - foreach (Transaction2 tran in trans) CommitTransaction(false, tran, null, "Dispose自动提交"); + var trans = _trans.Values.ToArray(); + foreach (var tran in trans) CommitTransaction(false, tran, null, "Dispose自动提交"); } catch { }