南京袁永福 报表软件 C#.NET ASP.NET

南京PX(对二甲苯)项目,这是个问题。
三位一体的电子表单工具,同时支持WinForm表单,HTML表单和XSLT表单,表单模板在B/S和C/S下具有相同的用户体验. ---- C#.NET新型报表工具,支持WinForm和ASP.NET,WEB报表工具.
袁永福 江西九江人 2001年东南大学动力系毕业 电子邮箱:yyf9989@hotmail.com QQ群:41118220

C#发现之旅第三讲 使用C#开发基于XSLT的代码生成器

C#发现之旅第三讲 使用C#开发基于XSLT的代码生成器

袁永福 2008-5-15

系列课程说明

    为了让大家更深入的了解和使用C#,我们开始这一系列的主题为“C#发现之旅”的技术讲座。考虑到各位大多是进行WEB数据库开发的,而所谓发现就是发现我们所不熟悉的领域,因此本系列讲座内容将是C#在WEB数据库开发以外的应用。目前规划的主要内容是图形开发和XML开发,并计划编排了多个课程。在未来的C#发现之旅中,我们按照由浅入深,循序渐进的步骤,一起探索和发现C#的其他未知的领域,更深入的理解和掌握使用C#进行软件开发,拓宽我们的视野,增强我们的软件开发综合能力。

本系列课程配套的演示代码下载地址为 http://files.cnblogs.com/xdesigner/cs_discovery.zip

本课程说明

    经过以前的学习,我们大体上了解了XML/XSLT开发,在本课程中,我们将在ASP.NET中使用C#开发一个基于XSLT技术的代码生成器。
本系列课程已发布的文章有
C#发现之旅第一讲 C#-XML开发
C#发现之旅第二讲 C#-XSLT开发
C#发现之旅第三讲 使用C#开发基于XSLT的代码生成器
C#发现之旅第四讲 Windows图形开发入门
C#发现之旅第五讲 图形开发基础篇
C#发现之旅第六讲 C#图形开发中级篇
C#发现之旅第七讲 C#图形开发高级篇
C#发现之旅第八讲 ASP.NET图形开发带超链接的饼图
C#发现之旅第九讲 ASP.NET验证码技术
C#发现之旅第十讲 文档对象模型

代码生成器

    首先说说什么是代码生成器。个人认为是一种工具软件,它能根据某种已经固定的信息,使用程序来机械的大批量的生成有结构上有比较简单规律的源代码,从而减少软件开发人员的编码量。

    从广义上讲,我们写的WEB数据库程序都是代码生成器,它们能根据保存在数据库中的固定数据自动生成大量的HTML代码。在这里我们限制代码生成器为通用代码生成器。代码生成器主要功能是帮助程序员自动生成大量的底层代码,这种代码可以是C#Java的程序源代码,也可以是SQL语句,或者HTML代码等等,是一种软件开发过程中的辅助工具软件。

    我们最常用的代码生成器是根据数据库结构自动生成能操作数据库记录的程序源代码,SQL语句或其他文档等等。对于这种代码生成器,其数据信息来源就是数据库的表结构和字段属性等信息,我们可以分析遍历数据库的系统表来货的表结构和字段信息,也可以从PowerDesigner等数据结构设计器保存的文档中获得。

    针对某个特定的项目,我们可以根据数据库结构临时写一个代码生成器,使用字符串拼凑来生成源代码,但这种代码生成器不通用,难于用于其他项目。因此我们更多的是使用通用的代码生成器。

    很多通用代码生成器的原理如图
 
    在这个图中,我们看到代码生成器包含了模板库和代码生成器处理引擎,模板库包含了若干个模板,一个模板一般是纯文本,其中可能包含了脚本代码,或者类似
ASP的结构。生成器引擎加载一个或者多个数据库表结构设计信息,然后调用用户指定的模板,通过某种操作来自动生成另外一个文本文件,这个文本文件内容可以是纯文本,HTML代码,C#代码或者其他。

    考察这个结构,可以发现这个原理和XSLT原理很相似。我们可以将数据库表结构设计信息保存在一个XML文档中,代码生成器模板就使用XSLT格式,代码生成器引擎就使用XSLT转换引擎,这样我们也可以达到代码生成器的功能,从而搞出基于XSLT的代码生成器。


软件设计

    根据基于XSLT来实现代码生成功能的思路,我们开始设计这个代码生成器。

数据来源

    这个代码生成器的数据来源就是数据库表结构设计信息。对于不同的数据库类型得使用不同的方法来获得其表结构设计信息。在此我们针对于Access2000MSSQLServerOracle这三种数据库研究获得其表设计信息的方法,对于其他类型的数据库则以后再说。

    对于MSSQLServer,数据库中有SysObjectsSysColumns这两个系统表,我们可以查询系统表来获得所有的表名称和字段名称以及格式,还有一个sp_helpindex 的系统预定义存储过程来获得指定表的字段索引信息。

    对于Oracle,数据库有一个名为Col的系统预定义视图,我们可以查询这个视图获得所有的表名和字段定义信息。还有一个 user_ind_columns的系统预定义视图,我们可以关键字段信息。

    对于Access2000数据库,没有这些系统表,因此我们使用。NET框架中的OleDB的数据连接对象的GetOleDbSchemaTable函数来获得数据库表和字段定义信息。

    我们的代码生成器还应当从一下数据结构设计器生成的文档导入表结构设计信息,这里我们决定从PowerDesigner生成的PDM文件中导入表设计信息,因为PDM文件是XML格式的,处理方便。

代码生成模板

    这里的代码生成模板就采用XSLT格式。为了方便软件开发和维护,我们将模板保存在程序目录下的XSLT扩展名的文件中,并约定文件名使用下划线开头。

程序

    在这里使用ASP.NET中实现该代码生成器,使用.NET框架自带的XSLT转换对象来作为代码生成器处理引擎,并使用HTML格式来展示生成的源代码。

程序说明

    根据上述的软件设计,我们开发出了这个代码生成器,现对其源代码进行详细说明。

xslcreatecode.aspx

    本代码生成器很简单,只有一个ASPX页面,打开该页面的设计界面,

    可以看到上面放置了一些简单的控件。其中比较重要的有

    数据表名下拉列表,该列表列出了数据库中所有数据表的名称。

    XSLT模板名称下拉列表,该列表列出了所有系统可用的XSLT模板文件的名称。

    刷新系统按钮,用于刷新系统数据设置,重新填充数据表名列表和模板名列表。

    创建代码,根据当前选择的数据表名和XSLT模板名称创建代码。

    生成的源代码文本标签,使用HTML格式来显示生成的源代码。

    打开这个页面的C#代码,可以看到其代码也不复杂。这个页面的Page_Load函数调用了刷新系统的方法RefreshSystem

/// <summary>
/// 刷新系统
/// </summary>
private void RefreshSystem( )
{
    DataBaseInfo info = this.GetInfo();
    this.lblResult.Text = info.Name ;
    if( cboTable.Items.Count == 0 )
    {
        cboTable.Items.Add( new ListItem("所有表" , "所有表" ));
        foreach( TableInfo table in info.Tables )
        {
            cboTable.Items.Add( new ListItem( table.Name , table.Name ));
        }
    }
    if( cboXSLT.Items.Count == 0 )
    {
        cboXSLT.Items.Add("XML代码");
        string[] names = System.IO.Directory.GetFiles( this.MapPath(".") , "_*.xslt");
        if( names != null && names.Length > 0 )
        {
            foreach( string name in names )
            {
                string name2 = System.IO.Path.GetFileNameWithoutExtension( name );
                this.cboXSLT.Items.Add( new ListItem( name2 , name2 ));
            }
        }
    }
}//private void RefreshSystem( )
/// <summary>
/// 获得数据库结构信息对象
/// </summary>
/// <returns>数据库结构信息对象</returns>
private DataBaseInfo GetInfo( )
{
    DataBaseInfo info = this.Session["info"] as DataBaseInfo ;
    if( info == null )
    {
        info = new DataBaseInfo();
        info.LoadFromAccess2000( this.MapPath("demomdb.mdb"));
        this.Session["info"] = info ;
    }
    return info ;
}      

    在RefreshSystem方法中,首先获得数据库结构信息对象,遍历其中的表结构信息对象,向数据表名下列列表填充项目。

    遍历网站目录下的所有以下划线开头的XSLT文件,将其文件名填充到XSLT模板下拉列表中。

    这里使用了另外一个函数GetInfo,该函数就是获得系统使用的数据库结构信息对象,它是缓存在session中的对象,它使用了程序目录下的演示数据库作为数据结构信息来源。

    页面代码中还有强制刷新系统按钮事件处理。

/// <summary>
/// 刷新系统按纽事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void cmdRefresh_Click(object sender, System.EventArgs e)
{
    this.Session["info"] = null;
    this.cboTable.Items.Clear();
    this.cboXSLT.Items.Clear();
    RefreshSystem( );
}//private void cmdRefresh_Click(object sender, System.EventArgs e)

    这个处理过程比较简单,将缓存的数据结构信息对象删除掉,清空数据表名列表和模板列表,然后调用RefreshSystem方法刷新界面。

    这个页面最重要的代码就是自动生成并显示代码的过程了。其C#代码为

mmary>
/// 创建代码按纽事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void cmdCreate_Click(object sender, System.EventArgs e)
{
    DataBaseInfo info = this.GetInfo();
    string xml = null ;
    if( cboTable.SelectedIndex == 0 )
    {
        xml = GetXMLString( info );
    }
    else
    {
        TableInfo table = info.Tables[ this.cboTable.SelectedValue ] ;
        if( table == null )
        {
            this.lblResult.Text = "请选择一个表";
            return ;
        }
        xml = GetXMLString( table );
    }
    string html = "";
    if( cboXSLT.SelectedIndex <= 0 )
    {
        // 没有使用任何模板,直接显示XML源代码
        html = @"<textarea
                    wrap=off
                    readonly
                    style='border:1 solid black;
                            overflow=visible;
                            background-color:#dddddd'>"
            + xml + "</textarea>";
    }
    else
    {
        // 启动了XSLT模板,执行XSLT转换
        System.Xml.Xsl.XslTransform transform = new System.Xml.Xsl.XslTransform();
        transform.Load( this.Server.MapPath( this.cboXSLT.SelectedValue ) + ".xslt" );
        System.IO.StringWriter writer = new System.IO.StringWriter();
        System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
        doc.LoadXml( xml );
        transform.Transform( doc , null , writer , null );
        writer.Close();
        html = writer.ToString();
    }
    this.lblResult.Text = "<b>共生成 "
        + html.Length
        + " 个字符</b><br />\r\n" + html ;
}

    在生成代码按钮事件处理中,首先根据数据表名列表获得当前数据表结构信息对象,并生成XML字符串。若用户指定了某个数据表则调用GetXMLString函数来生成该数据表的XML字符串,否则对整个数据结构信息对象生成XML字符串。

    若用户没有指定XSLT模板,则生成直接显示XML代码的HTML代码。这里使用textarea元素来显示XML代码,这样XML代码显示时不需要进行转义处理。

    若用户指定了XSLT模板名称,则创建一个XslTransform对象,使用Load方法从程序目录中加载该名称的XSLT模板文件。这里可以看出我们的程序需要访问程序目录的权限,因此部署这个代码生成器时是需要配置权限的,使得程序能访问程序所在目录。

    程序还创建一个XmlDocument对象,调用它的LoadXml方法来加载刚刚生成的XML字符串,然后执行XSLT转换,转换结果就当作HTML代码准备显示了。

    最后程序根据生成的HTML代码设置到一个标签控件中。

    代码中还定义了一个GetXMLString函数,该函数能将一个对象序列化成一个带缩进的XML字符串。其内部使用了XmlSerializer对象。使用XmlSerializer对象能很方便的将对象的公开属性导出为XML文档。

/// <summary>
/// 将指定对象序列化成XML文档,然后返回获得的XML字符串
/// </summary>
/// <param name="obj">对象</param>
/// <returns>XML字符串</returns>
private string GetXMLString( object obj )
{
    System.IO.StringWriter myStr = new System.IO.StringWriter();
    System.Xml.XmlTextWriter writer = new System.Xml.XmlTextWriter( myStr );
    writer.Indentation = 3 ;
    writer.IndentChar = ' ';
    writer.Formatting = System.Xml.Formatting.Indented ;
    System.Xml.Serialization.XmlSerializer sc =
        new System.Xml.Serialization.XmlSerializer( obj.GetType() );
    sc.Serialize( writer , obj );
    writer.Close();
    string xml = myStr.ToString();
    int index = xml.IndexOf("?>");
    if( index > 0 )
        xml = xml.Substring( index + 2 );
    return xml.Trim() ;
}

DataBaseInfo.cs

    本代码生成器包括了DataBaseInfo.cs文件,其中就是比较底层的描述数据库表结构和字段设计信息的对象。这个文件中定义了多个类型,其中DataBaseInfo表示一个数据库,TableInfo表示数据表设计信息可,FieldInfo表示数据库字段设计信息。这三个类型构成了一个数据库-数据表-字段的三层对象树状结构。TableInfoFieldInfo的代码不复杂。现在说一下DataBaseInfo类型中的一些代码。

    首先看看LoadFromPDMXMLFileLoadFromPDMXMLDocument函数,这个函数能分析PDM格式的XML文档,找出其中的数据表和字段设计信息并填充到内部结构中。某些版本的PowerDesigner生成的PDM文件是XML格式的,这方便其他程序能加载其中的数据结构设计信息。关于这个函数详细的处理过程,大家可以参考一个PDM的文件仔细分析。

/// <summary>
/// 从PDM数据结构定义XML文件中加载数据结构信息
/// </summary>
/// <param name="doc">XML文档对象</param>
/// <returns>加载的字段信息个数</returns>
public int LoadFromPDMXMLDocument( XmlDocument doc )
{
    intFillStyle = FillStyleConst.PDM ;
    int RecordCount = 0 ;
    myTables.Clear();
    XmlNamespaceManager nsm = new XmlNamespaceManager( doc.NameTable );
    nsm.AddNamespace( "a" , "attribute" );
    nsm.AddNamespace( "c" , "collection" );
    nsm.AddNamespace( "o" , "object");
    XmlNode RootNode = doc.SelectSingleNode("/Model/o:RootObject/c:Children/o:Model" , nsm );
    if( RootNode == null )
        return 0 ;
    strName = ReadXMLValue( RootNode , "a:Name" , nsm );
    strDescription = strName ;
    // 数据表
    foreach( XmlNode TableNode in RootNode.SelectNodes("c:Tables/o:Table" , nsm ))
    {
        TableInfo table = new TableInfo();
        myTables.Add( table );
        table.Name = ReadXMLValue( TableNode , "a:Code" , nsm );
        table.Remark = ReadXMLValue( TableNode , "a:Name" , nsm );
        string keyid = ReadXMLValue( TableNode , "c:PrimaryKey/o:Key/@Ref" , nsm );
        System.Collections.Specialized.StringCollection Keys =
            new System.Collections.Specialized.StringCollection();
        if( keyid != null )
        {
            foreach( XmlNode KeyNode in TableNode.SelectNodes(
                "c:Keys/o:Key[@Id = '" + keyid + "']/c:Key.Columns/o:Column/@Ref" , nsm ))
            {
                Keys.Add( KeyNode.Value );
            }
        }
        foreach( XmlNode FieldNode in TableNode.SelectNodes("c:Columns/o:Column" , nsm ))
        {
            RecordCount ++ ;
            string id = ( ( XmlElement )  FieldNode).GetAttribute("Id");
            FieldInfo field = new FieldInfo();
            table.Fields.Add( field );
            field.Name = ReadXMLValue( FieldNode , "a:Code" , nsm );
            field.Remark = ReadXMLValue( FieldNode , "a:Name" , nsm );
            field.Description = ReadXMLValue( FieldNode , "a:Comment" , nsm );
            string FieldType = ReadXMLValue( FieldNode , "a:DataType" , nsm );
            if( FieldType != null )
            {
                int index = FieldType.IndexOf("(");
                if( index > 0 )
                    FieldType = FieldType.Substring( 0 , index );
            }
            field.FieldType = FieldType ;
            field.FieldWidth = ReadXMLValue( FieldNode , "a:Length" , nsm );
            if( Keys.Contains( id ))
                field.PrimaryKey = true;
        }
    }
    return RecordCount ;
}
private string ReadXMLValue(
    System.Xml.XmlNode node ,
    string path ,
    System.Xml.XmlNamespaceManager nsm )
{
    System.Xml.XmlNode node2 = node.SelectSingleNode( path  , nsm );
    if( node2 == null )
        return null ;
    else
    {
        if( node2 is System.Xml.XmlElement )
            return ( ( System.Xml.XmlElement ) node2).InnerText ;
        else
            return node2.Value ;
    }
}

    LoadFromAccess2000函数能从一个Access2000格式的数据库中加载数据结构设计信息。这个函数内部使用了OleDbConnection对象的GetOleDbSchemaTable方法可以获得数据库的一些信息,具体什么样式可以参考MSND中关于GetOleDbSchema方法的详细说明。对于不同的数据库其处理过程是不同的,在这里的使用方法是我经过反复测试得到的,而且只适用于Access2000数据库。在这里首先是获得所有的数据表名和字段设计信息,然后获得字段索引信息。

/// <summary>
/// 从 Jet40( Access2000 ) 的数据库中加载数据结构信息
/// </summary>
/// <param name="myConn">数据库连接对象</param>
/// <returns>加载的字段信息个数</returns>
public int LoadFromAccess2000( OleDbConnection myConn )
{
    intFillStyle = FillStyleConst.Access2000 ;
    int RecordCount = 0 ;
    myTables.Clear();
    string dbName = myConn.DataSource ;
    if( dbName != null )
        strName = System.IO.Path.GetFileName( dbName );
    using(System.Data.DataTable myDataTable =
                myConn.GetOleDbSchemaTable( System.Data.OleDb.OleDbSchemaGuid.Columns , null))
    {
        foreach( System.Data.DataRow myRow in myDataTable.Rows )
        {
            string strTable = Convert.ToString( myRow["TABLE_NAME"] );
            if( ! strTable.StartsWith("MSys"))
            {
                TableInfo myTable = myTables[ strTable ] ;
                if( myTable == null )
                {
                    myTable = new TableInfo();
                    myTable.Name = strTable ;
                    myTables.Add( myTable );
                }
                FieldInfo myField = new FieldInfo();
                myTable.Fields.Add( myField );
                myField.Name  = Convert.ToString( myRow["COLUMN_NAME"]);
                myField.Nullable = Convert.ToBoolean( myRow["IS_NULLABLE"]);
                System.Data.OleDb.OleDbType intType = (System.Data.OleDb.OleDbType)
                    Convert.ToInt32( myRow["DATA_TYPE"]);
                if( System.DBNull.Value.Equals( myRow["DESCRIPTION"] ) == false )
                {
                    myField.Remark = Convert.ToString( myRow["DESCRIPTION"] ) ;
                }
                if( intType == System.Data.OleDb.OleDbType.WChar )
                {
                    myField.FieldType = "Char" ;
                }
                else
                {
                    myField.FieldType  = intType.ToString();
                }
                myField.FieldWidth  = Convert.ToString( myRow["CHARACTER_MAXIMUM_LENGTH"]);
                RecordCount ++ ;
            }
        }//foreach
    }//using
    using( System.Data.DataTable myDataTable =
                myConn.GetOleDbSchemaTable( System.Data.OleDb.OleDbSchemaGuid.Indexes , null))
    {
        foreach( System.Data.DataRow myRow in myDataTable.Rows )
        {
            string strTable = Convert.ToString( myRow["TABLE_NAME"] );
            TableInfo myTable = myTables[ strTable ];
            if( myTable != null )
            {
                FieldInfo myField = myTable.Fields[ Convert.ToString( myRow["COLUMN_NAME"])];
                if( myField != null)
                {
                    myField.Indexed  = true;
                    myField.PrimaryKey = ( Convert.ToBoolean( myRow["PRIMARY_KEY"]));
                }
            }
        }//foreach
    }//using
    return RecordCount ;
}//public int LoadFromAccess2000( OleDbConnection myConn )

    LoadFromOracle函数用于分析Oracle数据库而获得表结构和字段设计信息。其代码如下。

    在ORACLE数据库中,有一个名为COL的系统预定义视图,里面就是各个数据表名和字段定义信息,还有一个名为user_ind_columns的预定义视图,里面就保存着字段索引信息。

    我们首先查询遍历COL视图,获得该视图中保存的数据表名,字段名,字段数据类型,字段长度等信息。建立起基本的表和字段信息结构,然后查询遍历user_ind_columns视图,获得其关键字段信息。

/// <summary>
/// 从 Oracle 加载数据库结构信息
/// </summary>
/// <param name="myConn">数据库连接对象</param>
/// <returns>加载的字段信息个数</returns>
public int LoadFromOracle( IDbConnection  myConn )
{
    intFillStyle = FillStyleConst.Oracle ;
    int RecordCount = 0 ;
    string strSQL = null;
    strSQL = "Select TName,CName,coltype,width  From Col Order by TName,CName";
    myTables.Clear();
    if( myConn is OleDbConnection )
    {
        strName =  ( ( System.Data.OleDb.OleDbConnection ) myConn ).DataSource
            + " - " + myConn.Database ;
    }
    else
        strName = myConn.Database ;
    using( System.Data.IDbCommand myCmd = myConn.CreateCommand())
    {
        myCmd.CommandText = strSQL ;
        IDataReader myReader = myCmd.ExecuteReader( CommandBehavior.SingleResult );
        TableInfo LastTable = null;
        while( myReader.Read())
        {
            string TableName = myReader.GetString(0).Trim();
            if( LastTable == null || LastTable.Name != TableName )
            {
                LastTable = new TableInfo();
                myTables.Add( LastTable );
                LastTable.Name = TableName ;
            }
            FieldInfo NewField = new FieldInfo();
            LastTable.Fields.Add( NewField );
            NewField.Name = myReader.GetString(1);
            NewField.FieldType = myReader.GetString(2);
            NewField.FieldWidth = myReader[3].ToString();
            RecordCount ++ ;
        }//while
        myReader.Close();
        myCmd.CommandText = @"
select table_name ,
    column_name ,
    index_name
from user_ind_columns
order by table_name , column_name ";
        myReader = myCmd.ExecuteReader( CommandBehavior.SingleResult );
        TableInfo myTable = null;
        while( myReader.Read())
        {
            myTable = myTables[ myReader.GetString(0)];
            if( myTable != null )
            {
                string IDName = myReader.GetString(2);
                string FieldName = myReader.GetString(1);
                FieldInfo myField = myTable.Fields[ FieldName ];
                if( myField != null )
                {
                    myField.Indexed = true ;
                    if( IDName.StartsWith("PK") )
                    {
                        myField.PrimaryKey = true;
                    }
                }
            }
        }//while
        myReader.Close();
    }//using
    return RecordCount ;
}//public int LoadFromOracle( System.Data.IDbConnection myConn )

    LoadFromSQLServer函数用于分析一个MSSQLServer数据库,加载其表和字段设计信息。其代码如下。在SQLSERVER中包含了一些系统表,比如SysObjectsSysColumns等等,里面就存储了系统中所有对象的信息,比如表,字段,存储过程,触发器等等。我们就可以从这些系统表中查询所有的表和字段定义信息。SQLSERVER中还有一个名为sp_helpindex的系统预定义存储过程,可用来查询指定表的索引信息。

    在代码中我们首先使用一个比较复杂的SQL语句从系统表中查询数据库中所有的数据表名,字段名,字段类型和长度等信息。这里的SQL语句是我个人摸索的,相信大家可以写出更好更准确的SQL语句。我们读取查询结果就可以构造出基本的表和字段对象结构,然后针对每一个表对象,调用sp_helpindex存储过程,获得数据表中定义的关键字段信息。

/// <summary>
/// 从 SQLServer 中加载数据库结构信息
/// </summary>
/// <param name="myConn">数据库连接对象</param>
/// <returns>加载的字段信息个数</returns>
public int LoadFromSQLServer( IDbConnection myConn )
{
    intFillStyle = FillStyleConst.SQLServer ;
    int RecordCount = 0 ;
    if( myConn is OleDbConnection )
        strName = ( ( OleDbConnection ) myConn ).DataSource ;
    else if( myConn is System.Data.SqlClient.SqlConnection )
        strName = ( ( System.Data.SqlClient.SqlConnection ) myConn ).DataSource ;
    strName = strName + " - " + myConn.Database ;
    string strSQL = null;
    strSQL = @"
select
    sysobjects.name ,
    syscolumns.name  ,
    systypes.name ,
    syscolumns.length ,
    syscolumns.isnullable ,
    sysobjects.type
from
    syscolumns,
    sysobjects,
    systypes
where
    syscolumns.id=sysobjects.id
    and syscolumns.xusertype=systypes.xusertype
    and (sysobjects.type='U' or sysobjects.type='V' )
    and systypes.name <>'_default_'
    and systypes.name<>'sysname'
order by
    sysobjects.name,
    syscolumns.name";
    myTables.Clear();
    using( System.Data.IDbCommand myCmd = myConn.CreateCommand())
    {
        myCmd.CommandText = strSQL ;
        IDataReader myReader = myCmd.ExecuteReader( CommandBehavior.SingleResult );
        TableInfo LastTable = null;
        while( myReader.Read())
        {
            string TableName = myReader.GetString(0).Trim();
            if( LastTable == null || LastTable.Name != TableName )
            {
                LastTable = new TableInfo();
                myTables.Add( LastTable );
                LastTable.Name = TableName ;
                LastTable.Tag = Convert.ToString( myReader.GetValue( 5 ));
            }
            FieldInfo NewField = new FieldInfo();
            LastTable.Fields.Add( NewField );
            NewField.Name = myReader.GetString(1);
            NewField.FieldType = myReader.GetString(2);
            NewField.FieldWidth = myReader[3].ToString();
            if( myReader.IsDBNull( 4 ) == false)
                NewField.Nullable = (myReader.GetInt32(4) == 1);
            RecordCount ++ ;
        }//while
        myReader.Close();
        // 加载主键信息
        for( int iCount = myTables.Count - 1 ; iCount >= 0 ; iCount -- )
        {
            TableInfo myTable = myTables[ iCount ] ;
            if( string.Compare( ( string ) myTable.Tag , "U" , true ) == 0 )
            {
                try
                {
                    myCmd.CommandText = "sp_helpindex \"" + myTable.Name + "\"" ;
                    //myCmd.CommandType = System.Data.CommandType.Text ;
                    myReader = myCmd.ExecuteReader( );
                    while( myReader.Read())
                    {
                        string strKeyName = myReader.GetString(0);
                        string strDesc = myReader.GetString(1);
                        string strFields = myReader.GetString(2);
                        bool bolPrimary = ( strDesc.ToLower().IndexOf("primary") >= 0 );
                        foreach( string strField in strFields.Split(','))
                        {
                            FieldInfo myField = myTable.Fields[ strField.Trim()];
                            if( myField != null)
                            {
                                myField.Indexed = true;
                                myField.PrimaryKey = bolPrimary ;
                            }
                        }//foreach
                    }//while
                    myReader.Close();
                }
                catch( Exception ext )
                {
                    //this.List.Remove( myTable );
                    myTable.Name = myTable.Name + " " + ext.Message ;
                }
            }
        }//foreach
    }//using
    return RecordCount ;
}//public int LoadFromSQLServer( System.Data.IDbConnection myConn )

    目前DataBaseInfo对象只能分析Access2000SQLSERVEROralce数据库,大家以后可以完善它,使得它能分析比如DB2MYSQL等其他数据库类型。在未来的软件开发过程中,若需要分析数据库结构的,则只要调用这个DataBaseInfo就可以了。

    在本演示程序中,我们只是用程序目录下的一个Access2000数据库作为例子,因此也只调用了LoadFromAccesss2000这个函数,其他的分析SQLSERVERORACLE的函数没用到。在未来当这个代码生成器经过改善而投入实际应用时,它就能分析SQLSERVERORACLE等企业级数据库了。

    在主页面xslcreatecode.aspx中定义了一个GetXMLString函数,它能将一个对象序列化成一个XML文档。这里的DataBaseInfoTableInfoFieldInfo都能XML序列化。在执行XML序列化时,系统会分析对象类型,遍历对象所有的公开字段和可读写属性,然后将这些属性值输出到XML文档,若遇到对象树状结构,则会递归遍历这个树状结构,对对象中的每一个下属对象都会建立一个XML子元素进行输出。在这里DataBaseInfoTableInfoFieldInfo构成了三层的树状结构,因此生成的XML文档也是多层次的。

    一般来说,序列化生成的XML文档中,XML元素的名称等于对象类型的名称和公开字段属性的名称,但可以通过添加特性来改变这种默认行为,在类型TableInfo的定义前面加上了特性XmlType,在这里指明了为类型TableInfo生成的XML元素名称不是对象类型名称TableInfo,而是Table

[System.Xml.Serialization.XmlType("Table")]
public class TableInfo

    同样的方式,我们为类型FieldInfo指定了XML元素名称为Field,这里展示了特性在C#中的应用。关于特性在未来的某节课程中将讲到。

    由于能执行XML序列化的属性必须是可读写的,因此在类型FieldInfo中的IsStringIsInteger等属性为了能执行XML序列化,因此定义了毫无作用的set方法。

XSLT模板说明

    程序目录下放置了一些以下划线开头的扩展名为XSLT的文件,这就是代码生成器使用的代码生成模板。在主界面中使用不同的模板就能生成不同的代码。在这里我们以_cshaprhashtable.xslt为例子进行说明。

_cshaprhashtable.xslt

    首先我们在界面中选择数据表Customers,可以生成它的XML代码为.

<Table    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <Name>Customers</Name>
   <Fields>
      <Field>
         <Name>Address</Name>
         <Remark>地址</Remark>
         <FieldType>Char</FieldType>
         <IsString>true</IsString>
         <IsInteger>false</IsInteger>
         <IsBoolean>false</IsBoolean>
         <IsNumberic>false</IsNumberic>
         <IsDateTime>false</IsDateTime>
         <IsBinary>false</IsBinary>
         <ValueTypeName>System.String</ValueTypeName>
         <FieldWidth>60</FieldWidth>
         <Nullable>true</Nullable>
         <PrimaryKey>false</PrimaryKey>
         <Indexed>false</Indexed>
      </Field>
      <Field>
         <Name>City</Name>
         <Remark>城市</Remark>
         <FieldType>Char</FieldType>
         <IsString>true</IsString>
         <IsInteger>false</IsInteger>
         <IsBoolean>false</IsBoolean>
         <IsNumberic>false</IsNumberic>
         <IsDateTime>false</IsDateTime>
         <IsBinary>false</IsBinary>
         <ValueTypeName>System.String</ValueTypeName>
         <FieldWidth>15</FieldWidth>
         <Nullable>true</Nullable>
         <PrimaryKey>false</PrimaryKey>
         <Indexed>false</Indexed>
      </Field>
      <Field>其他字段....</Field>
   </Fields>
</Table>

    而模板_cshaprhashtable.xslt的代码为

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <!--
    根据表结构XML文档创建影射数据库字段的 C# 代码,内部使用 Hashtable 来存储字段数据
    编制 袁永福 2008-1-17
-->
    <xsl:template match="/*">
        <xsl:if test="name(.) != 'Table' ">
            <font color="red">本模板只能用于单表</font>
            <br />
        </xsl:if>
        <textarea wrap="off" readonly="1" style="
                border:1 solid black;
                overflow=visible;
                background-color:#dddddd">   
            <xsl:variable name="classname">
                <xsl:value-of select="concat('DB2_' , Name )" />
            </xsl:variable>
//*****************************************************************************
//  文件名 <xsl:value-of select="Name" />.cs
//*****************************************************************************
/// <summary>
/// 数据库表 <xsl:value-of select="Name" />
                <xsl:if test="Remark!=''">
                    <xsl:value-of select="concat(' [',Remark,']')" />
                </xsl:if> 操作对象
/// </summary>
/// <remark>
/// 该表有<xsl:value-of select="count(Fields/Field)" />个字段
/// 编制: 代码生成器
/// 时间:
///</remark>
[System.Serializable()]
public class <xsl:value-of select="$classname" />
{   
    ///<summary>返回数据表名称 <xsl:value-of select="Name" /></summary>
    public static string TableName
    {
        get{ return "<xsl:value-of select="Name" />" ; }
    }
    ///<summary>返回所有字段的名称</summary>
    <xsl:text>
    public static string[]FieldNames
    {
        get
        {
            return new string[]{ </xsl:text>
            <xsl:for-each select="Fields/Field">
                <xsl:if test="position()>1">
                    <xsl:text>
                               ,</xsl:text>
                </xsl:if>
                <