1

I am working on a framework that I have been building/planning for a month now. I have started to develop it and need some expertise guidance on how to structure it, I think i am either designing it wrong or simply overcomplicating it.

So the main project at the moment is a class library and it is split into the following:

Framework, Core etc (These house all extension methods and other useful goodies)

BusinessEntities (All the entity framework Entities, including configuration for entities, using Fluent API)

BusinessLogicLayer DataAccessLayer

Now all entities inherit from BaseEntity which also inherits IValidableObject. BaseEntity looks like this:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace uk.BusinessEntities
{
    public abstract class BaseEntity : IValidatableObject
    {
        public int PrimaryKey { get; set; }
        public DateTime DateCreated { get; set; }
        public DateTime? DateModified { get; set; }

        public abstract IEnumerable<ValidationResult> Validate(ValidationContext validationContext);
    }
}

Then for the DataAccessLayer each class inherits the GenericObject class which looks like this:

using System;
using System.Collections.Generic;
using System.Linq;

namespace uk.DataAccessLayer
{
    public class GenericObject<T> where T : class
    {


        public GenericObject() { }

        public static bool Add(T Entity, out IList<string> validationErrors)
        {

            using (var db = new DatabaseContext())
            {
                validationErrors = new List<string>();
                try
                {
                    db.Set<T>().Add(Entity);
                    db.SaveChanges();
                    return true;
                }
                catch (Exception ex)
                {
                    InsertValidationErrors(ex, validationErrors);
                    return false;
                }

            }

        }

        public static IList<T> Retrieve()
        {
            using (var db = new DatabaseContext())
            {
                IList<T> Query = (IList<T>)(from x in db.Set<T>()
                                            select x).ToList<T>();

                return Query;
            }
        }

        public static bool Update(T Entity, out IList<string> validationErrors)
        {
            validationErrors = new List<string>();
            using (var db = new DatabaseContext())
            {
                try
                {
                    db.Set<T>().Attach(Entity);
                    db.Entry(Entity).State = System.Data.Entity.EntityState.Modified;
                    db.SaveChanges();
                    return true;
                }
                catch (Exception ex)
                {

                    InsertValidationErrors(ex, validationErrors);

                    return false;
                }
            }

        }

        public static bool Delete(T Entity, out IList<string> validationErrors)
        {
            validationErrors = new List<string>();

            using (var db = new DatabaseContext())
            {

                try
                {

                    db.Entry(Entity).State = System.Data.Entity.EntityState.Deleted;
                    db.SaveChanges();
                    return true;

                }
                catch(Exception ex)
                {
                    InsertValidationErrors(ex, validationErrors);
                    return false;
                }
            }
        }

        protected static void InsertValidationErrors(Exception ex,  IList<string> validationErrors)
        {
            validationErrors.Insert(0, ex.Message);
            if (ex.InnerException != null)
            {
                validationErrors.Insert(0, ex.InnerException.Message);
                validationErrors.Insert(0, ex.InnerException.StackTrace);
            }
        }
    }
}

Now my main point lies all with validation of the entities. For example we got two entities Page and PageURLS URLS are stored separately for pages.

Now when adding a Page the PageURL also needs to be added as well, so if a developer called

PageDAL.AddPage(page, errors)

Would you also expect this method to also add the URL automatically or should this be a separate call?

Any help on this would be greatly appreciated.

  • possible duplicate of [.NET Managing Layers Relationships](http://stackoverflow.com/questions/31646525/net-managing-layers-relationships) – Tyree Jackson Aug 02 '15 at 20:21
  • To be honest, these static methods hurt my eyes. You're completely killing query composition and transactional units of work by handling each entity class by its own set of generic methods. – Gert Arnold Aug 02 '15 at 21:33
  • @GertArnold It would be helpful if you told me how I could improve it. The reason I am using a Generic Method is to avoid repetition, and to keep all the methods the same name per DAL (It avoids confusion and is good presentation). I could use an interface with all the required methods, but that still will require the method body to be added per entity which will probably result in repetitive code. –  Aug 03 '15 at 08:48

2 Answers2

2

I would suggest using the validation used by EF by default (link). With this approach you can also set validation error messages, and even localize them. Exception error messages aren't usually really useful to endusers. In addition, an exception when saving doesn't necessarily mean that validation failed. It could be that something else went wrong.

EF really does a good job of adding object graphs to the database. So I would let it do its job. So yes, have the PageDAL.Add(page) also add the page urls, since it's really the same operation in the end. I don't see a reason to be more explicit.

Unfortunately these kind of questions usually can't be answered objectively. For me it's always a struggle between YAGNI and adhering to all the principles. It really depends on the system you're building and on your own point of view.

I already made lots of mistakes in either on or the other direction. Talk to your peers, especially the ones that will work on the project with you, figure something out, and don't be afraid to adapt on the way...

I'm sorry if this answer isn't really satisfactory.

LueTm
  • 2,366
  • 21
  • 31
  • The issue I was having using PageDAL.Add(page) to add the URL was that first the page needs to be added and saved so we can get the inserted id. Since the URL entity needs the PageId (it has a foreign key). So basically the page needs to be added first then the URL, but if the page was added and then URL entity threw and exception then we'd have an issue. –  Aug 02 '15 at 22:09
  • 1
    You can actually admit them at the same time, using navigation properties! This is what I'm advocating. – LueTm Aug 03 '15 at 00:53
1

Regarding validation, I would perform some validations (simple, not business oriented) in the Controller to reject simple wrong inputs in case they were not caught on the client-side (or if the client-side validations were skipped). Then I would validate the entities in the Business Layer and return a custom object with a validation result and a list of validation messages.

I think you're not considering transactions and that's why you don't know how to deal with 2 related entities. For example:

public static bool Delete(T Entity, out IList<string> validationErrors)
        {
            validationErrors = new List<string>();

            using (var db = new DatabaseContext())
            {

                try
                {

                    db.Entry(Entity).State = System.Data.Entity.EntityState.Deleted;
                    db.SaveChanges();
                    return true;

                }
                catch(Exception ex)
                {
                    InsertValidationErrors(ex, validationErrors);
                    return false;
                }
            }
        }

You are creating a Database context, inserting an entity and disposing the context. If you need to insert many entities and the second entity fails, what would you do? the way you are doing it, the first entity will be saved and the second one would not be saved. You should read about Unit of Work pattern so you can create a transaction accross operations.

Take a look at these articles:

Read these articles:

1) https://msdn.microsoft.com/en-us/library/hh404093.aspx

2) http://www.asp.net/mvc/overview/older-versions-1/models-%28data%29/validating-with-a-service-layer-cs

3) http://blog.diatomenterprises.com/asp-net-mvc-business-logic-as-a-separate-layer/

4) http://sampathloku.blogspot.com.ar/2012/10/how-to-use-viewmodel-with-aspnet-mvc.html

Francisco Goldenstein
  • 13,299
  • 7
  • 58
  • 74