基于EF4.1的异构数据库访问组件(一)

开篇

     经过3年的努力与付出,终于实现了我的职业规划——系统架构师。先就职于某女性内衣电商ERP团队,由于一些原因,转至某捕鱼游戏公司,找份想要的工作真的不容易。呵呵,感慨一下。。

      特此记录一下作为架构师的第一个作品,与大家分享交流。。

      感谢Google,感谢Codeplex,感谢广州知微团队中曾经帮助我的同事们

需求背景

   在现有很多系统中,常常有多个数据库,多种数据库的使用。因此在程序中,我们需要一个这样的数据访问组件:

    1. 支持多个数据库;
    2. 支持多种数据库:SQL SERVER / MYSQL / ORACLE;
    3. 提供统一持久接口;
    4. 配置及初始化灵活;
    5. 业务对象与数据库对象之间的映射配置方便;

为啥是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 进行性能调优;

    希望园子里的朋友们能提出宝贵建议,一起提高!

posted @ 2011-10-20 00:09  青砖绿树  阅读(6115)  评论(53编辑  收藏  举报