我使用EF5并在POCO类中拥有实体。我的第一个问题是,实现业务规则和验证的最佳地点是哪里?
我的第一个猜测是将其直接放入POCO类中的某个Validate()函数中,该函数在SaveChanges()被触发时从DBContext调用。这样做效果很好,但有些规则需要跨多个实体进行验证Invoice类的示例:
if(this.Items.Where(i=>i.Price > 100).Count() > 0)
{
//mark invoice for review
this.IsForReview = true;
}
现在,单元测试将测试Validation函数(针对每个业务规则),但还必须用Items填充发票类(否则它将始终为空)
另一个想法是创建一个带有单独验证函数的invoicevalvalidation类(或者甚至每个规则对应一个类?),这样更容易进行单元测试,但它确实增加了需要维护的文件/类的数量。
对现有解决方案的任何建议或链接将不胜感激
最好的方法取决于你的依赖项。POCO/core组件是否依赖于EF ?您是否将对数据库的访问注入到您的核心库程序集中?等。
我个人使用repository/luw模式,其中各种存储库对象继承自通用的基本存储库对象。DAL依赖EF,但core中的POCO类不依赖EF。
存储库子类有一个特定的类型,并执行OTHER object业务检查。需要检查其他实体的IE业务规则,我在DAL中实现。
存储库类属于数据访问层项目,并且依赖于EF并注入了上下文。在下面的例子。
在POCO上执行的特定于实例的检查。需要访问数据库的检查是通过在基类责任类上实现的接口执行的,该接口根据需要返回覆盖。所以现在调用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;
}
}
我建议像Fluent Validation (http://fluentvalidation.codeplex.com/)这样的东西,它允许您收集规则并将它们放在一个单独的上下文中,与它正在验证的POCO类分开。
如果有人感兴趣,这是目前为止我发现的最好的例子:
http://codeinsanity.com/archive/2008/12/02/a-framework-for-validation-and-business-rules.aspx