“造轮运动”之 ORM框架系列(一)~谈谈我在实际业务中的增删改查

想想毕业已经快一年了,也就是大约两年以前,怀着满腔的热血正式跨入程序员的世界,那时候的自己想象着所热爱的技术生涯会是多么的丰富多彩,每天可以与大佬们坐在一起讨论解决各种牛逼的技术问题,喝着咖啡,翘着二郎腿,大致就是下面这幅场景:

可是现实却总是那么不尽如人意,现实的所谓技术生涯是永远写不完的增删改查,还有那日渐稀薄的头发,哎,难...

话说回来,如果连增删改查都做不好,还谈何技术生涯。

所以,我一直认为: 如果你认为自己是一个优秀的程序员,那就应该从最基础的增删改查中就能体现出来

有人可能会说,增删改查?很难吗?

其实,说难,也不难,无非就是写SQL,Add、Delete、Update、Select,仅此而已;但是如果只是从SQL的角度认为这很容易,而对其不懈一顾,那你可能就和当初刚刚开始增删改查大业的我一样,太天真了。

首先,我和大家分享一下我在工作这将近两年中在增删改查这条路上的心路历程。

一、初出茅庐——原生sql走天下,sql写到手抽筋

某年某月某日,大三尾声,翘课在宿舍睡大觉的我刚从床上艰难地爬起来,打开手机,发现刚工作的老学长阿威给我发来了一条信息:

"阿森,来活了,帮我搞个网站,会弄吗?"

"网站?没弄过诶。。"

"这个项目给的银两还挺多的。。"

"可以,可以,这个可以搞,没问题,阿威哥"

就这样,我就走上了Web开发的道路。初入坑,首先遇到的问题就是数据库的操作,但是经过自己的旁门左道的学习也大致摸清了使用ADO.NET操作数据库的方法:

第一步,使用DBConnection建立与数据库之间的连接,打开连接

第二步,然后创建DBCommand对象,初始化你想要执行的SQL语句

第三步,调用DBCommand的方法,执行SQL语句

第四步,使用DataReader逐条读取sql执行的结果,或者使用DataAdapter类将结果填充(Fill)到DataSet中,最后关闭Connection连接

到此为止,就实现了在.net中操作数据库执行sql语句返回执行结果的操作。

当然,我觉得任何一个初级的程序员也都会给自己封装一个叫做SQLHelper的帮助类,我也不例外,它可以帮助你节省频繁的建立数据库连接(DBConnection)、初始化DBCommand对象、读取sql执行结果等重复的代码量。所以,封装一个自己的SQLHelper工具类是一个初级程序员必备的素质。

我把一个当初自己封装的SQLHelper类贴出来献献丑:

   

  1     public static class SqlServerHelper
  2     {
  3         //数据库连接字符串,从配置文件的配置字段中读取
  4         private static readonly string connStr = ConfigurationManager.ConnectionStrings["connStr"].ConnectionString;
  5 
  6         /// <summary>
  7         /// 创建新的数据库连接
  8         /// </summary>
  9         public static SqlConnection CreateSqlConnection()
 10         {
 11             SqlConnection conn = new SqlConnection(connStr);
 12 
 13             conn.Open();   //打开数据库连接
 14 
 15             return conn;
 16         }
 17 
 18         /// <summary>
 19         /// 执行无结果返回的sql语句(共用同一个连接)
 20         /// </summary>
 21         /// <param name="sqlConn"></param>
 22         /// <param name="sqlStr"></param>
 23         /// <param name="sqlParamters"></param>
 24         /// <returns>返回受影响的数据总条数</returns>
 25         public static long ExecuteNoQuery(SqlConnection sqlConn, string sqlStr, params SqlParameter[] sqlParamters)
 26         {
 27             using (SqlCommand cmd = sqlConn.CreateCommand())
 28             {
 29                 cmd.CommandText = sqlStr;
 30                 cmd.Parameters.AddRange(sqlParamters);
 31 
 32                 long sum = cmd.ExecuteNonQuery();
 33 
 34                 return sum;
 35             }
 36         }
 37 
 38         /// <summary>
 39         /// 执行无结果返回的sql语句(不共用一个连接)
 40         /// </summary>
 41         /// <param name="sqlStr"></param>
 42         /// <param name="sqlParamters"></param>
 43         /// <returns>返回受影响的数据总条数</returns>
 44         public static long ExecuteNoQuery(string sqlStr, params SqlParameter[] sqlParamters)
 45         {
 46             using (SqlConnection sqlConn = CreateSqlConnection())
 47             using (SqlCommand cmd = sqlConn.CreateCommand())
 48             {
 49                 cmd.CommandText = sqlStr;
 50                 cmd.Parameters.AddRange(sqlParamters);
 51 
 52                 long sum = cmd.ExecuteNonQuery();
 53 
 54                 return sum;
 55             }
 56         }
 57 
 58         /// <summary>
 59         /// 执行只有一行一列返回数据的sql语句(共用同一个连接)
 60         /// </summary>
 61         /// <param name="sqlConn"></param>
 62         /// <param name="sqlStr"></param>
 63         /// <param name="sqlParamters"></param>
 64         /// <returns>返回结果的第一行第一列数据</returns>
 65         public static object ExecuteScalar(SqlConnection sqlConn, string sqlStr, params SqlParameter[] sqlParamters)
 66         {
 67             using (SqlCommand cmd = sqlConn.CreateCommand())
 68             {
 69                 cmd.CommandText = sqlStr;
 70                 cmd.Parameters.AddRange(sqlParamters);
 71 
 72                 object res = cmd.ExecuteScalar();
 73 
 74                 return res;
 75             }
 76         }
 77 
 78         /// <summary>
 79         /// 执行只有一行一列返回数据的sql语句(不共用同一个连接)
 80         /// </summary>
 81         /// <param name="sqlStr"></param>
 82         /// <param name="sqlParamters"></param>
 83         /// <returns>返回结果的第一行第一列数据</returns>
 84         public static object ExecuteScalar(string sqlStr, params SqlParameter[] sqlParamters)
 85         {
 86             using (SqlConnection sqlConn = CreateSqlConnection())
 87             using (SqlCommand cmd = sqlConn.CreateCommand())
 88             {
 89                 cmd.CommandText = sqlStr;
 90                 cmd.Parameters.AddRange(sqlParamters);
 91 
 92                 object res = cmd.ExecuteScalar();
 93 
 94                 return res;
 95             }
 96         }
 97 
 98         /// <summary>
 99         /// 执行有查询结果的sql语句(共用同一个连接)
100         /// </summary>
101         /// <param name="sqlConn"></param>
102         /// <param name="sqlStr"></param>
103         /// <param name="sqlParamters"></param>
104         /// <returns></returns>
105         public static DataTable ExcuteQuery(SqlConnection sqlConn, string sqlStr, params SqlParameter[] sqlParamters)
106         {
107             using (SqlCommand cmd = sqlConn.CreateCommand())
108             {
109                 cmd.CommandText = sqlStr;
110 
111                 cmd.Parameters.AddRange(sqlParamters);
112 
113                 //把sql语句的执行结果填充到SqlDataAdapter中
114                 SqlDataAdapter adapter = new SqlDataAdapter(cmd);
115 
116                 DataTable dt = new DataTable();
117                 adapter.Fill(dt);    //将执行结果填充到dt对象中
118 
119                 return dt;
120             }
121         }
122 
123         /// <summary>
124         /// 执行有查询结果的sql语句(共用同一个连接)
125         /// </summary>
126         /// <param name="sqlStr"></param>
127         /// <param name="sqlParamters"></param>
128         /// <returns></returns>
129         public static DataTable ExcuteQuery(string sqlStr, params SqlParameter[] sqlParamters)
130         {
131             using (SqlConnection sqlConn = CreateSqlConnection())
132             using (SqlCommand cmd = sqlConn.CreateCommand())
133             {
134                 cmd.CommandText = sqlStr;
135 
136                 cmd.Parameters.AddRange(sqlParamters);
137 
138                 //把sql语句的执行结果填充到SqlDataAdapter中
139                 SqlDataAdapter adapter = new SqlDataAdapter(cmd);
140 
141                 DataTable dt = new DataTable();
142                 adapter.Fill(dt);    //将执行结果填充到dt对象中
143 
144                 return dt;
145             }
146         }
147 
148         /// <summary>
149         /// 批量插入数据到数据库
150         /// </summary>
151         /// <param name="insertTable"></param>
152         /// <param name="dataTableName"></param>
153         public static void BatchInsert(DataTable insertTable, string dataTableName)
154         {
155             using (SqlBulkCopy sbc = new SqlBulkCopy(connStr))
156             {
157                 sbc.DestinationTableName = dataTableName;
158 
159                 for (int i = 0; i < insertTable.Columns.Count; i++ )
160                 {
161                     sbc.ColumnMappings.Add(insertTable.Columns[i].ColumnName, insertTable.Columns[i].ColumnName);
162                 }
163 
164                 sbc.WriteToServer(insertTable);
165             }
166         }
167 
168     }

SQLHelper

于是在那段开发的岁月里,是这个SQLHelper类陪我走完了全程,向它说声辛苦了... 

二、EF框架真香,Lambda表达式我最爱

大三暑假,由于抑制不住想要出去试一试的冲动,我来到了一家小型互联网公司实习,公司也是用.net开发网站的技术栈。

当上岗的第一天,我准备掏出我自认为牛逼的SQLHelper的时候,却被项目经理叫停了,项目经理阿勇对我说:

“小伙子,21世纪了,还在傻傻地用SQLHelper?来,试试EF吧,让你欲罢不能...”

于是,一脸懵逼的我,一头埋进了EF的世界中,不得不说,的确香。

EF全名EntityFramework,是微软官方的一款ORM框架,支持Object(对象)与数据库之间的数据映射,支持Linq的操作语法,受广大.NET程序员青睐。

其实在接触EntityFramework之前我就使用过Dapper,Dapper相对与EF来说是轻量的多的一款ORM框架,近乎于原生sql的写法,让它处于性能之最,也提供对象与数据库表之间的映射。

但正是由于用过了EF,我才知道了原来还有 Lambda to SQL 这种东西,相信大多数程序员都和我一样经历过写 原生SQL的痛苦

无论是多么简单的一条sql,你都要在表设计文档和sql编辑器中反复切换比对字段,生怕自己打错一个字母; 而且我们在基础的业务中的大部分增删改查其实都是相当简单的单表操作sql,正是使用了Lambda to SQL 的代码编写方式,你可以在VsStudio丰富的代码提示下飞快地完成一条简单的SQL语句。

为什么说简单的SQL编写才使用Lambda表达式的形式去编写呢,复杂的sql就不行了吗?

理由是,你要明白不管是Lambda还是Linq的语句,最终都是在EF的SQL语句解析器中被翻译成可供数据库执行的sql语句,不得不说EF的Linq To SQL 的解析器的确强大,但是毕竟还是机器翻译的sql,当你让它给你转化一个复杂的linq语句时,它是一定考虑不到sql性能的优化。

就像你去用金山词霸翻译一个单词,它可以给你翻译的很准确;但是你让它去帮你翻译一个长句,可能你读着就觉得别扭了;更有甚者把一篇论文粘贴复制到金山词霸里,那可能比你直接看英文原文还费劲,这是同样一个道理,有些东西还是需要人工来做,就比如优化sql语句。不能奢求什么都扔给Lambda和Linq,它帮我们做的已经足够多了。

总之一句话, Lambda很香,但绝对不能替代SQL

三、全部都是存储过程,调试太难

毕业后的我来到了一家大型制造业企业从事IT行业,说的这么体面,其实还是一个苦逼敲代码的。

上岗的第一天我就问旁边的程序媛,对,你没看错,是“媛”,不是“猿”,嘿嘿。想我 代码生涯一年以来,第一次见这么多程序媛,低调低调,不要声张,不然让隔壁的程序猿听到了会嫉妒的。

“姐姐,我们这用EF吗?”

“EF?是啥...”,姐姐一脸懵逼。。。

“那你们用什么操作数据库啊?自己封装SQLHelper吗?”

“用存储过程啊,都用了7、8年了”

“哦,这样啊”,我心想,都用了7、8年了,这个姐姐是个狠人。

入乡随俗,咱也不能坏了规矩,那就照着用吧,存储过程以前用过几次,但是那是在sql的逻辑比较复杂的时候才会写存储过程调用的,要是所有的sql都放在存储过程里面去调用的话,那就太繁琐了吧。

经过一段时间的使用,证明我的顾虑是对的。

全部使用存储过程后,发现,就是一条简单的Delete、Insert语句都要写在存储过程中,简直不要太麻烦。

首先的问题就是,不好编写,对于一个刚上手存储过程语法的程序员来说这简直就是灾难,目前的SQL编辑器没有语法提示功能而造成非常不好的代码编写体验,严重拖慢开发进程;

更要命的是,太难调试!当你在c#代码中调用完存储过程后,你发现程序安然无恙地执行完,但是并没有得到你想要的结果,于是你找啊找,找啊找,最后,你只能怀疑是不是存储过程有问题,但是运行过程中存储过程一点动静也没有啊,这就是使用存储过程最大的弊端,悄无声息地出错,完全不知道里面发生了什么,只能默默地把存储过程的参数记下,然后输入到sql的调试其中执行一遍才可以发现错误。

然后你的项目经理跑过来问你昨天的代码敲完了吗,心累...

于是,我对存储过程留下了非常不好的印象。但是存储过程的好处是显而易见的: 当你的业务逻辑用一条sql解决不了的时候你会考虑使用多条sql来配合完成查询,可是如果执行每一条sql语句都要与数据库建立连接然后再执行的话好像有点繁琐不说,主要是重建数据库连接的过程也是需要时间与资源的,所以这个时候存储过程就派上用场了,把你的sql都扔到存储过程里面去吧,多么令人心旷神怡的操作,这个时候你就不要去想存储过程出错难找的问题了,因为你想要获得方便总要有点牺牲精神。

四、没有哪一种是完全适合自己的,如果有,那就是自己造的

想要有Dapper的神速,又想要有EF中使用lambda表达式的便捷,但是二者好像并不能兼得。

Dapper虽然是效率之王,但是它没有EF中可以使用的便捷的lambda表达式;

EF虽然可以使用便捷的lambda、linq语法,可是由于EF的过分强大(EF还具有对象追踪的功能),让它显得稍微有点臃肿,属于重量级的ORM框架。

于是我就在想,有没有一款轻量级的ORM,既有不低的执行效率,又有简单实用的Lambda TO SQL?

答案当然是,有的,现在开源的国产sqlSuger、freeSql,比比皆是。但是,我就是想自己试试造轮子的感觉,目的并不是说造出多么好的ORM框架去超越他们,而是在这个过程中学到的东西和得到的历练是非常可观的。

我来评几句
登录后评论

已发表评论数()

相关站点

热门文章