简体   繁体   中英

Entity Framework N-Tier Application - How to design

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.

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM