.NET Managing Layers Relationships

2019-01-12 07:12发布

问题:

I'm actually recreating the default architecture of my projects. In this post I want you not just help me,but think,tell and improve presented ways. This can be very useful challenge. This default architecture can be freely shared with all. So here is description: Basically MVC is our point of view. We follow OOP and layer programming concepts. Also we will use SQL Server and EF DB First. So here is what ive done till now: Ive created 4 layers in my solution:

  1. Domain: JUST domain classes
  2. DAL: responsible for accessing data, containing UOW and repositories and data access related validations.
  3. BL: unique responsible of business logic and unique boss of DAL.
  4. UI: which is not so important! (as even it maybe a console app!)

Ive implemented BL with generic funcs like getAll and getById and crud funcs.these funcs would call DAL generic funcs which is also ready. Now an important question is: is it right to implement DAL funcs as GENERIC? Please consider this scenario :

From UI we post a model to action and in action we call a BL Func (BL.insert(model)). The BL.Insert func would call something like DAL.Add(model) (notice BL will call DAL once and will tell it what it wants). now the DAL.Add func would have to insert 3 records into 3 different tables (model is passed from UI layer). I think this cant be achieved by DAL GENERIC funcs. So what is the correct and standard way of implementing Layers and relationships with noticing the Separation Of Concerns?

In my action i would have:

[HttpPost]
public ActionResult Insert()
{ 
    Bl.EntityBase.Article.Insert(new Article()); 
    return RedirectToAction("Index");
}

in BL I would have:

public void Insert(T obj)
{
    Da.Repository<T>().Insert(obj);
}

And in my DAL I have:

public virtual void Insert(T entity)
{
    DbEntityEntry dbEntityEntry = Db.Entry(entity);
    if (dbEntityEntry.State != EntityState.Detached)
    {
        dbEntityEntry.State = EntityState.Added;
    }
    else
    {
        Set.Add(entity);
    }
}

回答1:

IF the model which you are passing to DAL through your BL is a view-model then the logic will be in your DAL otherwise you should not pass a model which will perform 3 insert for 3 different tables.



回答2:

You should implement Repository pattern with generic repositories in you DAL . In order to put abstraction you have to use IRepository interface an its implementation should be injected using dependency resolver . Also you can achieve the same in BL(Service) using interface .

Here is a good discussion : Difference between Repository and Service Layer?

You can also improve it using Unit Of Work pattern.

Here is a complete code snippet which shows how can we achieve abstraction between multiple layers :


Repository

public interface IRepository<T> where T : class
    {
        DbContext GetContext();
        IQueryable<T> GetAll();
        IQueryable<T> FindBy(Expression<Func<T, bool>> predicate);
        void Add(T entity);
        void Delete(T entity);
        void DeleteAll(IEnumerable<T> entity);
        void Edit(T entity);
        bool Any();
    }

public class Repository<T> : IRepository<T> where T : class
{
    private readonly DbContext _context;
    private readonly IDbSet<T> _dbset;

    public Repository(DbContext context)
    {
        _context = context;
        _dbset = context.Set<T>();
    }

    public virtual DbContext GetContext()
    {
        return _context;
    }

    public virtual IQueryable<T> GetAll()
    {
        return _dbset;
    }

    public IQueryable<T> FindBy(Expression<Func<T, bool>> predicate)
    {
        var query = _dbset.Where(predicate).AsQueryable();
        return query;
    }

    public virtual void Add(T entity)
    {
        _dbset.Add(entity);
    }

    public virtual void Delete(T entity)
    {
        var entry = _context.Entry(entity);
        entry.State = EntityState.Deleted;
        _dbset.Remove(entity);
    }

    public virtual void DeleteAll(IEnumerable<T> entity)
    {
        foreach (var ent in entity)
        {
            var entry = _context.Entry(ent);
            entry.State = EntityState.Deleted;
            _dbset.Remove(ent);
        }
    }

    public virtual void Edit(T entity)
    {
        var entry = _context.Entry(entity);
        _dbset.Attach(entity);
        entry.State = EntityState.Modified;
    }

    public virtual bool Any()
    {
        return _dbset.Any();
    }
}

UOW

public interface IUnitOfWork : IDisposable
    {
        IRepository<TEntity> GetRepository<TEntity>() where TEntity : class;      
        void Save();
    }

 public class UnitOfWork<TContext> : IUnitOfWork where TContext : DbContext, new()
    {
        private readonly DbContext _ctx;
        private readonly Dictionary<Type, object> _repositories;
        private bool _disposed;

        public UnitOfWork()
        {
            _ctx = new TContext();
            _repositories = new Dictionary<Type, object>();
            _disposed = false;
        }

        public IRepository<TEntity> GetRepository<TEntity>() where TEntity : class
        {
            // Checks if the Dictionary Key contains the Model class
            if (_repositories.Keys.Contains(typeof (TEntity)))
            {
                // Return the repository for that Model class
                return _repositories[typeof (TEntity)] as IRepository<TEntity>;
            }

            // If the repository for that Model class doesn't exist, create it
            var repository = new Repository<TEntity>(_ctx);

            // Add it to the dictionary
            _repositories.Add(typeof (TEntity), repository);

            return repository;
        }

        public void Save()
        {
            // save all changes together
            _ctx.SaveChanges();
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    _ctx.Dispose();
                }

                _disposed = true;
            }
        }
    }

Service


public interface IService
 {
   IList<Users> GetUserDetails(int userId);

 }

public class Service : IService
 {

 private readonly IRepository<Users> _userRepository;

 public Service (IUnitOfWork unitOfWork)
        {
           _unitOfWork = unitOfWork;
       _userRepository=_unitOfWork.GetRepository<Users>();
        }

    public  IList<Users> GetUserDetails(int userId)
    {
        return _userRepository.GetAll();

    }

 }

Controller


 public class HomeController

// use the same patter which has been used in service 

{

private readonly IService _service;

public HomeController(IService service)
{
_service=service;
}


public ActionResult Index()
{

return _service. GetUserDetails(/*userid*/);

}

}


Hope this code will help many users and you of course.



回答3:

Before we begin, it might make sense to separate the notions of contracts from layers. You listed 4 layers, but one could also describe them as 3 layers and one contract schema (the domain contract schema) which is cross cutting containing concerns for the DAL, BL and potentially higher level layers.

  1. Domain: JUST domain classes
  2. DAL: responsible for accessing data, containing UOW and repositories and data access related validations.
  3. BL: unique responsible of business logic and unique boss of DAL.
  4. UI: which is not so important! (as even it maybe a console app!)

With regards to typical LOB style SOA n-tier architectures, where the services may be consumed by other teams or clients, generally speaking it may make sense to strive for upwards of three contractual schemas.

The first are the contract schemas between the consumers and the services that you are looking to provide. These may be versioned. These schemas may be use case specific and provide "representations" of the underlying entity schema in various forms of normalization and composition. These schemas may even include supporting meta data for supporting use case specific requirements, such as population of lookup lists and indicating use case specific user permissions.

The second is the entity domain schema that all of your current implementation operates on. This is always current and represents the logical schema in an object oriented design fashion.

The third is the physical schema where your data resides. This is always current and contains your data in whatever appropriate design fashion the storage technology dictates, whether that be in relational design fashion or otherwise. Note that while generally the physical schema and the logical schema may look similar or even identical, sometimes there are restrictions in the physical storage technology that may require that the physical schema be different. For example, you should not have more than 8060 bytes per row in a MS Sql Server table, and in some cases a single logical entity data object may be stored in multiple one to one related physical tables if MS Sql Server is used.

With these three schemas in mind, you can then architect your solution to address the concerns of supporting these schemas.

To address storage and management of the physical schema select a storage technology/strategy (i.e.: Sql Server, Oracle, MySql, xml, MongoDb, etc.)

To address I/O to the physical schema and to translate from the physical schema to the entity schema, you introduce an IDataAccess layer interface.

To address the business rules that must always be enforced around an entity or set of entities regardless of use case, you introduce the IBusiness layer interface.

To address the rules that are use case specific that you do not want to have to repeat between multiple clients and to translate between the entity schema and the service contract schemas, you introduce the IApplicationService layer interface.

To address exposing the service and its contract schema transport objects externally, you introduce the appropriate host controller classes (ApplicationServiceController, ApplicationServiceWCFHost, or ApplicationService.asmx, etc..).

Here are a set of interfaces contained within a generic namespace strategy that you could implement to provide you with the abstractions for the entity schema and the data access and business layers.

AcmeFrameworkContracts.dll

public class Response {}
public class Response<TResponse> : Response where TResponse : Response<TResponse> {}

AcmeFramework.dll

references: AcmeFrameworkContracts.dll

namespace AcmeFramework
{

    namespace Entity
    {

        public abstract class Entity<TEntity, TDataObject, TDataObjectList, TIBusiness, TIDataAccess, TPrimaryKey>
            where   TEntity         : Entity<TEntity, TDataObject, TDataObjectList, TIBusiness, TIDataAccess, TPrimaryKey>
            where   TDataObject     : Entity<TEntity, TDataObject, TDataObjectList, TIBusiness, TIDataAccess, TPrimaryKey>.BaseDataObject
            where   TDataObjectList : Entity<TEntity, TDataObject, TDataObjectList, TIBusiness, TIDataAccess, TPrimaryKey>.BaseDataObjectList, new()
            where   TIBusiness      : Entity<TEntity, TDataObject, TDataObjectList, TIBusiness, TIDataAccess, TPrimaryKey>.IBaseBusiness
            where   TIDataAccess    : Entity<TEntity, TDataObject, TDataObjectList, TIBusiness, TIDataAccess, TPrimaryKey>.IBaseDataAccess
            where   TPrimaryKey     : IComparable<TPrimaryKey>, IEquatable<TPrimaryKey>
        {
            public class BaseDataObject
            {
                public TPrimaryKey Id;
            }

            public class BaseDataObjectList : CollectionBase<TDataObject> 
            {
                public TDataObjectList ShallowClone() { ... }
            }

            public interface IBaseBusiness
            {
                TDataObjectList LoadAll();
                TDataObject LoadById(TPrimaryKey id);
                TDataObject LoadByIds(IEnumerable<TPrimaryKey> ids);
                IQueryable<TDataObject> Query();
                IQueryable<TDataObject> FindBy(Expression<Func<T, bool>> predicate);
                void Delete(TPrimaryKey ids);
                void Delete(IEnumerable<TPrimaryKey> ids);
                void Save(TDataObject entity);
                void Save(TDataObjectList entities);
                ValidationErrors Validate(TDataObject entity);  // <- Define ValidationErrors as you see fit
                ValidationErrors Validate(TDataObjectList entities);  // <- Define ValidationErrors as you see fit
            }

            public abstract BaseBusiness : IBaseBusiness
            {
                private TIDataAccess dataAccess;
                protected BaseBusiness(TIDataAccess dataAccess) { this.dataAccess = dataAccess; }
            }

            public interface IBaseDataAccess
            {
                IQueryable<TDataObject> Query();
                IQueryable<TDataObject> FindBy(Expression<Func<T, bool>> predicate);
                void DeleteBy(Expression<Func<T, bool>> predicate);
                void Save(TDataObjectList entities);
            }
        }

    }

    namespace Application
    {

        public interface IBaseApplicationService {}

        public class BaseApplicationServiceWebAPIHost<TIApplicationService> : ApiController where TIApplicationService : IBaseApplicationService
        {
            private TIApplicationService applicationService;

            public BaseApplicationServiceWebAPIHost(TIApplicationService applicationService) { this.applicationService = applicationService; }
        }

        public class BaseApplicationServiceWCFHost<TIApplicationService> where TIApplicationService : IBaseApplicationService
        {
            private TIApplicationService applicationService;

            public BaseApplicationServiceWCFHost(TIApplicationService applicationService) { this.applicationService = applicationService; }
        }

    }

Use it like so:

UserDomain.dll

references: AcmeFramework.dll

namespace UserDomain
{

    public class User : Entity<User, User.DataObject, User.DataObjectList, User.IBusiness, User.IDataAccess, Guid>
    {

        public class DataObject : BaseDataObject
        {
            public string FirstName;
            public string LastName;
            public bool IsActive { get; }
        }

        public class DataObjectList : BaseDataObjectList {}

        public interface IBusiness : IBaseBusiness
        {
            void DeactivateUser(Guid userId);
        }

        public interface IDataAccess : IBaseDataAccess {}

        public class Business
        {
            public Business(User.IDataAccess dataAccess) : base(dataAccess) {}
            public void DeactivateUser(Guid userId)
            {
                ...
            }
        }

    }

    public class UserPermission : Entity<UserPermission, UserPermission.DataObject, UserPermission.DataObjectList, UserPermission.IBusiness, UserPermission.IDataAccess, Guid>
    {

        public class DataObject : BaseDataObject
        {
            public Guid PermissionId;
            public Guid UserId;
        }

        public class DataObjectList : BaseDataObjectList {}

        public interface IBusiness : IBaseBusiness {}

        public interface IDataAccess : IBaseDataAccess {}

        public class Business
        {
            public Business(UserPermission.IDataAccess dataAccess) : base(dataAccess) {}
        }

    }

    public class Permission : Entity<Permission, Permission.DataObject, Permission.DataObjectList, Permission.IBusiness, Permission.IDataAccess, Guid>
    {

        public class DataObject : BaseDataObject
        {
            public string Code;
            public string Description;
        }

        public class DataObjectList : BaseDataObjectList {}

        public interface IBusiness : IBaseBusiness {}

        public interface IDataAccess : IBaseDataAccess {}

        public class Business
        {
            public Business(Permission.IDataAccess dataAccess) : base(dataAccess) {}
        }

    }

}

UserManagementApplicationContracts.dll

references: AcmeFrameworkContracts.dll

namespace UserManagement.Contracts
{

    public class ReviewUserResponse : Response<ReviewUserResponse>
    {

        public class UserPermission
        {
            public  Guid    id;
            public  string  permission; // <- formatted as "permissionCode [permissionDescription]"
        }

        public class User
        {
            public  Guid                    id;
            public  string                  firstName;
            public  string                  lastName;
            public  List<UserPermissions>   permissions;
        }

        public User user;
    }

    public class EditUserResponse : Response<EditUserResponse>
    {

        public class Permission
        {
            public  Guid    id;
            public  string  permissionCode;
            public  string  description;
        }

        public class UserPermission
        {
            public  Guid    id;
            public  Guid    permissionId;
        }

        public class User
        {
            public  Guid                    id;
            public  string                  firstName;
            public  string                  lastName;
            public  List<UserPermissions>   permissions;
        }

        public  List<Permission>    knownPermissions;
        public  User                user;

    }

    public interface IUserManagementApplicationService : IBaseApplicationService
    {

        Response EditUser(Guid userId);
        Response SaveUser(EditUserResponse.user user);
        Response ViewUser(Guid userId);

    }

}

UserManagementApplicationImplementation.dll

references: AcmeFramework.dll, AcmeFrameworkContracts.dll, UserManagementApplicationContracts.dll, UserDomain.dll

namespace UserManagement.Implementation
{

    public class UserManagementApplicationService : IUserManagementApplicationService
    {
        private User.IBusiness              userBusiness;
        private UserPermissions.IBusiness   userPermissionsBusiness;
        private Permission.IBusiness        permissionBusiness;

        public UserManagementApplicationService(User.IBusiness userBusiness, UserPermission.IBusiness userPermissionsBusiness, Permission.IBusiness permissionBusiness)
        {
            this.userBusiness               = userBusiness;
            this.userPermissionsBusiness    = userPermissionsBusiness;
            this.permissionBusiness         = permissionBusiness;
        }

        public Response EditUser(Guid userId)
        {
            ...
        }

        public Response SaveUser(EditUserResponse.user user)
        {
            ...
        }

        public Response ViewUser(Guid userId)
        {
            ...
        }

    }

}

You can layer in UOW if you are so inclined. You can also expose services and hosts that are more logical entity specific instead of use case specific as you feel the need, but remember to not place your domain schema classes in your contracts library. That way you can evolve the contracts independently from the underlying implementations. And you can move the Entity<TEntity, TDataObject, TDataObjectList, TIBusiness, TIDataAccess> subclasses to a domain contracts library such as UserDomain.Contracts.dll, and leave the implementations in the domain libraries such as UserDomain.dll if you desire that isolation.