MVC+UnitOfWork+Repository+EF 之我见
2015-01-05 11:09
197 查看
UnitOfWork+Repository模式简介:
每次提交数据库都会打开一个连接,造成结果是:多个连接无法共用一个数据库级别的事务,也就无法保证数据的原子性、一致性。解决办法是:在Repository的CRUD操作基础上再包装一层,提供统一的入口,让服务层调用。同一个UnitOfWork实例对象下所有的Repository都共同一个数据库上下文对象(ps:EF用的是DbContext),也就是共用一个事物。提交数据库时,只要有一个操作失败,那么所有的操作都被视为失败。
项目结构:
关键代码:
AggregateRoot.cs:
Channel.cs:
IUnitOfWork.cs:
UnitOfWork.cs:
BaseRepository.cs:
IDbContext.cs:
CMSDbContext.cs:
UnityConfig.cs:
ChannelApplcationService.cs:
序列图:
心得体会:
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访问周期内提供的各种扩展点等。
每次提交数据库都会打开一个连接,造成结果是:多个连接无法共用一个数据库级别的事务,也就无法保证数据的原子性、一致性。解决办法是:在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 > 0 ? pageIndex : 1; var result = this.GetFiltered(filter, orderBy, ascending, includes); total = result.Count(); return result.Skip((pageIndex - 1) * 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 > 0) { 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 > 0) { 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
- ASP.NET MVC3.0+ JqGrid+Unit Of Work+ Repository/ EF 4.1 CRUD应用 (多层结构)
- MVC3+EF4.1学习系列(八)-----利用Repository and Unit of Work重构项目
- Implementing the Repository and Unit of Work Patterns in an ASP.NET MVC Application (9 of 10)
- MVC+MEF+UnitOfWork+EF架构,网站速度慢的原因总结!(附加ANTS Memory Profiler简单用法)
- 关于EF Unit of Work Repository的简单用法
- TinyFrame升级之七:重构Repository和Unit Of Work
- MVC UnitOfWork EntityFramework架构
- 基于NHibernate的UnitOfWork+Repository模式(AutoFac)–Part1
- 基于NHibernate的UnitOfWork+Repository模式(AutoFac)
- Using Repository and Unit of Work patterns with Entity Framework 4.0
- 基于NHibernate的UnitOfWork+Repository模式(AutoFac)–P1
- EF Unit Of Work
- ef unitofwork 主从表更新
- Revisiting the Repository and Unit of Work Patterns with Entity Framework
- Generic repository pattern and Unit of work with Entity framework
- MVC3+EF4.1学习系列(八)-----利用Repository and Unit of Work重构项目
- 【无私分享:ASP.NET CORE 项目实战(第五章)】Repository仓储 UnitofWork
- MVC UnitOfWork EntityFramework架构,网站速度慢的原因总结!