首页 > 代码库 > MVC+UnitOfWork+Repository+DomainObject+EF 之我见

MVC+UnitOfWork+Repository+DomainObject+EF 之我见

UnitOfWork+Repository模式简介:

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

项目结构:

技术分享

关键代码:

AggregateRoot.cs:

 1 using System; 2 using System.Collections.Generic; 3  4 namespace CMS.Domain.Core 5 { 6     /// <summary> 7     /// 表示聚合根类型的基类型。 8     /// </summary> 9     public abstract class AggregateRoot : IAggregateRoot10     {11         #region 方法12 13         public virtual IEnumerable<BusinessRule> Validate()14         {15             return new BusinessRule[] { };16         }17 18         #endregion19 20         #region Object 成员21 22         public override bool Equals(object obj)23         {24             if (obj == null)25                 return false;26 27             if (ReferenceEquals(this, obj))28                 return true;29 30             IAggregateRoot ar = obj as IAggregateRoot;31 32             if (ar == null)33                 return false;34 35             return this.Id == ar.Id;36         }37 38         public override int GetHashCode()39         {40             return this.Id.GetHashCode();41         }42 43         #endregion44 45         #region IAggregateRoot 成员46 47         public Guid Id48         {49             get;50             set;51         }52 53         #endregion54     }55 }

Channel.cs:

 1 using CMS.Domain.Core; 2  3 namespace CMS.Domain.Entities 4 { 5     public class Channel : AggregateRoot 6     { 7         public string Name 8         { 9             get;10             set;11         }12 13         public string CoverPicture14         {15             get;16             set;17         }18 19         public string Desc20         {21             get;22             set;23         }24 25         public bool IsActive26         {27             get;28             set;29         }30 31         public int Hits32         {33             get;34             set;35         }36     }37 }

IUnitOfWork.cs:

 1 using System; 2  3 namespace CMS.Domain.Core.Repository 4 { 5     /// <summary> 6     /// 工作单元 7     /// 提供一个保存方法,它可以对调用层公开,为了减少连库次数 8     /// </summary> 9     public interface IUnitOfWork : IDisposable10     {11         #region 方法12 13         IRepository<T> Repository<T>() where T : class, IAggregateRoot;14 15         void Commit();16 17         #endregion18     }19 }

UnitOfWork.cs:

 1 using CMS.Common; 2 using CMS.Domain.Core; 3 using CMS.Domain.Core.Repository; 4 using System; 5 using System.Collections; 6 using System.Collections.Generic; 7  8 namespace CMS.Infrastructure 9 {10     public class UnitOfWork : IUnitOfWork, IDisposable11     {12         #region 变量13 14         private bool _disposed;15         private readonly IDbContext _dbContext;16         private Hashtable _repositories;17 18         #endregion19 20         #region 构造函数21 22         public UnitOfWork(IDbContext dbContext)23         {24             this._dbContext = dbContext;25             this._repositories = new Hashtable();26         }27 28         #endregion29 30         #region 方法31 32         public virtual void Dispose(bool disposing)33         {34             if (!this._disposed)35                 if (disposing)36                     this._dbContext.Dispose();37 38             this._disposed = true;39         }40 41         #endregion42 43         #region IUnitOfWork 成员44 45         public IRepository<T> Repository<T>() where T : class, IAggregateRoot46         {47             var typeName = typeof(T).Name;48 49             if (!this._repositories.ContainsKey(typeName))50             {51                 var repositoryType = typeof(IRepository<T>);52 53                 var paramDict = new Dictionary<string, object>();54                 paramDict.Add("context", this._dbContext);55 56                 //Repository接口的实现统一在UnitOfWork中执行,通过Unity来实现IOC,同时把IDbContext的实现通过构造函数参数的方式传入57                 var repositoryInstance = UnityConfig.Resolve<IRepository<T>>(paramDict);58 59                 if (repositoryInstance != null)60                     this._repositories.Add(typeName, repositoryInstance);61             }62 63             return (IRepository<T>)this._repositories[typeName];64         }65 66         public void Commit()67         {68             this._dbContext.SaveChanges();69         }70 71         #endregion72 73         #region IDisposable 成员74 75         public void Dispose()76         {77             this.Dispose(true);78 79             GC.SuppressFinalize(this);80         }81 82         #endregion83     }84 }

BaseRepository.cs:

 1 using CMS.Domain.Core; 2 using CMS.Domain.Core.Repository; 3 using System; 4 using System.Collections.Generic; 5 using System.Data.Entity; 6 using System.Linq; 7 using System.Linq.Expressions; 8  9 namespace CMS.Infrastructure10 {11     public class BaseRepository<T> : IRepository<T> where T : class, IAggregateRoot12     {13         #region 变量14 15         private readonly DbContext _db;16         private readonly IDbSet<T> _dbset;17 18         #endregion19 20         #region 构造函数21 22         public BaseRepository(IDbContext context)23         {24             this._db = (DbContext)context;25             this._dbset = this._db.Set<T>();26         }27 28         #endregion29 30         #region IRepository 成员31 32         public void Add(T item)33         {34             this._dbset.Add(item);35         }36 37         public void Remove(T item)38         {39             this._dbset.Remove(item);40         }41 42         public void Modify(T item)43         {44             this._db.Entry(item).State = EntityState.Modified;45         }46 47         public T Get(Expression<Func<T, bool>> filter)48         {49             return this._dbset.Where(filter).SingleOrDefault();50         }51 52         public IEnumerable<T> GetAll()53         {54             return this._dbset.ToList();55         }56 57         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)58         {59             pageIndex = pageIndex > 0 ? pageIndex : 1;60 61             var result = this.GetFiltered(filter, orderBy, ascending, includes);62 63             total = result.Count();64 65             return result.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList();66         }67 68         public IEnumerable<T> GetFiltered<KProperty>(Expression<Func<T, bool>> filter, Expression<Func<T, KProperty>> orderBy, bool ascending = true, string[] includes = null)69         {70             var result = filter == null ? this._dbset : this._dbset.Where(filter);71 72             if (ascending)73                 result = result.OrderBy(orderBy);74             else75                 result = result.OrderByDescending(orderBy);76 77             if (includes != null && includes.Length > 0)78             {79                 foreach (var include in includes)80                 {81                     result = result.Include(include);82                 }83             }84 85             return result.ToList();86         }87 88         #endregion89     }90 }

IDbContext.cs:

 1 namespace CMS.Infrastructure 2 { 3     public interface IDbContext 4     { 5         #region 方法 6  7         int SaveChanges(); 8  9         void Dispose();10 11         #endregion12     }13 }

CMSDbContext.cs:

 1 using CMS.Infrastructures.Mapping; 2 using System.Data.Entity; 3 using System.Data.Entity.ModelConfiguration.Conventions; 4  5 namespace CMS.Infrastructure 6 { 7     public class CMSDbContext : DbContext, IDbContext 8     { 9         #region 构造函数10 11         public CMSDbContext()12             : base("SqlConnectionString")13         {14 15         }16 17         #endregion18 19         #region DbContext 重写20 21         protected override void OnModelCreating(DbModelBuilder modelBuilder)22         {23             modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();24 25             modelBuilder.Configurations.Add(new ChannelEntityConfiguration());26         }27 28         #endregion29     }30 }

UnityConfig.cs:

 1 using Microsoft.Practices.Unity; 2 using Microsoft.Practices.Unity.Configuration; 3 using System; 4 using System.Collections.Generic; 5  6 namespace CMS.Common 7 { 8     public class UnityConfig 9     {10         #region 属性11 12         public static IUnityContainer Container13         {14             get15             {16                 return container.Value;17             }18         }19 20         #endregion21 22         #region 方法23 24         private static Lazy<IUnityContainer> container = new Lazy<IUnityContainer>(25             () =>26             {27                 var container = new UnityContainer();28 29                 RegisterTypes(container);30 31                 return container;32             });33 34         private static void RegisterTypes(IUnityContainer container)35         {36             container.LoadConfiguration();37         }38 39         public static T Resolve<T>(IDictionary<string, object> paramDict = null)40         {41             var list = new ParameterOverrides();42 43             if (paramDict != null && paramDict.Count > 0)44             {45                 foreach (var item in paramDict)46                 {47                     list.Add(item.Key, item.Value);48                 }49             }50 51             return Container.Resolve<T>(list);52         }53 54         #endregion55     }56 }

ChannelApplcationService.cs:

 1 using AutoMapper; 2 using CMS.Domain.Core.Repository; 3 using CMS.Domain.Entities; 4 using CMS.DTO; 5 using Microsoft.Practices.Unity; 6  7 namespace CMS.Applcation 8 { 9     public class ChannelApplcationService10     {11         #region 属性12 13         [Dependency]14         public IUnitOfWork UnitOfWork { get; set; }15 16         #endregion17 18         #region 方法19 20         public Response<bool> Add(ChannelDTO dto)21         {22             var resp = new Response<bool>();23             var channel = Mapper.Map<Channel>(dto);24 25             using (this.UnitOfWork)26             {27                 var channelAddRepository = this.UnitOfWork.Repository<Channel>();28 29                 channelAddRepository.Add(channel);30 31                 this.UnitOfWork.Commit();32             }33 34             resp.Result = true;35 36             return resp;37         }38 39         #endregion40     }41 }

序列图:

技术分享

心得体会:

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+DomainObject+EF 之我见