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中输入预想的代码,让它来验证代码是否正确。

      posted @ 2013-05-21 09:27  信息化建设  阅读(1374)  评论(1编辑  收藏  举报