2013-01-14 42 views
0

我正在使用EF5並在POCO類中有實體。我的第一個問題是什麼是實施業務規則和驗證的最佳地點?如何實施和測試POCO類的複雜業務規則

我的第一個猜測是直接將它放到POCO類中,當它觸發SaveChanges()時,它會被一些Validate()函數從DBContext調用。 這種運作良好,但有些規則需要這樣 例如類發票的多個實體驗證:

if(this.Items.Where(i=>i.Price > 100).Count() > 0) 
{ 
    //mark invoice for review 
    this.IsForReview = true; 
} 

現在則該單元測試將測試驗證功能(每個業務規則),但也將有用項目填充發票類(否則它總是空的)

另一個想法是創建一個帶有單獨驗證功能(甚至每個規則的類)的InvoiceValidation類,它們更容易進行單元測試,但它增加了要維護的文件/類的數量。

任何建議或鏈接到現有的解決方案,將不勝感激

+0

標誌審查不是一個驗證任務。這是商業邏輯。驗證是檢查這些發票是否已被標記爲審查。一個小但重要的區別。驗證不應該改變對象的狀態。這並不改變你的問題的本質。 –

回答

0

我建議像流利的驗證(http://fluentvalidation.codeplex.com/),它允許你採取的規則的集合,並把它們放在一起整合到一個單獨的上下文中,與它正在驗證的POCO類分開。

1

最好的方法將取決於您的依賴關係。 POCO /核心組件是否依賴於EF? 您是否將Access數據庫注入到您的核心庫程序集?等等。

我個人使用repository/luw模式,其中各種資源庫對象都從通用的基礎資源庫對象繼承而來。 DAL依賴於EF,但核心中的POCO類不會。

存儲庫子類有一個特定的類型,並進行OTHER OBEJCT業務檢查。 需要檢查其他實體的IE業務規則,我在DAL中實現。

存儲庫類屬於數據訪問層項目和DO有依賴於EF和注入上下文。下面的例子。

檢查特定於我在POCO上執行的實例。 需要訪問數據庫的檢查我通過在基類Class respository類上實現的接口執行此類接口,該接口類需要重寫。所以現在調用CheckEntity被觸發時,改變一個對象。

如 ...注意一些代碼去除,以保持例如有關...

public class RepositoryEntityBase<T> : IRepositoryEntityBase<T>, IRepositoryEF<T> where T : BaseObject 
public virtual OperationStatus Add(T entity) 
    { 
     var opStatus = new OperationStatus(status: true, operation: OperationType.Add); 
     try 
     { 
      if (OnBeforeAdd != null) // registered listeners of added event? 
      { 
       var evtArg = PrepareEventArgs(entity, MasterEventType.Create); 
       OnBeforeAdd(this, evtArg); 
      } 
      opStatus = CheckBeforePersist(entity); 
      if (opStatus.Status) 
      { 
       Initialize(entity); 
       EntityDbSet.Add(entity); 

       if (OnAfterAdd != null) // registered listeners of added event? 
       { 
        var evtArg = PrepareEventArgs(entity, MasterEventType.Create); 
        OnAfterAdd(this, evtArg); 
       } 
      } 
     } 
     catch (Exception ex) 
     { 
      opStatus.SetFromException("Error Adding " + typeof(T), ex); 
     } 
     return opStatus; 
    } 

//... then in a specific repository class 



//... irepositorybase expects Check before persist. 

    public override OperationStatus CheckBeforePersist(MasterUser entity) 
    { 

     // base entity rule check first 
     var opStatus = new OperationStatus(true, OperationType.Check);   
     opStatus.ValidationResults = base.CheckEntity(entity);  
     if (opStatus.ValidationResults.Count > 0) 
     { 
      opStatus.Status = false; 
      opStatus.Message = "Validation Errors"; 
      return opStatus; 
     } 


     //now check the local memory 
     var masterUser = Context.Set<MasterUser>().Local //in context 
               .Where(mu => mu.Id != entity.Id // not this record 
                &&  mu.UserName == entity.UserName) // same name 
               .FirstOrDefault(); 
     if (masterUser != null) 
     { 
      opStatus.Status = false; 
      opStatus.Message = "Duplicate UserName :" + masterUser.UserName + " UserId:"+ masterUser.Id.ToString(); 
      return opStatus; 
     } 
     masterUser = Context.Set<MasterUser>().Local //in context 
              .Where(mu => mu.Id != entity.Id // not this record 
                && mu.Email == entity.Email) // same email 
              .FirstOrDefault(); 
     if (masterUser != null) 
     { 
      opStatus.Status = false; 
      opStatus.Message = "Duplicate Email :" + masterUser.Email + " Username:" + masterUser.UserName; 
      return opStatus; 
     }            

     // now check DB 
     masterUser = Get(mu => mu.Id != entity.Id    //not this record being checked 
          && mu.UserName == entity.UserName);  // has same username 
     if (masterUser != null) 
     { 
      opStatus.Status = false; 
      opStatus.Message = "Duplicate UserName :" + masterUser.UserName + " UserId:"+ masterUser.Id.ToString(); 
      return opStatus; 
     } 
     masterUser = Get(mu => mu.Id != entity.Id // not this record 
         && mu.Email == entity.Email); // but same email 
     if (masterUser != null) 
     { 
      opStatus.Status = false; 
      opStatus.Message = "Duplicate Email:" + masterUser.Email + " UserName:"+ masterUser.UserName; 
      return opStatus; 
     } 
     return opStatus; 
    } 

} 
+0

+1感謝您的貢獻。雖然這不是直接的答案(我意識到可能有多個)。我想問問你有什麼「擁有各種資源庫對象」的目的?如果你已經有一個處理所有標準函數的通用倉庫,爲什麼將它分成多個(我在其他例子中也看到了它,但是與直接使用通用倉庫相比,我看不到它的好處) –