zoukankan      html  css  js  c++  java
  • Fireasy.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>,这样就不存在延迟所产生的影响。

  • 相关阅读:
    HDU 5744
    HDU 5815
    POJ 1269
    HDU 5742
    HDU 4609
    fzu 1150 Farmer Bill's Problem
    fzu 1002 HangOver
    fzu 1001 Duplicate Pair
    fzu 1150 Farmer Bill's Problem
    fzu 1182 Argus 优先队列
  • 原文地址:https://www.cnblogs.com/faib/p/2531481.html
Copyright © 2011-2022 走看看