diff --git a/DI-UnitOfWorkManager.md b/DI-UnitOfWorkManager.md new file mode 100644 index 0000000..99bbe1a --- /dev/null +++ b/DI-UnitOfWorkManager.md @@ -0,0 +1,115 @@ +本篇文章内容引导,如何在 asp.net core 项目中使用特性(注解) 的方式管理事务。 + +> UnitOfWorkManager 只可以管理 Repository 仓储对象的事务,直接 fsql.Insert\() 是不行的!!但是可以用 repository.Orm.Insert\!!repository.Orm 是特殊实现的 IFreeSql,与 当前事务保持一致。 + +支持六种传播方式(propagation),意味着跨方法的事务非常方便,并且支持同步异步: + +- Requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,默认的选择。 +- Supports:支持当前事务,如果没有当前事务,就以非事务方法执行。 +- Mandatory:使用当前事务,如果没有当前事务,就抛出异常。 +- NotSupported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 +- Never:以非事务方式执行操作,如果当前事务存在则抛出异常。 +- Nested:以嵌套事务方式执行。 + +### 第一步:引入动态代理库 + +> dotnet add package Rougamo.Fody + +```csharp +[AttributeUsage(AttributeTargets.Method)] +public class TransactionalAttribute : Rougamo.MoAttribute +{ + public Propagation Propagation { get; set; } = Propagation.Required; + public IsolationLevel IsolationLevel { get => m_IsolationLevel.Value; set => m_IsolationLevel = value; } + IsolationLevel? m_IsolationLevel; + + static AsyncLocal m_ServiceProvider = new AsyncLocal(); + public static void SetServiceProvider(IServiceProvider serviceProvider) => m_ServiceProvider.Value = serviceProvider; + + IUnitOfWork _uow; + public override void OnEntry(MethodContext context) + { + var uowManager = m_ServiceProvider.Value.GetService(typeof(UnitOfWorkManager)) as UnitOfWorkManager; + _uow = uowManager.Begin(this.Propagation, this.m_IsolationLevel); + } + public override void OnExit(MethodContext context) + { + try + { + if (context.Exception == null) _uow.Commit(); + else _uow.Rollback(); + } + finally + { + _uow.Dispose(); + } + } +} +``` + +| UnitOfWorkManager 成员 | 说明 | +| -- | -- | +| IUnitOfWork Current | 返回当前的工作单元 | +| void Binding(repository) | 将仓储的事务交给它管理 | +| IUnitOfWork Begin(propagation, isolationLevel) | 创建工作单元 | + +### 第二步:配置 Startup.cs 注入、中间件 + +```csharp +//Startup.cs +public void ConfigureServices(IServiceCollection services) +{ + services.AddSingleton(fsql); + services.AddScoped(); + services.AddFreeRepository(null, typeof(Startup).Assembly); + //批量注入 Service +} + +public void Configure(IApplicationBuilder app, IWebHostEnvironment env) +{ + app.Use(async (context, next) => + { + TransactionalAttribute.SetServiceProvider(context.RequestServices); + await next(); + }); +} +``` + +### 第三步:在 Controller 或者 Service 或者 Repository 中使用事务特性 + +```csharp +public class SongService +{ + readonly IBaseRepository _repoSong; + readonly IBaseRepository _repoDetail; + readonly SongRepository _repoSong2; + + public SongService(IBaseRepository repoSong, IBaseRepository repoDetail, SongRepository repoSong2) + { + _repoSong = repoSong; + _repoDetail = repoDetail; + _repoSong2 = repoSong2; + } + + [Transactional] + public virtual void Test1() + { + //这里 _repoSong、_repoDetail、_repoSong2 所有操作都是一个工作单元 + this.Test2(); + } + + [Transactional(Propagation = Propagation.Nested)] + public virtual void Test2() //嵌套事务,新的(不使用 Test1 的事务) + { + //这里 _repoSong、_repoDetail、_repoSong2 所有操作都是一个工作单元 + } +} +``` + +是不是进方法就开事务呢? + +不一定是真实事务,有可能是虚的,就是一个假的 unitofwork(不带事务) + +也有可能是延用上一次的事务 + +也有可能是新开事务,具体要看传播模式 \ No newline at end of file diff --git a/DI-UnitOfWorkManager事务.md b/DI-UnitOfWorkManager事务.md deleted file mode 100644 index e603457..0000000 --- a/DI-UnitOfWorkManager事务.md +++ /dev/null @@ -1,141 +0,0 @@ -**中文** | [English](Unit-of-Work-Manager) - -## ASP.NET Core下FreeSql的仓储事务 -#### 第一步:配置 Startup.cs 注入 -引入包 -```bash -dotnet add package FreeSql -dotnet add package FreeSql.DbContext -dotnet add package FreeSql.Provider.MySqlConnector -``` -配置 Startup.cs 注入 -```csharp -public void ConfigureServices(IServiceCollection services) -{ - IConfigurationSection Mysql = Configuration.GetSection("Mysql"); - Fsql = new FreeSqlBuilder() - .UseConnectionString(DataType.MySql, Mysql.Value) - .UseAutoSyncStructure(true) - .UseNameConvert(NameConvertType.PascalCaseToUnderscoreWithLower) - .UseMonitorCommand(cmd => Trace.WriteLine(cmd.CommandText)) - .Build(); - services.AddSingleton(fsql); - services.AddScoped(); - services.AddFreeRepository(null, typeof(Startup).Assembly); - //新增自己的服务,这里只有实现 - services.AddScoped(); -} -``` - -- appsettings.json -```json -{ - "Mysql": "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=ovov_freesql_repository;Charset=utf8;SslMode=none;Max pool size=10", -} -``` - -| UnitOfWorkManager 成员 | 说明 | -| -- | -- | -| IUnitOfWork Current | 返回当前的工作单元 | -| void Binding(repository) | 将仓储的事务交给它管理 | -| IUnitOfWork Begin(propagation, isolationLevel) | 创建工作单元 | - - -- TransBlogService.cs -```csharp -private readonly IBaseRepository _blogRepository; -private readonly IBaseRepository _tagRepository; -private readonly UnitOfWorkManager _unitOfWorkManager; - -public TransBlogService(IBaseRepository blogRepository, IBaseRepository tagRepository,UnitOfWorkManager unitOfWorkManager) -{ - _blogRepository = blogRepository ; - _tagRepository = tagRepository ; - _unitOfWorkManager = unitOfWorkManager; -} - -public async Task CreateBlogUnitOfWorkAsync(Blog blog,ListtagList) -{ - using (IUnitOfWork unitOfWork = _unitOfWorkManager.Begin()) - { - try - { - await _blogRepository.InsertAsync(blog); - tagList.ForEach(r => - { - r.PostId = blog.Id; - }); - await _tagRepository.InsertAsync(tagList); - unitOfWork.Commit(); - } - catch (Exception e) - { - //实际 可以不Rollback。因为IUnitOfWork内部Dispose,会把没有Commit的事务Rollback回来,但能提前Rollback - - unitOfWork.Rollback(); - //记录日志、或继续throw;出来 - } - } -} - -public async Task UpdateBlogAsync(int id) -{ - using (IUnitOfWork unitOfWork = _unitOfWorkManager.Begin()) - { - try - { - Blog blog = _blogRepository.Select.Where(r => r.Id == id).First(); - blog.IsDeleted = true; - await _blogRepository.UpdateAsync(blog); - unitOfWork.Commit(); - } - catch (Exception e) - { - //记录日志、或继续throw;出来 - unitOfWork.Rollback(); - } - } -} -``` - - -| IUnitOfWork 成员 | 说明 | -| -- | -- | -| IFreeSql Orm | 该对象 Select/Delete/Insert/Update/InsertOrUpdate 与工作单元事务保持一致,可省略传递 WithTransaction | -| DbTransaction GetOrBeginTransaction() | 开启事务,或者返回已开启的事务 | -| void Commit() | 提交事务 | -| void Rollback()| 回滚事务 | -| DbContext.EntityChangeReport EntityChangeReport |工作单元内的实体变化跟踪 | - -#### 完整的代码 -- [Blog.cs](https://github.com/luoyunchong/dotnetcore-examples/blob/master/ORM/FreeSql/OvOv.Core/Domain/Blog.cs) -- [Tag.cs](https://github.com/luoyunchong/dotnetcore-examples/blob/master/ORM/FreeSql/OvOv.Core/Domain/Tag.cs) -- [TransBlogService.cs](https://github.com/luoyunchong/dotnetcore-examples/blob/master/ORM/FreeSql/OvOv.FreeSql.AutoFac.DynamicProxy/Services/TransBlogService.cs) - -以上使用的是泛型仓储,那我们如果是重写一个仓储 如何保持和``UnitOfWorkManager``同一个事务呢。 -继承现有的``DefaultRepository<,>``仓储,实现自定义的仓储``BlogRepository.cs``, -```csharp - public class BlogRepository : DefaultRepository, IBlogRepository - { - public BlogRepository(UnitOfWorkManager uowm) : base(uowm?.Orm, uowm) - { - } - - public List GetBlogs() - { - return Select.Page(1, 10).ToList(); - } - } -``` -其中接口。``IBlogRepository.cs`` -```csharp - public interface IBlogRepository : IBaseRepository - { - List GetBlogs(); - } -``` - -在 startup.cs注入此服务 -```csharp - services.AddScoped(); -``` diff --git a/Unit-of-Work-Manager.md b/Unit-of-Work-Manager.md deleted file mode 100644 index 520ef10..0000000 --- a/Unit-of-Work-Manager.md +++ /dev/null @@ -1,156 +0,0 @@ -[中文](DI-UnitOfWorkManager%E4%BA%8B%E5%8A%A1) | **English** - -## Use FreeSql's repository transaction in ASP.NET Core - -#### Step 1: Configure Startup.cs - -Install NuGet packages: - -```bash -dotnet add package FreeSql -dotnet add package FreeSql.DbContext -dotnet add package FreeSql.Provider.MySqlConnector -``` - -Configure `Startup.cs`: - -```csharp -public void ConfigureServices(IServiceCollection services) -{ - IConfigurationSection Mysql = Configuration.GetSection("Mysql"); - Fsql = new FreeSqlBuilder() - .UseConnectionString(DataType.MySql, Mysql.Value) - .UseAutoSyncStructure(true) - .UseNameConvert(NameConvertType.PascalCaseToUnderscoreWithLower) - .UseMonitorCommand(cmd => Trace.WriteLine(cmd.CommandText)) - .Build(); - services.AddSingleton(fsql); - services.AddScoped(); - services.AddFreeRepository(null, typeof(Startup).Assembly); - //Add your own service, here is only an implementation - services.AddScoped(); -} -``` - -Update your `appsettings.json`: - -```json -{ - "Mysql": "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=ovov_freesql_repository;Charset=utf8;SslMode=none;Max pool size=10", -} -``` - -| UnitOfWorkManager Members | Description | -| -- | -- | -| IUnitOfWork Current | Return the current unit of work | -| void Binding(repository) | Hand over repository transaction to ir for management | -| IUnitOfWork Begin(propagation, isolationLevel) | Create unit of work | - - -- TransBlogService.cs -```csharp -private readonly IBaseRepository _blogRepository; -private readonly IBaseRepository _tagRepository; -private readonly UnitOfWorkManager _unitOfWorkManager; - -public TransBlogService(IBaseRepository blogRepository, IBaseRepository tagRepository,UnitOfWorkManager unitOfWorkManager) -{ - _blogRepository = blogRepository ; - _tagRepository = tagRepository ; - _unitOfWorkManager = unitOfWorkManager; -} - -public async Task CreateBlogUnitOfWorkAsync(Blog blog,ListtagList) -{ - using (IUnitOfWork unitOfWork = _unitOfWorkManager.Begin()) - { - try - { - await _blogRepository.InsertAsync(blog); - tagList.ForEach(r => - { - r.PostId = blog.Id; - }); - await _tagRepository.InsertAsync(tagList); - unitOfWork.Commit(); - } - catch (Exception e) - { - //Actually, Rollback may not be used. - //Because the internal Dispose of IUnitOfWork will roll back the transaction without Commit. - //But here can be Rollback in advance. - - unitOfWork.Rollback(); - //Log, - //or use throw to continue throwing exceptions upwards - } - } -} - -public async Task UpdateBlogAsync(int id) -{ - using (IUnitOfWork unitOfWork = _unitOfWorkManager.Begin()) - { - try - { - Blog blog = _blogRepository.Select.Where(r => r.Id == id).First(); - blog.IsDeleted = true; - await _blogRepository.UpdateAsync(blog); - unitOfWork.Commit(); - } - catch (Exception e) - { - //Log, - //or use throw to continue throwing exceptions upwards - unitOfWork.Rollback(); - } - } -} -``` - - -| IUnitOfWork Members | Description | -| -- | -- | -| IFreeSql Orm | The object Select/Delete/Insert/Update/InsertOrUpdate is consistent with the unit of work transaction and can be omitted to pass WithTransaction | -| DbTransaction GetOrBeginTransaction() | Open the transaction, or return to the opened transaction | -| void Commit() | Commit transaction | -| void Rollback()| Rollback transaction | -| DbContext.EntityChangeReport EntityChangeReport |Entity change tracking within the unit of work | - -#### Complete code -- [Blog.cs](https://github.com/luoyunchong/dotnetcore-examples/blob/master/ORM/FreeSql/OvOv.Core/Domain/Blog.cs) -- [Tag.cs](https://github.com/luoyunchong/dotnetcore-examples/blob/master/ORM/FreeSql/OvOv.Core/Domain/Tag.cs) -- [TransBlogService.cs](https://github.com/luoyunchong/dotnetcore-examples/blob/master/ORM/FreeSql/OvOv.FreeSql.AutoFac.DynamicProxy/Services/TransBlogService.cs) - -The above uses generic repository. - -If you want to rewrite a repository, how do you keep the same transaction as `UnitOfWorkManager`? You can inherit the existing `DefaultRepository<,>` and implement a custom repository `BlogRepository.cs`: - -```csharp - public class BlogRepository : DefaultRepository, IBlogRepository - { - public BlogRepository(UnitOfWorkManager uowm) : base(uowm?.Orm, uowm) - { - } - - public List GetBlogs() - { - return Select.Page(1, 10).ToList(); - } - } -``` - -The interface is `IBlogRepository.cs`: - -```csharp - public interface IBlogRepository : IBaseRepository - { - List GetBlogs(); - } -``` - -Inject this service in `startup.cs` - -```csharp - services.AddScoped(); -``` diff --git a/Unit-of-Work.md b/Unit-of-Work.md index d0b4e61..3de37a8 100644 --- a/Unit-of-Work.md +++ b/Unit-of-Work.md @@ -33,7 +33,7 @@ using (var uow = fsql.CreateUnitOfWork()) } ``` -Reference: [Use TransactionalAttribute + UnitOfWorkManager in ASP.NET Core to achieve multiple transaction propagation](https://github.com/dotnetcore/FreeSql/issues/289) +Reference: [Use TransactionalAttribute + UnitOfWorkManager in ASP.NET Core to achieve multiple transaction propagation](DI-UnitOfWorkManager) ## Interface Definition diff --git a/_Sidebar.md b/_Sidebar.md index 27145bf..260ff20 100644 --- a/_Sidebar.md +++ b/_Sidebar.md @@ -26,7 +26,7 @@ * [工作单元](%e5%b7%a5%e4%bd%9c%e5%8d%95%e5%85%83) [Unit of Work](Unit-of-Work) * [联级保存](%e8%81%94%e7%ba%a7%e4%bf%9d%e5%ad%98) [Cascade Saving](Cascade-Saving) * [联级删除](%E8%81%94%E7%BA%A7%E5%88%A0%E9%99%A4) [Cascade Deletion](Cascade-Deletion) - * [工作单元管理器](DI-UnitOfWorkManager事务) [UoW Manager](Unit-of-Work-Manager) + * [工作单元管理器](DI-UnitOfWorkManager) * [DbContext](DbContext) * [CodeFirst](CodeFirst) * [实体特性✨](%e5%ae%9e%e4%bd%93%e7%89%b9%e6%80%a7) diff --git a/事务.md b/事务.md index b18e890..d07c500 100644 --- a/事务.md +++ b/事务.md @@ -1,6 +1,6 @@ 本文所有内容基于单机数据库事务,分布式数据库 TCC/SAGA 方案请移步:https://github.com/2881099/FreeSql.Cloud -## 0、[ASP.NET Core配置DI使用UnitOfWorkManager,此方法更简单](DI-UnitOfWorkManager事务) +## 0、[ASP.NET Core配置DI使用UnitOfWorkManager,此方法更简单](DI-UnitOfWorkManager) ## 1、UnitOfWork 事务 @@ -24,8 +24,6 @@ using (var uow = fsql.CreateUnitOfWork()) } ``` -参考:[在 asp.net core 中使用 TransactionalAttribute + UnitOfWorkManager 实现多种事务传播](https://github.com/dotnetcore/FreeSql/issues/289) - ## 2、DbContext 事务 ```csharp diff --git a/常见问题.md b/常见问题.md index 41f2353..fcc3f37 100644 --- a/常见问题.md +++ b/常见问题.md @@ -36,13 +36,7 @@ fsql.Aop.ConfigEntityProperty += (s, e) => { --- -### 4、TransactionalAttribute + UnitOfWorkManager 事务传播 - -[https://github.com/dotnetcore/FreeSql/issues/289](https://github.com/dotnetcore/FreeSql/issues/289) - ---- - -### 5、怎么执行 SQL 返回实体列表? +### 4、怎么执行 SQL 返回实体列表? ```csharp //直接查询 @@ -57,25 +51,25 @@ fsql.Select().WithMemory(list).ToList(); --- -### 6、错误:【主库】状态不可用,等待后台检查程序恢复方可使用。xxx +### 5、错误:【主库】状态不可用,等待后台检查程序恢复方可使用。xxx [https://github.com/dotnetcore/FreeSql/discussions/1080](https://github.com/dotnetcore/FreeSql/discussions/1080) --- -### 7、错误:【主库】对象池已释放,无法访问。 +### 6、错误:【主库】对象池已释放,无法访问。 [https://github.com/dotnetcore/FreeSql/discussions/1079](https://github.com/dotnetcore/FreeSql/discussions/1079) --- -### 8、错误:ObjectPool.Get 获取超时(10秒)。 +### 7、错误:ObjectPool.Get 获取超时(10秒)。 [https://github.com/dotnetcore/FreeSql/discussions/1081](https://github.com/dotnetcore/FreeSql/discussions/1081) --- -### 9、多平台代码参考,使用自定义SqliteProvider,例如Sqlite用Microsoft.Data.Sqlite或者反射Mono.Data.Sqlite. +### 8、多平台代码参考,使用自定义SqliteProvider,例如Sqlite用Microsoft.Data.Sqlite或者反射Mono.Data.Sqlite. [arm/树莓派](https://github.com/densen2014/FreeSqlDemos/tree/master/ARM_ConsoleApp) @@ -131,7 +125,7 @@ if (Device.RuntimePlatform == Device.iOS || Device.RuntimePlatform == Device.And --- -### 10、 2.6.100升级到3.0.100 后无法连接 sqlserver 提示证书无效, 提示证书链是由不受信任的颁发机构颁发的. +### 9、 2.6.100升级到3.0.100 后无法连接 sqlserver 提示证书无效, 提示证书链是由不受信任的颁发机构颁发的. 请尝试: