项目中如何使用EF
本文将在技术层面挑战园子里的权威大牛们,言语不敬之处敬请包涵。本文旨为技术交流,欢迎拍砖。
园子里面分享和推荐Entity Framework(以下简称EF)的Repository(仓储)设计模式的文章真不少,其中还有很多大牛很详细描述怎么去实现。但是这些文章真是害人不浅。我现在想问问这些大牛们,你们现在的项目真的还在这样用吗?
下面是在找找看里面随便挑的几篇,如果你从未了解过EF Repository,你可以看看:
- 分享基于Entity Framework的Repository模式设计(附源码)
- EF中Repository模式应用场景
- MVC+LINQToSQL的Repository模式之(三)Repository模式实现统一CURD操作,实现EF中的Find主键查找
- Using Repository and Unit of Work patterns with Entity Framework 4.0
我与EF的缘分
大约2年以前,我开始接触Entity Framework,当时公司大牛推荐了几篇EF的Repository设计模式的文章,公司内部也有一个使用EF Repository的框架,当时公司.NET项目基本都用那个框架,我们项目当然也不例外。
大约用了半年,做了2-3个项目,我越来越感觉EF仓储模式用起来一点都方便,代码写起来很别扭。但那会还不知道为什么,因为那会我也没有完全理解仓储模式到底是个什么。
2012年7月,我开始独立带领一个中型项目开发团队。数据库访问的架构仍然延续了公司一直推崇的EF+Repository模式。当开发进行到一半的时候,我感觉我们的数据库访问的代码已经很乱了。service层在访问数据库,UI层也在访问数据库。由于同时使用了构造函数方式的依赖注入,循环依赖的问题也让代码变得难以维护。但是直到项目结束我才明白应该怎么使用EF。
在随后的一个项目中,我完全抛弃了EF+Repository模式,使用一种全新的架构。在新架构上开发了一个多月,我意识到我们确实用对了,于是我跟公司推荐。但公司大牛们并不认可。几个月后,有另一位项目经理也提出EF+Repository模式用起来太恶心。
在接下来大半年时间到现在,我带的多个项目都采用了新的架构,用起来实在太爽了,我也多次建议在公司推广。后来公司大牛基本也都否认了EF+Repository模式,但由于各种原因,其他项目组使用我们新架构的只占少数。
写这么长一段经历我只想说“大牛”们的权威真的影响好大,但只有真理才经得起时间的考验。下面我将详细阐述我对EF+Repository模式的理解。
EF+Repository模式错在哪里
1. 没有理解到什么是 repository 设计模式。
首先我想说repository设计模式的原理。repository即仓库,通常我们会按照数据库中的表来建repository。repository外一定有一个包裹器,因为所有对repository的增删改都不会立即提交到数据库,而需要调用包裹器的提交才会真正跟数据库进行通讯。
当你看这篇文章( 分享基于Entity Framework的Repository模式设计(附源码))的时候,乍一看,确实是按照repository来设计的啊。IUnitOfWork即包裹器,数据的增删改查都放到了这个包裹器里面。所以啊,很多人就这样被欺骗了。
实际上这篇文章的中的repository框架性的代码没有一行有意义,因为EF本身即是按照repository来设计的。这个观点有很多人知道,但更多的人不知道。请看下面这行代码:
var db = new DemoDbContext();
这里的db即相当于是repository的包裹器。通过包裹器可以非常轻松的访问到其下的每一个repository,这也绝对是最简单的访问方式了,如:
db.Users....
db.Products....
而这篇文章中,如果要访问repository,还要通过IOC。不知道绕这么大个圈子干嘛,不是自己给自己找事吗。
2. EF 本身就是 repository 设计模式的
仓储模式最大的优点就是所有的数据访问首先是通过仓库的,对仓库的增删改都不会立即提交到数据库,而只有当调用了仓库包裹器,这些增删改的操作才会一次提交到数据库。
在EF中,DbSet<TEntity>即是定义的仓库,DbContext即是仓库包裹器,相当于UnitOfWork。所有对DbSet<TEntity>的增删改都只有在调用了DbContext的SaveChanges之后才会提交到数据库。这样看,EF是不是完全实现了Repository了呢?
另外,DbContext的SaveChanges本身包含了一个事务机制,再结合TransactionScope类,我认为EF真的是完美解决了所有事务问题。请看我之前的一篇博客中分享的基于EF事务机制的架构: http://www.cnblogs.com/leotsai/p/how-to-use-entity-framework-transaction-scope.html
我真不明白,这么多大牛们为什么要把EF本身的Repository再包裹一遍。关键是包裹一遍的代码不但丑,不但难用,而且有BUG!
EF+Repository模式的三大缺点
1. 代码丑
首先整个+Repository这一层的代码都是多余的,包括UnitOfWork类和继承自IRepository的类。 在这个架构下写代码,你会写大量重复的没有意义的IRepository的实现。而在service层,由于各个repository是独立的,但是实体类之间又是有关系的(导航属性),有时你的代码通过导航属性在访问另一个repository,有时又不是,有时为了统一全部从repository里面读数据,你不得不先定义要用到的所有repository,然后再写很多看不懂的inner join。当业务逻辑复杂的时候,这样的代码真的是要让维护人员崩溃。
2. 难用
首先你要写大量重复的代码。但更恶心的还不是这个,更恶心的是,明明有导航属性可以访问到另一张表,但是为了不跨repository,你只能通过unitOfWork.GetRepo来获取一个repository的实例,然后就是大量的inner join。并且这个规则实在很难执行,于是你发现有时候你也在用导航属性,然后你就会怀疑了,我定义的repository有何意义?
再加上很多人根本不知道UnitOfWork是什么意思,于是到处都有UnitOfWork,比如UI层都在用。可曾想,UnitofWork原来是用来包裹数据库访问的repository的,UI层怎能访问数据库呢?
3. 有BUG
这里说的BUG不是指运行会报错,而是指架构的BUG。说白一点,就是这个架构会勾引你犯错写BUG。
最最明显的一个BUG就是,IReposoitory<TEntity>下面有个void Update(TEntity entity)方法。当然也不是所有的EF+Reposoitory模式都这样写,但大多数是有的。
这个方法真的是害死人了。我相信被这个方法害过的人大有人在,我曾经也是其中之一。举个简单的例子,看能不能勾起你那忧伤的回忆。请看代码:
1 public ActionResult Update(Product product)
2 {
3 _productRepository.Update(product);
4 _unitOfWork.SaveChanges();
5 return View("UpdateSuccess");
6 }
这段代码看上去没有问题,但BUG是,每次更新一个product,createdTime就被设置为当前时间,因为在Product构造函数里设置了createdTime为now。哎,真是悲剧!作为一个项目经理,你可能无数次忠告你的程序员注意这个BUG,但这个BUG还是不断重现,因为过两天Status字段又被重置了,再过几天另一个字段的值又丢失了。
这个设计实际上跟Repository没有任何关系,但很多大牛这样写了,害了很多人,所以在此专门提出。
结论
EF本身即是按照Repository模式设计的,我们完全没有任何必要再自己去写一套repository把EF的repository再包裹一层;EF本身的事务机制本是完美的,额外包裹一层repository之后,让事务变得模糊。
我把我们项目的架构提取出来放到了github上面,大家可以看看:https://github.com/leotsai/mvcsolution 。注意Data和Services层的代码。
下面的代码是其中一个service,是不是代码简洁合理?
1 using System;
2 using System.Linq;
3 using MvcSolution;
4 using MvcSolution.Data.Entities;
5 using MvcSolution.Data.Entities;
6 using MvcSolution.Infrastructure.Security;
7
8 namespace MVCSolution.Services.Users
9 {
10 public class UserService : ServiceBase<User>, IUserService
11 {
12 #region Implementation of IUserService
13
14 public User Get(string username)
15 {
16 using(var db = base.NewDB())
17 {
18 return db.Users.Get(username);
19 }
20 }
21
22 public string[] GetRoles(string username)
23 {
24 using (var db = base.NewDB())
25 {
26 return db.Roles.WhereByUsername(username).Select(x => x.Name).Distinct().ToArray();
27 }
28 }
29
30 public bool CanLogin(string username, string password)
31 {
32 using (var db = base.NewDB())
33 {
34 var user = db.Users.Get(username);
35 return user != null && user.IsDisabled == false
36 && user.Password == CryptoService.MD5Encrypt(password);
37 }
38 }
39
40 public void Register(User user)
41 {
42 using (var db = base.NewDB())
43 {
44 if (db.Users.Get(user.Username) != null)
45 {
46 throw new Exception("username already registered.");
47 }
48 user.Password = CryptoService.MD5Encrypt(user.Password);
49 db.Users.Add(user);
50 db.SaveChanges();
51 }
52 }
53
54 #endregion
55 }
56 }
关于如何更好的使用EF的架构,我认为应该有以下3点:
- 在service层要给访问数据库的代码绝对的自由,就像sql一样,导航属性、跨表增删改查随便写,而不是UnitOfWork.GetRepo;
- 在service层之后要终止对数据库的访问,也就是每一个service方法必须将DbContext dispose掉,不管这个service是做增删改还是查询;
- 在UI层可使用TransactionScope来包裹多个service,从而实现跨service的事务机制。
完。
项目中如何使用EF的更多相关文章
- [翻译 EF Core in Action 1.10] 应该在项目中使用EF Core吗?
Entity Framework Core in Action Entityframework Core in action是 Jon P smith 所著的关于Entityframework Cor ...
- EF结合SqlBulkCopy在项目中的使用
这是我第一次写博客,由于水平有限,写不出什么好东西,还望见谅. 我现在参与的这个项目采用的是EF框架,方便了数据库的访问.但在实际中,发现项目中导入市县Excel数据耗时太长,于是趁这段时间专门研究了 ...
- visual studio 项目中使用EF创建的数据库,后续更新数据库操作(生产已经部署,不能删除数据库重新创建)
情景:SharePoint项目(其他类型的项目道理也一样),数据库是用EF(版本:6.0.0.0)创建的,生产环境已经使用,所以后续修改数据库,只能通过更新来实现. 下面是具体的操作方式: 1.vis ...
- 说说我在项目中为什么不用实体框架,如果说我在诋毁你所爱的EF,请进来.
1.坑多. 这一点没有人会否定.当然你可以说你很牛,但事实不会因为你牛就可以说不存在.从博客园中的博问中大家关于EF的提问量就问题的怪异程度就可以看出来. 1.Entity Framework 查询历 ...
- web项目中 集合Spring&使用junit4测试Spring
web项目中 集合Spring 问题: 如果将 ApplicationContext applicationContext = new ClassPathXmlApplicationContext(& ...
- Android 百度地图开发(一)--- 申请API Key和在项目中显示百度地图
标签: Android百度地图API Key 分类: Android 百度地图开发(2) 最近自己想研究下地图,本来想研究google Map,但是申请API key比较坑爹,于是从百度地 ...
- 解决项目中EF5.0升级到EF6.0无法安装包的方法
今天在vs2012上新建了一个mvc4的项目,mvc4中默认的Entity Framework是5.0的版本,如下所示: 或者:,但是项目中有些要用到EF6.0的相关方法,用EF5.0实在繁琐,于是在 ...
- Entity Framework 的小实例:在项目中添加一个实体类,并做插入操作
Entity Framework 的小实例:在项目中添加一个实体类,并做插入操作 1>. 创建一个控制台程序2>. 添加一个 ADO.NET实体数据模型,选择对应的数据库与表(Studen ...
- asp.net core 实战项目(一)——ef core的使用
数据库设计 数据结构图如下: 此次实例比较简单,暂时只设计到上述3张表 SMUser:用于存储用户信息. Role:用于存储角色信息. SMUser_Role:用建立用户和角色关系的一直关联表. ...
随机推荐
- php 正则表达式四,例子
PHP常用正则表达式汇总:http://bbs.php100.com/read-htm-tid-83266.html 1.非空匹配: .+ 2.浮点数匹配: ^\d+.\d{2}$ 3.手机号匹配: ...
- The OpenCV Coding Style Guide
https://github.com/opencv/opencv/wiki/Coding_Style_Guide
- java 字符串解析为json 使用org.json包的JSONObject+JSONArray
参考: https://blog.csdn.net/xingfei_work/article/details/76572550 java中四种json解析方式 JSONObject+JSONArray ...
- 分别用request和socket给百多发送请求
1.方式1 import socket client = socket.socket() # 百度创建连接: 阻塞 client.connect(('www.baidu.com',80)) # 问百度 ...
- git发布代码到github过程和常见错误
在对git有了基本了解之后,并且常常看到很多人在github上发布代码和开源项目时,就会想如何也把自己的代码发布到github上,并能够不断的跟踪版本变化. 现在就有几个想要做的事. 一.如何把自己已 ...
- golang的多协程实践
go语言以优异的并发特性而闻名,刚好手上有个小项目比较适合. 项目背景: 公司播控平台的数据存储包括MySQL和ElasticSearch(ES)两个部分,编辑.运营的数据首先保存在MySQL中,为了 ...
- C语言-随机数
C语言使用rand()函数产生随机数, 使用rand()函数之前要先使用srand(time(0)), 以当前时间作为种子, 否则产生的随机数将不会变化. #include <stdio.h&g ...
- php RFC兼容的电子邮件地址验证
php中,进行RFC兼容的电子邮件地址验证的方法,有需要的朋友参考下吧. 分享一个可以验证RFC兼容的电子邮件地址的代码,支持RFC1123,2396,3696,4291,4343,5321等的验证. ...
- Tornado的基本知识
Tornado是FriendReed使用的可扩展的非阻塞式的web服务器及其相关工具的开源版本. 这个框架看起来有些像web.py或者Google的webapp,不过为了能有效利用非阻塞服务器环境,这 ...
- s5_day5作业
# 1.写函数,用户传入修改的文件名,与要修改的内容,执行函数,完成批量修改操作 # def number_file(file,change_s,change): # import os # with ...