• 在Abp上使用CAP


    Abp 是什么。  大佬们把单体.net程序能涉及到的东西 都涉及到了。 对于单体web开发 一步到位的东西。 当然不能只用不理解, 不然出问题了就懵逼了。 通过看源码还是能学到很多东西

    Abp的git地址:  https://github.com/aspnetboilerplate/aspnetboilerplate

    ABP vNext  据说是全新的.net core思想的版本, 目前还是pre阶段     git地址: https://github.com/abpframework/abp

    CAP 

    CAP是一个解决分布式事务,带有分布式事务总线的一个东西。 作者是 Savorboard 。  git地址:  https://github.com/dotnetcore/CAP

    我理解是CAP可以用在微服务上,在服务之间保证数据一致性;

    目前项目上有多个系统之间的事务。变相的分布式事务问题。   项目是在Abp上写的单体应用。 所以就想在Abp上使用CAP这个东西;

    好了  场景介绍完了。 技术一般  若有错 请指正。 下面记录一下这次遇到的一些问题;

    先看一下Abp的Startup

       // Configure Abp and Dependency Injection
                return services.AddAbp<ms_CAPWebHostModule>(
                    // Configure Log4Net logging
                    options => options.IocManager.IocContainer.AddFacility<LoggingFacility>(
                        f => f.UseAbpLog4Net().WithConfig("log4net.config")
                    )
                );

     在ConfigureServices方法的最后调用AddAbp方法

    看一下AddAbp的源码

     public static IServiceProvider AddAbp<TStartupModule>(this IServiceCollection services, [CanBeNull] Action<AbpBootstrapperOptions> optionsAction = null)
                where TStartupModule : AbpModule
            {
                var abpBootstrapper = AddAbpBootstrapper<TStartupModule>(services, optionsAction);
    
                ConfigureAspNetCore(services, abpBootstrapper.IocManager);
    
                return WindsorRegistrationHelper.CreateServiceProvider(abpBootstrapper.IocManager.IocContainer, services);
            }

    把Abp的所有模块的类注入到IServiceCollection 然后加入到abp的依赖注入容器 Castle里面;  然后返回一个IServiceProvider;

    所以   必须在 AddAbp()方法之前调用CAP的AddCap()方法 

    看一下CAP的源码,EF+ MySql的例子

    services.AddDbContext<AppDbContext>();
    
                services.AddCap(x =>
                {
                    x.UseEntityFramework<AppDbContext>();
                    x.UseRabbitMQ("localhost");
                    x.UseDashboard();
                    x.FailedRetryCount = 5;
                    x.FailedThresholdCallback = (type, name, content) =>
                    {
                        Console.WriteLine($@"A message of type {type} failed after executing {x.FailedRetryCount} several times, requiring manual troubleshooting. Message name: {name}, message body: {content}");
                    };
                });

    我用的是EF + Sql server 数据库 不过相差不大。 只是大佬没写sql server的例子而已;

    看一下Sql server  的UseEntityFramework 这个方法

      public static CapOptions UseEntityFramework<TContext>(this CapOptions options, Action<EFOptions> configure)
                where TContext : DbContext
            {
                if (configure == null)
                {
                    throw new ArgumentNullException(nameof(configure));
                }
    
                options.RegisterExtension(new SqlServerCapOptionsExtension(x =>
                {
                    configure(x);
                    x.Version = options.Version;
                    x.DbContextType = typeof(TContext);
                }));
    
                return options;
            }

    RegisterExtension注册扩展;

    在SqlServerCapOptionsExtension的 AddSqlServerOptions()方法里面 问题来了。

      private void AddSqlServerOptions(IServiceCollection services)
            {
                var sqlServerOptions = new SqlServerOptions();
    
                _configure(sqlServerOptions);
    
                if (sqlServerOptions.DbContextType != null)
                {
                    services.AddSingleton(x =>
                    {
                        using (var scope = x.CreateScope())
                        {
                            var provider = scope.ServiceProvider;
                            var dbContext = (DbContext) provider.GetService(sqlServerOptions.DbContextType);
                            sqlServerOptions.ConnectionString = dbContext.Database.GetDbConnection().ConnectionString;
                            return sqlServerOptions;
                        }
                    });
                }
                else
                {
                    services.AddSingleton(sqlServerOptions);
                }
            }

    会在当前请求里面 去找 DbContext  然后把 DbContext的ConnectionString赋值给CAP的sqlServerOptions;

    但是Abp的DbContext 不是简单的由容器创建的, 在工作单位 里面 是由 ICurrentUnitOfWorkProvider 这个东西来管理的。 所有要拿到当前scope的dbcontext 不能由容器来 ;如下Abp 的 EfCoreUnitOfWork源码:

    public virtual TDbContext GetOrCreateDbContext<TDbContext>(MultiTenancySides? multiTenancySide = null, string name = null)
                where TDbContext : DbContext
            {
                var concreteDbContextType = _dbContextTypeMatcher.GetConcreteType(typeof(TDbContext));
    
                var connectionStringResolveArgs = new ConnectionStringResolveArgs(multiTenancySide);
                connectionStringResolveArgs["DbContextType"] = typeof(TDbContext);
                connectionStringResolveArgs["DbContextConcreteType"] = concreteDbContextType;
                var connectionString = ResolveConnectionString(connectionStringResolveArgs);
    
                var dbContextKey = concreteDbContextType.FullName + "#" + connectionString;
                if (name != null)
                {
                    dbContextKey += "#" + name;
                }
    
                DbContext dbContext;
                if (!ActiveDbContexts.TryGetValue(dbContextKey, out dbContext))
                {
                    if (Options.IsTransactional == true)
                    {
                        dbContext = _transactionStrategy.CreateDbContext<TDbContext>(connectionString, _dbContextResolver);
                    }
                    else
                    {
                        dbContext = _dbContextResolver.Resolve<TDbContext>(connectionString, null);
                    }
    
                    if (Options.Timeout.HasValue &&
                        dbContext.Database.IsRelational() && 
                        !dbContext.Database.GetCommandTimeout().HasValue)
                    {
                        dbContext.Database.SetCommandTimeout(Options.Timeout.Value.TotalSeconds.To<int>());
                    }
    
                    //TODO: Object materialize event
                    //TODO: Apply current filters to this dbcontext
    
                    ActiveDbContexts[dbContextKey] = dbContext;
                }
    
                return (TDbContext)dbContext;
            }

    拿到Abp 一scope的ef 的dbcontext代码如下:

      using (var scope = serviceProvider.CreateScope())
                    {
                        var provider = scope.ServiceProvider;
                        var currentUnitOfWorkProvider = provider.GetService<ICurrentUnitOfWorkProvider>();
                        var unitOfWork = currentUnitOfWorkProvider.Current;
                        var efCoreUnitOfWork = unitOfWork as EfCoreUnitOfWork;
                        foreach (var item in efCoreUnitOfWork.GetAllActiveDbContexts())
                        {
                            if (item.GetType() == sqlServerOptions.DbContextType)
                            {
                                _dbContext = efCoreUnitOfWork.GetAllActiveDbContexts()[0];
                                break;
                            }
                        }

    经过测试 在Abp的Startup 的ConfigureServices时,  efCoreUnitOfWork.GetAllActiveDbContexts() 数量为0。 表示这个时候没有数据库请求。。。

    走到这里走死了。。。。

    回头看了下 AddSqlServerOptions的代码

      private void AddSqlServerOptions(IServiceCollection services)
            {
                var sqlServerOptions = new SqlServerOptions();
    
                _configure(sqlServerOptions);
    
                if (sqlServerOptions.DbContextType != null)
                {
                    services.AddSingleton(x =>
                    {
                        using (var scope = x.CreateScope())
                        {
                            var provider = scope.ServiceProvider;
                            var dbContext = (DbContext) provider.GetService(sqlServerOptions.DbContextType);
                            sqlServerOptions.ConnectionString = dbContext.Database.GetDbConnection().ConnectionString;
                            return sqlServerOptions;
                        }
                    });
                }
                else
                {
                    services.AddSingleton(sqlServerOptions);
                }
            }

    只有去改CAP的 AddSqlServerOptions方法。 它里面的代码是注入一个单例SqlServerOptions  应该是程序第一次跑的时候 去给数据创建CAP的表。  需要指定ConnectionString;

    我在Abp的EF模块里面 找到。

     public override void PreInitialize()
            {
                if (!SkipDbContextRegistration)
                {
                    Configuration.Modules.AbpEfCore().AddDbContext<ms_UserDbContext>(options =>
                    {
                        if (options.ExistingConnection != null)
                        {
                            ms_UserDbContextConfigurer.Configure(options.DbContextOptions, options.ExistingConnection);
                        }
                        else
                        {
                            ms_UserDbContextConfigurer.Configure(options.DbContextOptions, options.ConnectionString, this.IocManager);
                        }
                    });
                }
            }

    因为上面的SqlServerOptions是单例 所以在这边应该是能找到它实例的, 在Configure方法里面给ConnectionString赋值:

       public static void Configure(DbContextOptionsBuilder<ms_UserDbContext> builder, string connectionString, IIocManager iocManager = null)
            {
                builder.UseSqlServer(connectionString);
    
                if (iocManager != null)
                {
                    var sqlServerOptions = iocManager.Resolve<SqlServerOptions>();
                    if (string.IsNullOrWhiteSpace(sqlServerOptions.ConnectionString))
                        sqlServerOptions.ConnectionString = connectionString;
                }
            }

    如此:CAP的初始化搞定了。 下面还有一个问题:

    在 CAP的AddServices里面  注入了事务:

    services.AddTransient<CapTransactionBase, SqlServerCapTransaction>();

    在SqlServerCapTransaction同样要通过容器去拿dbcontext。 这里又懵逼咯。

    abp这边大部分方法都是开启了工作单元 是一个事务。  所以把注入方式改为Scoped

       services.AddScoped<CapTransactionBase, SqlServerCapTransaction>();

    同样  修改获取dbcontext的地方:这里就要引用Abp.EntityFrameworkCore

       public SqlServerCapTransaction(
                IDispatcher dispatcher,
                SqlServerOptions sqlServerOptions,
                IServiceProvider serviceProvider) : base(dispatcher)
            {
                if (sqlServerOptions.DbContextType != null)
                {
                    using (var scope = serviceProvider.CreateScope())
                    {
                        var provider = scope.ServiceProvider;
                        var currentUnitOfWorkProvider = provider.GetService<ICurrentUnitOfWorkProvider>();
                        var unitOfWork = currentUnitOfWorkProvider.Current;
                        var efCoreUnitOfWork = unitOfWork as EfCoreUnitOfWork;
                        foreach (var item in efCoreUnitOfWork.GetAllActiveDbContexts())
                        {
                            if (item.GetType() == sqlServerOptions.DbContextType)
                            {
                                _dbContext = efCoreUnitOfWork.GetAllActiveDbContexts()[0];
                                break;
                            }
                        }
    
                    }
    
                }
    
                _diagnosticProcessor = serviceProvider.GetRequiredService<DiagnosticProcessorObserver>();
            }

     后面有个数据库事务锁级别的设置: 在Module里面

      public override void PreInitialize()
            {
                Configuration.UnitOfWork.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
    
    
            }

    这样 Abp上用CAP 算成功了。  当然 只成功了工作单元模式。  不用工作单元的情况 以后再调整下。

    最后  膜拜大神!

     
  • 相关阅读:
    CCF-CSP认证 C++题解目录
    LeetCode周赛#205
    LeetCode双周赛#34
    Codeforces Round #667 (Div. 3) B、C、D、E 题解
    Codeforces Round #656 (Div. 3) 题解
    牛客小白月赛#26 题解
    LeetCode周赛#204 题解
    LeetCode周赛#203 题解
    牛客小白月赛#27 题解
    N阶上楼梯问题——动态规划(递推求解)
  • 原文地址:https://www.cnblogs.com/luckstar007/p/10949811.html
Copyright © 2020-2023  润新知