基于EF4.1的异构数据库访问组件(一)
开篇
经过3年的努力与付出,终于实现了我的职业规划——系统架构师。先就职于某女性内衣电商ERP团队,由于一些原因,转至某捕鱼游戏公司,找份想要的工作真的不容易。呵呵,感慨一下。。
特此记录一下作为架构师的第一个作品,与大家分享交流。。
感谢Google,感谢Codeplex,感谢广州知微团队中曾经帮助我的同事们
需求背景
在现有很多系统中,常常有多个数据库,多种数据库的使用。因此在程序中,我们需要一个这样的数据访问组件:
- 支持多个数据库;
- 支持多种数据库:SQL SERVER / MYSQL / ORACLE;
- 提供统一持久接口;
- 配置及初始化灵活;
- 业务对象与数据库对象之间的映射配置方便;
为啥是EF4.1
拿到这些需求,我的第一反应是利用现有的ORM:NH或EF进行包装改造,自己造轮子虽然能学到不少东西,但可行性与风险相对较高。
经过一些思考与测试,我选择了EF4.1作为包装改造的目标,有以下几点原因:
- 微软系爱微软 :)
- EF4.1支持POCO,映射配置类,还有超强工具EF Power Tool
- 对比了EF4.1与NH3,两者的功能差不多,基于现有团队的情况,EF4.1相对学习成本较低;
- LINQ TO Entities 相对 LINQ TO NH来说,实现得更加全面;
多个数据库IDbContextStorage
确定使用EF4.1进行包装后,碰到了第一个问题:怎样支持多个数据库呢?
我们使用EF4.1时一般使用以下代码:
public class TestDbContext :DbContext { public DbSet<UserInfo> UserInfoes {get; set;} public DbSet<UserAddress> UserAddresses {get;set;} ... }using(var ctx = new TestDbContext()) { // doSomething }这下我就郁闷了,如果要支持多个数据库,可能是TestDb1Context,TestDb2Context的类写在项目中,这样就有以下几点坏处了:
- 无法提供统一的数据访问接口;
- 有几个数据库就会有几个DbContext,而且直接将DbContext暴露给了上层;
- 管理麻烦;
这时突然想起来在知微时的一个开源项目SharpArch,看了看它里面NH的一些写法,好眼熟呢,这不正是我想要的么。多个数据库的话,我也可以通过一个IDbContextStorage来进行管理。 代码如下:/// <summary> /// DbContext仓库接口/// </summary> public interface IDbContextStorage { /// <summary> /// 根据key获取DbContext /// </summary> /// <param name="key">key</param> /// <returns>DbContext</returns> DbContext GetByKey(string key); /// <summary> /// 根据Key将DbContext写入Storage /// </summary> /// <param name="key">Key</param> /// <param name="dbContext">DbContext</param> void SetByKey(string key, DbContext dbContext); /// <summary> /// 获取所有DDbContext /// </summary> /// <returns>所有DDbContext</returns> IEnumerable<DbContext> GetAllDbContexts(); }
接下来,我们可以实现IDbContextStorage,用于WEB应用程序的WebDbContextStorage:
/// <summary> /// 用于WEB应用程序的DbContext /// </summary> public class WebDbContextStorage :IDbContextStorage { // DbContext容器 private Dictionary<string, DbContext> _storage = new Dictionary<string, DbContext>(); /// <summary> /// 构造函数 /// 用于注册HttpApplication.EndRequest事件,关闭数据库连接 /// </summary> /// <param name="app"></param> public WebDbContextStorage(HttpApplication app) { app.EndRequest += (sender, args) => { DbContextManager.CloseAllDbContexts();}; } #region Implementation of IDbContextStorage /// <summary> /// 根据key获取DbContext /// </summary> /// <param name="key">key</param> /// <returns>DbContext</returns> public DbContext GetByKey(string key) { DbContext context; return !_storage.TryGetValue(key, out context) ? null : context; } /// <summary> /// 根据Key将DbContext写入Storage /// </summary> /// <param name="key">Key</param> /// <param name="dbContext">DbContext</param> public void SetByKey(string key, DbContext dbContext) { _storage.Add(key, dbContext); } /// <summary> /// 获取所有DDbContext /// </summary> /// <returns>所有DDbContext</returns> public IEnumerable<DbContext> GetAllDbContexts() { return _storage.Values; } #endregion }此处存在的疑问点:由于EF自己进行数据库连接的管理,由于HttpApplication.EndRequest事件中关闭数据库连接此种方法效果如何还没有进行过测试,不过不碍事。
动态组建DbContext:IDbContextBuilder
到了这一步,我得怎样去除项目代码中的多个DbContext,即动态地去组建DbContext呢。。
OK,Google,Codeplex,Google code轮番上阵,查呀查呀。目的只有一个,弄清楚DbContext是怎样组建的,终于在某段代码中发现了,原来有DbModelBuilder这个东东,爽呐,动手开搞
其原理是,DbContext分为2个步骤组建:
- 加载映射配置文件
- 根据连接字符串及数据库Provider准备数据库连接
/// <summary> /// DbContext组装接口/// </summary> public interface IDbContextBuilder<T> where T:DbContext { /// <summary> /// 组装DbContext /// </summary> /// <returns>DbContext</returns> T BuildDbContext(); }public class DbContextBuilder<T> : DbModelBuilder, IDbContextBuilder<T> where T : DbContext { // 数据库客户端ProviderFactory private readonly DbProviderFactory _factory; // 连接字符串配置 private readonly ConnectionStringSettings _cnStringSettings; // 如果数据库不存在是否创建 private readonly bool _recreateDatabaseIfExists; // 是否LazyLoad private readonly bool _lazyLoadingEnabled; public DbContextBuilder(string connectionStringName, string mappingAssemblyPath, string mappingNamespace, bool recreateDatabaseIfExists = false, bool lazyLoadingEnabled = true) { this.Conventions.Remove<IncludeMetadataConvention>(); _cnStringSettings = ConfigurationManager.ConnectionStrings[connectionStringName]; _factory = DbProviderFactories.GetFactory(_cnStringSettings.ProviderName); _recreateDatabaseIfExists = recreateDatabaseIfExists; _lazyLoadingEnabled = lazyLoadingEnabled; AddConfigurations(mappingAssemblyPath, mappingNamespace); } private void AddConfigurations(string mappingAssemblyPath, string mappingNamespace) { if (string.IsNullOrEmpty(mappingAssemblyPath)) throw new ArgumentNullException("mappingAssemblyPath"); if (string.IsNullOrEmpty(mappingNamespace)) throw new ArgumentNullException("mappingNamespace"); var hashMapping = false;// 关键代码 var asm = Assembly.LoadFrom(GetAssemblyPath(mappingAssemblyPath)); foreach (var type in asm.GetTypes().Where(c => !c.IsAbstract && c.BaseType != null && c.BaseType.IsGenericType && c.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>)) ) { hashMapping = true; dynamic configurationInstance = Activator.CreateInstance(type); Configurations.Add(configurationInstance); } if(!hashMapping) throw new ApplicationException("DbContext组装时,没有发现映射配置类"); } private static string GetAssemblyPath(string assemblyName) { return (assemblyName.IndexOf(".dll") == -1) ? assemblyName.Trim() + ".dll" : assemblyName.Trim(); } #region Implementation of IDbContextBuilder<T> /// <summary> /// 组é装°DbContext /// </summary> /// <returns>DbContext</returns> public T BuildDbContext() {// 关键代码 var cn = _factory.CreateConnection(); cn.ConnectionString = _cnStringSettings.ConnectionString; var dbModel = this.Build(cn); var ctx = dbModel.Compile().CreateObjectContext<ObjectContext>(cn); ctx.ContextOptions.LazyLoadingEnabled = this._lazyLoadingEnabled; if (!ctx.DatabaseExists()) { ctx.CreateDatabase(); } else if (_recreateDatabaseIfExists) { ctx.DeleteDatabase(); ctx.CreateDatabase(); } return (T)new DbContext(ctx, false); } #endregion }上述代码中,最关键的地方是:
- 将DbContext相关的映射配置类加载到DbModelBuilder的配置列表中,这里我提供给了外部2个参数mappingAssemblyPath-映射配置类DLL的路径,mappingNamespace-该DbContext的映射配置类所在的命名空间
- 准备数据库连接,根据DbProviderFactory,ConnectionString得到数据库类型与数据库连接字符串,另外还有2个参数recreateDatabaseIfExists-数据库不存在时是否自动创建,lazyLoadingEnabled-是否延迟加载。
后续
由于时间的原因,后续的介绍只能放到后续了。。
大致有以下几块:
- DbContexts在应用程序开始时的初始化装配器;
- 统一持久接口IRepository<T>及其实现EFRepository<T>,这个网上有很多例子了;
- UnitOfWork的实现:事件,批量提交,资源回收;
- PagedList<T>的分页机制;
- 多条件动态组合查询与排序;
性能优化
- 代码实战测试及SQL性能跟踪;
- 跨库关联查询,大表关联查询所带来性能问题的解决;
- 利用Sql Server Profiler 进行性能调优;
希望园子里的朋友们能提出宝贵建议,一起提高!