前段时间用C#做网站,用到了大量数据库相关的东西。网站采用3层结构,即数据访问层(Data Access Layer),业务逻辑层(Business Logic Layer),页面表现层().做了一段时间,发现向数据访问层和业务逻辑层加入新的类,数据库的表结构改了,还要对应的修改数据访问层和业务逻辑层的代码,这个工作很是繁琐,无聊,而且容易出错。做了几次之后就想有什么办法可以让机器自动完成呢?
联想到以前看过Java似乎有个Hibernate,可以很方便的实现对象关系映射(ORM),即自动的从数据库的表生成对应的对象,.Net也应该有类似的功能吧。于是找啊找,发现了很多.Net的ORM工具,不过都有缺点,就是代码得依赖于那些ORM工具,我希望能够让机器按我的要求生成我自己的代码,这样就更加灵活了。
于是乎,发现了CodeSmith和MyGeneration,CodeSmith是 网上传的.NET 程序员十种必备工具之一,我们写代码时,经常需要重复完成某些特定的任务,例如编写数据访问代码或者生成自定义集合。我们可以用CodeSmith编写模板自动完成这些任务,从而不仅提高工作效率,而且能够自动完成那些最为乏味的任务。可惜,CodeSmith是需要注册的,试用版只能用15天。而MyGeneration基本上和CodeSmith的功能差不多哦,但是他是开源的。我选软件的原则是能开源免费的就用,实在没替代了才选那些需要注册的,有版权的软件。所以就选MyGeneration了。
用过一段时间后感觉MyGeneration主要是为了自动生成数据库相关的代码的,可能C#用得比较多,其实我们可以用它生成任何代码,C++,JavaScript...而且还不仅仅局限于数据库,其他方面的代码也可以用MyGeneration自动生成。比如我们经常用数据访问层和业务逻辑层,用MyGeneration就可以自动生成这些代码,我们可以不用手动写代码了。比如数据访问层,我们需要调用一个存储过程,用MyGeneration我们只需要选择生成存储过程代码的模板,执行一下脚本,然后在界面上选择数据库上某个存储过程,然后就自动生成了数据库访问代码,整个过程只需要点几下鼠标,代码就自动生成了。这对于需要大量操作数据库的程序员来说,效率是多大的提升啊。
废话少说,还是来点实在的吧。首先声明,我的MyGeneration版本是:1.3.0.3
安装完MyGeneration后,第一次启动会要求进行一些数据库相关的配置。如图:
ConnectionString: 就是指定连接哪个数据库了,填好这个就可以点确定了。
下面来看一看其他的项都是什么。
Language Mapping:就是指定数据库和对象基本类型的映射关系。让我们打开Languages.xml文件看一下吧:
<Language From="SQL" To="C#">
<Type From="bigint" To="long" />
<Type From="binary" To="object" />
<Type From="bit" To="bool" />
<Type From="char" To="string" />
<Type From="datetime" To="DateTime" />
<Type From="decimal" To="decimal" />
<Type From="float" To="double" />
<Type From="image" To="byte[]" />
<Type From="int" To="int" />
<Type From="money" To="decimal" />
<Type From="nchar" To="string" />
<Type From="ntext" To="string" />
<Type From="numeric" To="decimal" />
<Type From="nvarchar" To="string" />
<Type From="real" To="float" />
<Type From="smalldatetime" To="DateTime" />
<Type From="smallint" To="short" />
<Type From="smallmoney" To="decimal" />
<Type From="text" To="string" />
<Type From="timestamp" To="byte[]" />
<Type From="tinyint" To="byte" />
<Type From="uniqueidentifier" To="Guid" />
<Type From="varbinary" To="byte[]" />
<Type From="varchar" To="string" />
<Type From="xml" To="string" />
<Type From="sql_variant" To="object" />
</Language>
这是里面的一段内容,很明显,是数据库SQL的字段转到C#是什么类型,里面没有C++的,假如我们要让它支持C++的话,需要在这里加入SQL到C++的类型转换。
Database Target Mapping:先看里面的内容吧:
<DbTarget From="ACCESS" To="DAO">
<Type From="Text" To="DAO.dbText" />
<Type From="Memo" To="DAO.dbMemo" />
<Type From="DateTime" To="DAO.dbDate" />
<Type From="Currency" To="DAO.dbCurrency" />
<Type From="Yes/No" To="DAO.dbBoolean" />
<Type From="OLE Object" To="DAO.dbLongBinary" />
<Type From="Hyperlink" To="DAO.dbMemo" />
<Type From="Double" To="DAO.dbDouble" />
<Type From="Replication ID" To="DAO.dbGUID" />
<Type From="Long" To="DAO.dbLong" />
<Type From="Single" To="DAO.dbSingle" />
<Type From="Decimal" To="DAO.dbDecimal" />
<Type From="Byte" To="DAO.dbByte" />
<Type From="Integer" To="DAO.dbInteger" />
</DbTarget>
呵呵,一目了然,就是Access数据库用DAO的方式访问,数据库的列的类型对应的DAO里是什么类型。
UseMetaData目前没什么用。
看看MyGeneration的界面吧:
Template Browser 面板 列出了一些模板,这是自动生成代码需要用到的模板。安装的时候会自己带很多模板。
MyMeta Browser则列出了当前连接的数据库上有些什么库,表,存储过程。
工作区则是具体模板对应的代码。
先让我们体验一下吧。
展开Template Browser面板下"d00dads - C#", 双击 “d00dads - Invoke a Stored Procedure", 让工作区显示其内容,
然后点击工具栏上的 "Execute" 按钮,如图红框所示:
弹出对话框。
选择数据库,存储过程,存储过程类型,点确定(OK)。
然后可以看到工作区 Output 里输出了代码了。例如:
using System.Data; using System.Collections.Specialized; using System.Data.SqlClient; public virtual void dm_exec_cursors (int spid) { ListDictionary parameters = new ListDictionary(); parameters.Add( new SqlParameter("@spid", SqlDbType.Int, 0), spid); LoadFromSqlNoExec("dm_exec_cursors", parameters); }
这就是MyGeneration自动获取了存储过程的输入参数,然后在代码里构造相应的参数,然后生成的代码。
这只是MyGeneration自带的模板生成的,大家可以试一试其他的模板的效果。
里面有自动根据表结构生成BLL的类......看下效果:
/* *=============================================================================== * Generated From - CSharp_dOOdads_BusinessEntity.vbgen * ** IMPORTANT ** * How to Generate your stored procedures: * * SQL = SQL_StoredProcs.vbgen * ACCESS = Access_StoredProcs.vbgen * ORACLE = Oracle_StoredProcs.vbgen * FIREBIRD = FirebirdStoredProcs.vbgen * POSTGRESQL = PostgreSQL_StoredProcs.vbgen * * The supporting base class OleDbEntity is in the Architecture directory in "dOOdads". * * This object is 'abstract' which means you need to inherit from it to be able * to instantiate it. This is very easilly done. You can override properties and * methods in your derived class, this allows you to regenerate this class at any * time and not worry about overwriting custom code. * * NEVER EDIT THIS FILE. * * public class YourObject : _YourObject * { * * } * *=============================================================================== */ // Generated by MyGeneration Version # (1.3.0.3) using System; using System.Data; using System.Data.OleDb; using System.Collections; using System.Collections.Specialized; using MyGeneration.dOOdads; namespace Your.Namespace { public abstract class _Users : OleDbEntity { public _Users() { this.QuerySource = "Users"; this.MappingName = "Users"; } //================================================================= // public Overrides void AddNew() //================================================================= // //================================================================= public override void AddNew() { base.AddNew(); } public override string GetAutoKeyColumn() { return "ID"; } public override void FlushData() { this._whereClause = null; this._aggregateClause = null; base.FlushData(); } //================================================================= // public Function LoadAll() As Boolean //================================================================= // Loads all of the records in the database, and sets the currentRow to the first row //================================================================= public bool LoadAll() { ListDictionary parameters = null; return base.LoadFromSql("[" + this.SchemaStoredProcedure + "proc_UsersLoadAll]", parameters); } //================================================================= // public Overridable Function LoadByPrimaryKey() As Boolean //================================================================= // Loads a single row of via the primary key //================================================================= public virtual bool LoadByPrimaryKey() { ListDictionary parameters = new ListDictionary(); return base.LoadFromSql("[" + this.SchemaStoredProcedure + "proc_UsersLoadByPrimaryKey]", parameters); } #region Parameters protected class Parameters { public static OleDbParameter ID { get { return new OleDbParameter("@ID", OleDbType.Integer, 0); } } public static OleDbParameter Alias { get { return new OleDbParameter("@Alias", OleDbType.VarChar, 2147483647); } } } #endregion #region ColumnNames public class ColumnNames { public const string ID = "ID"; public const string Alias = "Alias"; static public string ToPropertyName(string columnName) { if(ht == null) { ht = new Hashtable(); ht[ID] = _Users.PropertyNames.ID; ht[Alias] = _Users.PropertyNames.Alias; } return (string)ht[columnName]; } static private Hashtable ht = null; } #endregion #region PropertyNames public class PropertyNames { public const string ID = "ID"; public const string Alias = "Alias"; static public string ToColumnName(string propertyName) { if(ht == null) { ht = new Hashtable(); ht[ID] = _Users.ColumnNames.ID; ht[Alias] = _Users.ColumnNames.Alias; } return (string)ht[propertyName]; } static private Hashtable ht = null; } #endregion #region StringPropertyNames public class StringPropertyNames { public const string ID = "s_ID"; public const string Alias = "s_Alias"; } #endregion #region Properties public virtual Integer ID { get { return base.GetInteger(ColumnNames.ID); } set { base.SetInteger(ColumnNames.ID, value); } } public virtual String Alias { get { return base.GetString(ColumnNames.Alias); } set { base.SetString(ColumnNames.Alias, value); } } #endregion #region String Properties public virtual string s_ID { get { return this.IsColumnNull(ColumnNames.ID) ? string.Empty : base.GetIntegerAsString(ColumnNames.ID); } set { if(string.Empty == value) this.SetColumnNull(ColumnNames.ID); else this.ID = base.SetIntegerAsString(ColumnNames.ID, value); } } public virtual string s_Alias { get { return this.IsColumnNull(ColumnNames.Alias) ? string.Empty : base.GetStringAsString(ColumnNames.Alias); } set { if(string.Empty == value) this.SetColumnNull(ColumnNames.Alias); else this.Alias = base.SetStringAsString(ColumnNames.Alias, value); } } #endregion private AggregateClause _aggregateClause = null; #endregion protected override IDbCommand GetInsertCommand() { OleDbCommand cmd = new OleDbCommand(); cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = "[" + this.SchemaStoredProcedure + "proc_UsersInsert]"; CreateParameters(cmd); return cmd; } protected override IDbCommand GetUpdateCommand() { OleDbCommand cmd = new OleDbCommand(); cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = "[" + this.SchemaStoredProcedure + "proc_UsersUpdate]"; CreateParameters(cmd); return cmd; } protected override IDbCommand GetDeleteCommand() { OleDbCommand cmd = new OleDbCommand(); cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = "[" + this.SchemaStoredProcedure + "proc_UsersDelete]"; OleDbParameter p; return cmd; } private IDbCommand CreateParameters(OleDbCommand cmd) { OleDbParameter p; p = cmd.Parameters.Add(Parameters.ID); p.SourceColumn = ColumnNames.ID; p.SourceVersion = DataRowVersion.Current; p = cmd.Parameters.Add(Parameters.Alias); p.SourceColumn = ColumnNames.Alias; p.SourceVersion = DataRowVersion.Current; return cmd; } } }
这就是自动获得表结构,然后从字段映射成类里面的成员,并且还有一些插入,更新,删除的代码。
当然自带的模板生成的代码不一定符合我们的需要,但是我们可以根据需要自己写一些模板来生成符合自己需要的代码,这也是非常容易的事