分享

再谈代码生成器,xml+xslt,结合扩展,用上设计模式。

 quasiceo 2013-03-04

再谈代码生成器,xml+xslt,结合扩展,用上设计模式。

不记得有多久没写博客了,一贯写懒博客不喜欢写注释和解说,今天又是上班时间,看看博客园两个周末来没多少文章,也就奉献一下自己这几天的研究。

需求:应为公司有自己的crm,也就是我前面有介绍的CWF这一套。对于数据库来说model的生成成了问题,尝试过动软和其他的代码生成器,可惜都不能生成想要的完整代码,所以就自己去研究打算搞一个让自己比较满意的。

可是到现在为止还是算个半成品,本打算今天写完批量生成再发布的,后面已经没多少工作量了,发上来也没什么关系。

还未完成的:数据批量生成。解决方案:异步线程+访问者设计模式。

废话有点多了。

看效果:

image

 

解决方案:

xml+xslt方式解析代码。

设计模式:

多个模版提供了多模版操作接口,考虑到要支持多个数据库,所以做了接口。

 

IDBModel 针对不同数据库进行操作

ITemplate 可扩展多个模版处理方式

思路是向ITemplate 提供IDBModel  转换的xml数据库结构,然后生成代码。

ITemplate 接收xml。xml的格式如下:

<?xml version="1.0" encoding="utf-8" ?>
<table>
  <tablename>ArticleTag</tablename>
  <directions>CWF代码生成器自动生成代码</directions>
  <fileds>
    <filed>
      <name>ID</name>
      <isprimary>true</isprimary>
      <isidentify>false</isidentify>
      <dbtype>string</dbtype>
      <defaultvalue></defaultvalue>
      <length>50</length>
      <isforeign>false</isforeign>
    </filed>
    <filed>
      <name>ArticleID</name>
      <isprimary>false</isprimary>
      <isidentify>false</isidentify>
      <dbtype>string</dbtype>
      <defaultvalue></defaultvalue>
      <length>50</length>
      <isforeign>true</isforeign>
    </filed>
    <filed>
      <name>Identifier</name>
      <isprimary>false</isprimary>
      <isidentify>false</isidentify>
      <dbtype>string</dbtype>
      <defaultvalue></defaultvalue>
      <length>50</length>
      <isforeign>false</isforeign>
    </filed>
    <filed>
      <name>Updated</name>
      <isprimary>false</isprimary>
      <isidentify>false</isidentify>
      <dbtype>DateTime</dbtype>
      <defaultvalue></defaultvalue>
      <length>50</length>
      <isforeign>false</isforeign>
    </filed>
    <filed>
      <name>Created</name>
      <isprimary>false</isprimary>
      <isidentify>false</isidentify>
      <dbtype>DateTime</dbtype>
      <defaultvalue></defaultvalue>
      <length>50</length>
      <isforeign>false</isforeign>
    </filed>
  </fileds>
</table>

如果觉得以上的字段类型还不够你需要的,没问题,继承ITemplate ,然后自己写解析xml的方式,然后编辑xslt模版完全搞定。

先看接口的设计。

/// <summary>
/// 数据对象
/// </summary>
public interface IDBModel
{
     /// <summary>
     /// 数据操作对象
     /// </summary>
     IDbHelper DBH { get; set; }
     /// <summary>
     /// 返回所有节点
     /// </summary>
     TreeNode Tree { get; }
     /// <summary>
     /// 返回所有表
     /// </summary>
     TreeNode ReadTables { get; }
     /// <summary>
     /// 返回所有视图/查询
     /// </summary>
     TreeNode ReadViews { get; }
     /// <summary>
     /// 返回所有存储过程/方法
     /// </summary>
     TreeNode ReadProcs { get; }
     /// <summary>
     /// 返回所有函数
     /// </summary>
     TreeNode ReadFuncs { get; }
     /// <summary>
     /// 返回所有列
     /// </summary>
     /// <param name="TableObject">表名/表-objectID</param>
     /// <returns></returns>
     IList<ColumnAttribute> Columns(string TableObject);
     /// <summary>
     /// 转换表为xml
     /// </summary>
     /// <param name="TableObject">表名/表-objectID</param>
     /// <returns></returns>
     XmlNode Convert(string TableObject);
}
考虑到针对于不同数据库搜索表和表结构的方式不一样,这里就把相关操作写到数据的model接口里,这样就解耦合了,前面程序不必知道目前操作的是那中数据库,只关心业务逻辑。

也许你也注意到了,这个接口有个核心方法就是Convert,他负责把表结构解析成Itemplate认识的xml结构。

/// <summary>
    /// 模版操作接口
    /// </summary>
    public interface ITemplate
    {
        /// <summary>
        /// 代码路径
        /// </summary>
        string CodePath { get; set; }
        /// <summary>
        /// 模版内容
        /// </summary>
        string Content { get; set; }
        /// <summary>
        /// 模版路径
        /// </summary>
        string Path { get; set; }
        /// <summary>
        /// 创建新模版
        /// </summary>
        void Create();
        /// <summary>
        /// 读取模版
        /// </summary>
        /// <returns></returns>
        string Read();
        /// <summary>
        /// 根据路径读取模版
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        string Read(string path);
        /// <summary>
        /// 保存模版
        /// </summary>
        /// <returns></returns>
        bool Save();
        /// <summary>
        /// 保存指定路径
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        bool Save(string path);
        /// <summary>
        /// 生成model返回生成后的代码
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        string CreateCode(XmlNode node);
        /// <summary>
        /// 生成model返回生成后的代码
        /// </summary>
        /// <param name="doc"></param>
        /// <returns></returns>
        string CreateCode(XmlDocument doc);
        /// <summary>
        /// 保存代码
        /// </summary>
        /// <returns></returns>
        bool SaveCode(XmlNode node);
        /// <summary>
        /// 保存代码到指定文件夹
        /// </summary>
        /// <param name="codePath"></param>
        /// <returns></returns>
        bool SaveCode(XmlNode node, string codePath);

 

从这个接口的方法可以看出他有连个作用,1生成和操作xslt模版,还有一个就是生成代码和保存代码。为什么我没有把生成代码和该接口分开呢。原因很简单,我不想再去多一个业务逻辑类,免得维护麻烦,反正写来也是给自己用,不发布到商业上,也没多大关系,如果读者你觉得如此不妥也可以分开来。再者,也可以根据不同的模版解析方式生成代码,也就是你可以不使用xslt来解析。这样在另一方面也得到了灵活性。

系统里有个thiscontent的上下文对象,他负责创建template和model 还有常见的应用类,比如缓存什么的。也是系统的核心部分。他能根据选择的不同数据库来创建不同的template 和Model提供给前台业务对象。

public static class ThisContent
{
    public static IDbHelper DBH
    {
        get {
            if (Cache[SysCache.DBH].Value == null)
            {
                Cache[SysCache.DBH].Value=CreateDBH();
            }
            return (IDbHelper)Cache[SysCache.DBH].Value;
        }
    }
    public static string ConnectionString
    {
        get;
        set;
    }

//缓存操作类
    public static ICacheContent Cache
    {
        get { return CacheService.Cache.GetCahe()["ModelDesigner"]; }
    }
    public static string DataBaseType
    {
        get;
        set;
    }

///对日志操作
    public static IDocumentServer<string> Log
    {
        get
        {
            IDocumentServer<string> tls = null;
            if (Cache["log"].Value == null)
            {
                tls = ServerSession.log;
                Cache["log"].Value = tls;
            }
            else
            {
                tls = (IDocumentServer<string>)Cache["log"].Value;
            }
            return tls;
        }
    }

//获取系统根目录。内部操作方式请参见我前面的博客
    public static string AppPath
    {
        get { return ServerSession.AppPath(); }
    }

//默认模版路径
    public static string DefaultTmpPath
    {
        get { return "Template"; }
    }

//创建类工厂
    public static IFactory Factory {
        get {
            IFactory f;
            if (Cache["Facotry"].Value == null)
            {
                f = new ClassBuilderFacoty();
                Cache["Facotry"].Value = f;
            }
            else
            {
                f = (IFactory)Cache["Facotry"].Value;
            }
            return f;
        }
    }

//创建数据库操作类

    private static IDbHelper CreateDBH()
    {
        if (DataBaseType.Trim().Length <= 0) return null;
        if (ConnectionString.Trim().Length <= 0) throw new Exception("先设置链接字符串");
        switch (DataBaseType)
        {

              //如此这般你可以扩展。
            case "SQL Server":
                return new SQLHelper(ConnectionString);
                //break;
            case "Access":
                return new AccessHelper(ConnectionString);
                //break;
            default:
                return null;
        }
    }
    public static string DBName { get; set; }

//注释:你知道的。
    public static IDBModel ModelHelper {
        get {
            IDBModel m;
            if (Cache["ModelHelper"].Value == null)
            {
                switch (DataBaseType)
                {
                    case "Access":
                        m = Factory.Create<BaseAccessModel>();
                        break;
                    case "SQL Server":
                        m = Factory.Create<BaseSQLModel>();
                        break;
                    default:
                        m = Factory.Create<BaseSQLModel>();
                        break;
                }
                Cache["ModelHelper"].Value = m;
            }
            else
            {
                m = (IDBModel)Cache["ModelHelper"].Value;
            }
            return m;
        }
    }

//注释:你知道的。
    public static ITemplate Template
    {
        get {
            ITemplate t;
            if (Cache["Template"].Value == null)
            {

                 t = Factory.Create<BaseTemplate>();
                 Cache["Template"].Value = t;
            }
            else
            {
                t = (ITemplate)Cache["Template"].Value;
            }
            return t;
        }
    }

//注释:麻烦看方法名
    public static void BuilderConnectString(string dataServer,string DataName,string userName,string userPwd)
    {
        string connstr = string.Empty;
        StringBuilder sb=new StringBuilder();
        switch (DataBaseType)
        {
            case "Access":
                connstr = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0};Persist Security Info=True";
                break;
            case "SQL Server":
                connstr = "Data Source={0};Initial Catalog={1};Persist Security Info=True;User ID={2};Password={3}";
                break;
        }
        object[] str = new object[] { dataServer, DataName, userName, userPwd };
        ConnectionString = sb.AppendFormat(connstr, str).ToString();
        DBName = DataName;
    }
}
本人是懒了点,不复杂的东西也就不写注释了,很多方法看名称就应该懂。

开系统初始化的界面:image

我想这个代码就不用写出来了吧。让我再懒一下。

private void button1_Click(object sender, EventArgs e)
        {
            userName = textDataLoginName.Text;
            dataServer = textDataServer.Text;
            PassWord = textDataLoginPwd.Text;
            DataName = textDataBaseName.Text;
            ThisContent.DataBaseType = TextDataType.Text;
            ThisContent.BuilderConnectString( dataServer, DataName, userName, PassWord);
            try
            {
               IDbHelper DBH= ThisContent.DBH;
               isCheck = true;
               MessageBox.Show("链接成功!");
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

就这个OK了,其他的自己猜去。

对于主界面上,数据库对象用的tree。现在我们就转到主界面的操作上:

public FormDesignerMain()
        {
            InitializeComponent();
        }
        string TableName = string.Empty;
        private void FormDesignerMain_Load(object sender, EventArgs e)
        {
            appendTree();
            bindCommmond();
        }
        void appendTree()
        {
            DataTree.Nodes.Clear();
            DataTree.Nodes.Add(ThisContent.ModelHelper.Tree);//这里是重点,如此这般就吧数据库的对象全部给tree出来了。
            DataTree.Nodes[0].Expand();
        }
        void bindCommmond()//这里是读取目录下的所以模版xslt文件然后吧文件加载到下拉框去供选择。
        {
            string path = "Template";
            string[] files= Directory.GetFiles(path);
            foreach (string file in files)
            {
                if (file == null || file.Length <= 0)
                    continue;
                TempCheck.Items.Add(file);
            }
        }

我们看运行后的效果

image

为了节省我就只写主要代码了,空间不够,请谅解:

这是上面几个按钮的代码,不懂作用麻烦看按钮的名字,我够懒吧。

/// <summary>
        /// 新建模版
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>

private void button1_Click(object sender, EventArgs e)
{
    SaveFileDialog sfd = new SaveFileDialog();
    sfd.Filter = "模型xts文件(*.xsl)|*.xsl|模型xts文件(*.xslt)|*.xslt";
    string filePath = string.Empty;
    if (sfd.ShowDialog() == DialogResult.OK)
    {
        filePath = sfd.FileName;
    }
   
    ThisContent.Template.Path = filePath;
    ThisContent.Template.Create();
    textModel.Text = readTmp(filePath);
    tabPanle.SelectedTab = this.tabModel;
}

private void butCreateCode_Click(object sender, EventArgs e)
{
    if (string.IsNullOrEmpty(TableName))
    {
        MessageBox.Show("请先选择一个表");
        return;
    }
    if (TempCheck.Text.Length <= 0)
    {
        MessageBox.Show("请先选择一个模板");
        return;
    }
    ThisContent.Template.Content = readTmp(TempCheck.Text);//readTmp这个方法是读取模版代码。TempCheck是那个下拉框。
    this.textCode.Text= ThisContent.Template.CreateCode(ThisContent.ModelHelper.Convert(TableName));//这里是生成代码的核心方法
}

//下面就不解释了。

private void butSaveTmp_Click(object sender, EventArgs e)
{
    ThisContent.Template.Content = textModel.Text;
    ThisContent.Template.Save();
}

这个方法我就偷懒了一下,反正都是保存嘛,就随便写了一个

private void butSaveCode_Click(object sender, EventArgs e)
       {
           SaveFileDialog sfd = new SaveFileDialog();
           sfd.Filter = "C#文件(*.cs)|*.cs";
           string filePath = string.Empty;
           if (sfd.ShowDialog() == DialogResult.OK)
           {
               filePath = sfd.FileName;
           }
           StreamWriter sw = new StreamWriter(File.Open(filePath, FileMode.OpenOrCreate));
           try
           {
               sw.Write(this.textCode.Text.Trim());
               MessageBox.Show("代码生成成功!");
           }
           catch
           {
               MessageBox.Show(@"代码生成失败!\n请确认路径是否正确");
           }
           finally
           {
               sw.Close();
               sw.Dispose();
           }
       }
当然你要保存vb的代码,麻烦使用tmp提供的保存方法,或者自己写vb的模版。

最好结合我提供的xml文件结构看看这个默认模版xsl的。如果不会xlst语法就花10分钟去百度一下,我相信你10分钟就能学会。

<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www./1999/XSL/Transform">
  <xsl:output method="xml" omit-xml-declaration="yes" indent="yes" />
  <xsl:template match="/table">
    using System.Data;
    using CWF.MappingHelper;
    using System;
    using System.Text;
    using System.Collections.Generic;
    namespace CWF.Models
    {
      /// <summary>
      /// 表<xsl:value-of select="tablename"/>的模型
      ///</summary>
    [CWF.MappingHelper.Table("<xsl:value-of select="tablename"/>")]
    public class <xsl:value-of select="tablename"/>
    {
    public <xsl:value-of select="tablename"/>()
    {}
    <xsl:for-each select="fileds/filed">
      <xsl:value-of select="dbtype"/> _<xsl:value-of select="name"/>;
    </xsl:for-each>
    <xsl:for-each select="fileds/filed">
      /// <summary>
      /// <xsl:value-of select="name"/>
      /// </summary>
      [CWF.MappingHelper.Column("<xsl:value-of select="name"/>", <xsl:value-of select="isprimary"/>, <xsl:value-of select="isidentify"/>, DbType.<xsl:value-of select="dbtype"/>)]
      public <xsl:value-of select="dbtype"/><xsl:value-of select="string(' ')"/><xsl:value-of select="name"/>
      {
      get {return _<xsl:value-of select="name"/>;}
      set {_<xsl:value-of select="name"/>=value;}
      }
    </xsl:for-each>
    }
    }
  </xsl:template>
</xsl:stylesheet>

看最后生成的效果:

image

OK万事大吉。

欢迎板砖,第一次写博客写了这么多字,也发了图片,大家还是鼓励一下,下次我写得再具体点。

技术没有巅峰,追求卓越成就未来..

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约