15
\$\begingroup\$

I need some thoughts about an approach a colleague and I am are taking with Entity Framework. Basically, the entities are represented by contracts. These contracts hold a collection of business rules so when they are finally persisted we know they are valid. We can also check the validity from Entity Framework, but that is not as important. Since EF requires you not to have a parameterized constructor we do not pass the actual domain entity into EF, but rather the contract that represents it.

So how this works is, first a context is established. Next, we create a contract and then create the domain entity by passing it the contract. Inside the constructor of the domain entity a call to an IsValid method is made. This runs through all the business rules. If any one fails, the domain entity is considered invalid and an exception of some kind is thrown. If it is valid, we then add the contract to the context and persist the changes and pass back the domain entity. Here is some code that demonstrates the idea:

    /// <summary>
    /// Interface the all Contracts will impelement
    /// </summary>
    public interface IEntityContract
    {
        bool IsValid();
    }

    /// <summary>
    /// Business Rule Loigc
    /// </summary>
    public class BusinessRule<TEntity> where TEntity: IEntityContract
    {
        //Logic to handle business rules
    }


    /// <summary>
    /// Abstract class that represents a Base Contract
    /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    public abstract class EntityContract<TEntity>: IEntityContract where TEntity: IEntityContract
    {

        private readonly List<BusinessRule<TEntity>> _businessRules = new List<BusinessRule<TEntity>>();

        public bool IsValid()
        {
            var entityIsValid = true;
            foreach (var businessRule in _businessRules)
            {
                //IF any rule fails the entityIsValid = fale
            }

            return entityIsValid;
        }


        protected void RegisterBusinessRule(BusinessRule<TEntity> rule)
        {
            _businessRules.Add(rule);
        }
    }

    public interface IUserContract: IEntityContract
    {
        string UserName { get; set; }
        string Password { get; set; }
    }

    internal sealed class UserContract: EntityContract<IUserContract>, IUserContract
    {

        public UserContract()
        {
            this.RegisterBusinessRule(new BusinessRule<IUserContract>());
            this.RegisterBusinessRule(new BusinessRule<IUserContract>());
        }

        public string UserName { get; set; }
        public string Password { get; set; }
    }

    /// <summary>
    /// Abstract class the represetn a base domain entity
    /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    public abstract class EntityBase<TEntity> where TEntity: IEntityContract
    {
        protected EntityBase(TEntity contract)
        {
            if(!contract.IsValid())
            {
                throw new Exception("Business Rule Violation");
            }
        }

        public Guid Id { get; set; }
        protected void BuildProperties()
        {
            //This method will iterate the TEntity properties from the contract and build the actual domain entity
        }

    }

    /// <summary>
    /// Class that represent a domain entity
    /// </summary>
    public class User: EntityBase<IUserContract>
    {
        public User(IUserContract contract) : base(contract)
        {
            this.BuildProperties();
        }
    }

    //Domonstration of idea
    public class TestClass
    {
        public void TestUserEnity()
        {

            //Using Entity Framework 4.1 we instantiate a DbContext
            var dataContext = new SomeEntityFrameworkDbContext();

            //Create the UserContract
            var userContract = new UserContract {UserName = "[email protected]", Password = "thePassword"};
            try
            {
                //Create a new user and pass it the UserContract
                var user = new User(userContract);

                //If evertyhing goes ok, add the new eneity to the context
                dataContext.Add(userContract);

                //Persist the data
                dataContext.SaveChanges();
            }
            catch (Exception)
            {

                //throw the excpetion and send back the failures
            }

        }
    }
\$\endgroup\$

2 Answers 2

6
+50
\$\begingroup\$

Considering Entity Framework does not like parameterized constructors, I think the only solution is when the entity is attached to the context or on SaveChanges we evaluate the the entity.

Perhaps instead of evaluating the entity directly in the context we do it in a class that employs the repository pattern. Now, this code is obviously not complete and we would want to leverage a Unit Of Work that abstracts the context, possibly. Here is the refactored code

public class User
    {
        public string Username { get; set; }
        public string Password { get; set; }
    }

    public interface IRepository<TEntity> where TEntity : class
    {
        void Create(TEntity entity);
        void Update(TEntity entity);
        void Delete(TEntity entity);
    }


    public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
    {
        private readonly IEntityContract<TEntity> _contract;
        private readonly DbContext _context;

        public Repository(IEntityContract<TEntity> contract, DbContext context)
        {
            _contract = contract;
            _context = context;
        }

        public void Create(TEntity entity)
        {
            //beofre we try to create it, lets evaluate it
            if (!_contract.IsValid(entity))
            {
                //possibly throw an exception or let the client know there was an issue.
            }


            //Now implement your create code.
            _context.Add(entity);
            _context.SaveChanges();
        }

        public void Update(TEntity entity)
        {
            //beofre we try to update it, lets evaluate it
            if (!_contract.IsValid(entity))
            {
                //possibly throw an exception or let the client know there was an issue.
            }

            //Now implement your update code.
            _context.Attach(entity);
            _context.SaveChanges();
        }

        public void Delete(TEntity entity)
        {
           //delete code
        }
    }

    /// <summary>
    /// Interface the all Contracts will impelement
    /// </summary>
    public interface IEntityContract<TEntity> where TEntity: class 
    {
        bool IsValid(TEntity entity);
        void RegisterBusinessRule(BusinessRule<TEntity> rule);
        IList<BusinessRule<TEntity>> Rules { get; set; }
    }

    /// <summary>
    /// Business Rule Loigc
    /// </summary>
    public class BusinessRule<TEntity> where TEntity : class
    {
        //Logic to handle business rules
    }


    /// <summary>
    /// Abstract class that represents a Base Contract
    /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    public abstract class EntityContract<TEntity> : IEntityContract<TEntity> where TEntity : class
    {

        protected EntityContract()
        {
            Rules = new List<BusinessRule<TEntity>>();
        }

        protected EntityContract(IList<BusinessRule<TEntity>> rules)
        {
            Rules = rules;
        }

        public virtual bool IsValid(TEntity entity)
        {
            var entityIsValid = true;
            foreach (var businessRule in Rules)
            {
                //IF any rule fails the entityIsValid = false
            }

            return entityIsValid;
        }

        public IList<BusinessRule<TEntity>> Rules { get; set; }


        public void RegisterBusinessRule(BusinessRule<TEntity> rule)
        {
            Rules.Add(rule);
        }

    }

    public interface IUserContract : IEntityContract<User>
    {

    }

    public class UserContract : EntityContract<User>, IUserContract
    {

    }

    //Domonstration of idea
    public class TestClass
    {
        public void TestUserEntity()
        {

            //Using Entity Framework 4.1 we instantiate a DbContext
            var dataContext = new SomeEntityFrameworkDbContext();

            var respository = new Repository<User>(new UserContract(), dataContext);
            //Create the UserContract

            var user = new User()
            {
                Password = "123456",
                Username = "[email protected]"
            };


            try
            {
                respository.Create(user);
            }
            catch (Exception ex)
            {

               //lets assume an excpetion gets thrown if the IsVlaid fails.
            }

        }
    }

As you can see, now we take the responsibility of validating the entity away from the entity itself. Which, I feel is better to keep the entity as clean as possible and leave any logic out of the entity. This is how I have been feeling int he past few couple years.

\$\endgroup\$
4
  • 1
    \$\begingroup\$ I was thinking that the Interfaces weren't holding much, and I was going to post something to that effect, but I was so tired last night I don't think it would have been very coherent. but it looks like you covered that here and made the interfaces do more. I approve. \$\endgroup\$
    – Malachi
    Commented Nov 20, 2013 at 14:41
  • \$\begingroup\$ please don't be afraid to down vote my answer. \$\endgroup\$
    – Malachi
    Commented Nov 20, 2013 at 14:41
  • \$\begingroup\$ I'm reluctant to employ Repository/UoW with EF. I find it's an abstraction over an abstraction. I have yet to be convinced that DbContext is a bad UoW. \$\endgroup\$ Commented Nov 22, 2013 at 14:34
  • 1
    \$\begingroup\$ @DDiVita you can mark it as accepted, thanks for posting your answer here, many wouldn't have bothered. Is the review monster awake? \$\endgroup\$ Commented Nov 24, 2013 at 5:36
3
\$\begingroup\$

your IUserContract inherits IEntityContract but doesn't implement the bool IsValid(); method, I always assumed that if you inherit an interface that you have to implement everything inside that interface.

that is the only thing that I can see that looks out of place.

EDIT

as Retailcoder pointed out whatever inherits the last Interface would have to implement everything in all the interfaces in the chain. this means that inside the internal sealed class UserContract there should be an implementation of the bool IsValid(); method, which there isn't.

The abstract class EntityBase is also missing the bool IsValid(); method


Merged answer

Having an Interface that only does one thing doesn't seem right to me.

the way that you set up your User Class in your answer is much better than having an interface that forces you to create the User Credentials. I like that much better.

I also like the new IEntityContract<TEntity> as it forces more than just a IsValid Method. it looks like it has more of the logic that is needed for other things that will be forced in one way or another, reducing the amount of Interfaces and eventually code.

I like the new layout and structure that you have provided in your answer, it seems a lot clearer and more readable.

I know that this is a review of a review, but seeing as the OP is the one that posted the review that I am reviewing (still referring back to the original review) I think that this answer is still valid.

\$\endgroup\$
3
  • \$\begingroup\$ I have never tried to inherit an interface into another interface, so I am not certain, I am running reports at this minute but I will look into it more when I am done \$\endgroup\$
    – Malachi
    Commented Nov 19, 2013 at 17:25
  • 2
    \$\begingroup\$ An interface can derive from another without any problems - interfaces don't implement anything. You could do public interface ISomething : ISomethingElse and then a type that implements ISomething would have to implement all members from both interfaces. \$\endgroup\$ Commented Nov 19, 2013 at 17:26
  • 1
    \$\begingroup\$ @Malachi, The UserContract Inherits from EntityContract<TEntity> which implements the IsValid method. The IsValid method will be available without having to specifically implement it. We could make the IsValid virtual in the EntityContract<TEntity> so it could be overridden in the UserContract. \$\endgroup\$
    – DDiVita
    Commented Nov 20, 2013 at 2:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.