2013-01-14 45 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感谢您的贡献。虽然这不是直接的答案(我意识到可能有多个)。我想问问你有什么“拥有各种资源库对象”的目的?如果你已经有一个处理所有标准函数的通用仓库,为什么将它分成多个(我在其他例子中也看到了它,但是与直接使用通用仓库相比,我看不到它的好处) –