Question

I'm using EF5 and have entities in POCO classes. My first question is what is the best place to implement business rules and validation?

My first guess is to place it directly into POCO class into some Validate() function that gets called from DBContext when SaveChanges() is triggered. This works well but some of the rules require validation across multiple entities like this example for class Invoice:

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

Now the unit tests would then test the Validation function (for each business rule) but will also have to populate the invoice class with Items (otherwise it would be always empty)

Another idea is to create an InvoiceValidation class with separate validation functions (or even class per each rule?) that are easier to unit test but it does increase the number of files/classes to maintain.

Any suggestions or links to existing solutions would be appreciated

Was it helpful?

Solution 3

If anyone is interested this is the best example I found so far:

http://codeinsanity.com/archive/2008/12/02/a-framework-for-validation-and-business-rules.aspx

OTHER TIPS

The best way will depend on your dependencies. Does the POCO / core assembly depend on EF ? Do you inject Access to DB into your core Library assembly? etc.

I personally use repository/luw pattern, where the various repository objects inherit from a base repository object that is generic. the DAL depends on EF but the POCO classes in core dont.

The repository sub class has a a specific type and DOES the OTHER OBEJCT business checks. IE business rules that require other entities to be checked , i implement in DAL.

The repository classes belong to the Data Access layer Project and DO have a dependency on EF and have the Context injected. Example below.

Checks specific to the instance I perform on the POCO. Checks that require DB access I perform via an Interface implemented on a base Class respository class that is inturn overriden as requried. So now calls CheckEntity are triggered when adding or changing an object.

eg ... NOTE some code removed to keep example relevant...

 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;   
    }

}

I would suggest something like Fluent Validation (http://fluentvalidation.codeplex.com/) which allows you to take a collection of rules and put them together into a single context separate from the POCO class it's validating.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top