简单ORM工具的设计和编写,自己项目中曾经用过的

http://www.cnblogs.com/szp1118/archive/2011/03/30/ORM.html

在之前的一个项目中自己编写了一个简单的ORM小工具,这次重新整理和重构了一下代码,之所以说简单是因为该小工具仅仅实现了增删改查的简单功能,不具备数据缓存,延迟加载,关联操作等高级功能。正因为简单所以用起来也不麻烦,代码也不是很复杂,但是在数据层至少可以减少70%以上的代码编写量,可以减少至少50%以上的SQL语句编写量。

  

设计思想:实体类中的非null属性都会作为SQL语句中的参数进行处理(在增删改查的SQL语句中的位置有所不同)

接下来就从这个设计思想入手:

先来看一下两个简单的类和一张数据库表:

类UserEntity继承于UserBriefEntity,当然只定义一个UserEntity类也可以,之所以定义两个类,是因为在列表中等某些场合比较适合使用字段较少的UserBriefEntity类,而在详细信息显示等场合适合使用包含全部字段的UserEntity类。

设计思路:

1.数据库中的一张表需要和实体类进行对应,表中的字段对应到实体类的属性,不一定都要一一对应,可以有属性不对应到表中,但是一般情况下数据库字段都应该对应到某个属性,否则就没法对该字段进行操作了。有时候在列表页和详细页需要获取的数据不同(列表页是少数几个字段),这种情况下可以定义一个少量字段的实体类,再定义一个所有字段的实体类(继承至前一个类)。

2.由于.net的基本类型例如int,bool等都是值类型,意味着无法赋值为null,它们都有默认初始值,int为0,bool为false,这样的话自动处理就无法分辨出是默认赋值还是用户自己赋值,所以实体类中的属性就必须是引用类型,对于.net基本类型就要做可空处理了(.net 2.0新增功能),如 int? , bool? 这样来定义实体类的属性类型。

增删改查的具体处理:

新增:

insert操作都是对一张表进行的,对一张表的新增无非就是SQL语句中字段的多少问题了,这个可以通过自动生成SQL语句来完成,新增操作的SQL语句应该说100%可以自动生成,生成SQL语句的原则是实体类中非null值的属性都作为需要新增的字段进行处理,而值为null的属性不会生成到SQL语句中去。

例如如下代码:

Zhezhe.Common.DataAccess.EntityHelper.Insert(
                new UserBriefEntity { Id = Guid.NewGuid(), Age = 111, Birthday = new DateTime(1980, 12, 23) });

我们new了一个UserBriefEntity对象,对三个属性进行了赋值,那么自动生成的SQL语句如下:

INSERT INTO T_USER (Id,Age,Birthday) VALUES (@Id,@Age,@Birthday)

注意,本工具生成的SQL语句都是参数化的,所以不存在SQL注入漏洞。

如下代码和自动生成的SQL语句

Zhezhe.Common.DataAccess.EntityHelper.Insert(
                new UserBriefEntity { Id = Guid.NewGuid(), Name = "wanglx", UserName = "wlx" });

INSERT INTO T_USER (Id,UserName,REALNAME) VALUES (@Id,@UserName,@REALNAME)

使用 UserEntity 类也没有问题,示例代码和自动生成的SQL语句如下:

Zhezhe.Common.DataAccess.EntityHelper.Insert(
                new UserEntity { Id = Guid.NewGuid(), Age = 112, Birthday = new DateTime(1981, 1, 11), SN = 12034012, Pwd = "111111", Sex = false });

INSERT INTO T_USER (PASSWORD,Sex,SN,Id,Age,Birthday) VALUES (@PASSWORD,@Sex,@SN,@Id,@Age,@Birthday)

Zhezhe.Common.DataAccess.EntityHelper.Insert(
                new UserEntity { Id = Guid.NewGuid(), ChildrenNumber = 2, Desc = "哈哈", Grade = Grade.Normal, SN = 1234 });

INSERT INTO T_USER (DESCRIPTION,Grade,ChildrenNumber,SN,Id) VALUES (@DESCRIPTION,@Grade,@ChildrenNumber,@SN,@Id)

Insert方法的返回值是插入数据的条数。

从以上示例代码可以看出,自动生成insert语句的原则完全是按照“实体类中的非null属性都会作为SQL语句中的参数进行处理”这一设计思想展开的,只要是非null属性都会被拼接到SQL语句中去。

Insert方法签名如下:

public static int Insert<T>(T instance)

结论:insert的SQL语句100%可以自动生成

删除:

delete操作也是对一张表进行的,删除操作比较复杂,主要是where条件的不同,自动生成的SQL语句不可能做到能生成各种复杂条件,这里如果要设计复杂的话就会很复杂了,还是简单化一些吧,本工具只自动生成实体类中非null属性作为where条件“和”相等性判断的SQL语句,具体来说就是,如果实体类中所有属性都是null,那么生成的DELETE SQL语句就不会带有任何条件了,这样整个表的数据就被删除了,如果实体中A属性的值为”a”,其余属性值为null,则生成的SQL语句中 where 条件为 A = “a” ,如果A属性值为”a”,B属性值为”b”,则相应的where条件为A = “a” and B=”b”,自动生成的DELETE语句中无或(or)判断。

示例代码如下:

Zhezhe.Common.DataAccess.EntityHelper.Delete(new UserEntity() { Age = 21, Grade = Grade.Diamond });

自动生成的SQL语句如下:

DELETE FROM T_USER  WHERE  [email protected]  AND  [email protected]

从以上示例代码可以看出,自动生成delete语句的原则也完全是按照“实体类中的非null属性都会作为SQL语句中的参数进行处理”这一设计思想展开的,只要是非null属性都会被拼接到SQL语句的where条件中去。在拼接的过程中收到以下限制:1.条件只能为=号,2.条件之间只能是and关系。之所以按照这么个逻辑生成因为是这种方式最常用。

结论:delete的SQL语句50%可以自动生成,如果结合下面的先查询再删除的方法进行的话则可以做到90%的delete语句可以自动生成,无需手写。

Delete方法的签名如下:

public static int Delete<T>(T instance)

修改:

update操作也是对一张表进行的,update操作相比较insert和delete更为复杂,insert语句的所有参数都位于同一个位置(values 后面),delete语句的所有参数也都位于同一个位置(where 后面),而update语句的参数位置则有两个地方,一个是set后面,另一个是where后面,实体类中的非null属性如果作为SQL语句参数的话就不知道是放在这两个位置的哪个位置了,这里作了如下的处理:主键属性作为where条件,其它的属性都作为set语句,也就意味着自动生成的update语句只能根据主键字段作为条件进行更新操作。

示例代码如下:

Zhezhe.Common.DataAccess.EntityHelper.Update(new UserEntity { Id = list3[0].Id, Birthday = new DateTime(1985, 1, 4), Desc = "我被修改过了" });

自动生成的update语句如下:

UPDATE T_USER SET   [email protected]  , [email protected]  WHERE [email protected]

注意:这里UserEntity实体的主键字段属性必须赋值,否则会报异常(今后可能会修改为主键字段属性如果null,则更新整个表的数据)。

结论:update的SQL语句30%可以自动生成,如果结合下面的先查询再更新的方法的话则可以做到80%的delete语句可以自动生成,无需手写。

Update方法的签名如下:

public static int Update<T>(T instance)

查询:

select是最复杂的,以上的增删改都是针对一张表进行的,而且返回值都是整型类型的受影响的行数,而select则可能针对几张表操作,返回值是一个结果集,如果使用ADO.NET的话则需要存放至DataTable,或者是通过DataReader赋值给对象。本程序中进行select操作的方法为GetList方法,具有多个重载方法。本程序中将返回的结果集全部转换为对象的集合,都是通过DataReader的方式进行的,没有使用DataTable。结果集中的字段列表也需要相应的实体对应。本程序中仅对一种情况自动生成select SQL语句:对单张表(视图)查询,条件为非null属性,条件仅为相等,各条件之间仅为and连接,整个delete语句的条件生成方式是一样的。

方法签名如下:

public static IList<T> GetList<T>(T instance)

示例代码如下:

var list1 = Zhezhe.Common.DataAccess.EntityHelper.GetList<UserEntity>(new UserEntity() { Age = 20 });

自动生成的SQL语句如下:

SELECT * FROM T_USER WHERE 1=1  AND  [email protected]

注:上面的1=1条件仅仅是当无条件的时候语句仍旧不会报错的处理方式(相信很多人都这样写过),当然也可以在没条件时把where关键字去掉,这里为了方便加了1=1条件。

如果要查询整个表的数据可以这样做:

var list1_1 = Zhezhe.Common.DataAccess.EntityHelper.GetList<UserEntity>(new UserEntity() { });

自动生成的SQL语句如下:

SELECT * FROM T_USER WHERE 1=1

上述方法的泛型参数可以是UserEntity,也可以是UserBriefEntity,只要类中的属性在查询的结果集中有对应的字段就可以(查询结果集中的字段如果在类中无属性对应则忽略)。

结论:select的SQL语句估计仅有10%-20%可以自动生成,仅当对单张的表的字段的相等查询的情况可以自动生成。

以上的增删改查是主要的几个API方法,所有的SQL语句均为自动生成。除了上述几个方法之外还定义了以下几个API方法:

delete操作我们经常会遇到批量删除的情况,比如用户通过多选批量删除数据,本程序定义了通过主键批量删除数据的方法,签名如下:

public static int BatchDelete<TE, TK>(TK[] ids)

泛型参数TE是删除的实体(需要通过该实体知道对哪张进行删除操作),泛型删除TK是主键字段对应的实体属性的具体类型。

示例代码如下:

Zhezhe.Common.DataAccess.EntityHelper.BatchDelete<UserEntity,Guid?>(list1.Select(e => e.Id).ToArray());

list1是前述中查询得到的结果集,见本文前面所述。

该方法调用自动产生的SQL语句如下:

DELETE FROM T_USER WHERE   [email protected]_ID_0  OR  [email protected]_ID_1  OR  [email protected]_ID_2  OR  [email protected]_ID_3  OR  [email protected]_ID_4  OR  [email protected]_ID_5  OR  [email protected]_ID_6

参数的个数有数组TK[] ids中的个数决定。

如果没有此批量删除方法也可以通过循环调用之前的Delete方法删除,只是效率不高而已,当然此批量删除方法也仅仅是针对主键的批量删除。

update也定义了通过主键批量更新的方法,签名如下:

public static int BatchUpdate<TE, TK>(TK[] ids, TE instance)

泛型参数TE定义了更新的实体,TK定义了实体的主键属性类型。

示例代码如下:

Zhezhe.Common.DataAccess.EntityHelper.BatchUpdate(list1.Select(e => e.Id).ToArray(), new UserEntity { QQ = "2222222", Name = "改名了" });

list1是前述中查询得到的结果集,见本文前面所述。

自动生成的SQL语句如下:

UPDATE T_USER SET   [email protected]  , [email protected]  WHERE      [email protected]_ID_0  OR  [email protected]_ID_1  OR  [email protected]_ID_2  OR  [email protected]_ID_3

条件中的参数个数有TK[] ids数组中的元素个数决定。

对于查询操作,本程序定义了多个可以自定义SQL语句的重载函数,最主要的一个API如下:

public static IList<T> GetList<T, TW>(string sql, SqlParameter[] parms, CommandType ct, TW instance)

泛型参数T是实际返回的集合中的实体类型,该实体类型中的需要映射的属性必须包含在查询结果中(结果中的字段值可以为null),TW定义了另一个实体,该实体中的非null属性可以作为查询的参数。parms参数可以自己传入。最终的SQL语句的参数是parms中的参数和TW 实体中非null属性组成的参数的和。

当然自定义的SQL语句可以没有参数,也可以仅有来自TW实体的参数,也可以仅有来自parms的参数,所以,该方法又定义了如下重载方法:

public static IList<T> GetList<T>(string sql, SqlParameter[] parms, CommandType ct)
        {
            return EntityHelper.GetList<T, Util.NoPropertyClass>(sql, parms, ct, new Util.NoPropertyClass());
        }

public static IList<T> GetList<T>(string sql, SqlParameter[] parms, CommandType ct, T instance)
        {
            return EntityHelper.GetList<T, T>(sql, parms, ct, instance);
        }

public static IList<T> GetList<T, TW>(string sql, CommandType ct, TW instance)
        {
            return EntityHelper.GetList<T, TW>(sql, null, ct, instance);
        }

public static IList<T> GetList<T>(string sql, CommandType ct, T instance)
        {
            return EntityHelper.GetList<T, T>(sql, null, ct, instance);
        }

public static IList<T> GetList<T>(string sql, CommandType ct) where T:class
        {
            return EntityHelper.GetList<T>(sql, null, ct, null);
        }

示例代码如下:

1.查询语句无参数的情况

var lsit4 = Zhezhe.Common.DataAccess.EntityHelper.GetList<UserBriefEntity>("select * from T_USER where AGE>=21 AND SEX=1", CommandType.Text);

2.查询语句带有两个参数,这两个参数没有手动传入,而是通过传入一个UserEntity实体类型取得,这个实体类型实例必须包含这个两个参数对应的属性,而且属性值必须为非null,如下所示:

var list5 = Zhezhe.Common.DataAccess.EntityHelper.GetList<UserBriefEntity, UserEntity>(
                "select * from T_USER where AGE>[email protected] AND [email protected]", CommandType.Text, new UserEntity { Age = 22, Sex = true });

可以看出,得到的结果集的实体类型和取参数的实体类型可以不同,总而言之,只要非null属性能够包含相同参数名称就可以。

注意:自定义的SQL语句中的参数命名,默认和字段名称一致。因为从实体实例中自动获取参数就是这个默认的命名规则。

3.两个表的情况:返回结果集实体是OrderEntity类型,而取参数的实体类型是UserEntity

var list6 = Zhezhe.Common.DataAccess.EntityHelper.GetList<OrderEntity,UserEntity>(
               "select * from ORDERENTITY where USER_ID in (select ID from T_USER where [email protected])", CommandType.Text, new UserEntity { Name = "Zhezhe1" });

4.自己传入参数的情况

var list7 = Zhezhe.Common.DataAccess.EntityHelper.GetList<OrderEntity>(
               "select * from ORDERENTITY where USER_ID in (select ID from T_USER where [email protected]_REALNAME)", new SqlParameter[] { new SqlParameter("@MY_REALNAME", "Zhezhe1") }, CommandType.Text);

由于是自己传入参数,所以参数的命名可以随意自己取名,这里取名为@MY_REALNAME

5.自己传入参数和从实体实例中获取参数相结合

var list8 = Zhezhe.Common.DataAccess.EntityHelper.GetList<OrderEntity,UserEntity>(
               "select * from ORDERENTITY where USER_ID in (select ID from T_USER where [email protected]_REALNAME AND [email protected])", new SqlParameter[] { new SqlParameter("@MY_REALNAME", "Zhezhe1") }, CommandType.Text, new UserEntity { Age = 22 });

参数@MY_REALNAME来自自己传入,参数@AGE从实体中获得

以上的select的结果的对应实体可以自己随意定义,只要能和select结果中的字段对应即可,不一定要和表对应,因为select的结果可能来自多个表。

总结:虽然自定义的SQL语句需要自己手写SQL,但是基本可以处理所有各种复杂查询,这些API主要在以下几点给您省时省力,1.参数可以通过实体实例取得,不一定都要自己传入,免去了大量的定义参数的代码,2.返回的结果程序自动转换为对应实体的集合,免去了大量的DataReader取数据赋值的代码。

前文曾经提到删除和修改操作可以通过先查询后执行删除或修改的办法来实现。 可以首先通过自定义的SQL查询得到结果,然后再执行批量删除和批量更新操作。

以上只是一个大概介绍和设计思想,具体代码实现会在下一篇中讨论,并且会提供代码下载。

时间: 2024-08-06 15:02:44

简单ORM工具的设计和编写,自己项目中曾经用过的的相关文章

web报表工具Stimulsoft Reports.Web在mvc项目中使用

Stimulsoft Reports.Web,是一款可以直接在Web中编辑报表的报表工具 web项目技术框架mvc4+easyui+knockoutjs 1.在项目中添加引用 Stimulsoft.Base.dll, Stimulsoft.Report.dll, Stimulsoft.Report.Web.dll, Stimulsoft.Report.WebDesign.dll 2.定义模板文件:新建web窗体文件report.aspx 前台代码: <%@ Page Language="C

【工具与解决方案】从做项目中积累学习

[Java-Swing] 1.http://java-mans.iteye.com/blog/1650786     JAVA-SWT如何在Jtable单元格中加入复选框Jcheckbox,Jbutton,JcomboBox 2.环境搭建 http://blog.csdn.net/ghuil/article/details/40652645 http://www.cnblogs.com/yaowukonga/archive/2013/02/28/2937169.html http://www.t

Hyperledger项目中使用的工具

Hyperledger作为一个众多IT厂商参与的项目,全球化的开源社区,其项目的组织形式.流程.工具,都值得借鉴.好工匠离不开好工具,我注意到Hyperledger项目中使用了大量的好工具,包括项目管理.源代码管理.wiki.视频会议.会议备份.IM.maillist.Wiki等等,可以列一个长长的list.先埋个坑,回头来填这篇文章. 原文地址:https://www.cnblogs.com/huahuayu/p/8513680.html

关于Socket编写简单聊天工具的总结(原创)

这段时间再看socket编程,虽然现在是刚刚接触,但是还是忍不住想写一篇总结,来激励自己努力学习,写的不好的地方,还请大家指教啊! 下面针对一个简单的发送消息和文件的程序说说吧.   首先是服务器需要准备二个Socket和二个Thread如下: //和客户机进行通信 private Socket sckCommit; //监听客户机 private Socket sckListen; private Thread thdListen; private Thread thdCommit; 对客户机

分享一个自己写的.Net的ORM工具

注册博客园帐号也有好几年了,之前注册帐号主要是为了看别人的文章下载东西的时候方便.从来没有写过什么博客,一直以为只要注册了帐号就可以写博客,最近用到了才发现还得申请一下,于是就申请了博客,算了也不扯这么多没用的了,直接进入主题吧! 网上开源的ORM工具也不少,开源中国上就有不少,很多都下载试用过,不过感觉用起来都不是很方便,园子里面也有不少人分享自己写的ORM工具,用过一个叫 MySoft.Data 的ORM工具,感觉里面的链式调用函数的写法不错,但是用起来感觉也有些不如意的地方,于是某个周末的

【转】【UML】使用Visual Studio 2010 Team System中的架构师工具(设计与建模)

Lab 1: 应用程序建模 实验目标 这个实验的目的是展示如何在Visual Studio 2010旗舰版中进行应用程序建模.团队中的架构师会通过建模确定应用程序是否满足客户的需求. 你可以创建不同级别的详细模型,并将它们彼此结合.测试然后发布到你的开发计划里. 在这个实验中, 我们将重点放在如何创建一系列简单的系统建模图形上. 每个练习应该在 30分钟内完成. Exercise 1 – 理解用户需求 绘制活动.类以及其他UML图形能帮助架构师清晰辨别客户的习惯.业务规则以及其他需求,从而使设计

c语言中如何设计和编写一个应用系统?

C程序中,如何设计和编写一个应用系统? 一. C语言文件的操作 1. 文件操作的基本方法: C语言将计算机的输入输出设备都看作是文件.例如,键盘文件.屏幕文件等. 向屏幕输出一个信息,例如"Hello"是 #include.h> int main() { printf("Hello\\n"); } 从键盘接收一个字符串然后显示是 #include.h> int main() { char a[10]; scanf("%s",&

网站发布助手V1.1 (去年写的简单小工具)

去年在更新网站内容时老是要输入重复的话,所以就写了一个很简单的工具(很菜的). 网站发布助手V1.1(使用快捷键快速输入自定义的文本)    网站发布助手是一个多次复制粘贴工具,能够快速将自己事先定义好的的文本通过快捷键粘贴到想要发布的地方,软件有8个快捷键,分别是F1.F2.F3.F4.数字键1.数字键2.数字键3.数字键4,在软件目录下的st.txt能够自定义要粘贴的内容,由上到下每行分别对应一个相应快捷键.当你每天重复大量相同文本录入的时候本软件可以减轻你的负担,让你快捷的输入文本.   

十个简单好用的设计技巧

本文作者Mark Praschan是一位具有将近十年经验的网页设计师,Web开发师,Web项目经理人. 文中强调复杂的高级效果能为设计增色不少,但如果用得不对,只会影响用户对重点内容的关注.高级效果可能正好是一项好的设计的冲击力所在,但即便如此,也还是需要一些更简单的效果与其配合. 简单的效果和技巧是建造当今设计的基石.比方说,如果你都不知道如何正确选择颜色和文字效果,灿烂的 星光效果又能有什么用? 本着“少就是多”的理念,通过十个简单好用的设计技巧 ,就足以大大提升你设计的专业性和感染力.基础