Need Help in applying SOLID principles

2019-03-30 06:34发布

Really impressed with Juile Lerman's pluralsight course on "EF in Enterprise" and decided to build my demo app.

I am using VS 2012 and latest versions of EF,SQL Server and MVC. I am building a demo application which applies SOLID principles. I am doing this to better understand how to implement DI & unit testing.

I have used DB first approach for this demo application. It contains only one table named UserDetails and below is how it looks in SQL server. I will use this table for CRUD operations. enter image description here

Below is how i have layered my application :

1. WESModel Solution: This layer contains my Model1.edmx file and the context class as below.

namespace WESModel
{
    using System;
    using System.Data.Entity;
    using System.Data.Entity.Infrastructure;
    using WESDomain;

    public partial class WESMVCEntities : DbContext
    {
        public WESMVCEntities()
            : base("name=WESMVCEntities")
        {
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            throw new UnintentionalCodeFirstException();
        }

        public DbSet<UserDetail> UserDetails { get; set; }
    }
}

2. WESDomain Solution: This layer contains my Domain classes (or POCO classes). These POCO classes were actually auto generated in my WESModel layer. I moved them out to this layer. Here is how the single POCO class looks.

namespace WESDomain
{
    using System;
    using System.Collections.Generic;

    public partial class UserDetail:IUserDetail
    {
        public int Id { get; set; }
        public string UserName { get; set; }
    }
}

3: WESDataLayer Solution: This layer contains reference to dlls from my above 2 layers. This layer has my Repository class as shown below. For now , I am keeping the IRepository in the same class :)

namespace WESDataLayer
{ 
    public class UserDetailRepository : IUserDetailRepository
    {
        WESMVCEntities context = new WESMVCEntities();

        public IQueryable<IUserDetail> All
        {
            get { return context.UserDetails; }
        }

        public IQueryable<IUserDetail> AllIncluding(params Expression<Func<IUserDetail, object>>[] includeProperties)
        {
            IQueryable<IUserDetail> query = context.UserDetails;
            foreach (var includeProperty in includeProperties) {
                query = query.Include(includeProperty);
            }
            return query;
        }

        public IUserDetail Find(int id)
        {
            return context.UserDetails.Find(id);
        }

        public void InsertOrUpdate(UserDetail userdetail)
        {
            if (userdetail.Id == default(int)) {
                // New entity
                context.UserDetails.Add(userdetail);
            } else {
                // Existing entity
                context.Entry(userdetail).State = EntityState.Modified;
            }
        }

        public void Delete(int id)
        {
            var userdetail = context.UserDetails.Find(id);
            context.UserDetails.Remove(userdetail);
        }

        public void Save()
        {
            context.SaveChanges();
        }

        public void Dispose() 
        {
            context.Dispose();
        }
    }

    public interface IUserDetailRepository : IDisposable
    {
        IQueryable<IUserDetail> All { get; }
        IQueryable<IUserDetail> AllIncluding(params Expression<Func<UserDetail, object>>[] includeProperties);
        UserDetail Find(int id);
        void InsertOrUpdate(UserDetail userdetail);
        void Delete(int id);
        void Save();
    }
}

4:ConsoleApplication1 Solution: This is my UI layer. It will be my MVC application in my final app. Here i simply query the DB and display the data. This is how the code looks.

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
             IUserDetailRepository repo = new UserDetailRepository();

             var count = repo.All.ToList().Count().ToString();
             Console.WriteLine("Count: {0}", count);
             Console.ReadLine();

        }
    }
}

Question: My UI layer does not have any ref to EF DLL. However, It has an instance of Repository class. In MVC application, my controller will have an instance of repository class or UnitOfWork.

a) Is this the right thing to do ?

b) Is there any way i can abstract it ?

c) What if in future i want to swap out EF with Dapper or any other ORM tool ?

d) How do i fit my DI tool in this project ? In which layer it should be ?

e) Unit testing. I am aware of StructureMap and want to make use of it in this project in such a way that in future i should be able to swap it out with Ninject. How do i achieve this ?

Thanks for reading this big question and i really appreciate if anyone can point me in right direction.

3条回答
smile是对你的礼貌
2楼-- · 2019-03-30 07:07

Question: My UI layer does not have any ref to EF DLL. However, It has an instance of Repository class. In MVC application, my controller will have an instance of repository class or UnitOfWork.

Yes, UI layer classes must not have any reference to EF. But to do this, they can't have a reference to the concrete repository. In MVC Application, if you don't use a Service Layer, the Controller will have a reference just on IUserDetailRepository, and wait a concrete type from construction. About UnitOfWork, it depends on your implementation :-)

a) Is this the right thing to do ?

The right thing to do is called "loose coupling", it seems that your design is choosing this way.

b) Is there any way i can abstract it ?

Yes, you can use a Dependency Resolver. This way, no need to reference concrete types, you will have a code only based on abstractions

c) What if in future i want to swap out EF with Dapper or any other ORM tool ?

You must have a Data Access Layer, for example, a library containing your concrete implementations of your IXxxRepository contracts. In your case, it will be EF implementations. When you will change for Dapper, you will have to re-implement this layer. The refactoring has an acceptable limit.

d) How do i fit my DI tool in this project ? In which layer it should be ?

Best place to place your DI tool will be the UI layer. On application start, you will configure dependencies bindings and everything will work automagically ;)

e) Unit testing. I am aware of StructureMap and want to make use of it in this project in such a way that in future i should be able to swap it out with Ninject. How do i achieve this ?

You want to unplug your Dependency Resolver to plug an other? No problem, just have a forecast when coding configuration of your DR to have the minimum coupling with your application. There are some tips to limit coupling in some cases... In the project I am currently working on, we have first an MVC application and a Service Layer, Business Layer, Data Access Layer, and Infrastructure Layer. We use Ninject as DR, and the infratructure and Web UI layers are the only that have a reference on Ninject. It's very easy to unplug it, and we already tried Unity this way.

One more thing, you shouldn't have a contract for UserDetail. There is no need for that, use Dependency Injection on stateless classes rather than on all classes like DTOs.

查看更多
Fickle 薄情
3楼-- · 2019-03-30 07:08

If you use implicit variable typing instead of explicit variable typing (i.e. eliminate the var keyword) you can much more easily determine dependencies. Wherever possible, prefer the use of an interface (IUserDetailRepository) over the use of a class (UserDetailRepository).

examples:

1) allow compiler to determine type

var repo = new UserDetailRepository();

2) type determined by class reference

UserDetailRepository repo = new UserDetailRepository();

3) type determined by interface

IUserDetailRepository repo = new UserDetailRepository();

By allowing the type to be determined by Interface instead of by the compiler, you can swap in different references that conform to the same interface (i.e. IUserDetailRepository repo = new DapperUserDetailRepository();

Also, you are on the border of a principle called Inversion of Control (IoC), which is the practice of using a specific IoC container (Ninject, CastleWinsor, Unity, etc) to resolve your dependencies automatically, whereby you never call the new keyword directly.

Since you mention StructureMap, here's an example of how that works:

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            IContainer container = ConfigureDependencies();
            IUserDetailRepository repo = container.GetInstance<IUserDetailRepository>();

            var count = repo.All.ToList().Count().ToString();
            Console.WriteLine("Count: {0}", count);
            Console.ReadLine();

        }

        private static IContainer ConfigureDependencies() {
            return new Container(x =>{
                x.For<IUserDetailRepository>().Use<UserDetailRepository>();
            });
        }
    }
}
查看更多
闹够了就滚
4楼-- · 2019-03-30 07:20

Briefly:

Your Model has a dependency on IRepository (the implementation of IRepository could be anything, Dapper, EF, ADO.Net, etc.) to persist data, do query, etc. Your model has the business rules.

Your view (console, WPF, Web) has dependency on a layer in between view and model, either a presenter (MVP), controller (MVC) or viewmodel (MVVM).

That intermediate layer works with the Model to persist data.

You can use the dependency points to use DI.

Unit tests could be applied on any layer, but make sure you cover the business rules specially.

IUserDetailRepository looks like a repository, and your model should be using that. This way you separate your database implementation behind the abstraction of an interface, and as stated earlier, the real implementation could be anything like EF, dapper, etc.

In MVC model, the controller calls on the Model to apply business rules and persist data.

查看更多
登录 后发表回答