MVC+UnitOfWork+Repository+EF 之我见
UnitOfWork+Repository模式简介:
每次提交数据库都会打开一个连接,造成结果是:多个连接无法共用一个数据库级别的事务,也就无法保证数据的原子性、一致性。解决办法是:在Repository的CRUD操作基础上再包装一层,提供统一的入口,让服务层调用。同一个UnitOfWork实例对象下所有的Repository都共同一个数据库上下文对象(ps:EF用的是DbContext),也就是共用一个事物。提交数据库时,只要有一个操作失败,那么所有的操作都被视为失败。
项目结构:

关键代码:
AggregateRoot.cs:
using System;
using System.Collections.Generic; namespace CMS.Domain.Core
{
/// <summary>
/// 表示聚合根类型的基类型。
/// </summary>
public abstract class AggregateRoot : IAggregateRoot
{
#region 方法 public virtual IEnumerable<BusinessRule> Validate()
{
return new BusinessRule[] { };
} #endregion #region Object 成员 public override bool Equals(object obj)
{
if (obj == null)
return false; if (ReferenceEquals(this, obj))
return true; IAggregateRoot ar = obj as IAggregateRoot; if (ar == null)
return false; return this.Id == ar.Id;
} public override int GetHashCode()
{
return this.Id.GetHashCode();
} #endregion #region IAggregateRoot 成员 public Guid Id
{
get;
set;
} #endregion
}
}
Channel.cs:
using CMS.Domain.Core; namespace CMS.Domain.Entities
{
public class Channel : AggregateRoot
{
public string Name
{
get;
set;
} public string CoverPicture
{
get;
set;
} public string Desc
{
get;
set;
} public bool IsActive
{
get;
set;
} public int Hits
{
get;
set;
}
}
}
IUnitOfWork.cs:
using System; namespace CMS.Domain.Core.Repository
{
/// <summary>
/// 工作单元
/// 提供一个保存方法,它可以对调用层公开,为了减少连库次数
/// </summary>
public interface IUnitOfWork : IDisposable
{
#region 方法 IRepository<T> Repository<T>() where T : class, IAggregateRoot; void Commit(); #endregion
}
}
UnitOfWork.cs:
using CMS.Common;
using CMS.Domain.Core;
using CMS.Domain.Core.Repository;
using System;
using System.Collections;
using System.Collections.Generic; namespace CMS.Infrastructure
{
public class UnitOfWork : IUnitOfWork, IDisposable
{
#region 变量 private bool _disposed;
private readonly IDbContext _dbContext;
private Hashtable _repositories; #endregion #region 构造函数 public UnitOfWork(IDbContext dbContext)
{
this._dbContext = dbContext;
this._repositories = new Hashtable();
} #endregion #region 方法 public virtual void Dispose(bool disposing)
{
if (!this._disposed)
if (disposing)
this._dbContext.Dispose(); this._disposed = true;
} #endregion #region IUnitOfWork 成员 public IRepository<T> Repository<T>() where T : class, IAggregateRoot
{
var typeName = typeof(T).Name; if (!this._repositories.ContainsKey(typeName))
{ var paramDict = new Dictionary<string, object>();
paramDict.Add("context", this._dbContext); //Repository接口的实现统一在UnitOfWork中执行,通过Unity来实现IOC,同时把IDbContext的实现通过构造函数参数的方式传入
var repositoryInstance = UnityConfig.Resolve<IRepository<T>>(paramDict); if (repositoryInstance != null)
this._repositories.Add(typeName, repositoryInstance);
} return (IRepository<T>)this._repositories[typeName];
} public void Commit()
{
this._dbContext.SaveChanges();
} #endregion #region IDisposable 成员 public void Dispose()
{
this.Dispose(true); GC.SuppressFinalize(this);
} #endregion
}
}
BaseRepository.cs:
using CMS.Domain.Core;
using CMS.Domain.Core.Repository;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions; namespace CMS.Infrastructure
{
public class BaseRepository<T> : IRepository<T> where T : class, IAggregateRoot
{
#region 变量 private readonly DbContext _db;
private readonly IDbSet<T> _dbset; #endregion #region 构造函数 public BaseRepository(IDbContext context)
{
this._db = (DbContext)context;
this._dbset = this._db.Set<T>();
} #endregion #region IRepository 成员 public void Add(T item)
{
this._dbset.Add(item);
} public void Remove(T item)
{
this._dbset.Remove(item);
} public void Modify(T item)
{
this._db.Entry(item).State = EntityState.Modified;
} public T Get(Expression<Func<T, bool>> filter)
{
return this._dbset.Where(filter).SingleOrDefault();
} public IEnumerable<T> GetAll()
{
return this._dbset.ToList();
} public IEnumerable<T> GetPaged<KProperty>(int pageIndex, int pageSize, out int total, Expression<Func<T, bool>> filter, Expression<Func<T, KProperty>> orderBy, bool ascending = true, string[] includes = null)
{
pageIndex = pageIndex > ? pageIndex : ; var result = this.GetFiltered(filter, orderBy, ascending, includes); total = result.Count(); return result.Skip((pageIndex - ) * pageSize).Take(pageSize).ToList();
} public IEnumerable<T> GetFiltered<KProperty>(Expression<Func<T, bool>> filter, Expression<Func<T, KProperty>> orderBy, bool ascending = true, string[] includes = null)
{
var result = filter == null ? this._dbset : this._dbset.Where(filter); if (ascending)
result = result.OrderBy(orderBy);
else
result = result.OrderByDescending(orderBy); if (includes != null && includes.Length > )
{
foreach (var include in includes)
{
result = result.Include(include);
}
} return result.ToList();
} #endregion
}
}
IDbContext.cs:
namespace CMS.Infrastructure
{
public interface IDbContext
{
#region 方法 int SaveChanges(); void Dispose(); #endregion
}
}
CMSDbContext.cs:
using CMS.Infrastructures.Mapping;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions; namespace CMS.Infrastructure
{
public class CMSDbContext : DbContext, IDbContext
{
#region 构造函数 public CMSDbContext()
: base("SqlConnectionString")
{ } #endregion #region DbContext 重写 protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>(); modelBuilder.Configurations.Add(new ChannelEntityConfiguration());
} #endregion
}
}
UnityConfig.cs:
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;
using System;
using System.Collections.Generic; namespace CMS.Common
{
public class UnityConfig
{
#region 属性 public static IUnityContainer Container
{
get
{
return container.Value;
}
} #endregion #region 方法 private static Lazy<IUnityContainer> container = new Lazy<IUnityContainer>(
() =>
{
var container = new UnityContainer(); RegisterTypes(container); return container;
}); private static void RegisterTypes(IUnityContainer container)
{
container.LoadConfiguration();
} public static T Resolve<T>(IDictionary<string, object> paramDict = null)
{
var list = new ParameterOverrides(); if (paramDict != null && paramDict.Count > )
{
foreach (var item in paramDict)
{
list.Add(item.Key, item.Value);
}
} return Container.Resolve<T>(list);
} #endregion
}
}
ChannelApplcationService.cs:
using AutoMapper;
using CMS.Domain.Core.Repository;
using CMS.Domain.Entities;
using CMS.DTO;
using Microsoft.Practices.Unity; namespace CMS.Applcation
{
public class ChannelApplcationService
{
#region 属性 [Dependency]
public IUnitOfWork UnitOfWork { get; set; } #endregion #region 方法 public Response<bool> Add(ChannelDTO dto)
{
var resp = new Response<bool>();
var channel = Mapper.Map<Channel>(dto); using (this.UnitOfWork)
{
var channelAddRepository = this.UnitOfWork.Repository<Channel>(); channelAddRepository.Add(channel); this.UnitOfWork.Commit();
} resp.Result = true; return resp;
} #endregion
}
}
序列图:

心得体会:
1. Repository的CRUD操作只能作用于继承了AggregateRoot基类的DomainObject(ps:以实际项目情况为准,可以做适当的妥协)。
2. DomainObject中涉及到集合类型(如IList,ISet等)的聚合属性需要加“virtual”关键字,让ORM框架识别做Lazyload处理。
3. 各自独立的业务逻辑写在对应的DomainObject方法中,方法体内只能处理自身以及内部聚合对象的数据和状态等信息,被聚合的对象不建议里面再有方法,只需定义相关属性即可(ps:涉及到对外通知、发布消息等场景以DomainEvent的方式处理,关于DomainEvent的概念和使用会开新章进行简述)。
4. 把需要多个DomainObject交互和协调的业务逻辑放到DomainService中(ps:在Applcation Layer中调用。另外DomainService是否能调用Repository对象我一直很困惑,因为看过有代码是这么写的,但又有人不建议这么做......)。
5. 在AggregateRoot基类中定义验证BusinessRule的虚方法,供子类重写,并在统一的地方执行(比如Applcation Layer)
6. 定义DomainException,用来封装Domain Layer层的异常信息,对上层(Applcation Layer)暴露。
7. Applcation Layer代码的主要作用(可用WCF、WebAPI或直接Dll引用等方式对上层(UI Layer)暴露)
- 接收UI Layer传递的DTO对象。
- 通过AutoMapper组件转换成对应的DomainObject,并调用其方法(ps:内部业务逻辑的封装)。
- 调用Repository对象来实现CRUD操作(ps:这时数据还只是在内存中)。
- 调用UnitOfWork的Commit方法来实现数据的真正提交(ps:事物级别的)。
所以可以看出Applcation Layer主要用来处理业务的执行顺序,而不是关键的业务逻辑。
Applcation Layer如果用WCF或WebAPI的方式对外暴露有个好处,可以针对其作负载均衡,坏处是额外增加了IIS的请求开销。
8. DTO和DomainObject区别
DTO(ps:为了简单起见,这里把DTO和ViewModel放在一块说了):
- 根据实际业务场景加上Required、StringLength等验证特性,结合MVC框架的内部验证机制,可在Controller层做到数据的有效性验证(ps:永远都不要轻易相信浏览器端提交的数据,即使已经有了js脚本验证......)。
- 负责View数据的展现和表单提交时数据的封装。
- 负责把数据从UI Layer传递到Applcation Layer,里面只能有属性,而且是扁平的,结构简单的属性。
DomainObject:通俗点说就是充血模型,包括属性和行为,在DDD整个框架设计体系中占非常重要的地位,其涵盖了整个软件系统的业务逻辑、业务规则、聚合关系等方面。(ps;如果业务很简单,可以只有属性)
9. UI Layer:自我学习UnitOfWork+Repository以来,一直用的是MVC框架做前台展现,选择的理由:1. Unity4MVC的IOC功能非常强大,2. 天生支持AOP的思想,3. 更加传统、原始的Web处理方式,4. Areas模块对插件化设计的支持,5. Ajax、ModelBuilder、JSON、验证,6. 整个Http访问周期内提供的各种扩展点等。
MVC+UnitOfWork+Repository+EF 之我见的更多相关文章
- MVC+UnitOfWork+Repository+EF
MVC+UnitOfWork+Repository+EF UnitOfWork+Repository模式简介: 每次提交数据库都会打开一个连接,造成结果是:多个连接无法共用一个数据库级别的事务,也就无 ...
- MVC中使用EF(2):实现基本的CRUD功能
MVC中使用EF(2):实现基本的CRUD功能 By Tom Dykstra |July 30, 2013 Translated by litdwg Contoso University示例网站 ...
- MVC UnitOfWork EntityFramework架构
MVC UnitOfWork EntityFramework架构,网站速度慢的原因总结! 最近参考使用了郭明峰的一套架构来做新的项目架构,这套架构看起来还是不错的,先向小郭同学的分享精神致敬! (郭同 ...
- ASP.NET MVC 5 with EF 6 上传文件
参考 ASP.NET MVC 5 with EF 6 - Working With Files Rename, Resize, Upload Image (ASP.NET MVC) ASP ...
- 解析ASP.NET Mvc开发之EF延迟加载
目录: 1)从明源动力到创新工场这一路走来 2)解析ASP.NET WebForm和Mvc开发的区别 3)解析ASP.NET Mvc开发之查询数据实例 ------------------------ ...
- MVC 中使用EF
EF 1)简单查询 后台代码 using MvcApplication18.Models; using System; using System.Collections.Generic; using ...
- MVC中使用EF(1):为ASP.NET MVC程序创建Entity Framework数据模型
为ASP.NET MVC程序创建Entity Framework数据模型 (1 of 10) By Tom Dykstra |July 30, 2013 Translated by litdwg ...
- mvc+webapi+dapper+ef codefirst项目搭建
首先项目是mvc5+webapi2.0+orm数据处理(dapper)+ef动态创建数据库. 1.项目框架层次结构: mvc项目根据不同的业务和功能进行不同的区域划分[今后项目维护起来方便],mode ...
- .NET Core2.0 MVC中使用EF访问数据
使用环境:Win7+VS2017 一.新建一个.NET Core2.0的MVC项目 二.使用Nuget添加EF的依赖 输入命令:Install-Package Microsoft.EntityFram ...
随机推荐
- 动态拼接linq 使用Expression构造动态linq语句
最近在做动态构造linq语句,从网上找了很多,大多数,都是基于一张表中的某一个字段,这样的结果,从网上可以搜到很多.但如果有外键表,需要动态构造外键表中的字段,那么问题来了,学挖掘机哪家强?哦,不是, ...
- maven_spring mvc_mina_dome(实体,文件,批传)(spring mina 初学dome)
看我们群里经常有人在问mina心跳问题,虽然俺是菜鸟可是觉得挺简单的啊,就写了个dome,希望大家多多提意见. 俺做过一段时间网络协议.所以觉得挺简单的吧.哎呀,反正技术就那样了没啥难的. 废话不多说 ...
- C++ 知道虚函数表的存在
今天翻看陈皓大大的博客,直接找关于C++的东东,看到了虚函数表的内容,找一些能看得懂的地方记下笔记. 0 引子 类中存在虚函数,就会存在虚函数表,在vs2015的实现中,它存在于类的头部. 假设有如下 ...
- ROS笔记——创建简单的主题发布节点和主题订阅节点
在安装好ROS后,接着学习如何创建节点和节点之间的通信方式,以一个简单的主题发布节点和主题订阅节点说明. 节点是连接ROS网络等可执行文件,是实现某些功能的软件包,也是一个主要计算执行的进程. 一.创 ...
- zynq学习01 新建一个Helloworld工程
1,好早买了块FPGA板,zynq 7010 .终极目标是完成相机图像采集及处理.一个Window C++程序猿才开始学FPGA,一个小菜鸟,准备转行. 2,关于这块板,卖家的官方资料学起来没劲.推荐 ...
- 多线程的学习与python实现
学习了进程与线程,现对自己的学习进行记录. 目录: 一.进程与线程的概念,以及联系与区别 二.多线程 三.python中多线程的应用 四.python实例 五.参考文献 一.进程与线程的概念.以及联系 ...
- MFC-01-Chapter01:Hello,MFC---1.3 第一个MFC程序(04)
1.3.3 框架窗口对象 MFC的CWnd类及其派生类为窗口或应用程序创建的窗口提供了面向对象的接口. CMainWindow是从CFrameWnd类派生而来,CFrameWnd模仿框架窗口的行为,可 ...
- http://tool.oschina.net 在线API文档库java jquery ,php,很全的文档库
http://tool.oschina.net 1.6API文档(中文)的下载地址: ZIP格式:http://download.java.net/jdk/jdk-api-localizations ...
- $(document).ready() 与 window.onload 之间的区别
1.执行时机 window.onload 是网页中所有的元素都加载到浏览器后才执行 $(document).ready() 是dom完全就续就可以调用 例如:如果给一副图片添加点击事件,window. ...
- DevExpress gridControl 设置分组
有些代码非常有用,但是用的时候就记不清怎么写,所以就在这里打个草稿. //设置组汇总 private void SetSummation() { this.gridViewShipment.Group ...