不记得有多久没写博客了,一贯写懒博客不喜欢写注释和解说,今天又是上班时间,看看博客园两个周末来没多少文章,也就奉献一下自己这几天的研究。
需求:应为公司有自己的crm,也就是我前面有介绍的CWF这一套。对于数据库来说model的生成成了问题,尝试过动软和其他的代码生成器,可惜都不能生成想要的完整代码,所以就自己去研究打算搞一个让自己比较满意的。
可是到现在为止还是算个半成品,本打算今天写完批量生成再发布的,后面已经没多少工作量了,发上来也没什么关系。
还未完成的:数据批量生成。解决方案:异步线程+访问者设计模式。
废话有点多了。
看效果:
解决方案:
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;
}
}
本人是懒了点,不复杂的东西也就不写注释了,很多方法看名称就应该懂。
开系统初始化的界面:
我想这个代码就不用写出来了吧。让我再懒一下。
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);
}
}
我们看运行后的效果
为了节省我就只写主要代码了,空间不够,请谅解:
这是上面几个按钮的代码,不懂作用麻烦看按钮的名字,我够懒吧。
/// <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>
看最后生成的效果:
OK万事大吉。
欢迎板砖,第一次写博客写了这么多字,也发了图片,大家还是鼓励一下,下次我写得再具体点。
技术没有巅峰,追求卓越成就未来..