FreeSql接入CAP的实践
CAP
CAP 是一个基于 .NET Standard 的 C# 库,它是一种处理分布式事务的解决方案,同样具有 EventBus 的功能,它具有轻量级、易使用、高性能等特点。
ADO.NET事务
1.DotNetCore.CAP.MySql中引用 了如下类库.在Commit事务时,会调用 Flush方法推送消息
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.7" />
<PackageReference Include="MySqlConnector" Version="1.0.1" />
    public class MySqlCapTransaction : CapTransactionBase
    {
        public MySqlCapTransaction(
            IDispatcher dispatcher) : base(dispatcher)
        {
        }
        public override void Commit()
        {
            Debug.Assert(DbTransaction != null);
            switch (DbTransaction)
            {
                case IDbTransaction dbTransaction:
                    dbTransaction.Commit();
                    break;
                case IDbContextTransaction dbContextTransaction:
                    dbContextTransaction.Commit();
                    break;
            }
            Flush();
        }
    }
其中我们能看到,事务的提交,会调用父类CapTransactionBase中的方法Flush。他是protected类型的,并未开放出此接口。
       protected virtual void Flush()
        {
            while (!_bufferList.IsEmpty)
            {
                _bufferList.TryDequeue(out var message);
                _dispatcher.EnqueueToPublish(message);
            }
        }
我们来看一下集成 的demo调用
    [Route("~/adonet/transaction")]
    public IActionResult AdonetWithTransaction()
    {
        using (var connection = new MySqlConnection(AppDbContext.ConnectionString))
        {
            using (var transaction = connection.BeginTransaction(_capBus, true))
            {
                //your business code
                connection.Execute("insert into test(name) values('test')", transaction: (IDbTransaction)transaction.DbTransaction);
                //for (int i = 0; i < 5; i++)
                //{
                _capBus.Publish("sample.rabbitmq.mysql", DateTime.Now);
                //}
            }
        }
        return Ok();
    }
代码中通过扩展IDbConnection类,增加BeginTransaction方法,传递了注入的_capBus类,传了autoCommit
private readonly ICapPublisher _capBus;
public PublishController(ICapPublisher capPublisher)
{
    _capBus = capPublisher;
}
/// <summary>
/// Start the CAP transaction
/// </summary>
/// <param name="dbConnection">The <see cref="IDbConnection" />.</param>
/// <param name="publisher">The <see cref="ICapPublisher" />.</param>
/// <param name="autoCommit">Whether the transaction is automatically committed when the message is published</param>
/// <returns>The <see cref="ICapTransaction" /> object.</returns>
public static ICapTransaction BeginTransaction(this IDbConnection dbConnection,
    ICapPublisher publisher, bool autoCommit = false)
{
    if (dbConnection.State == ConnectionState.Closed)
    {
        dbConnection.Open();
    }
    var dbTransaction = dbConnection.BeginTransaction();
    publisher.Transaction.Value = publisher.ServiceProvider.GetService<ICapTransaction>();
    return publisher.Transaction.Value.Begin(dbTransaction, autoCommit);
}
autoCommit:false,(此属性会自动提交事务,集成其他ORM,不建议开启)因为,我们只要调用 了Publish,他会调用MySqlCapTransaction中的Commit(),并执行Flush,即消息 会发出去。
IDbContextTransaction
这段代码是非常 重要的。
    publisher.Transaction.Value = publisher.ServiceProvider.GetService<ICapTransaction>();
从CapPublisher中可以看出,事务是通过AsyncLocal实现状态共享的。
internal class CapPublisher : ICapPublisher
{
     public AsyncLocal<ICapTransaction> Transaction { get; }
}
publisher.Transaction.Value的类型实现上才是ICapTransaction ,
CapTransactionExtensions.cs还有一个扩展方法,调用Begin,相当于给当前控制器上注入的ICapPublisher设置了new MySqlConnection(AppDbContext.ConnectionString).BeginTransaction()的值。
      public static ICapTransaction Begin(this ICapTransaction transaction,
            IDbTransaction dbTransaction, bool autoCommit = false)
        {
            transaction.DbTransaction = dbTransaction;
            transaction.AutoCommit = autoCommit;
            return transaction;
        }
对于ADO.NET,我们只要传递此transaction,就能保证发送消息和操作DB是一个事务了。。
EF Core事务
同样,我们看扩展方法和使用方式
    public static IDbContextTransaction BeginTransaction(this DatabaseFacade database,
        ICapPublisher publisher, bool autoCommit = false)
    {
        var trans = database.BeginTransaction();
        publisher.Transaction.Value = publisher.ServiceProvider.GetService<ICapTransaction>();
        var capTrans = publisher.Transaction.Value.Begin(trans, autoCommit);
        return new CapEFDbTransaction(capTrans);
    }
dbContext.Database就是DatabaseFacade类型。直接能BeginTransaction事务。
[Route("~/ef/transaction")]
public IActionResult EntityFrameworkWithTransaction([FromServices]AppDbContext dbContext)
{
    using (var trans = dbContext.Database.BeginTransaction(_capBus, autoCommit: false))
    {
        dbContext.Persons.Add(new Person() { Name = "ef.transaction" });
        for (int i = 0; i < 1; i++)
        {
            _capBus.Publish("sample.rabbitmq.mysql", DateTime.Now);
        }
        dbContext.SaveChanges();
        trans.Commit();
    }
    return Ok();
}
同样,还有一个Begin扩展方法,仅仅是给ICapTransaction赋下值。
public static ICapTransaction Begin(this ICapTransaction transaction,
    IDbContextTransaction dbTransaction, bool autoCommit = false)
{
    transaction.DbTransaction = dbTransaction;
    transaction.AutoCommit = autoCommit;
    return transaction;
}
在这个demo,上,,autoCommit是false,因为dbContext有自己的SaveChanges(),如果发送不太合适。SaveChanges()要做好些操作,具体不太情况是什么,但要在Commit事务前的吧。。具体不详细研究。
但我们可以看下CapTransactionBase源码,DbTransaction是Object类型。
EF Core中的事务类型是IDbContextTransaction
ADO.NET实际是IDbTransaction类型。
 public object DbTransaction { get; set; }
所以在最开始的那段代码,判断DbTransaction,是哪种类型,然后调用自身内部使用的事务进行Commit()。如果要集成其他ORM,但又想去掉EFCore的依赖,然后增加其他ORM,如下类似的处理,就是关键,比如CommitAsync,Commit,Roolback()
    public override void Commit()
    {
        Debug.Assert(DbTransaction != null);
        switch (DbTransaction)
        {
            case IDbTransaction dbTransaction:
                dbTransaction.Commit();
                break;
            case IDbContextTransaction dbContextTransaction:
                dbContextTransaction.Commit();
                break;
        }
        Flush();
    }
还有MySqlDataStorage.cs
判断dbTransaction的类型,然后获取当前事务,引用其他ORM,记得修改此处。
    var dbTrans = dbTransaction as IDbTransaction;
    if (dbTrans == null && dbTransaction is IDbContextTransaction dbContextTrans)
    {
        dbTrans = dbContextTrans.GetDbTransaction();
    }
参考项目(不打算维护)
- 维护就要保证与上层Dotnetcore/cap项目保持同步,这是一件困难的事。 
- 还有一个重要的原因是:我们有更简单的方式。 
FreeSql接入CAP(最简单的方式)
关于此问题的想法
我们还是引用各自官方的库
Install-Package DotNetCore.CAP.Dashboard
Install-Package DotNetCore.CAP.MySql
Install-Package DotNetCore.CAP.RabbitMQ
Install-Package FreeSql
Install-Package FreeSql.DbContext
Install-Package FreeSql.Provider.MySqlConnector
关于CAP集成的方式,配置项,这里不做详情,官方地址有中文: http://cap.dotnetcore.xyz/
- 代码参考。因为只有一个类,我们自行复制项目即可。
- https://github.com/luoyunchong/lin-cms-dotnetcore/blob/master/src/LinCms.Application/CapUnitOfWorkExtensions.cs
重写扩展方法,BeginTransaction。是基于IUnitOfWork的扩展。
提交事务调用Commit(IUnitOfWork)时,内部再通过反射调用 ICapTransaction中protected类型的方法Flush。
  public static class CapUnitOfWorkExtensions
    {
        public static void Flush(this ICapTransaction capTransaction)
        {
            capTransaction?.GetType().GetMethod("Flush", BindingFlags.Instance | BindingFlags.NonPublic)?.Invoke(capTransaction, null);
        }
        public static ICapTransaction BeginTransaction(this IUnitOfWork unitOfWork, ICapPublisher publisher, bool autoCommit = false)
        {
            publisher.Transaction.Value = (ICapTransaction)publisher.ServiceProvider.GetService(typeof(ICapTransaction));
            return publisher.Transaction.Value.Begin(unitOfWork.GetOrBeginTransaction(), autoCommit);
        }
        public static void Commit(this ICapTransaction capTransaction, IUnitOfWork unitOfWork)
        {
            unitOfWork.Commit();
            capTransaction.Flush();
        }
    }
注入我们的FreeSql
public void ConfigureServices(IServiceCollection services)
 {
    IConfigurationSection configurationSection = Configuration.GetSection($"ConnectionStrings:MySql");
    IFreeSql fsql = new FreeSqlBuilder()
           .UseConnectionString(DataType.MySql, configurationSection.Value);
           .UseNameConvert(NameConvertType.PascalCaseToUnderscoreWithLower)
           .UseAutoSyncStructure(true)
           .UseNoneCommandParameter(true)
           .UseMonitorCommand(cmd =>
           {
               Trace.WriteLine(cmd.CommandText + ";");
           }
           )
           .Build();
    services.AddSingleton(fsql);
    services.AddFreeRepository();
    services.AddScoped<UnitOfWorkManager>();
}
示例
    [HttpGet("~/freesql/unitofwork/{id}")]
    public DateTime UnitOfWorkManagerTransaction(int id, [FromServices] IBaseRepository<Book> repo)
    {
        DateTime now = DateTime.Now;
        using (IUnitOfWork uow = _unitOfWorkManager.Begin())
        {
            ICapTransaction trans = _unitOfWorkManager.Current.BeginTransaction(_capBus, false);
            repo.Insert(new Book()
            {
                Author = "luoyunchong",
                Summary = "2",
                Title = "122"
            });
            _capBus.Publish("freesql.time", now);
            trans.Commit(uow);
        }
        return now;
    }
    [NonAction]
    [CapSubscribe("freesql.time")]
    public void GetTime(DateTime time)
    {
        Console.WriteLine($"time:{time}");
    }
注意trans不需要using,freesql内部会释放资源。,也可using,但请更新到最新的freesql版本。
ICapTransaction trans = _unitOfWorkManager.Current.BeginTransaction(_capBus, false);
提交事务,也请调用扩展方法,否则事务无法正常。
trans.Commit(uow);
源码位置
FreeSql接入CAP的实践的更多相关文章
- 分布式事务最终一致性-CAP框架轻松搞定
		前言 对于分布式事务,常用的解决方案根据一致性的程度可以进行如下划分: 强一致性(2PC.3PC):数据库层面的实现,通过锁定资源,牺牲可用性,保证数据的强一致性,效率相对比较低. 弱一致性(TCC) ... 
- 智能家居实践(番外篇)—— 接入 HomeKit 实现用 Siri 控制家电
		转载:智能家居实践(番外篇)—— 接入 HomeKit 实现用 Siri 控制家电 前面我写了一个系列共三篇的智能家居实践,用的是 Amazon Echo 实现语音控制,但是 Amazon Echo ... 
- 【转】.NET(C#):浅谈程序集清单资源和RESX资源  关于单元测试的思考--Asp.Net Core单元测试最佳实践  封装自己的dapper lambda扩展-设计篇  编写自己的dapper lambda扩展-使用篇  正确理解CAP定理  Quartz.NET的使用(附源码)  整理自己的.net工具库  GC的前世与今生  Visual Studio Package 插件开发之自动生
		[转].NET(C#):浅谈程序集清单资源和RESX资源 目录 程序集清单资源 RESX资源文件 使用ResourceReader和ResourceSet解析二进制资源文件 使用ResourceM ... 
- Android开发和測试实践 - 接入友盟统计
		这两年一直在做无线的測试,兴许还会继续去做无线的測试,可是之前由于时间的原因一直都没有非常细致的了解到代码层面. 最近抽出时间自己做了些app的开发,决定假设想把移动的測试做好做深入.有一定的app开 ... 
- 分布式应用监控: SkyWalking 快速接入实践
		分布式应用,会存在各种问题.而要解决这些难题,除了要应用自己做一些监控埋点外,还应该有一些外围的系统进行主动探测,主动发现. APM工具就是干这活的,SkyWalking 是国人开源的一款优秀的APM ... 
- 分布式应用监控:SkyWalking 快速接入实践
		分布式应用,会存在各种问题.而要解决这些难题,除了要应用自己做一些监控埋点外,还应该有一些外围的系统进行主动探测,主动发现. APM工具就是干这活的,SkyWalking 是国人开源的一款优秀的APM ... 
- 超详细,自动化测试接入Jenkins+Sonar质量门禁实践
		大家好,我叫董鑫,一名在测试开发道路上的新手.第一阶段的学习已然结束,收获颇多,了解了很多在自己平时测试工作无法接触到的新知识,比如这次在这里分享的Sonarqube进行静态代码扫描并集成Jenkin ... 
- 【从小白到专家】Istio技术实践专题(四):应用接入Istio的正确姿势
		上一篇文章中,我们介绍了Istio针对单集群的三种主流部署安装方式:使用Istioctl安装.使用Helm自定义安装.独立Operator安装.本文将向大家介绍kubernetes中的应用接入Isti ... 
- 《大型网站系统与Java中间件实践》读书笔记——CAP理论
		分布式事务希望在多机环境下可以像单机系统那样做到强一致,这需要付出比较大的代价.而在有些场景下,接收状态并不用时刻保持一致,只要最终一致就行. CAP理论是Eric Brewer在2000年7月份的P ... 
随机推荐
- 【译】自动发现 .NET 5 中代码的潜在错误
			写代码是一件令人兴奋的事情,特别是对于 .NET 开发人员来说,平台越来越智能化了.我们现在默认在 .NET SDK 中包含丰富的诊断和代码建议.在您需要安装 NuGet 包或其他独立工具来进行更多 ... 
- MeteoInfoLab脚本示例:AIRS Swath HDF数据
			例子中的AIRS Swath HDF数据在Polar Stereographic(南极)投影中接近矩形,需要先从数据中读出经纬度及相关数据数组,利用surfacem函数绘制Swath数据(散点),在s ... 
- day61 Pyhton 框架Django 04
			内容回顾 1.django处理请求的流程: 1. 在浏览器的地址栏输入地址,回车发get请求: 2. wsgi模块接收请求: 3. 在urls.py文件中匹配地址,找到对应的函数: 4. 执行函数,返 ... 
- 用CentOS 7自制Vagrant Box文件
			写在前面 利用vagrant保持开发生产环境一致是一个很好的方法,不过vagrant官网上的box文件下载是真的很慢,因此,这里教大家如何自制box文件. 这篇文章你会接触到: vagrant使用 ... 
- selenium 浏览器最大化
			from time import sleep from selenium import webdriver from selenium.webdriver.chrome.options import ... 
- [转]CSS学习笔记
			原文:http://www.fx114.net/qa-266-93710.aspx 01.什么是CSS. CSS指层叠样式表(Cascading Style Sheets). ·样式定义如 ... 
- .NET Core开源任务调度平台ScheduleMaster上新了
			ScheduleMaster上一次比较大的更新还是在6月份,转眼已经快过去4个月了,这段时间比较忙,中间只更新过一次修复了几个小bug.要总结这次更新的话,必须要用"千呼万唤始出来" ... 
- ABAP分享十: 文件的上传 方法一
			前提条件:PARAMETERS P_files TYPE RLGRAP-FILENAME. AT SELECTION-SCREEN ON VALUE-REQUEST FOR P_files.一.文件的 ... 
- IDEA提示类注释的wrong tag警告的解决办法
			当创建一个类的时候,给类写上注释时,出现类似于"Wrong tag 'date' less... (Ctrl+F1)"这种警告,解决方式是: alt + enter -> a ... 
- 分布式文件存储数据库 MongoDB
			MongoDB 简介 Mongo 并非芒果(Mango)的意思,而是源于 Humongous(巨大的:庞大的)一词. MongoDB 是一个基于分布式文件存储的 NoSQL 数据库.由 C++ 语言编 ... 
