• LLBL Gen 元数据编程 LLBL Gen Metadata Programming


    LLBL Gen作为ORM工具,有时候为了能生成一些基础的元数据,也需要了解它的对象及其之前的关系,这在通用的框架代码中的作用更加明显。举例说明,它生成的解决方案视图一般是这样的

    image

    现在有如下的需求需要满足,以提供基础的元数据,参考测试代码如下

    string AssemblyFile = @"E:\Solution\Enterprise\Bin\Northwind.CRM.BusinessLogic.dll";
    string TableName = "Employees";
    
    string projectName = EntityClassHelper.PrefixProjectName(AssemblyFile);
    
    string entity = EntityClassHelper.GetEntityName(TableName, AssemblyFile);
    string entityName = "EmployeeEntity";
    string str = EntityClassHelper.TrimEntityName(entityName);
    
    IEntity2 currencyEntity = EntityClassHelper.GetEntityObject(TableName, AssemblyFile);
    string typeName = "EmployeeEntity";
    string table = EntityClassHelper.GetSourceTableName(typeName, AssemblyFile);
    
    string entityColumnName = EntityClassHelper.GetObjectProperyName(AssemblyFile, TableName, "EmployeeId");
    entityColumnName = EntityClassHelper.GetObjectProperyName(AssemblyFile, TableName, "LastName");
    List<string> path = EntityClassHelper.GetPrefetchPath(AssemblyFile, TableName);
    
    IEntity2 entity2 = EntityClassHelper.GetEntityObject(TableName, AssemblyFile);
    List<string> relations = EntityClassHelper.GetPrefetchPathEx(entity2);

    需求列出如下图表所示

    序号         需求描述
    1 如何获取LLBL Gen代码生成器生成的项目名称,这可以确定类型所在的命名空间
    2 如何根据数据库表名(Customers),获取它对应的生成的实体名(CustomerEntity)
    3 如何根据实体名(CustomerEntity),获取它映射到的数据库表名(Customers)
    4 如何根据实体名,获取它的主键属性/字段
    5 如何根据表名及指定的字段表,获取对应的生成的实体的属性
    6 如何获取实体的子集合的关系

    项目名称空间

    image

    这里要获取的就是Root namespace,顶层的命名空间,依照这个名称,从而可以得到实体所在的名称空间。

    LLBL Gen从3.x开始,项目文件改用xml配置文件,这为大量的第三方工具的产生提供的便利。如果要获取上图所示的Root namespace,可以采用Xml技术(XmlDocument)或Linq to Xml读取它的配置节

     <CodeGenerationCyclePreferences>
        <OutputType Value="3">
          <LastUsedPreferences>
            <DestinationRootFolder Value="E:\Solution\Development\MIS Solution\Source\Enterprise\BusinessLogic" />
            <FrameworkName Value="LLBLGen Pro Runtime Framework" />
            <LanguageName Value="C#" />
            <PlatformName Value=".NET 3.5" />
            <PresetName Value="Northwind" />
            <RootNamespace Value="Foundation.Northwind" />
            <TemplateGroup Value="Adapter" />
            <TemplateBindings>
              <Binding Name="ISL Template" />
              <Binding Name="SD.AdditionalTemplates.DbEditor.Net2.0 v3.x" />
              <Binding Name="SD.TemplateBindings.SharedTemplates.NET35" />
              <Binding Name="SD.TemplateBindings.SqlServerSpecific.NET20" />
              <Binding Name="SD.TemplateBindings.SharedTemplates.NET20" />
              <Binding Name="SD.TemplateBindings.General" />
            </TemplateBindings>
          </LastUsedPreferences>
        </OutputType>
      </CodeGenerationCyclePreferences>

    如上面的Xml文本所示,RootNamespace就是我们需要的顶层命名空间。

    观察LLBL Gen生成的项目文件,它的实体定义的代码所下的例子所示

    using SD.LLBLGen.Pro.ORMSupportClasses;
    
    namespace Northwind.DAL.EntityClasses
    {
        // __LLBLGENPRO_USER_CODE_REGION_START AdditionalNamespaces
        // __LLBLGENPRO_USER_CODE_REGION_END
        /// <summary>Entity class which represents the entity 'Category'.<br/><br/></summary>
        [Serializable]
        public partial class CategoryEntity : CommonEntityBase
            // __LLBLGENPRO_USER_CODE_REGION_START AdditionalInterfaces
            // __LLBLGENPRO_USER_CODE_REGION_END    
        {
        }
    }

    反射生成的解决方案文件类库,获取它的类型,如果是IEntity2(Adapter模式),去掉必须的EntityClasses,前面的部分即是我们需要的顶层(Root namespace)命名空间,实现代码如下所示

    Assembly assebly=Assembly.Load(businessLogic); 
    Type[] types = assembly.GetTypes();
    string rootNamespace = "";            
    foreach (Type type in types)
    {
           if (!string.IsNullOrEmpty(type.Namespace))
           {
                        string nspace = type.Namespace.Substring(type.Namespace.LastIndexOf('.') + 1);
                        if (type.Name.EndsWith("Entity") & nspace == "EntityClasses")
                        {
                            rootNamespace = type.Namespace;
                            int idx = rootNamespace .LastIndexOf(".");
                            rootNamespace  = rootNamespace .Substring(0, idx);
                            break;
                        }
           }                 
    }

    变量rootNamespace就是我需要的顶层命名空间。

    数据库表与它生成的实体对象名

    来观察一下LLBL Gen提供的数据访问接口类型DataAccessAdapter,这提供一个保护的方法成员GetFieldPersistenceInfos,使用Reflector得到它的源代码跟踪进去,看到有数个方法

    // Summary: Retrieves the persistence info for the field passed in.
    // Parameters:
    //   field: Field which fieldpersistence info has to be retrieved
    // Returns:the requested persistence information
    protected virtual IFieldPersistenceInfo GetFieldPersistenceInfo(IEntityField2 field);
    // Summary: Retrieves the persistence info objects for the fields of the entity passed in.
    // Parameters: entity:
    //  Entity bject which fields the persistence information should be retrieved for
    // Returns:  the requested persistence information
    protected virtual IFieldPersistenceInfo[] GetFieldPersistenceInfos(IEntity2 entity); // Summary: Retrieves the persistence info for the fields passed in. // Parameters: fields: Fields for which the persistence info has to be determined // Returns: the requested persistence information protected virtual IFieldPersistenceInfo[] GetFieldPersistenceInfos(IEntityFields2 fields); // Summary: Retrieves the persistence info objects for the fields of the entity passe in. // Parameters: entityName: //Entity name for entity type which fields the persistence information should be retrieved for // Returns: the requested persistence information protected virtual IFieldPersistenceInfo[] GetFieldPersistenceInfos(string entityName);

    IFieldPersistenceInfo就是实体属性对应的数据库字段的映射类型,不过这几个方法都是保护类型的,需要在派生类中访问,如下的代码所示

    namespace Northwind.DAL
    {
        public sealed class DataAccessAdapter : Northwind.DAL.DatabaseSpecific.DataAccessAdapter
        { 
           public string GetSourceTableName(string entityType)
            {
                return base.GetFieldPersistenceInfos(entityType)[0].SourceObjectName;
            }
    }
           

    因为同一个实体对应的表,表中的所有字段所属的表名肯定是相同的,所以直接取它的第零个字段的SourceObjectName。这样,我们就做到了根据实体的名称,来获取它应对的数据库表名称。在我的Management Console开发工具中,没有直接从DatabaseSpecific.DataAccessAdapter类型派生,这样的方式会产生很多麻烦。每新建一个项目就要派生一个类型出来,这样不符合工具的含义,观察一下生成的文件PersistenceInfoProvider

    internal static class PersistenceInfoProviderSingleton
    {
            #region Class Member Declarations
            private static readonly IPersistenceInfoProvider _providerInstance = new PersistenceInfoProviderCore();
            #endregion
    
            /// <summary>Dummy static constructor to make sure threadsafe initialization is performed.</summary>
            static PersistenceInfoProviderSingleton()
            {
            }
    
            /// <summary>Gets the singleton instance of the PersistenceInfoProviderCore</summary>
            /// <returns>Instance of the PersistenceInfoProvider.</returns>
            public static IPersistenceInfoProvider GetInstance()
            {
                return _providerInstance;
            }
        }
    }
    internal class PersistenceInfoProviderCore : PersistenceInfoProviderBase
    {
            /// <summary>Initializes a new instance of the <see cref="PersistenceInfoProviderCore"/> class.</summary>
            internal PersistenceInfoProviderCore()
            {
                Init();
            }
    
            /// <summary>Method which initializes the internal datastores with the structure of hierarchical types.</summary>
            private void Init()
            {
                this.InitClass((13 + 2));
                InitCategoryEntityMappings();
                InitCustomerEntityMappings();    
            }
    }

    这两个类型暴露了IPersistenceInfoProvider 给外部类型库来获取它的映射关系,这一点我是通过分析LLBL Gen ORM Support Classes得到的。因为LLBL Gen框架要自动生成SQL语句,必然需要一种机制或是映射来保存这种数据库表和实体的映射关系,NHibernate是使用外部xml配置文件的方式,LLBL Gen则直接使有代码,并且代码直接由生成工具维护,不需要开发人员参与,这一点应该比NHibernate更加合理优秀一些。来看一下经过反射后的得到的代码

    public abstract class PersistenceInfoProviderBase : IPersistenceInfoProvider
    {
            protected PersistenceInfoProviderBase();
    
            protected void AddElementFieldMapping(string elementName, string elementFieldName, string sourceColumnName, bool isSourceColumnNullable, string sourceColumnDbType, int sourceColumnMaxLength, byte sourceColumnScale, byte sourceColumnPrecision, bool isIdentity, string identityValueSequenceName, TypeConverter typeConverterToUse, Type actualDotNetType, int fieldIndex);
            protected void AddElementMapping(string elementName, string catalogName, string schemaName, string targetName, int numberOfFields);
            public IFieldPersistenceInfo[] GetAllFieldPersistenceInfos(IEntity2 entity);
            public IFieldPersistenceInfo[] GetAllFieldPersistenceInfos(string elementName);
            public IFieldPersistenceInfo GetFieldPersistenceInfo(string elementName, string fieldName);
            protected void InitClass(int capacity);
    }

    方法AddElementMapping和AddElementFieldMapping添加表及其字段与实体的映射到内部集合中,并通过GetAllFieldPersistenceInfos接口向外公布映射数据。这就解释了方法GetSourceTableName的原理。

    Management Console因为要适应新项目的需要,所以不能直接用派生的方式,直接用反射来获取元数据信息。

    1 反射项目root namespace名称,得到DatabaseSpecific.PersistenceInfoProviderSingleton类型,反射它的GetInstance()方法得到IPersistenceInfoProvider

    2 传入需要的参数,得到IFieldPersistenceInfo类型,观察它的属性,可以看到SourceObjectName,其它的属性如下

     public interface IFieldPersistenceInfo
     {
            Type ActualDotNetType { get; }
            string IdentityValueSequenceName { get; }
            bool IsIdentity { get; }
            string SourceCatalogName { get; }
            string SourceColumnDbType { get; }
            bool SourceColumnIsNullable { get; }
            int SourceColumnMaxLength { get; }
            string SourceColumnName { get; }
            byte SourceColumnPrecision { get; }
            byte SourceColumnScale { get; }
            string SourceObjectName { get; }
            string SourceSchemaName { get; }
            TypeConverter TypeConverterToUse { get; }
    }

    这些属性的含义,就代表了数据库字段的内存映射,于DataTable不同。DataTable是装载数据,而IFieldPersistenceInfo是装载字段的元数据,也就是下图中的Column Properties

    image

    这个类型在生成SQL语句方面有重要的作用,你可以通过查看它的源代码(反射得到,没有加密)看到它的重要作用。

    讲到这里,所有的元数据的信息都可以通过上面的接口和方法获取到。

    SQL Trace

    如果对LLBL Gen生成的SQL语句感兴趣,可以通过Trace机制来跟踪它的SQL输出,配置过程如下所示。修改配置文件

    <system.diagnostics>
            <!-- LLBLGen Trace
                Trace Level:    0 - Disabled
                                3 - Info
                                4 - Verbose
            <switches>
                <add name="SqlServerDQE" value="0" />
                <add name="ORMGeneral" value="0" />
                <add name="ORMStateManagement" value="0" />
                <add name="ORMPersistenceExecution" value="0" />
            </switches>
            <trace autoflush="true">
                <listeners>
                    <add name="textWriterTraceListener"
                         type="System.Diagnostics.TextWriterTraceListener"
                         initializeData="D:\\erp solution\esolution_log.txt" />
                </listeners>
            </trace>
            -->
        </system.diagnostics>

    这样配置,可以将LLBL Gen生成的SQL语句输出到文本文件中,不过这种方式不适合即时查看。需要重写一个TraceListener来截获它的SQL输出。

     public class ORMTraceListener : TraceListener
        {
            static Socket m_socClient;
            //在可以连接的情况下,才能保证去发送消息
            static bool m_serverAvailable=false;
    
            static ORMTraceListener()
            {
                m_server = false;
                OnConnect("127.0.0.1", 2907);
            }
          
            public override void Write(string message)
            {
                if (!String.IsNullOrEmpty(message) && m_server&&NeedSend(message))
                    OnSendData(message);
            }
    
            public override void WriteLine(string message)
            {
                if (!String.IsNullOrEmpty(message) && m_server && NeedSend(message))
                    OnSendData(message+Environment.NewLine);
            }
    }

    这里应用Socket把截取的SQL语句输出到我的监控程序中,修改上面的type为ORMTraceListener 即可。

    截取的SQL语句不是标准的SQL Server语句。原因是LLBL Gen是跨数据库平台的,独立于数据库方言,所以要用一种公共的方法来描述生成的SQL语句,这也提供了一种通用SQL语句生成的方法(学技术的同时,也看到了技术的最佳实践,这在以后的工作中会提供相当大的帮助)。SQL语句格式如下所示

    Generated Sql query: 
    
        Query: SELECT DISTINCT [Enterprise].[dbo].[Company].[CompanyCode] FROM [Enterprise].[dbo].[Company]  
         WHERE ( ( [Enterprise].[dbo].[Company].[Suspended] = @Suspended1)) ORDER BY 
             [Enterprise].[dbo].[Company].[CompanyCode] ASC
    
        Parameter: @Suspended1 : String. Length: 1. Precision: 0. Scale: 0. Direction: Input. Value: "N".

    在此基础上,再创建一个解析工具,把这里的SQL解析成可以直接在SQL Server中运行的T-SQL脚本。

  • 相关阅读:
    jQuery,from标签,歪路子小技巧
    UniApp随笔
    JS的一些操作
    文本环绕
    Dictionary 存储函数,方法
    GIT项目管理
    Vue2 学习记录
    VSCode + Vue 学习笔记
    Mysql,Insert,Select时自增长问题
    ASP.NET SignalR Troubeshooting
  • 原文地址:https://www.cnblogs.com/JamesLi2015/p/2303020.html
Copyright © 2020-2023  润新知