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 : IAggregateRoot
10     {
11         #region 方法
12
13         public virtual IEnumerable<BusinessRule> Validate()
14         {
15             return new BusinessRule[] { };
16         }
17
18         #endregion
19
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         #endregion
44
45         #region IAggregateRoot 成员
46
47         public Guid Id
48         {
49             get;
50             set;
51         }
52
53         #endregion
54     }
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 CoverPicture
14         {
15             get;
16             set;
17         }
18
19         public string Desc
20         {
21             get;
22             set;
23         }
24
25         public bool IsActive
26         {
27             get;
28             set;
29         }
30
31         public int Hits
32         {
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 : IDisposable
10     {
11         #region 方法
12
13         IRepository<T> Repository<T>() where T : class, IAggregateRoot;
14
15         void Commit();
16
17         #endregion
18     }
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, IDisposable
11     {
12         #region 变量
13
14         private bool _disposed;
15         private readonly IDbContext _dbContext;
16         private Hashtable _repositories;
17
18         #endregion
19
20         #region 构造函数
21
22         public UnitOfWork(IDbContext dbContext)
23         {
24             this._dbContext = dbContext;
25             this._repositories = new Hashtable();
26         }
27
28         #endregion
29
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         #endregion
42
43         #region IUnitOfWork 成员
44
45         public IRepository<T> Repository<T>() where T : class, IAggregateRoot
46         {
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         #endregion
72
73         #region IDisposable 成员
74
75         public void Dispose()
76         {
77             this.Dispose(true);
78
79             GC.SuppressFinalize(this);
80         }
81
82         #endregion
83     }
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.Infrastructure
10 {
11     public class BaseRepository<T> : IRepository<T> where T : class, IAggregateRoot
12     {
13         #region 变量
14
15         private readonly DbContext _db;
16         private readonly IDbSet<T> _dbset;
17
18         #endregion
19
20         #region 构造函数
21
22         public BaseRepository(IDbContext context)
23         {
24             this._db = (DbContext)context;
25             this._dbset = this._db.Set<T>();
26         }
27
28         #endregion
29
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             else
75                 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         #endregion
89     }
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         #endregion
12     }
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         #endregion
18
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         #endregion
29     }
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 Container
13         {
14             get
15             {
16                 return container.Value;
17             }
18         }
19
20         #endregion
21
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         #endregion
55     }
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 ChannelApplcationService
10     {
11         #region 属性
12
13         [Dependency]
14         public IUnitOfWork UnitOfWork { get; set; }
15
16         #endregion
17
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         #endregion
40     }
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访问周期内提供的各种扩展点等。

时间: 2024-08-09 06:16:41

MVC+UnitOfWork+Repository+DomainObject+EF 之我见的相关文章

MVC+UnitOfWork+Repository+EF 之我见

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

Contoso 大学 - 1 - 为 ASP.NET MVC 应用程序创建 EF 数据模型

原文地址:Creating an Entity Framework Data Model for an ASP.NET MVC Application (1 of 10) Contoso 大学 Web 示例应用演示了如何使用 EF 技术创建 ASP.NET MVC 应用.示例中的 Contoso 大学是虚构的.应用包括了类似学生注册.课程创建以及教师分配等功能. 这个系列教程展示了创建 Contoso 大学应用的步骤.你可以 下载完整 的程序,或者按照教程一步一步创建它,这个教程中使用 C# 进

MVC学习一:EF

目录 一.EF修改和删除的多种方法 二.标准查询where 三.include 四.skip take 五.反射获取实例属性 六.EF DLL数据访问帮助类 一.EF修改和删除的多种方法 方法1:官方推荐 先查询在修改 或者删除 1 var student = db.Students.FirstOrDefault(t => t.Id == Mid); 2 student.Name = "修改后";//修改值数据 3 db.SaveChanges();//保存 4 db.Stude

MVC与三层架构区别之我见

我是刚学习MVC和三层架构不久,通过学习,对MVC与三层架构也大概了解了一些,我来谈谈我对MVC与三层结构之间的区别.如果有什么不对的地方,烦请各位指出,谢谢! 好了,回到正题... 首先,我们可以先从逻辑上区分,举个不恰当的例子:就拿房屋来说,三层架构就像房屋的基本结构,MVC就像你理想的房子设计图. 接下来,我再具体谈谈三层结构和MVC... (一)三层架构 1.通常意义上的三层架构就是将整个业务应用划分为:表现层(UI).业务逻辑层(BLL).数据访问层(DAL). 2.区分层次的目的即为

EF DI & MVC

The Repository Pattern with EF Code First & Dependency Injection in ASP.NET MVC3 Ray_Liang, 5 Jul 2011 GPL3 156.8K 8.1K 156 ? ?4.65 (58 votes) ? 1 2 3 4 5 4.65/5 - 58 votes 3 removed μ 4.53, σa 1.20 [?] Rate: vote 1vote 2vote 3vote 4vote 5 Add a reas

Contoso 大学 - 使用 EF Code First 创建 MVC 应用,实例演练

Contoso 大学 Web 示例应用演示了如何使用 EF 技术创建 ASP.NET MVC 应用.示例中的 Contoso 大学是虚构的.应用包括了类似学生注册.课程创建以及教师分配等功能. 这个系列教程展示了创建 Contoso 大学应用的步骤.你可以 下载完整 的程序,或者按照教程一步一步创建它,这个教程中使用 C# 进行演示,下载的代码中同时包含 C# 和 VB 实现.如果你有与这个教程没有直接相关的问题,可以张贴到 ASP.NET Entity Framework forum  或者

Contoso 大学 - 使用 EF Code First 创建 MVC 应用

Contoso 大学 Web 示例应用演示了如何使用 EF 技术创建 ASP.NET MVC 应用.示例中的 Contoso 大学是虚构的.应用包括了类似学生注册.课程创建以及教师分配等功能. 这个系列教程展示了创建 Contoso 大学应用的步骤.你可以 下载完整 的程序,或者按照教程一步一步创建它,这个教程中使用 C# 进行演示,下载的代码中同时包含 C# 和 VB 实现.如果你有与这个教程没有直接相关的问题,可以张贴到 ASP.NET Entity Framework forum  或者 

在MVC程序中,使用泛型仓储模式和工作单元实现增删查改

在这片文章中,我将自己动手为所有的实体:写一个泛型仓储类,还有一个工作单元. 工作单元的职责就是:为每一个实体,创建仓储实例.仓储(仓库)的职责:增删查改的功能实现. 我们将会在控制器中,创建工作单元类(UnitOfWork)的实例,然后根据实体,创建仓储实例,再就是使用仓储里面的方法,做操作了. 下面的图中,解释了,仓储和EF 数据上文的关系,在这个图里面,MVC控制器和仓储之间的交互,是通过工作单元来进行的,而不是直接和EF接触. 那么你可能就要问了,为什么要使用工作单元??? 工作单元,就

项目中如何使用EF

本文将在技术层面挑战园子里的权威大牛们,言语不敬之处敬请包涵.本文旨为技术交流,欢迎拍砖. 园子里面分享和推荐Entity Framework(以下简称EF)的Repository(仓储)设计模式的文章真不少,其中还有很多大牛很详细描述怎么去实现.但是这些文章真是害人不浅.我现在想问问这些大牛们,你们现在的项目真的还在这样用吗? 下面是在找找看里面随便挑的几篇,如果你从未了解过EF Repository,你可以看看: 分享基于Entity Framework的Repository模式设计(附源码