• 4.4 异构、多数据库的存取组件


    在一个大型系统中,应该允许访问多个数据库,甚至是多个异构的数据库。例如表单模块使用mysql,数据仓库模块使用oracle等等。按照这个目标,数据的配置信息:

     1     "Database": {
     2       "ConnectionStrings": [
     3         {
     4           "Name": "MicroStrutLibrary",
     5           "ConnectionString": "Data Source=XXXX;Initial Catalog=XXXX;User Id=OperUser;Password=OperUser;MultipleActiveResultSets=true;Persist Security Info=true",
     6           "ProviderName": "System.Data.SqlClient"
     7         },
     8         {
     9           "Name": "CMS",
    10           "ConnectionString": "Data Source=XXXX;Initial Catalog=XXXX;User Id=OperUser;Password=OperUser;MultipleActiveResultSets=true;Persist Security Info=true",
    11           "ProviderName": "System.Data.SqlClient"
    12         }
    13       ],
    14       "Providers": [
    15         {
    16           "Name": "System.Data.SqlClient",
    17           "Type": "MicroStrutLibrary.Infrastructure.Core.Data.Entity.SqlServerDbContextOptionsBuilderProvider, MicroStrutLibrary.Infrastructure.Core.Data.Entity"
    18         }
    19       ]
    20     }

    每个数据库都有一个Name(名称,以后都用这个名称访问)、ConnectionString(数据库链接串)、ProviderName(提供程序名)。对于ProviderName(提供程序名)在Providers中描述了对应的实现类描述TypeDescription。从上面我们可以看出,我们可以设置多个数据库,不同的数据库可以设置不同的Provider,也就是不同的数据库类型,从而实现了多个异构数据库的存取操作。

    对于数据库配置信息类DataConfigInfo,实现ConfigInfo,应该很简单,就不赘述了,可以参见可换源的配置

    框架中,每个模块(例如公共模块、数据仓库模块、表单模块、CMS模块、授权认证模块等)都对应于一个数据库上下文DbContext。这个数据库上下文指向一个数据库,也就是要对应上数据配置中的Name属性。我们的做法是新建一个DbNameAttribute,放在DbContext上,以确定具体数据库的Name。

     1     /// <summary>
     2     /// 指定数据库名称特性
     3     /// </summary>
     4     [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
     5     public class DbNameAttribute : Attribute
     6     {
     7         /// <summary>
     8         /// 数据库名称
     9         /// </summary>
    10         public string Name { get; set; }
    11 
    12         /// <summary>
    13         /// 构造函数
    14         /// </summary>
    15         /// <param name="name"></param>
    16         public DbNameAttribute(string name)
    17         {
    18             this.Name = name;
    19         }
    20 }

    DBNameAttribute中的Name应该设置的就是配置中的Name,从而确保DbContext对应的是哪个数据库。

    例如公共模块部分的DbContext写法如下:

     1     [DbName("MicroStrutLibrary")]
     2     public class CommonDbContext : EntityDbContext
     3     {
     4         public CommonDbContext(DbContextOptions<CommonDbContext> options) : base(options)
     5         {
     6         }
     7 
     8         public DbSet<AccessoryInfo> Accessories { get; set; }
     9 
    10         public DbSet<SystemParameterInfo> SystemParameters { get; set; }
    11         public DbSet<SystemParameterDetailInfo> SystemParameterDetails { get; set; }
    12     ……
    13     }

    大家会注意到CommonDbContext继承EntityDbContext,他是框架的数据库上下文抽象基类,继承DbContext。这个抽象类的重写OnModelCreating方法,找出当前具体实现EntityDbContext类(例如CommonDbContext)的所有DbSet属性,形成该EntityDbContext用到的所有DbSet泛型的参数类,就是AccessoryInfo、SystemParameterInfo等类。然后找出所有继承ORMapping关系映射基类EntityTypeConfiguration的子类,创建映射绑定关系。

    抽象的关系映射基类EntityTypeConfiguration代码如下:

    1     public abstract class EntityTypeConfiguration<T> where T: class
    2     {
    3         public void Bind(ModelBuilder modelBuilder)
    4         {
    5             InnerBind(modelBuilder.Entity<T>());
    6         }
    7 
    8         protected abstract void InnerBind(EntityTypeBuilder<T> builder);
    9     }

    例如系统参数的ORMapping类就可以写成:

     1     /// <summary>
     2     /// 系统参数 映射信息
     3     /// </summary>
     4     public class SystemParameterMapper : EntityTypeConfiguration<SystemParameterInfo>
     5     {
     6         protected override void InnerBind(EntityTypeBuilder<SystemParameterInfo> builder)
     7         {
     8             builder.ToTable("SYSTEM_PARAMETER_INFO");
     9 
    10             builder.Property(p => p.AppCode).HasColumnName("APP_CODE").IsRequired();
    11             builder.Property(p => p.SystemParaCode).HasColumnName("SYSTEM_PARA_CODE").IsRequired();
    12             builder.Property(p => p.SystemParaName).HasColumnName("SYSTEM_PARA_NAME").IsRequired();
    13             builder.Property(p => p.SortOrder).HasColumnName("SORT_ORDER").IsRequired();
    14             builder.Property(p => p.Remark).HasColumnName("REMARK");
    15 
    16             builder.HasKey(p => new { p.AppCode, p.SystemParaCode });
    17 
    18             builder.HasMany(p => p.DetailList).WithOne().HasForeignKey(f => new { f.AppCode, f.SystemParaCode }).OnDelete(DeleteBehavior.Cascade);
    19         }
    20     }

    前面讲了数据库的配置说明,DbContext的具体实现和ORMapping的实现。但是在程序中如何嵌入这些内容,如何将DbContext与配置关联呢?尤其是数据库的Provider是写在配置中的,不能再在Startup中写死services.AddDbContext<BloggingContext>(options => options.UseSqlServer(connection))吧?这种写法,背离了我们使用配置方式的初衷了。

    为了解决这个问题,我们写一个startup的扩展方法,将配置和DbContext等关联起来:

     1         public static DbContextOptionsBuilder UseDb<TContext>(this DbContextOptionsBuilder optionsBuilder, IServiceProvider serviceProvider) where TContext : DbContext
     2         {
     3             DataConfigInfo config = serviceProvider.GetService<IOptions<DataConfigInfo>>().Value;
     4 
     5             DbNameAttribute attribute = typeof(TContext).GetTypeInfo().GetCustomAttribute<DbNameAttribute>(false);
     6 
     7             ConnectionStringSettingInfo connectionStringSetting = config.ConnectionStrings.SingleOrDefault(o => o.Name == attribute.Name);
     8             ProviderSettingInfo providerSetting = config.Providers.SingleOrDefault(o => o.Name == connectionStringSetting.ProviderName);
     9 
    10             Type providerType = Type.GetType(providerSetting.Type);
    11 
    12             DbContextOptionsBuilderProvider providerInstance = Activator.CreateInstance(providerType) as DbContextOptionsBuilderProvider;
    13 
    14             providerInstance.ConnectionStringSetting = connectionStringSetting;
    15             providerInstance.ProviderSetting = providerSetting;
    16 
    17             providerInstance.Build(optionsBuilder);
    18 
    19             EntityDbContext.DbContexts.TryAdd(connectionStringSetting.Name, typeof(TContext));
    20 
    21             return optionsBuilder;
    22         }

    这个方法的主要作用是:TContext泛型参数是EntityDbContext的实现类,例如CommonDbContext。以上面介绍的数据库配置信息和CommonDbContext为例说明:

    1、获取当前CommonDbContext类的DbNameAttribute,也就是MicroStrutLibrary。

    2、然后获取MicroStrutLibrary数据库连接串信息

    "Name": "MicroStrutLibrary",

    "ConnectionString": "Data Source=XXXX;Initial Catalog=XXXX;User Id=OperUser;Password=OperUser;MultipleActiveResultSets=true;Persist Security Info=true",

    "ProviderName": "System.Data.SqlClient"

    3、再找出对应的DbProvider信息

    "Name": "System.Data.SqlClient",

    "Type": "MicroStrutLibrary.Infrastructure.Core.Data.Entity.SqlServerDbContextOptionsBuilderProvider, MicroStrutLibrary.Infrastructure.Core.Data.Entity"

    4、根据DbProvider信息创建DbContextOptionsBuilderProvider类的实例,并设置属性,执行实例的Build方法。具体这个类在下面讲解。

    5、将当前TContext追加到EntityDbContext基类的静态属性DbContexts字典中。这个字典主要是存放数据库的Name和TContext的对应关系。目前暂且用不到(在通用数据查询功能中用的,以后会介绍)。

    重点来了,DbContextOptionsBuilderProvider类就是针对每种类型数据库SQL Server、MySql、Oracle等的提供程序。Build方法的参数是各种数据库类型的OptionsBuilder,这个OptionsBuilder在.net core类库中,只需要在他们的基础上封装一下即可:

     1     public abstract class DbContextOptionsBuilderProvider
     2     {
     3         public ProviderSettingInfo ProviderSetting { get; set; }
     4 
     5         public ConnectionStringSettingInfo ConnectionStringSetting { get; set; }
     6 
     7         public abstract void Build(DbContextOptionsBuilder optionsBuilder);
     8    }
     9 
    10     public class SqlServerDbContextOptionsBuilderProvider : DbContextOptionsBuilderProvider
    11     {
    12         public override void Build(DbContextOptionsBuilder optionsBuilder)
    13         {
    14             //SQLServer 2008 R2以以下版本
    15             optionsBuilder.UseSqlServer(this.ConnectionStringSetting.ConnectionString, ob => ob.UseRowNumberForPaging());
    16             //SQLSever 2012以上版本
    17             //optionsBuilder.UseSqlServer(this.ConnectionStringSetting.ConnectionString);
    18         }
    19    }

    上面就是一个SQLServer的具体实现,不过有个说明的是SQLServer 2008R2及以下版本没有offset fetch next语句,只能使用row_number() over方式,因此当数据库是SQLServer 2008R2及以下版本使用optionsBuilder.UseSqlServer(this.ConnectionStringSetting.ConnectionString, ob => ob.UseRowNumberForPaging()),而SQLServer 2012以上版本使用optionsBuilder.UseSqlServer(this.ConnectionStringSetting.ConnectionString)。

    还有一点说明的是SQLServer数据库使用datetime2,而不是datetime,否则转换会报错。

    面向云的.net core开发框架目录

  • 相关阅读:
    Qt error LNK2005: “找到一个或多个多重定义的符号” 已经在 main.obj 中定义 的解决方法
    Qt 重载QGraphicsItem的type()函数
    C++ std::vector使用简介
    Qt char*,wchar_t*与QString之间的转换(利用reinterpret_cast和_stprintf函数,fromWCharArray从字符数组里读取数据)
    Qt 解决报错 This application failed to start because it could not find or load the Qt platform plugin
    Qt 无法解析的外部符号“public: virtual struct QMetaObject const ...“
    Qt item setZValue() 函数
    C++ 再谈谈注册(本质是建立映射)与回调
    C++ std::vector
    C++ 写SDK算法心得体会
  • 原文地址:https://www.cnblogs.com/BenDan2002/p/6000248.html
Copyright © 2020-2023  润新知