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  Grade=@Grade  AND  Age=@Age 
 从以上示例代码可以看出,自动生成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   DESCRIPTION=@DESCRIPTION  , Birthday=@Birthday  WHERE Id=@Id
注意:这里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  Age=@Age 

注:上面的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   Id=@P_ID_0  OR  Id=@P_ID_1  OR  Id=@P_ID_2  OR  Id=@P_ID_3  OR  Id=@P_ID_4  OR  Id=@P_ID_5  OR  Id=@P_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   QQ=@QQ  , REALNAME=@REALNAME  WHERE      Id=@P_ID_0  OR  Id=@P_ID_1  OR  Id=@P_ID_2  OR  Id=@P_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>=@AGE AND SEX=@SEX", 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 REALNAME=@REALNAME)", 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 REALNAME=@MY_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 REALNAME=@MY_REALNAME AND AGE=@AGE)", 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查询得到结果,然后再执行批量删除和批量更新操作。

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

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

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

    Stimulsoft Reports.Web,是一款可以直接在Web中编辑报表的报表工具 web项目技术框架mvc4+easyui+knockoutjs 1.在项目中添加引用 Stimulsoft.B ...

  2. [分享]一个String工具类,也许你的项目中会用得到

    每次做项目都会遇到字符串的处理,每次都会去写一个StringUtil,完成一些功能. 但其实每次要的功能都差不多: 1.判断类(包括NULL和空串.是否是空白字符串等) 2.默认值 3.去空白(tri ...

  3. Java中使用elasticsearch搜索引擎实现简单查询、修改等操作-已在项目中实际应用

    以下的操作环境为:jdk:1.8:elasticsearch:5.2.0 maven架包下载坐标为: <dependency> <groupId>org.elasticsear ...

  4. 轻量级ORM工具Simple.Data

    今天推举的这篇文章,本意不是要推举文章的内容,而是据此介绍一下Simple.Data这个很有意思的类ORM工具. 现在大家在.NET开发中如果需要进行数据访问,那么基本都会使用一些ORM工具,比如微软 ...

  5. Hyperledger项目中使用的工具

    Hyperledger作为一个众多IT厂商参与的项目,全球化的开源社区,其项目的组织形式.流程.工具,都值得借鉴.好工匠离不开好工具,我注意到Hyperledger项目中使用了大量的好工具,包括项目管 ...

  6. 【转】.NET(C#):浅谈程序集清单资源和RESX资源 关于单元测试的思考--Asp.Net Core单元测试最佳实践 封装自己的dapper lambda扩展-设计篇 编写自己的dapper lambda扩展-使用篇 正确理解CAP定理 Quartz.NET的使用(附源码) 整理自己的.net工具库 GC的前世与今生 Visual Studio Package 插件开发之自动生

    [转].NET(C#):浅谈程序集清单资源和RESX资源   目录 程序集清单资源 RESX资源文件 使用ResourceReader和ResourceSet解析二进制资源文件 使用ResourceM ...

  7. 在简单的JDBC程序中使用ORM工具

    本文来自[优锐课]——抽丝剥茧,细说架构那些事. ORM(对象关系映射)是用于数据库编程的出色工具.只需一点经验和Java注释的强大功能,我们就可以相对轻松地构建复杂的数据库系统并利用生产力.关系数据 ...

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

    Lab 1: 应用程序建模 实验目标 这个实验的目的是展示如何在Visual Studio 2010旗舰版中进行应用程序建模.团队中的架构师会通过建模确定应用程序是否满足客户的需求. 你可以创建不同级 ...

  9. 基于WebServices简易网络聊天工具的设计与实现

    基于WebServices简易网络聊天工具的设计与实现 Copyright 朱向洋 Sunsea ALL Right Reserved 一.项目内容 本次课程实现一个类似QQ的网络聊天软件的功能:服务 ...

随机推荐

  1. Uploading File using Ajax and receiving binary data in Asp.net (C#)[转]

    基础知识,可由此衍生.原文:http://uniapple.net/blog/?p=2050 In this post, I will show you how to upload a file us ...

  2. LoadRunner javavuser错误排查

    Loadrunner 9.5/11 使用java 开发vsuer script需要的环境配置 本文从两个方面来讲:windows 32位操作系统:windows 64 操作系统开始之前,先说下java ...

  3. CIO需加强对战略管理层面的掌控-精华篇

    当代CIO面临提升信息化作用的新机遇.CIO在企业中,不能满足于职能性的技术支撑角色,要找到新的着力点,以发挥信息化在全局战略中的作用,把信息化力量聚焦于做强做优,提高国际竞争力上来,成为企业不可或缺 ...

  4. Python中dataframe数据框中选择某一列非空的行

    利用pandas自带的函数notnull可以很容易判断某一列是否为null类型,但是如果这一列中某一格为空字符串"",此时notnull函数会返回True,而一般我们选择非空行并不 ...

  5. 实现一个div,左边固定div宽度200px,右边div自适应

    实现一个div,左边固定div宽度200px,右边div自适应<div class= "container"> <div class="left&quo ...

  6. Go语言的类型转换和类型断言

    https://my.oschina.net/chai2010/blog/161418 https://studygolang.com/articles/9335  类型转换.类型断言和类型切换 ht ...

  7. Ognl_JSTL_学习笔记

    控制标签 使用Struts2标签必须先导入标签库,在页面使用如下代码导入Struts2标签:<%@taglib prefix="s" uri="/struts-ta ...

  8. SpringMVC由浅入深day01_8springmvc和mybatis整合

    8 springmvc和mybatis整合 为了更好的学习 springmvc和mybatis整合开发的方法,需要将springmvc和mybatis进行整合. 整合目标:控制层采用springmvc ...

  9. 8 -- 深入使用Spring -- 1...两种后处理器

    8.1 两种后处理器 Spring框架提供了很好的扩展性,出了可以与各种第三方框架良好整合外,其IoC容器也允许开发者进行扩展,这种扩展甚至无须实现BeanFactor或ApplicationCont ...

  10. Git Step by Step – (8) Git的merge和rebase

    前面一篇文章中提到了"git pull"等价于"git fetch"加上"git merge",然后还提到了pull命令支持rebase模式 ...