MVC+UnitOfWork+Repository+EF


MVC+UnitOfWork+Repository+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                 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訪問周期內提供的各種擴展點等。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM