博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
4.4 异构、多数据库的存取组件
阅读量:5344 次
发布时间:2019-06-15

本文共 7842 字,大约阅读时间需要 26 分钟。

在一个大型系统中,应该允许访问多个数据库,甚至是多个异构的数据库。例如表单模块使用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     ///  2     /// 指定数据库名称特性 3     ///  4     [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] 5     public class DbNameAttribute : Attribute 6     { 7         ///  8         /// 数据库名称 9         /// 10         public string Name { get; set; }11 12         /// 13         /// 构造函数14         /// 15         /// 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
options) : base(options) 5 { 6 } 7 8 public DbSet
Accessories { get; set; } 9 10 public DbSet
SystemParameters { get; set; }11 public DbSet
SystemParameterDetails { get; set; }12 ……13 }

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

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

 

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

 

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

 

1     ///  2     /// 系统参数 映射信息 3     ///  4     public class SystemParameterMapper : EntityTypeConfiguration
5 { 6 protected override void InnerBind(EntityTypeBuilder
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
(this DbContextOptionsBuilder optionsBuilder, IServiceProvider serviceProvider) where TContext : DbContext 2 { 3 DataConfigInfo config = serviceProvider.GetService
>().Value; 4 5 DbNameAttribute attribute = typeof(TContext).GetTypeInfo().GetCustomAttribute
(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 : DbContextOptionsBuilderProvider11     {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,否则转换会报错。

 

转载于:https://www.cnblogs.com/BenDan2002/p/6000248.html

你可能感兴趣的文章
Mybatis输入类型和结果类型
查看>>
移动端px转rem的两种方法
查看>>
excel中连接字符函数
查看>>
MongoDB 之 手把手教你增删改查 MongoDB - 2
查看>>
Collection框架中实现比较要实现什么接口
查看>>
基于数组实现线性表
查看>>
css 内联与块
查看>>
CSS float 理解
查看>>
python字符decode与encode的问题
查看>>
随手写 --- 贪吃蛇
查看>>
oracle_面试题01
查看>>
python selenium 最简单示例
查看>>
UITabBarController动态添加TabBarItem
查看>>
Nginx 切片模块、断点续传
查看>>
MySQL 创建用户并分配用户权限
查看>>
typedef 函数指针
查看>>
UIScrollView的代理(delegate)
查看>>
【原创】.Net WebForm Calendar 日历控件常用方法
查看>>
对javascript中的匿名函数的理解
查看>>
noip模拟赛 道路分组
查看>>