zoukankan      html  css  js  c++  java
  • LLBL Gen 实体映射工具技术原理剖析

    应用LLBL Gen作为ORM工具时,经常会遇到想查一个实体所代表的数据库表名,或是想已知一个数据库表名,想知道它在程序中对应的实体名,这两者之间相互查找,这个需求经常会碰到。

    前一种需求产生于,系统报错时,会显示调用的堆栈和错误信息,依据最后一层堆栈提供的对象参数,可以查到表名,以此追查数据为什么会出错。

    后一种需求,常常想知道业务逻辑算法。比如单价的计算方法,总金额的计算方法,因此需要从数据库中追踪到实体表及其属性,再跟踪属性之间的运算逻辑。

    第一种解决方案,可以修改LLBL Gen生成的源代码的模板,把表名生成到实体映射文件的注释中,修改模板

    1. entityAdapter.template  #31行
      ///Mapped on table: <[ElementTargetObjectName]></summary>
    2. 这种方案,当鼠标放到实体的名称上时,会显示它映射的表名。
    3. 继续修改LLBL Gen模板,让它能显示出属性对应的数据库字段。
      1. entityIncludeAdatper.template #549行
        ///Mapped on <[ CaseCamel TargetType ]> field: <[SourceObjectName]>.<[SourceColumnName]></summary>

        第二种方案,解析LLBL Gen生成的源项目文件,它的存储格式如下

        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((158 + 0));
                    InitAccountEntityMappings();
                                         }
        }

        具体的每个实体的映射

            /// <summary>Inits AccountEntity's mappings</summary>
                private void InitAccountEntityMappings()
                {
                    this.AddElementMapping( "AccountEntity", "MIS", @"dbo", "Account", 58 );
                    this.AddElementFieldMapping( "AccountEntity", "AcctName", "ACCT_NAME", false, "NVarChar", 50, 0, 0, false, "", null, typeof(System.String), 0 );
                    this.AddElementFieldMapping( "AccountEntity", "AcctNo", "ACCT_NO", false, "NVarChar", 30, 0, 0, false, "", null, typeof(System.String), 1 );
                
        }
         

        分析这个结果的数据存放格式和地方,不过有些遗憾。目前我只能做到,根据实体生成SQL语句,也就是框架的做法,对于根据SQL表名或是字段名称,得到它对应的实体名称或是属性名,这个还没有做到。

        第三种技术方案,也就是这里的方案,主动的解析LLBL Gen的设计器项目文件,Framework.llblgenproj

        image

        有三个网格,第一个是显示表名和对应的实体名称,下面的一个,显示这个表的主键,右边的则显示表的所有字段,如果字段是主键,则用阴影背景显示。

        解析Framework.llblgenproj项目文件,它是Xml文件,首推Linq to xml技术。

        首先找到表与实体的映射关联。EntityMappings节点下面的内容,是表与实体映射内容

        EntityName=":Attachment" TargetName="Framework:dbo:Attachment"

        这一层最容易查找,于是填充第一个网格。

        其次找字段,在节点EntityDefinitions下面,分解它的源代码如下所示

        foreach (XElement field in item.Elements("Fields").Elements())
        {
         EntityMappingClass mappingClass = new EntityMappingClass()
               {
               FieldIndex = Convert.ToInt16(field.Attribute("FieldIndex").Value),
               Name = Convert.ToString(field.Attribute("Name").Value),
               Type = Convert.ToString(field.Attribute("Type").Value)
                                                                  };
               if (field.Attribute("MaxLength")!=null)
                     mappingClass.MaxLength = Convert.ToDecimal(field.Attribute("MaxLength").Value);
                            
               if (field.Attribute("Precision")!=null)
                     mappingClass.Precision = Convert.ToInt16(field.Attribute("Precision").Value);
        
               if (field.Attribute("IsOptional")!=null)
                   mappingClass.IsOptional = Convert.ToBoolean(field.Attribute("IsOptional").Value);
                            
               fields.Add(mappingClass);
        }

        对于不一定会存在的节点属性(attribute),要行判断是否存在。

        经过上面一层的查找,已经找到实体及其所有的属性,还需要找实体的属性对应的数据库字段,于是继续分析EntityMappings节点。

        foreach (XElement field in entity.Elements().First().Elements())
        {
            //<FieldMapping FieldName="AttmFile" TargetFieldName="File" 
             var items = (from item in targetfields
                                 where item.Name == field.Attribute("FieldName").Value
                                             select item).First();
        
              items.TargetFieldName = field.Attribute("TargetFieldName").Value;
          }

        如上代码所示,找到属性映射的字段值之后,更新它所映射的字段名称。

        到此,第三个网格,显示实体的属性,已经完成。这里再加点功能,给是主键的字段背景颜色,以方便识别。

        于是,继续查找节点TargetDatabases,它的子节点中有存储字段是否为主键的信息。

        foreach (XElement field in xElement.Elements().First().Elements())
        {
                IsPrimaryKey = false;
                 //<Field Name="Recnum" Ordinal="1"  IsPrimaryKey="true" IsIdentity="true" DbType="5" Precision="8" />
                if (field.Attribute("IsPrimaryKey") != null)
                          IsPrimaryKey = Convert.ToBoolean(field.Attribute("IsPrimaryKey").Value);
        
                var items = (from item in targetfields
                              where item.TargetFieldName == field.Attribute("Name").Value
                              select item).First();
                items.IsPrimarykKey = IsPrimaryKey;
         }

        于是,第二个网格的数据已经找到。

        至此,我已经完整的设计出Entity Mapping功能:根据实体名找出数据库表名,根据表名称,找出对应的程序中的实体名称,二者相互查找。

        Linq技术读取Xml文件确实相当方便,但是不可以用XPath功能,不可以从根节点直接定位到某一层的子节点,Linq to Xml只能一层层的钻取,First/Last然后Elements,再钻取。有时候为了验证代码的正确性,常常借助于Visual Studio的Debugger来写代码,在Immediate Window中输入预想的代码,让它来验证代码是否正确。

      1. 相关阅读:
        axios封装
        python 分析列表中的字典
        python 列表解析学习
        Java常用ORM框架
        Kafka 会不会丢消息?怎么处理的?
        Node.js学习笔记【八】
        Node.js学习笔记【七】
        Node.js学习笔记【六】
        Node.js学习笔记【五】
        Node.js学习笔记【四】
      2. 原文地址:https://www.cnblogs.com/JamesLi2015/p/3090125.html
      Copyright © 2011-2022 走看看