Data系列——线程内IDatabase的传递

    在前面的一篇文章中,提到 IProviderService 接口的时候,我们附加了一个 ProviderContext,该对象中仅包含了一个当前的 IDatabase。因为在使用插件的时候,或多或少会用到 IDatabase 来进行处理。

    但是,这感觉这是个累赘,也不雅观,本篇期望达到的目的是,在定义一个IDatabase的变量域范围内,任何代码都能够通过一个静态方法就能够获取到 IDatabase,而无需将 IDatabase带着满街跑。

    借助TransactionScope的思想,来实现一个 DatabaseScope,目的就是解决 IDatabase 的传递问题。

 

    一、Scope<T> 类

    这个类可以作为其他扩展,它已经解决了最基本的问题。该类主要由静态的Current属性与外界进行联系,同时还是临时数据的存放容器。

 

     ///   <summary>
    
///  抽象类,用于在当前线程内标识一组用户定义的数据,能够确保这些数据在当前线程内唯一。
    
///   </summary>
    
///   <typeparam name="T"></typeparam>
     public  abstract  class Scope<T> : IDisposable  where T : Scope<T>
    {
         private  readonly Dictionary< stringobject> m_data =  new Dictionary< stringobject>();
         private  readonly  bool m_isSingleton;

        [ThreadStatic]
         private  static Stack<T> m_stack =  new Stack<T>();

         ///   <summary>
        
///  获取当前线程范围内的当前  <typeparam name="T"></typeparam>  对象。
        
///   </summary>
         public  static T Current
        {
             get
            {
                 var stack = GetScopeStack();
                 return stack.Count ==  0 ?  null : stack.Peek();
            }
        }

         ///   <summary>
        
///  初始化类的新实例。
        
///   </summary>
        
///   <param name="singleton"> 是否为单例模式。 </param>
         protected Scope( bool singleton =  true)
        {
            m_isSingleton = singleton;
             var stack = GetScopeStack();
             if (singleton)
            {
                 if (stack.Count ==  0)
                {
                    stack.Push((T) this);
                }
            }
             else
            {
                stack.Push((T) this);
            }
        }

         ///   <summary>
        
///  在当前范围内添加一个数据。
        
///   </summary>
        
///   <typeparam name="TV"></typeparam>
        
///   <param name="key"> 键名。 </param>
        
///   <param name="data"> 数据值。 </param>
         public  void SetData<TV>( string key, TV data)
        {
            m_data.AddOrReplace(key, data);
        }

         ///   <summary>
        
///  获取当前范围内指定键名的数据。
        
///   </summary>
        
///   <typeparam name="TV"></typeparam>
        
///   <param name="key"> 键名。 </param>
        
///   <returns></returns>
         public TV GetData<TV>( string key)
        {
             object data;
            m_data.TryGetValue(key,  out data);
             if (data  is TV)
            {
                 return (TV)data;
            }
             return  default(TV);
        }

         ///   <summary>
        
///  清除当前范围内的所有数据。
        
///   </summary>
         public  void ClearData()
        {
            m_data.Clear();
        }

         ///   <summary>
        
///  清除当前范围内指定键名的数据。
        
///   </summary>
        
///   <param name="keys"> 一组表示键名的字符串。 </param>
         public  void RemoveData( params  string[] keys)
        {
             if (keys ==  null)
            {
                 return;
            }
             foreach ( var key  in keys.Where(key => m_data.ContainsKey(key)))
            {
                m_data.Remove(key);
            }
        }

         ///   <summary>
        
///  释放当前范围的数据。
        
///   </summary>
         public  virtual  void Dispose()
        {
             var stack = GetScopeStack();
             if (stack.Count >  0)
            {
                 if (m_isSingleton)
                {
                     // 单例模式下,要判断是否与 current 相等
                     if (stack.Peek().Equals( this))
                    {
                        stack.Pop();
                    }
                }
                 else
                {
                    stack.Pop();
                }
            }
        }

         private  static Stack<T> GetScopeStack()
        {
             // 如果不是 web 程序,则使用 ThreadStatic 标记的静态变量
             if (HttpContext.Current ==  null)
            {
                 return m_stack ?? (m_stack =  new Stack<T>());
            }
             // 如果是 web 程序,则使用 HttpContext.Current.Items
             else
            {
                 var tt =  " __scope: " +  typeof(T).FullName;
                 if (HttpContext.Current.Items[tt] ==  null)
                {
                    HttpContext.Current.Items[tt] =  new Stack<T>();
                }
                 return (Stack<T>)HttpContext.Current.Items[tt];
            }
        }
    }

    在构造 Scope 时,singleton起到一个很好的作用,如果该值为 true,则在同一线程中,多次定义 Scope时只会将第一个 Scope 设置为 Current,TransactionScope 就需要这种模式;如果该值为 false,则使用一个栈来保存 Scope ,这样能够保证在多个 T 变量范围内,能够准确的取到相应的 T。

    在 web 程序中,ThreadStatic 标记的变量无法初始化,因此需要使用HttpContext.Current.Items 集合来放置这个栈。

 

    二、DatabaseScope 类

    继承自 Scope<T> 类,提供一个只读属性 Database。并且构造时传入 singleton 为 false,即不使用单例。

 

     ///   <summary>
    
///  在当前线程范围内检索  <see cref="IDatabase"/>  对象。
    
///   </summary>
     public  sealed  class DatabaseScope : Scope<DatabaseScope>
    {
         ///   <summary>
        
///  初始化  <see cref="DatabaseScope"/>  类的新实例。
        
///   </summary>
        
///   <param name="database"> 当前的  <see cref="IDatabase"/>  对象。 </param>
         internal DatabaseScope(IDatabase database)
            :  base ( false)
        {
            Database = database;
        }

         ///   <summary>
        
///  返回当前线程内的  <see cref="IDatabase"/>  对象。
        
///   </summary>
         public IDatabase Database {  getprivate  set; }
    }

 

     三、Database的改动

     需要在 Database 实例里构造一个 DatabaseScope 对象,并且在 Database 销毁时也将 scope 销毁。

     构造函数的改动:

 

         private DatabaseScope m_scope;

         protected Database()
        {
            m_tranStack =  new TransactionStack();
        }

         ///   <summary>
        
///  初始化  <see cref="Database"/>  类的新实例。
        
///   </summary>
        
///   <param name="connectionString"> 数据库连接字符串。 </param>
        
///   <param name="provider"> 数据库提供者。 </param>
         public Database (ConnectionString connectionString, IProvider provider)
            :  this ()
        {
            Guard.ArgumentNull(provider,  " provider ");
            Provider = provider;
            ConnectionString = connectionString;
            m_scope =  new DatabaseScope( this);
        }

 

    Dispose 函数的改动:

 

         ///   <summary>
        
///  释放所使用的非托管资源。
        
///   </summary>
        
///   <param name="disposing"> 为 true 则释放托管资源和非托管资源;为 false 则仅释放非托管资源。 </param>
         protected  virtual  void Dispose( bool disposing)
        {
             if (m_disposed)
            {
                 return;
            }
             if (disposing)
            {
                 if (Transaction !=  null)
                {
                    Transaction.Dispose();
                    Transaction =  null;
                }
                 if (Connection !=  null)
                {
                    Connection.Dispose();
                    Connection =  null;
                }
            }
            m_scope.Dispose();
            m_disposed =  true;
        }

 

    四、建立测试程序

 

    [TestFixture]
     public  class DatabaseScopeTests
    {
         /*
         IDatabase 必须显示或隐式的Dispose
        
*/
        [Test]
         public  void Test()
        {
             using ( var database = DatabaseFactory.CreateDatabase( " sqlserver "))
            {
                TestNested1();

                 // 这里输出的是sqlserver的连接字符串
                Print( " sqlserver ");
            }

             // DatabaseScope.Current 已经卸载了
            Print( null);
        }

         private  void TestNested1()
        {
             using ( var database = DatabaseFactory.CreateDatabase( " mysql "))
            {
                 // 这里输出的是mysql的连接字符串
                Print( " mysql ");

                TestNested2();
            }
        }

         private  void TestNested2()
        {
             using ( var database = DatabaseFactory.CreateDatabase( " oracle "))
            {
                 // 这里输出的是oracle的连接字符串
                Print( " oracle ");
            }
        }

         private  void Print( string instanceName)
        {
             if (DatabaseScope.Current ==  null)
            {
                Console.WriteLine( " DatabaseScope.Current 已经卸载了 ");
                 return;
            }
            Console.WriteLine( " ======= " + instanceName +  " ======= ");
            Console.WriteLine( " 连接字符串: " + DatabaseFactory.GetByScope().ConnectionString);
            Console.WriteLine( " SyntaxProvider: " + DatabaseFactory.GetByScope().Provider.GetService<ISyntaxProvider>());
            Console.WriteLine( " SchemaProvider: " + DatabaseFactory.GetByScope().Provider.GetService<ISchemaProvider>());
        }
    }

    运行测试程序,输出为:

 

=======mysql=======
连接字符串:Data Source=localhost;database=test;User Id=root;password=faib;pooling= true;
SyntaxProvider:Fireasy.Data.Syntax.MySqlSyntax
SchemaProvider:Fireasy.Data.Schema.MySqlSchema
=======oracle=======
连接字符串:Data Source=local;User ID=Northwind;Password=faib;
SyntaxProvider:Fireasy.Data.Syntax.OracleSyntax
SchemaProvider:Fireasy.Data.Schema.OracleSchema
=======sqlserver=======
连接字符串:data source=(local);user id=sa;password= 123;initial catalog=Northwind;
SyntaxProvider:Fireasy.Data.Syntax.MsSqlSyntax
SchemaProvider:Fireasy.Data.Schema.MsSqlSchema
DatabaseScope.Current 已经卸载了

 

    通过这番改造,所有的插件都不需要传递 IDatabase 对象了,真是方便,如:

 

     ///   <summary>
    
///  实用于MsSql的数据库备份与恢复。
    
///   </summary>
     public  sealed  class MsSqlBackup : IBackupProvider
    {
         ///   <summary>
        
///  对指定的数据库进行备份。
        
///   </summary>
        
///   <param name="option"> 备份选项。 </param>
         public  void Backup(BackupOption option)
        {
            Guard.ArgumentNull(option,  " option ");
            Guard.ArgumentNull(DatabaseScope.Current,  " DatabaseScope.Current ");

             using ( var connection = DatabaseFactory.GetByScope().CreateConnection())
            {
                 try
                {
                     if ( string.IsNullOrEmpty(option.Database))
                    {
                        option.Database = connection.Database;
                    }

                    connection.OpenClose(() =>
                        {
                             var sql =  string.Format( " BACKUP DATABASE {0} TO DISK = '{1}' ", option.Database, option.FileName);
                             using ( var command = DatabaseFactory.GetByScope().Provider.CreateCommand(connection,  null, sql))
                            {
                                command.ExecuteNonQuery();
                            }
                        });
                }
                 catch (Exception exp)
                {
                     throw  new BackupException(exp);
                }
            }
        }

         ///   <summary>
        
///  使用指定的备份文件恢复数据库。
        
///   </summary>
        
///   <param name="option"> 备份选项。 </param>
         public  void Restore(BackupOption option)
        {
            Guard.ArgumentNull(option,  " option ");
             var sb =  new StringBuilder();
            sb.AppendFormat( " RESTORE DATABASE {0} FROM DISK = '{1}' ", option.Database, option.FileName);
             using ( var connection = DatabaseFactory.GetByScope().CreateConnection())
            {
                 try
                {
                    connection.TryOpen();
                     using ( var command = DatabaseFactory.GetByScope().Provider.DbProviderFactory.CreateCommand())
                    {
                        command.CommandText = sb.ToString();
                        command.Connection = connection;
                        command.ExecuteNonQuery();
                    }
                }
                 catch (Exception exp)
                {
                     throw  new BackupException(exp);
                }
            }
        }
    }

    DatabaseFactory.GetByScope是对DatabaseScope.Current的有效判断,如果 Current 不有时,抛出一个异常。

 

         ///   <summary>
        
///  从  <see cref="DatabaseScope"/>  中获取  <see cref="IDatabase"/>  对象。
        
///   </summary>
        
///   <returns></returns>
         public  static IDatabase GetByScope()
        {
             if (DatabaseScope.Current ==  null)
            {
                 throw  new UnableGetDatabaseScopeException();
            }
             return DatabaseScope.Current.Database;
        }

 

    但是,当使用 Linq 查询延迟加载时,如果处理不当,这个 DatabaseScope 将无效。以下分两种情况进行分析:

    (1)、正常的情况,直接使用 EntityPerisiter。

 

         ///   <summary>
        
///  获取模板列表。
        
///   </summary>
        
///   <param name="type"> 模板类别。 </param>
        
///   <param name="ownerId"> 所属ID。 </param>
        
///   <param name="categoryId"> 分类ID。 </param>
        
///   <param name="keyword"> 关键字。 </param>
        
///   <param name="state"> 状态。 </param>
        
///   <param name="pager"> 分页参数。 </param>
        
///   <returns></returns>
         public IEnumerable<Template> GetTemplates(TemplateType type,  string ownerId,  string categoryId =  nullstring keyword =  null, TemplateState? state =  null, IDataPager pager =  null)
        {
             using ( var persister =  new EntityPersister<TemplateModel>())
            {
                 var category = ! string.IsNullOrEmpty(categoryId) ? persister.Query<TemplateCategoryModel>(s => s.OwnerId == ownerId && s.CategoryId == categoryId).FirstOrDefault() :  null;

                 var list = persister.Query(s => s.OwnerId == ownerId && s.Type == type && s.State != TemplateState.Invalid)
                    .AssertWhere(category !=  null, s => s.CategoryId.StartsWith(category.InnerId))
                    .AssertWhere(! string.IsNullOrEmpty(keyword), s => s.Name.Contains(keyword))
                    .AssertWhere(state !=  null, s => s.State == state.Value)
                    .OrderBy(s => s.Name)
                    .Segment(pager  as IDataSegment);
                 return list.ToList().Select(ConvertTemplate);
            }
        }

 

    伴随着 EntityPerister 的 Dispose ,IDatabase才被 Dispose,因此这个Linq查询并不存在延迟的问题,在这期间,解释器和执行器都能够从 DatabaseScope得到 IDatabase。

 

    (2)、使用三层架构

    首先看一下DA层的代码定义,这个类非常简单:

 

     ///   <summary>
    
///  管理员 数据访问类。
    
///   </summary>
     public  partial  class UserDA : EntityPersister<User>
    {
         ///   <summary>
        
///  初始化  <see cref="UserDA" />  类的新实例。
        
///   </summary>
        
///   <param name="instanceName"> 实例名。 </param>
         public UserDA( string instanceName =  null)
          :  base (instanceName)
        {
        }
    }

 

    再看一下BL层的代码定义:

 

     ///   <summary>
    
///  管理员 业务逻辑类
    
///   </summary>
     public  partial  class UserBL
    {
         private  readonly  string _instanceName;

         ///   <summary>
        
///  初始化  <see cref="UserBL" />  类的新实例。
        
///   </summary>
        
///   <param name="instanceName"> 实例名。 </param>
         public UserBL( string instanceName =  null)
        {
            _instanceName = instanceName;
        }

         #region 模板生成的方法
         ///   <summary>
        
///  创建  <see cref="UserDA" />  的新实例。
        
///   </summary>
        
///   <returns></returns>
         protected UserDA CreateDAObject()
        {
             return  new UserDA(_instanceName);
        }

         ///   <summary>
        
///  使用  <see cref="UserDA" />  来操作一组方法。
        
///   </summary>
        
///   <param name="action"> 要执行的方法。 </param>
         protected  void UsingDA(Action<UserDA> action)
        {
             using ( var da = CreateDAObject())
            {
                 if (action !=  null)
                {
                    action(da);
                }
            }
        }

         ///   <summary>
        
///  使用  <see cref="UserDA" />  来返回一个对象。
        
///   </summary>
        
///   <param name="action"> 要执行的函数。 </param>
         protected T UsingRetDA<T>(Func<UserDA, T> func)
        {
             using ( var da = CreateDAObject())
            {
                 if (func !=  null)
                {
                     return func(da);
                }
            }
             return  default(T);
        }

         ///   <summary>
        
///  使用  <see cref="UserDA" />  上下文来操作一组方法。
        
///   </summary>
        
///   <param name="action"> 要执行的方法。 </param>
         protected  void UsingDAContext(Action<UserDA> action)
        {
             using ( var context =  new EntityPersistentScope())
            {
                 using ( var da = CreateDAObject())
                {
                     if (action !=  null)
                    {
                        action(da);
                    }
                }
                context.Commit();
            }
        }

         ///   <summary>
        
///  将一个新的实体对象创建到数据库。
        
///   </summary>
        
///   <param name="entity"> 要创建的实体对象。 </param>
         public  void Create(User entity)
        {
            UsingDA(da => da.Create(entity));
        }

         ///   <summary>
        
///  将实体对象的改动保存到数据库。
        
///   </summary>
        
///   <param name="entity"> 要保存的实体对象。 </param>
         public  void Save(User entity)
        {
            UsingDA(da => da.Save(entity));
        }

         ///   <summary>
        
///  将一组实体对象的更改保存到数据库。不会更新实体的其他引用属性。
        
///   </summary>
        
///   <param name="entities"> 要保存的实体序列。 </param>
         public  void Save(IEnumerable<User> entities)
        {
            UsingDA(da => da.Save(entities));
        }

         ///   <summary>
        
///  使用一个参照的实体对象更新满足条件的一序列对象。
        
///   </summary>
        
///   <param name="predicate"> 用于测试每个元素是否满足条件的函数。 </param>
        
///   <param name="entity"> 保存的参考对象。 </param>
         public  void Update(User entity, Expression<Func<User,  bool>> predicate =  null)
        {
            UsingDA(da => da.Update(entity, predicate));
        }

         ///   <summary>
        
///  将指定的实体对象从数据库中移除。
        
///   </summary>
        
///   <param name="entity"> 要移除的实体对象。 </param>
        
///   <param name="fake"> 如果具有 IsDeletedKey 属性,则提供对数据假删除的支持。 </param>
         public  void Remove(User entity,  bool fake =  true)
        {
            UsingDA(da => da.Remove(entity));
        }

         ///   <summary>
        
///  根据主键值将对象从数据库中移除。
        
///   </summary>
        
///   <param name="primaryValues"> 主键的值。数组的长度必须与实体所定义的主键相匹配。 </param>
        
///   <param name="fake"> 如果具有 IsDeletedKey 属性,则提供对数据假删除的支持。 </param>
         public  void Remove( object[] primaryValues,  bool fake =  true)
        {
            UsingDA(da => da.Remove(primaryValues, fake));
        }

         ///   <summary>
        
///  将满足条件的一组对象从数据库中移除。
        
///   </summary>
        
///   <param name="predicate"> 用于测试每个元素是否满足条件的函数。 </param>
        
///   <param name="fake"> 如果具有 IsDeletedKey 的属性,则提供对数据假删除的支持。 </param>
         public  void Remove(Expression<Func<User,  bool>> predicate =  nullbool fake =  true)
        {
            UsingDA(da => da.Remove(predicate, fake));
        }

         ///   <summary>
        
///  返回满足条件的一组实体对象。
        
///   </summary>
        
///   <param name="predicate"> 用于测试每个元素是否满足条件的函数。 </param>
        
///   <returns></returns>
         public QuerySet<User> Query(Expression<Func<User,  bool>> predicate =  null)
        {
             return UsingRetDA(da => da.Query(predicate));
        }

         ///   <summary>
        
///  返回满足条件的一组对象。
        
///   </summary>
        
///   <typeparam name="T"> 对象类型。 </typeparam>
        
///   <param name="predicate"> 用于测试每个元素是否满足条件的函数。 </param>
        
///   <returns></returns>
         public QuerySet<T> Query<T>(Expression<Func<T,  bool>> predicate =  null)
        {
             return UsingRetDA(da => da.Query<T>(predicate));
        }

         ///   <summary>
        
///  根据自定义的T-SQL语句查询返回一组对象,
        
///   </summary>
        
///   <typeparam name="T"> 对象类型。 </typeparam>
        
///   <param name="queryCommand"> 查询命令。 </param>
        
///   <param name="setment"> 数据分段对象。 </param>
        
///   <param name="parameters"> 查询参数集合。 </param>
        
///   <returns></returns>
         public IEnumerable<T> Query<T>(IQueryCommand queryCommand, IDataSegment setment =  null, ParameterCollection parameters =  nullwhere T :  new()
        {
             return UsingRetDA(da => da.Query<T>(queryCommand, setment, parameters));
        }

         ///   <summary>
        
///  返回满足条件的一组实体对象。
        
///   </summary>
        
///   <param name="condition"> 一般的条件语句。 </param>
        
///   <param name="orderBy"> 排序语句。 </param>
        
///   <param name="setment"> 数据分段对象。 </param>
        
///   <param name="parameters"> 查询参数集合。 </param>
        
///   <returns></returns>
         public IEnumerable<User> Query( string condition,  string orderBy, IDataSegment setment =  null, ParameterCollection parameters =  null)
        {
             return UsingRetDA(da => da.Query(condition, orderBy, setment, parameters));
        }

         ///   <summary>
        
///  使用主键值查询返回一个实体。
        
///   </summary>
        
///   <param name="primaryValues"> 主键的值。数组的长度必须与实体所定义的主键相匹配。 </param>
        
///   <returns></returns>
         public User First( params  object[] primaryValues)
        {
             return UsingRetDA(da => da.First(primaryValues));
        }
         #endregion

         #region 自定义代码
         // code-begin

        /// <summary>
        
/// 登录并返回用户信息
        
/// </summary>
        
/// <param name="account"></param>
        
/// <param name="password"></param>
        
/// <returns></returns>
        public User Login(string account, string password)
        {
            return Query(s => s.Enabled && s.Account == account && s.Password == password).FirstOrDefault();
        }
        //code-end
        #endregion
    }

 

    在页面端进行用户登录验证时,是直接调用Login方法的,但此时,发抛出 UnableGetDatabaseScopeException 异常。我们来分析一下:

    在Query方法内,是通过使用 using 一个 DA 对象来进行查询的,Query方法返回的是一个 QuerySet<User> 集合,它支持 Linq 的查询,因此,从 Query 调用来看,存在着延迟,在没有返回数据之前,DA已经Dispose了,随之IDatabase也Dispose了,在对 Linq 进行解释的时候,已经取不到 IDatabase了。

    解决这个问题的一种方法是,让BL也实现IDisposable接口,在实例期间,不要销毁 Da 对象,而是在Dispose里进行销毁。另一种方法就是对 Linq的查询返回结果不使用QuerySet<T> 而是将它转换为 List<T>,这样就不存在延迟所产生的影响。

 

你可能感兴趣的:(database)