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>,这样就不存在延迟所产生的影响。

  • 相关阅读:
    Windows系统开机硬盘自检问题解决
    更改软件默认安装路径
    windows添加开机启动项
    xp 如何打开(进行)远程桌面连接
    xp看系统位数
    教你创建一个别人打不开删不掉的文件夹(干坏事爱好者必看)
    无操作一段时间显示器无信号
    长时间用电脑,(给窗口选一个合适的颜色)设置窗口绿色
    Windows右键菜单设置与应用技巧
    Outlook如何定时发邮件
  • 原文地址:https://www.cnblogs.com/faib/p/2531481.html
Copyright © 2011-2022 走看看