Fireasy.Data系列——数据库架构的整合查询

      在Ado.Net中,DbConnection类的GetSchema方法用于获取数据库提供者的相关架构信息,比如数据类型、表、列等等,然而每种数据库架构的元数据结构都是不一样的。Fireasy.Data提供了一个扩展服务接口,以将四类数据库的架构信息整合在一起,统一定义了最大公有的架构元数据,并在此基础上提供Linq查询的支持。

 

      一、架构元数据的接口

      由于要使用统一的查询,因此需要定义一个标识接口,然后使不同的架构元数据类来实现它。

 

     ///   <summary>
    
///  数据库架构元数据结构。
    
///   </summary>
     public  interface ISchemaMetadata
    {
    }

 

      二、架构集合的枚举

      首先定义要支持的架构的类别,基本上每一种数据库都支持以下的这些集合名称。

 

     ///   <summary>
    
///  数据库架构集合的类别。
    
///   </summary>
     public  enum SchemaCategory
    {
         ///   <summary>
        
///  所有定义的列的有关信息。
        
///   </summary>
        Columns,
         ///   <summary>
        
///  所支持的数据类型的有关信息。
        
///   </summary>
        DataTypes,
         ///   <summary>
        
///  所有定义的外键的有关信息。
        
///   </summary>
        ForeignKeys,
         ///   <summary>
        
///  作为索引的列的有关信息。
        
///   </summary>
        Indexes,
         ///   <summary>
        
///  所有定义的索引相关列的有关信息。
        
///   </summary>
        IndexColumns,
         ///   <summary>
        
///  所有架构集合的有关信息。 
        
///   </summary>
        MetaDataCollections,
         ///   <summary>
        
///  所有定义的存储过程的有关信息。
        
///   </summary>
        Procedures,
         ///   <summary>
        
///  存储过程中所有参数的有关信息。
        
///   </summary>
        ProcedureParameters,
         ///   <summary>
        
///  数据库公开所保留的关键字的有关信息。
        
///   </summary>
        ReservedWords,
         ///   <summary>
        
///  所支持的限制的有关信息。
        
///   </summary>
        Restrictions,
         ///   <summary>
        
///  所有定义的表的有关信息。
        
///   </summary>
        Tables,
         ///   <summary>
        
///  数据库所定义的用户的有关信息。
        
///   </summary>
        Users,
         ///   <summary>
        
///  所有定义的视图的有关信息。
        
///   </summary>
        Views,
         ///   <summary>
        
///  视图所定义的列的有关信息。
        
///   </summary>
        ViewColumns
    }

 

       三、架构元数据类

       有了以上两个后,就可以定义具体的架构元数据类了,比如Table:

 

     ///   <summary>
    
///  数据库表信息。
    
///   </summary>
    [SchemaCategory(SchemaCategory.Tables)]
     public  sealed  class Table : ISchemaMetadata
    {
         ///   <summary>
        
///  获取分录名称。
        
///   </summary>
        [SchemaQueryableAttribute( 0, ProviderType.MsSql)]
        [SchemaQueryableAttribute( 0, ProviderType.SQLite)]
        [SchemaQueryableAttribute( 0, ProviderType.MySql)]
         public  string TableCatalog {  getinternal  set; }

         ///   <summary>
        
///  获取架构名称。
        
///   </summary>
        [SchemaQueryableAttribute( 1, ProviderType.MsSql)]
        [SchemaQueryableAttribute( 0, ProviderType.Oracle)]
        [SchemaQueryableAttribute( 1, ProviderType.MySql)]
         public  string TableSchema {  getinternal  set; }

         ///   <summary>
        
///  获取表名称。
        
///   </summary>
        [SchemaQueryableAttribute( 2, ProviderType.MsSql)]
        [SchemaQueryableAttribute( 1, ProviderType.Oracle)]
        [SchemaQueryableAttribute( 2, ProviderType.SQLite)]
        [SchemaQueryableAttribute( 2, ProviderType.MySql)]
         public  string TableName {  getinternal  set; }

         ///   <summary>
        
///  获取表类型。
        
///   </summary>
        [SchemaQueryableAttribute( 3, ProviderType.MsSql)]
        [SchemaQueryableAttribute( 3, ProviderType.SQLite)]
        [SchemaQueryableAttribute( 3, ProviderType.MySql)]
         public  string TableType {  getinternal  set; }

         ///   <summary>
        
///  获取表的描述。
        
///   </summary>
         public  string Description {  getinternal  set; }
    }

        在以上的代码中,分别用到了两个特性,SchemaCategoryAttribute标识了该类所属的架构类别,使用特性的目的,在于避免使用字符串,这个将在后面介绍。

        另一个特性SchemaQueryableAttribute特性则是定义了元数据属性在查询限制数组中的索引位置,因为每一种数据库类型对于同一个属性所限制的位置是不同的,因此需要为每一种数据库类别定义一个特性。

 

        四、架构扩展服务类

        首先定义一个抽象类,对底层的处理进行封装,然后开放每一类架构的信息获取方法出来,不同的数据库类型再进行重写,以使信息之间一一对应。

 

     ///   <summary>
    
///  一个抽象类,提供获取数据库架构的方法。
    
///   </summary>
     public  abstract  class BaseSchema : ISchemaProvider
    {
         ///   <summary>
        
///  获取或设置提供者服务的上下文。
        
///   </summary>
         public ServiceContext ServiceContext {  getset; }

         ///   <summary>
        
///  获取指定类型的数据库架构信息。
        
///   </summary>
        
///   <typeparam name="T"> 架构信息的类型。 </typeparam>
        
///   <param name="predicate"> 用于测试架构信息是否满足条件的函数。 </param>
        
///   <returns></returns>
         public  virtual IEnumerable<T> GetSchemas<T>(Expression<Func<T,  bool>> predicate =  nullwhere T : ISchemaMetadata
        {
             var category = GetSchemaCategory<T>();
             var restrictionValues = SchemaQueryTranslator.GetRestriction(ServiceContext.Database.Provider.ProviderType,  typeof(T), predicate);
            DataTable table;

             using ( var connection = ServiceContext.Database.CreateConnection())
            {
                 var collectionName = GetSchemaCategoryName(category);
                 try
                {
                    connection.TryOpen();
                    table = connection.GetSchema(collectionName, InitRestrictionValues(connection, category, restrictionValues));
                }
                 catch (Exception ex)
                {
                     throw  new SchemaNotSupportedtException(collectionName, ex);
                }
                 finally
                {
                    connection.TryClose();
                }
            }
             return ReturnSchemaElements<T>(category, table);
        }

         ///   <summary>
        
///  获取指定类型的数据库架构信息。
        
///   </summary>
        
///   <param name="collectionName"> 架构信息类别名称。 </param>
        
///   <param name="restrictionValues"> 列限制数组。 </param>
        
///   <returns></returns>
         public  virtual DataTable GetSchema( string collectionName,  string[] restrictionValues)
        {
            DataTable table;
             using ( var connection = ServiceContext.Database.CreateConnection())
            {
                connection.TryOpen();
                table = connection.GetSchema(collectionName, restrictionValues);
                connection.TryClose();
            }
             return table;
        }
    }

        在以上的代码中,第一步:使用GetSchemaCategoryName方法获得Ado.Net中所支持集合名称,如Tables、Columns。

 

         ///  获取架构的名称。
        
///   </summary>
        
///   <param name="category"> 架构信息类别。 </param>
        
///   <returns></returns>
         protected  virtual  string GetSchemaCategoryName(SchemaCategory category)
        {
             return category.ToString();
        }

 

         如果集合名称不是使用枚举的名称,则在具体的子类中重写这个方法指定就可以了。

 

         第二步,使用SchemaQueryTranslator类对传入的Linq查询表达式进行解析,得到原生的restrictionValues,这个数组作为connection.GetSchema方法的第二个参数传入。

 

         第三步,对查询得到的DataTable进行解析,返回我们需要的IEnumerable<T>序列:

 

         private IEnumerable<T> ReturnSchemaElements<T>(SchemaCategory category, DataTable table)
        {
            IEnumerable @enum =  null;
             switch (category)
            {
                 case SchemaCategory.Columns:
                    @enum = GetColumns(table,  null);
                     break;
                 case SchemaCategory.DataTypes:
                    @enum = GetDataTypes(table,  null);
                     break;
                 case SchemaCategory.ForeignKeys:
                    @enum = GetForeignKeys(table,  null);
                     break;
                 case SchemaCategory.IndexColumns:
                    @enum = GetIndexColumns(table,  null);
                     break;
                 case SchemaCategory.Indexes:
                    @enum = GetIndexs(table,  null);
                     break;
                 case SchemaCategory.MetaDataCollections:
                    @enum = GetMetaDataCollections(table,  null);
                     break;
                 case SchemaCategory.ProcedureParameters:
                    @enum = GetProcedureParameters(table,  null);
                     break;
                 case SchemaCategory.Procedures:
                    @enum = GetProcedures(table,  null);
                     break;
                 case SchemaCategory.ReservedWords:
                    @enum = GetReservedWords(table,  null);
                     break;
                 case SchemaCategory.Restrictions:
                    @enum = GetRestrictions(table,  null);
                     break;
                 case SchemaCategory.Tables:
                    @enum = GetTables(table,  null);
                     break;
                 case SchemaCategory.Users:
                    @enum = GetUsers(table,  null);
                     break;
                 case SchemaCategory.ViewColumns:
                    @enum = GetViewColumns(table,  null);
                     break;
                 case SchemaCategory.Views:
                    @enum = GetViews(table,  null);
                     break;
            }

             if (@enum !=  null)
            {
                 foreach ( var item  in @enum)
                {
                     yield  return (T)item;
                }
            }
        }

        每一个初始架构信息的方法都定义成了虚方法了,因此在子类中还可以进行信息的转换,就象在OracleSchema中,我们可以对Table的信息进行丰富,增加了获取表描述信息的提取:

 

         ///   <summary>
        
///  获取  <see cref="Table"/>  元数据序列。
        
///   </summary>
        
///   <param name="table"> 架构信息的表。 </param>
        
///   <param name="action"> 用于填充元数据的方法。 </param>
        
///   <returns></returns>
         protected  override IEnumerable<Table> GetTables(DataTable table, Action<Table, DataRow> action)
        {
             foreach (DataRow row  in table.Rows)
            {
                 var item =  new Table
                    {
                        TableSchema = row[ " OWNER "].ToString(),
                        TableName = row[ " TABLE_NAME "].ToString(),
                        TableType = row[ " TYPE "].ToString()
                    };
                item.Description = OracleSchemaHelper.GetTableDescription(ServiceContext.Database, item.TableSchema, item.TableSchema);

                 if (action !=  null)
                {
                    action(item, row);
                }
                 yield  return item;
            }
        }

 

        五、架构查询的表达式解析类

        其实本篇的重点在于此类,它对传入查询的表达式进行解析,并返回一个限制数组,如果你对表达式有所了解,相信一看就明白其中的原理了。

 

     internal  sealed  class SchemaQueryTranslator : Common.Linq.Expressions.ExpressionVisitor
    {
         private Dictionary< intstring> m_dic;
         private  int m_index = - 1;
         private  int m_maxIndex;
         private  readonly Type m_metadataType;
         private  readonly ProviderType m_providerType;

         public SchemaQueryTranslator(ProviderType providerType, Type metadataType)
        {
            m_providerType = providerType;
            m_metadataType = metadataType;
            InitDictionary();
        }

         ///   <summary>
        
///  对表达式进行解析,并返回限制数组。
        
///   </summary>
        
///   <param name="providerType"> 数据提供者类别。 </param>
        
///   <param name="metadataType"> 架构元数组类型。 </param>
        
///   <param name="expression"> 查询表达式。 </param>
        
///   <returns></returns>
         public  static  string[] GetRestriction(ProviderType providerType, Type metadataType, Expression expression)
        {
             var translator =  new SchemaQueryTranslator(providerType, metadataType);
             return translator.GetRestrictionValues(expression);
        }

         private  string[] GetRestrictionValues(Expression expression)
        {
             if (expression !=  null)
            {
                Visit(expression);
            }
             return TrimEmptyArray();
        }

         ///   <summary>
        
///  初始化字典,找出架构元数据类中定义了  <see cref="SchemaQueryableAttribute"/>  特性的所有属性。
        
///   </summary>
         private  void InitDictionary()
        {
            m_dic =  new Dictionary< intstring>();
             var properties = m_metadataType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
             foreach ( var property  in properties)
            {
                 var attribute = property.GetCustomAttributes<SchemaQueryableAttribute>().FirstOrDefault(s => s.ProviderType == m_providerType);
                 if (attribute ==  null)
                {
                     continue;
                }
                 // 使用索引作为键值
                m_dic.Add(attribute.Index,  null);
            }
        }

         ///   <summary>
        
///  访问表达式树。
        
///   </summary>
        
///   <param name="expression"></param>
        
///   <returns></returns>
         protected  override Expression Visit(Expression expression)
        {
             switch (expression.NodeType)
            {
                 case ExpressionType.MemberAccess:
                     return VisitMember((MemberExpression)expression);
                 case ExpressionType.Equal:
                     return VisitBinary((BinaryExpression)expression);
                 case ExpressionType.Constant:
                     return VisitConstant((ConstantExpression)expression);
            }
             return  base.Visit(expression);
        }

         ///   <summary>
        
///  访问二元运算表达式。
        
///   </summary>
        
///   <param name="binaryExp"></param>
        
///   <returns></returns>
         protected  override Expression VisitBinary(BinaryExpression binaryExp)
        {
             // 属性在运算符的右边
             var memberExp = binaryExp.Right  as MemberExpression;
             if (memberExp !=  null &&
                memberExp.Member.DeclaringType == m_metadataType)
            {
                Visit(binaryExp.Right);
                Visit(binaryExp.Left);
            }
             else
            {
                Visit(binaryExp.Left);
                Visit(binaryExp.Right);
            }
             // 复位
            m_index = - 1;
             return binaryExp;
        }

         protected  override Expression VisitMember(MemberExpression memberExp)
        {
             // 如果属性是架构元数据类的成员
             if (memberExp.Member.DeclaringType == m_metadataType)
            {
                 var attribute = memberExp.Member.GetCustomAttributes<SchemaQueryableAttribute>().FirstOrDefault(s => s.ProviderType == m_providerType);
                 if (attribute ==  null)
                {
                     throw  new SchemaQueryNotSupportedException(memberExp.Member.Name);
                }
                 // 记录下当前的索引,以及目前的最大索引
                m_index = attribute.Index;
                m_maxIndex = Math.Max(m_maxIndex, m_index +  1);
                 return memberExp;
            }
             else
            {
                 // 值或引用
                 var exp = (Expression)memberExp;
                 if (memberExp.Type.IsValueType)
                {
                    exp = Expression.Convert(memberExp,  typeof( object));
                }
                 var lambda = Expression.Lambda<Func< object>>(exp);
                 var fn = lambda.Compile();
                 // 转换为常量表达式
                 return Visit(Expression.Constant(fn(), memberExp.Type));
            }
        }

         protected  override Expression VisitConstant(ConstantExpression constExp)
        {
             if (m_index == - 1)
            {
                 return constExp;
            }
             // 没有复位的情况下,记录值
            m_dic[m_index] = constExp.Value.ToString();
             return constExp;
        }

         ///   <summary>
        
///  删除空的数据元素
        
///   </summary>
        
///   <returns></returns>
         private  string[] TrimEmptyArray()
        {
             // 最大范围
             var array =  new  string[m_maxIndex];
             for ( var i =  0; i < m_maxIndex; i++)
            {
                 if (m_dic.ContainsKey(i))
                {
                    array[i] = m_dic[i];
                }
            }
             return array;
        }
    }

 

        六、测试

        没有条件的架构查询:

        [Test]
         public  void GetTables()
        {
            Console.WriteLine(TimeWatcher.Watch(() =>
                InvokeTest(database =>
                    {
                         var schema = database.Provider.GetService<ISchemaProvider>();
                         foreach ( var table  in schema.GetSchemas<Table>())
                        {
                            PrintSchema(table);
                        }
                        Console.WriteLine();
                    })));
        }

        使用表达式的架构查询:

 

        [Test]
         public  void GetTablesQuery()
        {
            Console.WriteLine(TimeWatcher.Watch(() =>
                InvokeTest(database =>
                    {
                         var schema = database.Provider.GetService<ISchemaProvider>();
                         foreach ( var table  in schema.GetSchemas<Table>(s => s.TableName ==  " products "))
                        {
                            PrintSchema(table);
                        }
                        Console.WriteLine();
                    })));
        }

 

         当然,虽然在一定程度上解决了架构查询的问题,但是仍然在于一些缺陷,主要表达在数据库之间一些微妙的差别,比如oracle的大小写敏感问题,以及它是使用owner,而sqlserver使用schema,因此还有改进的空间。

你可能感兴趣的:(Data)