asp.net mvc 简单项目框架的搭建过程(一)对Bll层和Dal层进行充分解耦
学习asp.net 已经有近三个月的时间了,在asp.net mvc上花的时间最多,但个人真是有些菜,不得不说,asp.net mvc的水真的还是蛮深的。目前在公司实习,也见过公司几个项目的代码了。对项目的代码始终停留在一知半解的地步,能改一些简单的bug,但关于项目的来龙去脉始终云里雾里。对于asp.net mvc的架构始终看不懂。因此,照着传智博客的学习视频,学了一下简单的架构搭建。真个架构的搭建我看了将近两遍视频,才稍稍有些头绪,今天在这里记录一下,一方面加深理解,一方面如果以后忘记了,还能快速的想起来,当然如果我的这篇简陋的随笔能有幸被有需要的人看见,并对他们产生一些帮助,我心里肯定也是非常欢欣的。
好了,闲言少叙,那我就开始写了。
本篇博客我主要想讲如何使用 asp.net mvc + ef + spring.net 搭建一个简单的项目框架,我也并不是对前前后后所有的内容都理解,有的地方是囫囵吞枣,水平太菜,见谅。
在开始搭框架之前,先做一些准备工作:
(1)新建解决方案:
新建一个解决方案,然后在解决方案下间五个文件夹,分别为 common、bll、dal、model、ui.如下图所示:
(说明:我的项目命名为iotpf.proj,因为之前做过一个物联网云平台的项目,这里打算用.net重做实践一下,练练手)
(2)在model层中新建类库 model,然后添加ef数据库实体
添加实体的操作我就不啰嗦了,我以前的博客有讲到。结果如下:
(3)分别在bll里新建类库 xxx.bll 和 xxx.ibll,在dal里新建xxx.dal和xxx.idal,如图所示:
(4)在ui下面新建应用程序iotpf.ui
好了,准备工作已经做好了,下面进入正题。
1.首先在iofpf.dal中新建usersdal.cs文件,这个里面写对于user表的增删改查等系列数据库操作。如下入图所示:
下面列出在userdal中写的关于增删改查几个最常用操作的最基本写法,然后对其进行调用(当然只是那种最简单的调用,不涉及如和进行解耦的操作):
看代码:
1 public class userdal 2 { 3 iotpfmodel context = new iotpfmodel(); 4 //使用lambda表示式查询 5 public iqueryable<users> getusersbylambda(expression<func<users,bool>> wherelambda) 6 { 7 return context.users.where(wherelambda).asqueryable(); 8 9 } 10 11 //分页查询 12 #region 增删改 13 public users adduser(users user) 14 { 15 context.users.add(user); 16 context.savechanges(); 17 return user; 18 } 19 20 public bool deleteuser(users user) 21 { 22 context.entry(user).state = system.data.entity.entitystate.deleted; 23 return context.savechanges() > 0; 24 } 25 26 public bool updateuser(users user) 27 { 28 context.entry(user).state = system.data.entity.entitystate.modified; 29 return context.savechanges() > 0; 30 } 31 #endregion 32 }
上面的代码写了增删改查四个方法,其中查询的方法是根据lambda表达式自定义查询条件。
上面的代存在一下几个方面的问题:(以很通俗化的语言描述,不写菜鸟看不懂的话)
(1)首先,userdal中对数据库进行操作首先要做的就是实例化上下文。目前,我们使用的是单一的ef操作数据库,所以实例化的就是ef的上下文,但是,操作数据的方式不止ef这一种,如果有一天我们的项目突然要求要用nh(另一种操作数据库的方式,但我没学过)操作数据库,那么如果我们像这样写的话,麻烦就大了。我们的项目肯定不会只有一个xxxdal,有很多个的,如果上下文变了,那我们就要改好多个xxxdal里的代码,这就牵一发而动全身了,相当麻烦,这就是第一个问题。
解决方法:可以把实例化上下文的操作单独的放在一个类中,在类中定义一个 getdalcontext()的方法,然后所有的xxxdal都可以调用这个方法,如果上下文变了,那么只需要改这一个文件就可以了,这就灵活一些了。
具体做法:
在iotpf.dal中新建dbcontextfactory.cs文件,然后编写代码如下:
1 public class dbcontextfactory 2 { 3 public static dbcontext getcurrentdbcontext() 4 { 5 return new iotpfmodel(); 6 } 7 }
优化后的写法如下:
1 public static dbcontext getcurrentdbcontext() 2 { 3 dbcontext context = callcontext.getdata("dbcontext") as dbcontext; 4 if (context == null) 5 { 6 context = new iotpfmodel(); 7 callcontext.setdata("dbcontext", context); 8 } 9 10 return context; 11}
(别问我为啥这么优化,尴尬的告诉你,本菜鸡不知道啊!)
(2)其次,userdal中的这几个方法是最最常用的方法,几乎每个xxxdal中都会用到这四个方法,那我们就在每个xxxdal中都写一遍吗?这显然是不合理的,这是无用功;
解决方法:常用的公共的方法,可以封装到一个基类中,所有需要用到的类都继承这个基类就可以了;
具体做法:
在iotpf.dal下新建一个basedal.cs,然后编辑代码如下:
1 public class basedal<t> where t : class ,new() 2 { 3 //实例化上下文 4 dbcontext context = dbcontextfactory.getcurrentdbcontext(); 5 //使用lambda条件进行查询 6 public iqueryable<t> getentitybylambda(expression<func<t,bool>> wherelambda) 7 { 8 return context.set<t>().where(wherelambda).asqueryable(); 9 } 10 11 //增删改 12 #region 增删改 13 public t addentity(t entity) 14 { 15 context.set<t>().add(entity); 16 context.savechanges(); 17 return entity; 18 } 19 public bool deleteentity(t entity) 20 { 21 context.entry<t>(entity).state = entitystate.deleted; 22 return context.savechanges() > 0; 23 } 24 public bool updateentity(t entity) 25 { 26 context.entry<t>(entity).state = entitystate.modified; 27 return context.savechanges() > 0; 28 } 29 #endregion 30 }
然后,在userdal中的代码就可以注释掉了,直接让userdal继承basedal就可以了,其他的xxxdal也是如此:
1 public class userdal:basedal<users>
2.在iofpf.bll中编写业务逻辑层代码
在业务逻辑层中新建userservice.cs文件,然后书写一个添加数据的方法:
public class userservice { userdal userdal = new userdal();//这个地方问题严重 public users adduser(users user) { userdal.addentity(user); return user; } }
好了,问题又来了,又到了一个要解耦的地方。
userdal userdal = new userdal();//这个地方问题严重
着重要讲的是这一句:
(1)首先,bll层(userservice)中直接用了dal层中的类(userdal),这使得两层之间的联系很紧,耦合度太高,只要userdal有任何变化,那么bll层就要进行相应的改动。
改进1:在dal层和bll层之间添加接口层idal,以后调用xxxdal的时候,使用ixxxdal进行调用,这样就用接口层把两个层隔离开来了:
具体做法:
在iotpf.idal下新建接口文件iuserdal.cs,然后让userdal继承iuserdal,当然,basedal的接口也是同理,下面给出代码如下:
ibasedal:
1 public interface ibasedal<t> where t : class,new() 2 { 3 iqueryable<t> getentitybylambda(expression<func<t, bool>> wherelambda); 4 5 t addentity(t entity); 6 7 bool deleteentity(t entity); 8 9 bool updateentity(t entity); 10 }
iuserdal:
public interface iuserdal:ibasedal<users> { }
userdal和basedal都要继承iuserdal和ibasedal才行哦,不然调不动的呢。
使用接口后则调用过程变为这样:
iuserdal userdal = new userdal();
其实像这样写问题仍然蛮严重的。为啥呢?且看第二个问题:
(2)我们肯定不止一个文件会用到userdal吧,实际上很多个文件都会用到userdal。但是,如果有一天,我的dal要由ef写的userdal换为nh写的nhuserdal,那可怎么整呢,我难道要在每个用到userdal的文件里分别去把new userdal() 换为 new nhuserdal()?,显然不啊,和上面那个上下文的类似,我们新建一个类,把new userdal的操作单独取出来,放在一个单独的方法中,这样只需要改一个地方就可以了。
具体做法:
在iotpf.factory下新建abstractfactory.cs,然后在里面添加如下代码:
1 public class abstractfactory 2 { 3 public static iuserdal getcurrentuserdal() 4 { 5 return new userdal(); 6 } 7 }
然后调用过程进一步改进为如下:
iuserdal userdal = abstractfactory.getcurrentuserdal();
这样就要灵活多了。
这种做法其实就是工厂的概念啦。
但是,这样仍然不完美的啦,是不是很恼火,怎么这么麻烦,还能不能愉快的写代码了。哈哈哈,莫慌,麻烦是麻烦,但这种思想还是很牛逼的,好处多多哇。下面看问题:
(3)我接下来说 abstractfactory.cs里面的不足之处。什么不足之处呢?
现在我只写了一个getcurrentuserdal() 对吧,这改起来倒是方便了,但是做项目不可能只有一个userdal啊,以后肯定会有 adal啊,bdal,cdal等各种dal,一旦ef变为nh,那在这个工厂里不是又要改一堆名字了吗?这也太烦了啊。那怎么弄呢?看下面:
我们可以把所使用的数据库操作方式(ef还是nh还是ado.net)写进一个配置文件里面。假设我现在有两种方式操作数据库,ef和nh,一种操作数据库的方法写在项目efdal下,另一种写在nhdal项目下,然后让两个项目下各个操作方法的名字保持一致。如果要用ef操作,那就在配置文件中引用 efdal,如果用nh操作,那就在配置文件中引用nhdal,这样就可以切换自如了,是不是很方便呢,哈哈,确实挺溜的呀。
具体做法:
首先,在ui下的web.config文件中此处添加如下代码:
代码:
<add key="dalassemblyname" value="iotpf.dal"/>
然后在 abstractfactory.cs下添加如下代码:
//配置文件设置 public static string assemblyname = system.configuration.configurationmanager.appsettings["dalassemblyname"]; public static iuserdal getuserdal() { //配置文件设置 ,移到方法外面,会被多个getxxxdal使用 // string assemblyname = system.configuration.configurationmanager.appsettings["dalassemblyname"]; return assembly.load(assemblyname).createinstance(assemblyname + ".userdal") as iuserdal; }
这里因为使用了配置文件,所以不要忘记添加下面这个引用哦:
这就是所谓的 抽象工厂的概念啦。
就像我现在用的是iotpf.dal,那我在配置文件里的value那个地方就写iotpf.dal,以后如果我需要用iotpf.xxxdal,那我就把value改为iotpf.xxxdal就好了。
3.下面我们来讲一下dbsession
dbsession是啥呢?说起dbsession那就要从上面的userservice接着讲啦。下面要说一下 上下文的 context.savechanges()了。
我们应该都知道的,如果我们操作上下文对数据库进行了 增、删、改的操作,那么操作结束后需要有一个 context.savechanges()的操作,用来把对实体的修改保存到数据库中。目前,我们这个操作是写在userdal中的。但是,这样做有一个问题,什么问题呢,我们先看一下下面这段代码:
1 iuserdal userdal = abstractfactory.getuserdal(); 2 public users adduser(users user) 3 { 4 userdal.addentity(user); 5 userdal.deleteentity(user); 6 userdal.updateentity(user); 7 return user; 8 }
这段代码中调用了三个userdal中的方法,分别对数据库进行了三次不同的操作,每次操作之后相应的都会执行一次context.savechanges()的操作,也就是说与数据库交互了三次。有没有觉得三次有点多了呢,明明可以更少的呀,以后如果代码量大了,可能会交互更多次,那性能就很差了呀。这里呀,我们明明可以只交互一次就搞定的呀,怎么做呢,我们可以在把savechanges()的操作从userdal中迁移到userservice中的呀,在userdal中的每个方法先不写context.savechanges()的操作,而是在userservice中把增删改这单个操作一起完成之后再统一进行提交,那岂不是更好吗。就像下面这样:
(此代码仅说明统一提交的意思,不是实际程序中的写法,看这个的同学不要粘这段代码)
1 iuserdal userdal = abstractfactory.getuserdal(); 2 public users adduser(users user) 3 { 4 userdal.addentity(user); 5 userdal.deleteentity(user); 6 userdal.updateentity(user);
context.savechanges();//统一提交 7 return user; 8 }
下面具体来讲做法:
首先,在iotpf.factory下新建类 dbsession.cs,然后在里面编写如下代码:
1 public class dbsession 2 { 3 //声明userdal 4 public iuserdal userdal 5 { 6 get 7 { 8 return abstractfactory.getuserdal(); 9 } 10 } 11 //保存实体 12 public int savechanges() 13 { 14 return dbcontextfactory.getcurrentdbcontext().savechanges(); 15 } 16 }
看到没,声明userdal的操作现在搬到dbsession中了。
然后,userservice.cs现在变成下面这种写法:
1 public class userservice 2 { 3 4 //iuserdal userdal = abstractfactory.getuserdal(); 5 dbsession dbsession = new dbsession(); 6 public users adduser(users user) 7 { 8 dbsession.userdal.addentity(user); 9 dbsession.userdal.deleteentity(user); 10 dbsession.userdal.updateentity(user); 11 dbsession.savechanges(); 12 return user; 13 } 14 }
保存操作已经不再由dal层控制了,而是提交到bll层,由bll层控制。当然,不要忘记回到dal层,把原来代码中的savechanges操作去掉:去掉后代码如下:
1 public class basedal<t> where t : class , new() 2 { 3 //实例化上下文 4 dbcontext context = dbcontextfactory.getcurrentdbcontext(); 5 //使用lambda条件进行查询 6 public iqueryable<t> getentitybylambda(expression<func<t,bool>> wherelambda) 7 { 8 return context.set<t>().where(wherelambda).asqueryable(); 9 } 10 11 //增删改 12 #region 增删改 13 public t addentity(t entity) 14 { 15 context.set<t>().add(entity); 16 // context.savechanges(); 17 return entity; 18 } 19 public bool deleteentity(t entity) 20 { 21 context.entry<t>(entity).state = entitystate.deleted; 22 // return context.savechanges() > 0; 23 return true; 24 } 25 public bool updateentity(t entity) 26 { 27 context.entry<t>(entity).state = entitystate.modified; 28 // return context.savechanges() > 0; 29 return true; 30 } 31 #endregion 32 }
现在是不是就好多了,以后与数据库交互的次数就大大减少了,这样性能应该能提高不少吧,哈哈。
紧接着,我们需要将dbsession和bll层进行隔离,也就是这个操作:
现在应该已经轻车熟路了,先新建接口层 idbsession.cs ,然后新建dbsessionfactory.cs,下面给出代码:
idbsession.cs
1 public interface idbsession 2 { 3 iuserdal userdal { get; } 4 int savechanges(); 5 }
dbsessionfactory.cs:
1 public static idbsession getcurrentdbsession() 2 { 3 idbsession dbsession = callcontext.getdata("dbsession") as idbsession; 4 if (dbsession == null) 5 { 6 dbsession = new dbsession(); 7 callcontext.setdata("dbsession", dbsession); 8 } 9 return dbsession; 10 }
现在userservice.cs中的代码就变成这样:
1 idbsession dbsession = dbsessionfactory.getcurrentdbsession(); 2 public users adduser(users user) 3 { 4 dbsession.userdal.addentity(user); 5 dbsession.userdal.deleteentity(user); 6 dbsession.userdal.updateentity(user); 7 dbsession.savechanges(); 8 return user; 9 }
写到这里,数据访问层dal和业务逻辑成bll之间的解耦基本已经说完了,我感觉应该还是讲的比较清楚的了哈。
这个框架搭建的前半部分就已经讲完了,后面关于业务逻辑层bll和展示层ui之间的系列操作,我留到下一篇讲啦,哈哈,一下子写完我自己hold不住,你估计也看不下去了吧。
下面总结一下哈:
本篇内容扯了那么长,我也真是够有尿性的了,总结起来其实也就几个内容而已啦:
(1)使用接口层进行层与层之间的隔离;
(2)对重复多次使用的方法进行基类封装;
(3)使用工厂的概念,对数据库操作方法进行配置,便于应对数据库的更换,提高框架灵活性;
(4)使用dbsession将数据库保存操作的权限有dal层提交到业务逻辑层,减少与数据库交互次数,提高代码性能;
下一篇将介绍spring.net的使用,虽然我自己也不咋会 ,但还是把自己理解的写出来吧,以写代练,加深理解,大神们不要见笑啊。
我的email:3074596466@qq.com
完成框架代码就不往上贴了哈,有需要的同学可以给我发邮件索取。
明天就是国庆节啦,提前祝大家国庆节快乐,祝祖国繁荣昌盛!
下一篇: 概率近乎0