1 引言
目前大多数项目或产品都使用关系型数据库实现业务数据的存储,这样在开发过程中,常常有一些业务逻辑需要直接用写sql语句实现,但这样开发的结果是:遍地布满sql语句。这些藕合较高的sql语句给系统的改造和升级带来很多无法预计的障碍。或者说可以使用服务端存储子程序实现,但只是将这种耦合搬迁到后端,问题依然没有根本解决,且服务端驻留过多的存储子程序也影响服务器的性能并给多人合作维护和更新部署带来许多障碍。为了提高项目的灵活性,特别是快速开发,ORM是一个不错的选择。举个简单的例子:在使用ORM的系统中,当数据库模型改变时,不再需要理会逻辑代码和sql语句中涉及到该模型的所有改动,只需要将该模型映射的对象稍作改动,甚至不做改动就可以满足要求。
ORM的全称是Object Relational Mapping,即对象关系映射。它的实质就是将关系数据(库)中的业务数据用对象的形式表示出来,并通过面向对象(Object-Oriented)的方式将这些对象组织起来,实现系统业务逻辑的过程。在ORM过程中最重要的概念是映射(Mapping),通过这种映射可以使业务对象与数据库分离。从面向对象来说,数据库不应该和业务逻辑绑定到一起,ORM则起到这样的分离作用,使数据库层透明,开发人员真正的面向对象。下图简单说明了ORM在多层系统架构中的这个作用。
图1 ORM在多层系统架构中的作用
当然ORM并非是万能的,面对纷繁复杂的业务逻辑,当遇到特别复杂的数据处理及海量数据处理和弥补拙劣的设计不足时还应归结到sql或存储过程来实现,但它却很好的体现了“80/20(或90/10)法则”(也被称为“帕累托法则”),也就是说:花比较少(10%-20%)的力气就可以解决大部分(80%-90%)的问题,这样通过利用ORM框架,我们就仅需要付出极少数时间和精力来解决剩下的少部分问题了,这无疑缩短了整个项目开发的周期。因此快速开发、面向对象和性能优化等必须灵活兼顾才好,这些该工具都提供了很好解决方案,下文分别作一介绍。
2 内容
2.1 ORM的工具实现:VB/C#.Net实体代码生成工具(EntitysCodeGenerate)
好的ORM工具不仅可以帮助我们很好的理解对象及对象的关系,而且工具本身会帮助我们维护这些关系,并且帮助我们记住字段属性业务含义及提供辅助的应用等。基于这个理念,我于多年的项目实践和业余时间设计开发了一个基于.NET的ORM工具——VB/C#.Net实体代码生成工具(EntitysCodeGenerate),该工具运行于dotnetframework2.0框架上,希望多多交流并指正。
VB/C#.Net实体代码生成工具(EntitysCodeGenerate) 为ORM提供对象持久、简单对象查询、事务处理等功能。数据持久包括一些对象的Insert、Update、Save、Delete、Select等功能,简单对象查询则提供一些基于对象的简单对象查询GetEntity及构造函数获取对象信息等。该工具是基于VS.NET 2005的开发的应用程序,职责是从数据库中提取生成实体类代码并帮助开发人员快速映射关系数据库中的业务模型的映射实体类,目前直接提供从Oracle、sqlServer、Access、MysqL、Sybase、sqlite数据库生成C#代码的支持,可以生成实体及实体集的相关文件,并自动提取数据库表和字段的注释说明和对应的数据类型等。
另外所生成的代码文件只需修改数据库连接,即可用于目前市场上ado.net支持的各种类型的数据库,如Oracle、sqlServer、MysqL、Access、Sybase、sqlite、Excel、DB2等。所生成代码文件的类关系图如下所示:
工具3.x版本之后的实体层代码有个基类(BaseEntity),基类里其实也正是你项目的数据连接的配置位置,该工具的实际应用时配置其实也就在这里,默认为生成代码时所填写的数据库连接信息,并可手工扩展修改(如从config配置文件读取、实现对用户数据库连接信息的加密/解密等;大多数时候我们只需在GetConnectionString()修改数据连接字符串即可):
public class BaseEntity { public static string GetConnectionString() { return "User ID=scott;Password=tiger;Data Source=85";//数据库连接设置可修改从别处读取 } public static DatabaseType GetDatabaseType() { return DatabaseType.Oracle; //数据库连接类型设置也可修改从别处读取 } …… } |
这里可设置该命名空间当前实体下的数据库连接类型及数据库连接字符串,当然也可以修改成从其它如配置文件中读取(以前的版本是放在全局设置DbConnectString这个类里面,这样的缺陷是当一个项目有多个数据库时将不好处理,而现在通过使用基类继承及命名空间则很容易解决)。
在ORM实现的前期工作中,为了实现屏蔽各种数据库之间的操作差异,我们需要定义数据操作公有接口,封装基本的数据库增、删、改、查等操作。以数据库Oracle的为例介绍,我们需要定义下层各种数据库操作的公共组件:
namespace System.Database { { public int ExecuteNonQuery(DBCommandWrapper command){…} public DataSet ExecuteDataSet(DBCommandWrapper command){…} …… } |
再定义数据库操作类及数据库的基类和数据库连接的工厂类,实现各种不同类型的数据。
public abstract class DBCommandWrapper {……} |
internal abstract class Database{……} |
internal sealed class DatabaseFactory{……} |
…… |
然后实现各种数据库的操作类,以Oracle为例
internal class OracleDatabase : Database{…} |
public class OracleCommandWrapper : DBCommandWrapper{…} |
最后在生成的实体类及基类文件中生成相应的数据库Insert,Update,Delete,GetEntity,Save等操作和类型映射代码如下所示:
public int Insert() { ORMap<BaseEntity> ormap = new ORMap<BaseEntity>(this); DEPT entity = new DEPT(); return ormap.Insert(entity); } …… public static System.Data.DbType GetDBTypeByFullName(string strTypeFullName) { switch (strTypeFullName) { case "System.Byte": return System.Data.DbType.Byte; case "System.Boolean": return System.Data.DbType.Boolean; …… } } |
在生成代码的同时,工具自动添加实体类的属性标示及字段注释说明等如下所示:
[Serializable(),Description("Primary:DEPTNO")] public class DEPT:BaseEntity { …… /// <summary> /// 主键 /// </summary> [DataObjectField(true)] public int DEPTNO { set{ _deptno=value;} get{return _deptno;} } /// <summary> /// /// </summary> [DataObjectField(false)] public string DNAME { set{ _dname=value;} get{return _dname;} } …… } |
实体中的summary中的注释自动将数据中表及字段的注释说明提取到这里,方便程序员在代码编写及维护中交流使用,理解其所对应的业务含义。示例是以Oracle数据库自带的示例库为例介绍的,数据库自带的实例库scott/tiger表注释为空,所以这里实体字段提取出注释也是为空。
实体定义完成后,我们需要根据实体类中绑定的属性构造出运行期需要的sql语句,收集实体类定义中的数据结构描述,再定义一个类来说明实体在运行期所引用到的所有关于数据持久的信息,包括关键字字段,一般字段等。同时需要一个字段的元数据字段在数据库中的名称,大小,是否可为空,列类型等信息。这些条件具备后,再定义解析类,负责转换数据的程序类型到数据库字段类型,并且构造出Insert,Update,Delete,Select,Save等操作所需要的sql语句,再去调用数据操作公有接口(DbCore),即可实现。同时数据操作公有接口System.Database.DbCore结合实体类可将简单和复杂及事务的操作更为方便的实现,下文着重介绍在实际中的使用。数据库操作默认以实体对应表的主键为准,当然也可以指定条件;并引入保存操作,即将增加和更新合并为一个保存操作,由实体对象本身自己根据主键或指定条件判断是增加还是更新操作。
2.2 在开发中的实际应用
VB/C#.Net实体代码生成工具(EntitysCodeGenerate)安装后,在生成对应的实体代码后都会有个“相关配置”文件夹,里面有“配置说明”文档和相关文件,实际使用时只须按“配置说明”文档操作即可。使用该工具开发的项目,可以做到很好的切换到异构数据库,且只需变动数据库连接接口即可(即只须修改GetDatabaseType()/GetConnectionString())。同时提供了ORMaping.dll(1.0版本引用),System.Database.dll(2.0/3.0/3.x/4.0版本引用)安装目录下有相应的chm格式帮助文档,是对这两个dll单独使用的说明,支持自定义的数据库访问,并可结合生成的实体操作数据库,提供良好的事务处理等。其中ORMaping.dll支持Oracle;System.Database.dll默认支持Oracle,并可用于各种类型的数据库访问,如sqlServer、MysqL、Sybase、DB2、sqlite、Postgresql、OleDb、Odbc等。工具所生成的代码和提供的文件,都是可选配的,可单独使用也可配置使用,比如:你可以只生成实体代码,而不生成对应的数据库操作,这样就不需要相应的配置文件,只须将代码文件拷贝或生成到对应目录下即可。但是选择带数据库操作的实体文件的功能更强大些,否则只能发挥实体及实体集的基本通用功能。实体代码的生成界面比较简单,如下所示:
这里,只须选择数据库类型、输入正确的数据库连接字符串、代码文件的输出目录和代码命名空间即可。实体数据类型以”数据类型映射文件”为准,工具提供了系统默认的类型映射,若有自定义的类型,可在”数据类型映射文件”里修改,可配置到数据类型的精确刻度。若修改过程中想恢复默认的配置,只须点击”生成”按钮即可。准备工作做好后,单击”生成代码”按钮,生成成功之后按提示生成的配置说明文档,将实体及实体集代码文件和基类文件拷贝到指定目录并添加ORMap.dll、System.Database.dll的引用即可。操作简单这里就不在此赘述,下面C#语言为例开始介绍实体对象的数据库操作的工作。
先介绍单个实体类的数据库操作的工作:
2.2.1 常用单实体对象数据库操作
这里还是以Oracle附带库DEPT为例来做说明,首先做对象的声明
DEPT entity = new DEPT(); |
下面以该对象来做阐述。
1、获取一个实体对象信息
实体的操作默认以主键为准,对单个实体信息的获取可简单如下实现:
entity.DEPTNO = 50; entity = entity.GetEntity(); |
返回主键字段DEPTNO=50的对象,若数据库中没有对应的记录则返回null。DEPT表的主键字段是DEPTNO,同时对联合主键也是支持的,下文同此;GetEntity同时提供其它指定条件的重载,也可以使用GetEntityByEntityCondition,当指定条件有多个记录符合时只返回首条记录;返回多条记录可使用GetDataTable方法。
DEPT entity = new DEPT(50); //通过主键字段条件获取实体对象 DEPT entity = new DEPT(new string[] { "DEPTNO" },new object[] { 50 }); |
另外,可通过实体集获取多实体对象信息的查询( 实体集对象查询),如:
DEPTS entity = new DEPTS(true); //获取所有信息 EMPS entitys1 = new EMPS(new string[] { "DEPTNO" },new object[] { 50 }); |
2、新增一个实体对象
新增一个对象代码可如下所示:
entity.DEPTNO = 51; entity.DNAME = “DNAME1”; entity.LOC = “LOC1”; entity.Insert(); |
同时Insert提供多种相应的重载和InsertAll方法;Insert和InsertAll区别是:Insert在插入记录时会比较对象初始的字段值,将与初始值不同的值插入,其余的以表字段的默认方式处理;InsertAll则是插入全部字段,即使是对象的初始值没有改变也会插入。
3、更新一个实体对象
更新一个对象代码可如下所示:
entity.DEPTNO = 51; entity.DNAME = “DNAME2”; entity.LOC = “LOC2”; entity.Update(); |
Update也提供多种相应的重载和UpdateAll方法;Update和UpdateAll区别是:Update在更新记录时会比较对象初始的字段值,将与初始值不同的值进行更新,其余的表字段不更新;UpdateAll则是更新全部字段,即使是对象的初始值没有改变也会将对象的初始值更新到表里。可根据情况选择使用。
4、保存一个实体对象
保存是该工具新增的功能,即将新增和更新合并到一个保存功能里,ORM会根据主键约束自动解析是新增还是更新,保存一个对象的代码可如下所示:
entity.DEPTNO = 51; entity.DNAME = “DNAME3”; entity.LOC = “LOC3”; entity.Save(); |
保存操作也是默认以主键为准,对多个联合主键也是支持的,不带参数的保存默认是按主键判断有对应的记录就更新,没有就插入新记录。同时Save提供多种重载和SaveAll、SaveByEntityCondition方法,Save和SaveAll区别同新增和更新。
5、删除一个对象
entity.DEPTNO = 51; entity.Delete(); |
删除操作也是默认以主键为准,对多个联合主键也支持,同时也提供多种指定条件的重载方法。最后附加说明实体对象的增删改保存操作都会返回一个int值,该值返回表中记录受影响的行数。
从这些代码可以明显的看到,这里常用的数据增、删、改、查操作只需很简单几句即可实现,少写了很多代码,是不是很好?
6、取得实体映射表数值字段的最大ID+1
代码可如下:
int intID = entity.GetInt32MaxID(); |
这里获取实体对象对应表字段默认第一个主键的最大值ID+1,类型为整型,同时提供GetInt?MaxID多种重载,即有整型和长整型及指定字段等。
本节介绍的都是单表无事务的操作,下节介绍多表及事务处理的操作。
2.2.2 多个实体对象及事务处理并结合System.Database 的工作
这里简略介绍实体对象结合System.Database.DbCore及事务处理是如何工作的,先看以下代码(可参见安装示例代码System.Database.Demo):
Entitys.Common.LC_WORKTYPE entity = new Entitys.Common.LC_WORKTYPE(); entity.ID = 1; entity.TYPENAME = "TYPENAME"; string strConnection = "Password=newaqfx;User ID= newaqfx;Data Source=85"; DbCore dbCore = new DbCore(DatabaseType.Oracle,strConnection); dbCore.Open(); dbCore.BeginTransaction(); dbCore.Save(new Entitys.Common.LC_WORKTYPE(),entity); entity.DESCRIPTION = "类型描述"; dbCore.Save(new Entitys.Common.LC_WORKTYPE(),entity); entity.TYPENAME = "作业类型"; dbCore.Save(new Entitys.Common.LC_WORKTYPE(),entity); DataSet dst = dbCore.ExecuteDataSet("select * from lc_worktype");
entity.ID = 1; DataTable dt = dbCore.GetDataTableByEntityKey(entity); int intRecord = dbCore.Delete(entity); dt = dbCore.GetDataTableByEntityKey(entity);
dbCore.CommitTransaction(); dbCore.Close(); |
这里使用另外一个实体LC_WORKTYPE(映射安全风险系统的"作业类型"表),BeginTransaction()为开始事务的标志,CommitTransaction()为提交当前事务,还一个是RollbackTransaction()表示回滚当前事务,放弃当前事务下所有数据的更改。这样在事务开始和提交或回滚之间可以进行多个实体的操作,并将结果最终一起提交或回滚撤销。这里Save有两个参数第一个是实体对象的初始类用于比较实体的初始值,第二个是要保存的对象,该方法依据主键自动判断表数据是更新还是插入;同时与Save类似的方法有SaveAll保存全部字段,同时也有Insert、InsertAll、Update、UpdaAll、Delete、IsExitByEntityKey、Exists、Get?MaxId等等方法,均可相互结合使用,方法都有详尽的说明及示例代码。该方法执行过程中可单步跟踪,查看该事务下每步命令执行后对应的数据集信息。
下面再看以Oracle自带的scott库为例一段代码Delete、Insert、Update并结合事务使用的代码:
DbCore dbCore = null; try { EMP entity1 = new EMP(); DataSet dst = new DataSet(); entity1.EMPNO = 7369; //设置主键EMPNO为 entity1 = entity1.GetEntity(); //取得主键EMPNO为实体对象信息 @H_301_3053@//"User ID=scott;Password=tiger;Data Source=85"; dbCore = new DbCore(Entitys.Common.BaseEntity.GetConnectionString()); dbCore.Open(); dbCore.BeginTransaction(); //选择当前事务下的所有雇员EMP的信息 dst = dbCore.SelectAll().From(entity1).ExecuteDataSet(); dbCore.Delete(entity1);//删除主键EMPNO为7369的记录 dst = dbCore.SelectAll().From(entity1).ExecuteDataSet();//查看当前事务下记录,当前删除记录将不在此显示 dbCore.Insert(new EMP(),entity1);//插入刚才删除主键EMPNO为7369的记录=dbCore.Save(new EMP(),entity1); dst = dbCore.SelectAll().From(entity1).ExecuteDataSet();//查看当前事务下记录,可见刚刚插入的新记录 entity1.SAL = entity1.SAL + 100;//薪水加100 dbCore.Update(new EMP(),entity1);//更新=dbCore.Save(new EMP(),entity1); dst = dbCore.SelectAll().From(entity1).ExecuteDataSet();//查看当前事务下记录,对应薪水SAL已更新 entity1.SAL = entity1.SAL - 100;// 薪水减100 dbCore.Update(new EMP(),entity1); dst = dbCore.SelectAll().From(entity1).ExecuteDataSet();//查看当前事务下记录,对应薪水SAL已更新 dbCore.CommitTransaction(); dbCore.Close(); } catch (Exception ex) { if (dbCore != null) { if (dbCore.IsTransaction) { dbCore.RollbackTransaction();//如果已经开始事务,则回滚事务 } dbCore.Close(); } } |
上面的Insert、Update方法都可以Save方法来取代,Save方法会自动判断是Update还是Insert,这里只是用来展示之用。
诚然ORM并不是万能的,当遇到特别复杂的数据处理及海量数据处理、性能优化和弥补拙劣的设计时,还应归结到sql语句或存储过程来实现才是好的选择。实际项目中仍然会有海量复杂的数据处理及复杂查询和不同类型数据库、sql语句优化和存储过程等,工具组件System.Database.DbCore提供了良好的解决方案,很好的解决了项目中10%-20%的那部分功能。该组件支持目前市场上ADO.NET支持的绝大多数类型的数据库,可执行自定义编写的sql语句和存储过程等,这样可针对复杂功能特殊处理及性能优化等操作。
System.Database.DbCore可直接用于Oracle、sqlServer、MysqL、Sybase、DB2、sqlite、Postgresql、Informix和支持OleDb、Odbc类型的数据库,实例代码分别如下:
DbCore dbCore = new DbCore(DatabaseType.Oracle,“OracleConnectionString”); |
DbCore dbCore = new DbCore(DatabaseType.sqlServer,“sqlServerConnectionString”); |
DbCore dbCore = new DbCore(DatabaseType.MysqL,“MysqLConnectionString”); |
DbCore dbCore = new DbCore(DatabaseType.Sybase,“SybaseConnectionString”); |
DbCore dbCore = new DbCore(DatabaseType.DB2,“DB2ConnectionString”); |
DbCore dbCore = new DbCore(DatabaseType.sqlite,“sqliteConnectionString”); |
DbCore dbCore = new DbCore(DatabaseType.Postgresql,“PostgresqlConnectionString”); |
DbCore dbCore = new DbCore(DatabaseType.Informix,“InformixConnectionString”); |
DbCore dbCore = new DbCore(DatabaseType.OleDb,“OleDbConnectionString”); |
DbCore dbCore = new DbCore(DatabaseType.Odbc,“OdbcConnectionString”); |
下面看一段适合Oracle和sqlServer访问的通用代码:
DbCore dbCore = PublicClass.GetNewDbCore(); string strParaToken = dbCore.GetCurrentParameterToken; string strsql = "INSERT INTO dept (deptno,dname,loc) VALUES (" + strParaToken + "deptno," + strParaToken + "dname," + strParaToken + "loc)"; dbCore.Open(); //打开数据库连接 dbCore.BeginTransaction(); //开始事务 @H_301_3053@ DBCommandWrapper cmd = dbCore.GetsqlStringCommandWrapper(strsql); @H_301_3053@//cmd.AddParameter(..);//为命令增加一个参数实例 cmd.AddInParameter(strParaToken + "deptno",DbType.Int32,99); cmd.AddInParameter(strParaToken + "dname",DbType.String,"部门名称"); cmd.AddInParameter(strParaToken + "loc",DbType.AnsiString,"locTest");
int intMaxDeptId = dbCore.GetInt32MaxId("dept","deptno");//当前表的deptno最大值
dbCore.ExecuteNonQuery(cmd); intMaxDeptId = dbCore.GetInt32MaxId("dept","deptno");//插入数据deptno=99之后当前表的deptno最大值
strsql = "DELETE dept WHERE deptno = " + strParaToken + "deptno"; DBCommandWrapper cmd1 = dbCore.GetsqlStringCommandWrapper(strsql); cmd1.AddInParameter(strParaToken + "deptno",99); dbCore.ExecuteNonQuery(cmd1); intMaxDeptId = dbCore.GetInt32MaxId("dept","deptno");//删除数据deptno=99之后当前表的deptno最大值
dbCore.RollbackTransaction();//回滚撤销事务。等于该方法什么都没做,只是演示作用
intMaxDeptId = dbCore.GetInt32MaxId("dept","deptno");
|
其中第一句的PublicClass.GetNewDbCore()方法体代码可以是new DbCore(DatabaseType.Oracle,“OracleConnectionString”)也可以是new DbCore(DatabaseType.sqlServer,“sqlServerConnectionString”),当new的是Oracle时即表示操作访问的是Oracle数据库,当new的是sqlServer即表示操作访问的是sqlServer数据库。dbCore.GetCurrentParameterToken即是获取对应数据库连接参数的前导符(如:Oracle是“:”,sql Server是“@”等),这里也可以结合使用dbCore.StandardsqlWithParameters方法对当前带参数的sql语句进行标准化通用处理,即所写sql可以用于如MysqL/Access数据库等。这里的数据库操作同样也是可以同实体对象一块协同工作。dbCore.GetsqlStringCommandWrapper(…)创建一个sql语句的命令,dbCore. GetStoredProcCommandWrapper(…)创建一个执行存储过程的命令,可根据项目自身实际需要选择使用。
对专有数据库命令也可以转化为指定数据库命令来使用,这样可针对该数据库特性使用更多的方法,如Oracle、sqlServer的命令转化可像下列代码来转化:
OracleCommandWrapper cmd = dbCore.GetsqlStringCommandWrapper(strsql) as OracleCommandWrapper; |
sqlCommandWrapper cmd = dbCore.GetsqlStringCommandWrapper(strsql) as sqlCommandWrapper; |
…… |
这里顺便说明一下当程序执行出现错误时可使用dbCore.Close()来关闭当前打开的数据库连接,如下代码所示:
catch (Exception ex) { if (dbCore != null) { if (dbCore.IsTransaction) { dbCore.RollbackTransaction();//如果已经开始事务,则回滚事务 } dbCore.Close(); } MessageBox.Show(ex.Message); } |
最后再说一个System.Database.DbCoreConnectLimit.AllDBMaxConnectionCount,可以设置数据库可打开的最大连接数目,默认不受限制。
2.2.3 数据查询
如一般的关系型数据库所具有的查询功能一样,EntitysCodeGenerate也有着非常丰富的查询功能,如对象查询、数据集查询、函数查询、条件查询、排序查询、分组等。这里对数据查询做简单介绍。EntitysCodeGenerate提供ENTITYColumn类帮助System.Database.DbCore.Select从数据源查询数据,Select通过ENTITYColumn实体属性,构造查询语句。Select在运行时定义查询筛选语句,并执行返回结果数据集。
常用实体对象查询,见上节常用实体对象数据库操作一节的第一段所述,这里就不在赘述。
这里先介绍表实体原描述信息类ENTITYColumn,ENTITY为表实体类对应的占位符,其类下有实体所对应的表的表名和表字段的公共静态只读信息。其中的TableName为每个该类的对应表名,各属性字段为表名、字段名、字段对应类型的完全限定名,以键盘不能直接输入的偏僻符号’┋’为分割拼接而成。ORM结构化查询类可使用该类。
2.1、Select查询
System.Database.DbCore类提供Select方法及其相应的重载,以sqlServer自带的pubs库为例,可见如下代码:
DataSet dst = new DataSet(); |
DataSet dst = dbCore.SelectAll().From("sales").ExecuteDataSet(); |
DataSet dst = dbCore.SelectAll().From(SALESColumn.TableName).ExecuteDataSet(); |
DataSet dst = |
DataSet dst = |
DataSet dst = dbCore.SelectAll().From(new SALES()).ExecuteDataSet(); |
其中的dbCore.SelectAll()方法缺省默认参数为查询所有字段,Select()不选择出任何字段,仅初始化实例,同时也可直接使用sql语法的字符串也可使用相应的ENTITYColumn类SALESColumn的属性字段作为参数,或数组集合信息。也可用SelectColumn添加要查询的字段,SelectColumn(…)接受sql表字段名字符串或ENTITYColumn类的属性字段格式的参数,SelectColumn(…)接受符合SelectColumn格式的数组参数,同时也提供SelectCustom(…)接受自定义的sql语法的字符参数。同时鉴于SelectColumn等方法名较长,提供Add替代SelectColumn,AddMax、Min、Avg分别替代SelectColumnMax、Min、AvgValue各种同构方法,便于代码的抒写。
From(…)为查询的目标表名,这里使用的是SALESColumn.TableName,也可直接使用表名字符串,同时接受实体对象作为参数。ExecuteDataSet()执行查询并返回在内存当中的DataSet格式的结果数据集。ExecuteReader()执行查询并返回只读向前的数据结果集流IDataReader;ExecuteScalar()快速执行查询并返回第一行第一列的Object值,下同。
2.2、From连接查询
先见如下代码:
DataSet dst = new DataSet();DataSet dst1 = new DataSet(); |
dst = dbCore.SelectAll().From().JoinInner("sales","stor_id ","stores","stor_id") |
dst = dbCore.SelectAll().From().JoinInner(SALESColumn.stor_id,STORESColumn.stor_id) |
dst = dbCore.SelectAll().From().JoinLeft("sales","stor_id") |
dst = dbCore.SelectAll().From().JoinLeft(SALESColumn.stor_id,STORESColumn.stor_id) |
dst = dbCore.SelectAll().From().JoinRight("sales","stor_id") |
dst = dbCore.SelectAll().From().JoinRight(SALESColumn.stor_id,STORESColumn.stor_id) |
dst = dbCore.SelectAll().From().JoinFull("sales","stor_id") |
dst = dbCore.SelectAll().From().JoinFull(SALESColumn.stor_id,STORESColumn.stor_id) |
这里是以sqlServer系统自带pubs示例库的表sales、stores为例介绍的,JoinInner为内连接、JoinLeft为左外连接、JoinRight为右外连接、JoinFull完全外连接。参数可直接使用表名及表字段名字符串,或使用SALESColumn、 STORESColumn类,其中使用实体类静态属性字段代码可读性及维护性更好些。下面看下Where的使用。
2.3、Where语句的Condition条件
DataSet dst = new DataSet(); |
dst1 = dbCore.SelectAll().From().JoinInner(SALESColumn.stor_id,STORESColumn.stor_id) |
|
dst = dbCore.SelectAll().From().JoinInner("sales","stor_id") |
dst1 = dbCore.SelectAll().From().JoinInner(SALESColumn.stor_id,STORESColumn.stor_id) |
|
dst = dbCore.SelectAll().From("sales").FromTable("stores") .Where().ConditionColumnAndEqual("sales","stor_id"). |
dst1 = dbCore.SelectAll().From(SALESColumn.TableName).FromTable(STORESColumn.TableName) |
这里再次展示了使用字符串和ENTITYColumn类的相互比较,可见使用ENTITYColumn更直观,可读及维护性更好些。Where()是不带参数的实例化方法,所添加的查询条件均以Condition…开头,在这里可以添加关系运算包括Equal(=),Less(<),Great(>),LessEqual(<=),GreatEqual (>=),NotEqual(<>、!=),BetweenAnd,in,not in,null,not null,like,not like和各自的关系and,or比较及表字段与字段之间的=,<,>,<=,>=,<>关系and,or的连接等;另外还可以添加自定义的Where条件语句等。
2.4、Order By排序功能
这里切换到Oracle数据库,以Oracle自带的scott用户为例,先看如下代码:
DataSet dst = new DataSet(); |
dst = dbCore.SelectAll().From(entity) |
dst = dbCore.SelectAll().From(entity) |
dst = dbCore.SelectAll().From(entity) |
dst1 = dbCore.SelectAll().From(EMPColumn.TableName) |
其中entity为雇员表EMP对应的实体对象,这里也使用查询表名的地方也可直接用实体对象。OrderBy默认不带排序参数,只实例化对象,也可带排序字段,排序方式为升序;其后使用Asc添加升序字段,Desc添加降序字段。
2.5、Group By分组及分组条件和排序功能
这里同样以Oracle自带的scott用户为例,先看如下代码:
DataSet dst = new DataSet(); |
dst = dbCore.Select().SelectColumnMaxValue(EMPColumn.EMPNO) |
dst1 = dbCore.Select().AddMax(EMPColumn.EMPNO) |
|
dst = dbCore.Select().SelectColumn(EMPColumn.DEPTNO).SelectColumn(EMPColumn.SAL) .GroupBy(EMPColumn.DEPTNO).Column(EMPColumn.SAL) |
dst1 = dbCore.Select().Add(EMPColumn.DEPTNO).Add(EMPColumn.SAL) .AddMax(EMPColumn.EMPNO).AddMin(EMPColumn.EMPNO).AddAvg(EMPColumn.EMPNO) |
|
dst = dbCore.Select().SelectColumn(EMPColumn.DEPTNO).SelectColumn(EMPColumn.SAL) |
dst1 = dbCore.Select().Add(EMPColumn.DEPTNO).Add(EMPColumn.SAL) |
|
dst = dbCore.Select().SelectColumn(EMPColumn.DEPTNO).SelectColumn(EMPColumn.SAL) |
dst = dbCore.Select().SelectColumn(EMPColumn.DEPTNO).SelectColumn(EMPColumn.SAL) |
dst = dbCore.Select().SelectColumn(EMPColumn.DEPTNO).SelectColumn(EMPColumn.SAL) |
这里dst、dst1变量分别对应同样功能不同语句写法的数据集信息。同时展示了Add替代SelectColumn,AddMax、Min、Avg分别替代SelectColumnMax、Min、AvgValue的示例代码。Where和GroupBy、Having、OrderBy可同时使用,也可分开使用。最后三个展示了分别以Where和GroupBy、Having、OrderBy分开使用的例子。其中分组使用Column(…)添加分组字段。可以很明显的看出些语句的功能,和sql语句的抒写几乎一致。如:倒数第四、五段的代码(粗体部分,两段代码功能相同,只是使用方法略异)换算成sql语句就是(假设今天是2009-10-11):
SELECT emp.deptno,emp.sal, |
显而易见和直接编写sql很相似,并无须编写参数替换等的编码,省去许多代码量,且可读性也高,维护也方便。
以上介绍的都是没有使用事务的功能,下面介绍结合事务的使用,先看如下代码:
DataSet dst = new DataSet(); try { #region 使用事务 //--打开数据库连接 dbCore.Open(); dbCore.BeginTransaction();//开始事务 int intRecordCount = dbCore.DeleteFrom(EMPColumn.TableName).ExecuteNonQuery(); dst = dbCore.SelectAll().From(EMPColumn.TableName).ExecuteDataSet(); dbCore.RollbackTransaction();//回滚撤销事务 dst1 = dbCore.SelectAll().From(new EMP()).ExecuteDataSet(); dbCore.Close(); #endregion } catch (Exception ex) { if (dbCore != null) { dbCore.Close(); } MessageBox.Show(ex.Message); } |
这里仍然是以Oracle自带的scott用户为例,其中的Data Source可以选择任何Oracle的服务名。这里有必要说明的是使用了打开、关闭数据库连接,因为要使用事务所以需要先打开数据库连接,前文介绍的都是没有使用事务的,所以该步骤可略过。该例的功能是先删除EMP表的所有数据再查询该表的所以信息,可见再当前事务下是没有任何信息的,然后在回滚事务,再查询就又有数据了,这正是事务所起的作用。同时需要注意的是,数据库连接打开后要关闭,且当中间出现异常时也应关闭已打开的数据库连接,以释放资源。
仍然以Oracle自带的scott用户为例,并结合事务处理,先看如下代码:
try { dst = dbCore.SelectAll().From(new EMP()).ExecuteDataSet(); //--打开数据库连接,开始使用事务 dbCore.BeginTransaction(); EMP entity = new EMP(); entity.EMPNO = 7782; intRecordCount = dbCore.Delete(entity); dst = dbCore.SelectAll().From(EMPColumn.TableName).ExecuteDataSet(); intRecordCount = dbCore.DeleteFrom(EMPColumn.TableName) dst = dbCore.SelectAll().From(EMPColumn.TableName).ExecuteDataSet(); intRecordCount = dbCore.DeleteFrom(new EMP()).ExecuteNonQuery(); dst = dbCore.SelectAll().From(EMPColumn.TableName).ExecuteDataSet(); dbCore.RollbackTransaction(); dst = dbCore.SelectAll().From(EMPColumn.TableName).ExecuteDataSet(); dbCore.Close(); } catch (Exception ex) { if (dbCore != null) dbCore.Close(); MessageBox.Show(ex.Message); } |
这里先声明一个EMP雇员类的实体对象entity,并将主键EMPNO赋以值7782,然后执行Delete操作,即将主键为7782的记录删除之,接下来的查询跟踪运行可见到。关于使用实体对象插入(Insert)、更新(Update)、删除(Delete)、保存(Save)操作可参加(上篇)或示例源代码。下面的DeleteFrom方法,并结合Where条件,将部门编号DEPTNO等于10的雇员信息全部删除,同样再下面的查询跟踪运行可看到。最后将雇员表EMP的记录全部删除之,再查询没有任何信息。之后回滚事务,再次查询,又可见到雇员表的所有数据信息。
4、Update更新
仍然以Oracle自带的scott用户为例,并结合事务处理,先看如下代码:
DbCore dbCore = null; try { dbCore = PublicClass.GetNewDbCore(); dbCore.Open(); dbCore.BeginTransaction(); EMP entity = new EMP(); entity.EMPNO = 7499; entity = entity.GetEntity(dbCore); dbCore.Update(EMPColumn.TableName).Set(EMPColumn.SAL,entity.SAL + 100) .Set(EMPColumn.COMM,entity.COMM + 100) .Set(EMPColumn.HIREDATE,DateTime.Today) .Where().ConditionAndEqual(EMPColumn.EMPNO,7499).ExecuteNonQuery(); DataSet dst = dbCore.SelectAll().From(EMPColumn.TableName) .Where().ConditionAndEqual(EMPColumn.EMPNO,7499).ExecuteDataSet();//查询 dbCore.Update(EMPColumn.TableName).Set(EMPColumn.SAL,entity.SAL) .Set(EMPColumn.COMM,entity.COMM) .Set(EMPColumn.HIREDATE,entity.HIREDATE) .Where().ConditionAndEqual(EMPColumn.EMPNO,7499).ExecuteNonQuery();//恢复原值 dst = dbCore.SelectAll().From(EMPColumn.TableName) .Where().ConditionAndEqual(EMPColumn.EMPNO,7499).ExecuteDataSet();//查询 dbCore.CommitTransaction();//提交事务 dbCore.Close(); } catch (Exception ex) { if (dbCore != null) { if (dbCore.IsTransaction) { dbCore.RollbackTransaction();//如果已经开始事务,则回滚事务 } dbCore.Close(); } throw ex; } |
这里先声明一个EMP雇员类的实体对象entity,并将主键EMPNO赋以值7499,然后执行获取信息到对应的实体对象,查询跟踪运行可见到。之后分别将该员工的薪水SAL和COMM分别加100和将HIREDATE更新为今天,再查询可见到刚刚更新的记录数据,然后又将该员工的SAL、 COMM和HIREDATE更新恢复原值,跟踪可见查询的数据信息,最后提交事务,该段程序只是介绍演示之用。
2.2.4 Extend辅助扩展功能
许多工具都提供例外辅助的功能,该工具也不例外,简要介绍如下:
1、TableHelp辅助扩展
DbCore dbCore = PublicClass.GetNewDbCore(); DataTable dt1 = dbCore.SelectAll().From(EMPColumn.TableName).ExecuteDataSet().Tables[0]; DataTable dt2 = dbCore.SelectAll().From(DEPTColumn.TableName).ExecuteDataSet().Tables[0]; DataTable dt3 = TableHelp.MergeTable(dt1,dt2,"DEPTNO");//按部门编号DEPTNO列将表dt2的数据合并到dt1 DataTable dt3_ = TableHelp.MergeTable(dt2,dt1,"DEPTNO");//按部门编号DEPTNO列将表dt1的数据合并到dt2,dt1中有多行数据对应,取首行的数据,没有对应的数据为空 DataTable dt4 = TableHelp.AddTableRowNumCol(dt3); //给dt3添加行号 DataTable dt5 = TableHelp.GetTableTopRows(dt4,5); //获取前5行 DataTable dt6 = TableHelp.GetTableSubRows(dt4,6,10); //获取dt4从第6行到第10行 DataTable dt7 = TableHelp.GetTableSubRows(dt4,11,20); //获取dt4从第11行到第20行,注:无20行取到最后一行 DataTable dt8 = TableHelp.GetTableBottomRows(dt4,5); //获取dt4后5行 dt8 = TableHelp.GetTableBottomRows(dt4,50); //获取dt4后50行;dt4没有后50行,从后面往前取到最前面存在行 DataTable dt9 = TableHelp.JoinInner(dt1,"DEPTNO"); //内连接 DataTable dt10 = TableHelp.JoinInner(dt1,"deptno"); //内连接 DataTable dt11 = TableHelp.JoinLeft(dt1,"DEPTNO"); //左外连接 DataTable dt12 = TableHelp.JoinRight(dt1,"DEPTNO"); //右外连接 DataTable dt13 = TableHelp.JoinLeft(dt2,"DEPTNO"); //左外连接 DataTable dt14 = TableHelp.JoinFull(dt1,"DEPTNO"); //完全外连接 @H_301_3053@ DataTable dt15 = TableHelp.SortTable(dt1,"deptno",SortDirection.Asc); DataTable dt16 = TableHelp.SortTable(dt1,"deptno"); DataTable dt17 = TableHelp.SortTable(dt1,SortDirection.Asc,"sal",SortDirection.Asc); DataTable dt18 = TableHelp.SortTable(dt1,"sal"); DataTable dt19 = TableHelp.SortTable(dt1,SortDirection.Desc,SortDirection.Desc); DataTable dt20 = TableHelp.SortTableDesc(dt1,"sal"); DataTable dt21 = TableHelp.SortTable(dt1,SortDirection.Desc);
TableHelp.DataTableToExcel(dt1,@"C:/Documents and Settings/楚涛/桌面/temp1.xls"); DataSet dst = new DataSet(); DataTable dt22 = dt1.Copy(); @H_301_3053@//修改表名,DataTable默认TableName="Table",DataSet集合的DataTable.TableName不能同名 dt22.TableName = "EMP"; DataTable dt23 = dt2.Copy(); @H_301_3053@//修改表名,DataSet集合的DataTable.TableName不能同名 dt23.TableName = "DEPT"; dst.Tables.Add(dt22); dst.Tables.Add(dt23); TableHelp.DataSetToExcel(dst,@"C:/Documents and Settings/楚涛/桌面/temp2.xls"); |
TableHelp辅助扩展类,提供合并DataTable数据表、获取数据表指定行的数据、内连接、左外连接、右外连接、完全外连接、对数据表排序、将数据表DataTable或数据集DataSet输出到Excel文件等,可见上述程序代码的释义说明文字。
同时也提供通过过滤条件选择DataTable行、合并数据表行信息、转换数据表列值对并以DataTable的形式返回的常用方法,如下所示:
DataTable dt24 = TableHelp.GetTableSelect(dt1,"deptno=10");//选取deptno=10的所有信息,并以DataTable的形式返回 DataTable dt25 = TableHelp.GetTableSelect(dt1,"deptno=20");//选取deptno=20的所有信息,并以DataTable的形式返回 DataTable dt26 = TableHelp.TableAppend(dt24,dt25);//将dt23数据按行附加到dt22,并以新的结果数据表的形式返回 string[,] strArray = new string[1,2]; strArray[0,0] = "SCOTT"; strArray[0,1] = "scott/tiger"; DataTable dt27 = TableHelp.ReplacleTableColValue(dt26,"ename",strArray); |
MergeTable和TableAppend区别是,前者是横向合并,即列的合并;后者是纵向合并,即行的合并。ReplacleTableColValue功能既是如字面意思所示替换Table列值,没有匹配的保留原值,功能类似Oracle的decode函数。
CommonHelp提供了许多常用方法,如财务上人民币金额大写的转换、字符串中中文的检查、唯一随机数字固定长度为20的数字字符串的产生、HTML代码和对应格式化的字符串的相互转化、整型和浮点型数字字符串检查、Email地址和日期时间格式字符串的检查、获得中文字符串的汉语拼音码首字母等常用功能,可见以下示例:
string str1 = CommonHelp.NumberToRMB(1); //"壹元整" str1 = CommonHelp.NumberToRMB(102); //"壹佰零贰元整" str1 = CommonHelp.NumberToRMB(1000234); //"壹佰万零贰佰叁拾肆元整" str1 = CommonHelp.NumberToRMB(1000023456); //"壹拾亿零贰万叁仟肆佰伍拾陆元整" str1 = CommonHelp.NumberToRMB(100000234567); //"壹仟亿零贰拾叁万肆仟伍佰陆拾柒元整" decimal dec = 1234007890123.45M; str1 = CommonHelp.NumberToRMB(dec); //"壹万贰仟叁佰肆拾亿零柒佰捌拾玖万零壹佰贰拾叁元肆角伍分" string str = string.Empty; for (int i = 0; i < 1000; i++) { str += CommonHelp.GetOnlyRandomID() + "/r/n";//唯一随机数字固定长度为20的数字字符串 } MessageBox.Show(str); string str1 = "abcdEFGH"; bool isHasChinese = CommonHelp.IsHasChineseWord(str1); //false不含有中文字符 str1 = "abcd啊EFGH"; isHasChinese = CommonHelp.IsHasChineseWord(str1); //true含有中文字符 string str2 = "<input type=/"button/" value=/"button/"><input type=/"image/" >"; string str3 = CommonHelp.HTML_CodeToString(str2);//Html代码和对应格式化的字符串的相互转化 string str4 = CommonHelp.StringToHTML_Code(str2);//Html代码和对应格式化的字符串的相互转化 string str5 = CommonHelp.HTML_CodeToString(str3);//Html代码和对应格式化的字符串的相互转化 |
还有其他常用方法和加密/解密常用方法扩展类CryptographyHelp、OfficeHelp常用方法辅助扩展类等就不在此一一列举了。
3 结束语
以上就是VB/C#.Net实体代码生成工具(EntitysCodeGenerate)对ORM实现及使用过程和实体(集)代码的批量生成及各种数据库访问的统一操作的简单介绍,详细可见示例代码及工具帮助文档。使用该工具的还一个好处就是,当项目需要切换数据库或使用不同的数据库时,只须适当修改数据库连接类型及连接字符串即可(或将数据库连接写入到配置文件更灵活)。工具安装后附带大量的示例代码,里面会有更多的示例代码和chm格式的帮助文档。
使用工具建立表之前建议:数据库表名及字段名最好都大写,且表名及字段名首字符最好以大写英文字母开头且不含有'┋'字符。另外文字命名约定俗成为单数形式,可以是可以是英文字母及数字和下划线(_)组合,此外表命名建议以"T_"开头,表字段以"C_"开头,视图以"V_"开头等,这样做的目的可使数据库设计更规范化,同时也避免了与关键字的重复,同时许多数据库都是英文字母开头的且不能含有特殊字符,如Oracle就是这样,且表名称和字段名都是大写(对Postgresql建议采用小写命名),长度不超过30个字符(命名过长也无意义),多出的信息可以在命名注释里多写些注释信息。若采用Oledb、Odbc方式连接,表字段名最好不要这样FIELD1,2,3...以数字顺序结尾,因为这样容易与参数机制冲突,导致数据访问时带来一些参数上不必要的麻烦,且这样命名也不是好的规范,必要时可以是FIELDA,B,C...。每个表建议都设置主键,可以是联合主键。若没有主键,则在使用主键判断记录的唯一性时须指定字段。
当前网上也有许多类似的.net代码生成工具,但每个工具总缺少某个功能,比如:大量的配置、属性字段注释提取的缺少、数据类型之间的映射死板、不稳定、生成代码的规范、代码修改困难、复杂数据访问及性能问题或内部配置文件复杂报错或其它使用问题等等。该工具很好的解决了这些问题,并结合多年具体项目的需求,可以像抽屉一样单独使用或分开或相互结合使用,也可和其它组件并行使用,如可以动态得从诸多公司平台或其它工具中读取数据库参数而不必写死在文件里,这样若要实现对用户数据库连接信息加密/解密也很好处理。
理论的实现总是从简单到复杂,覆盖所有可能,实际应用就需要结合实际从复杂到简单,化复杂为简单,凡是要将复杂的东西以简单的方式解决为好。就如模型思想提出时需要从众多实践中总结出来,再升华并须得到全面的验证,而模型应用则要结合具体实际,拿来适当的从简应用,回归到实践。
软件开发都希望从大量重复繁重的代码中解放出来,缩短工期并提高项目的质量,同时当需要的时候可以准确的将绝大多数后台代码快速完成,并要便于后期维护,这些就需要适当借助工具和方法为开发、维护和交流提供良好的保障。
由于时间仓促,加之考虑欠缺及水平所限,不足之处在所难免,有待进一步完善,敬请各路高手流批评斧正!Thanks!
下载:
http://download.csdn.net/source/2128799
http://download.enet.com.cn/html/030212009031901.html
http://www.skycn.com/soft/53715.html