在应用SOLID原则时需要帮助

时间:2013-07-27 14:33:28

标签: asp.net-mvc entity-framework oop dapper n-tier-architecture

Juile Lerman关于“企业中的EF”的复数课程给我留下了深刻的印象,并决定构建我的演示应用程序。

我正在使用VS 2012以及最新版本的EF,SQL Server和MVC。我正在构建一个应用SOLID原则的演示应用程序。我这样做是为了更好地理解如何实现DI&单元测试。

我在此演示应用程序中使用了DB first方法。它只包含一个名为UserDetails的表,下面是它在SQL Server中的外观。我将使用此表进行CRUD操作。 enter image description here

以下是我对应用程序进行分层的方式:

1。 WESModel解决方案:该层包含我的Model1.edmx文件和上下文类,如下所示。

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解决方案:该层包含我的Domain类(或POCO类)。这些POCO类实际上是在我的WESModel层中自动生成的。我把它们移到了这一层。以下是单个POCO类的外观。

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解决方案:此图层包含对上述2层dll的引用。 该层具有我的Repository类,如下所示。现在,我将IRepository保留在同一个类中:)

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解决方案:这是我的UI图层。它将是我最终应用程序中的MVC应用程序。在这里,我只是查询数据库并显示数据。这就是代码的外观。

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();

        }
    }
}

问题:我的UI图层没有任何引用EF DLL。但是,它有一个Repository类的实例。在MVC应用程序中,我的控制器将具有存储库类或UnitOfWork的实例。

a)这是正确的做法吗?

b)有什么方法可以抽象出来吗?

c)如果将来我想用Dapper或任何其他ORM工具换掉EF会怎么样?

d)我如何在我的项目中使用我的DI工具?它应该在哪一层?

e)单元测试。我知道StructureMap,并希望在这个项目中使用它,以便将来我应该能够与Ninject交换它。我如何实现这一目标?

感谢您阅读这个大问题,如果有人能指出我正确的方向,我真的很感激。

3 个答案:

答案 0 :(得分:3)

  

问题:我的UI图层没有任何引用EF DLL。但是,它有   Repository类的一个实例。在MVC应用程序中,我的控制器   将有一个存储库类或UnitOfWork的实例。

是的,UI图层类不得对EF有任何引用。但要做到这一点,他们不能引用具体的存储库。在MVC应用程序中,如果您不使用服务层,Controller将只在IUserDetailRepository上有一个引用,并等待构造中的具体类型。 关于UnitOfWork,它取决于您的实现: - )

  

a)这是正确的做法吗?

正确的做法是称为“松耦合”,似乎你的设计选择了这种方式。

  

b)有什么方法可以抽象出来吗?

是的,您可以使用依赖性解析程序。这样,无需引用具体类型,您将只有基于抽象的代码

  

c)如果将来我想用Dapper或任何其他ORM工具换掉EF会怎么样?

您必须拥有数据访问层,例如,包含IXxxRepository合同的具体实现的库。在您的情况下,它将是EF实现。当您为Dapper进行更改时,您将不得不重新实现此图层。重构有一个可接受的限制。

  

d)我如何在我的项目中使用我的DI工具?它应该在哪一层?

放置DI工具的最佳位置是UI图层。在应用程序启动时,您将配置依赖项绑定,一切都将自动运行;)

  

e)单元测试。我知道StructureMap,并希望在这个项目中使用它,以便将来我应该能够与Ninject交换它。我如何实现这一目标?

您想拔掉您的依赖关系解析器以插入另一个吗?没问题,只需在编码DR的配置时进行预测,以便与您的应用程序保持最小耦合。在某些情况下,有一些限制耦合的技巧......在我目前正在开发的项目中,我们首先有一个MVC应用程序和一个服务层,业务层,数据访问层和基础设施层。我们使用Ninject作为DR,并且基础结构和Web UI层是唯一具有Ninject参考的层。拔掉它很容易,我们已经用这种方式尝试过Unity。

还有一件事,你不应该有UserDetail的合同。没有必要,在无状态类上使用依赖注入,而不是在DTO之类的所有类上使用。

答案 1 :(得分:2)

如果使用隐式变量类型而不是显式变量类型(即消除var关键字),则可以更轻松地确定依赖关系。尽可能使用接口(IUserDetailRepository)而不是使用类(UserDetailRepository)。

的示例:

1)允许编译器确定类型

var repo = new UserDetailRepository();

2)类型由类引用确定

UserDetailRepository repo = new UserDetailRepository();

3)类型由接口

确定

IUserDetailRepository repo = new UserDetailRepository();

通过允许接口而不是编译器确定类型,您可以交换符合相同接口的不同引用(即IUserDetailRepository repo = new DapperUserDetailRepository();

此外,您处于一个名为Inversion of Control(IoC)的原则的边界,这是使用特定IoC容器(NinjectCastleWinsorUnity的做法,等)自动解决你的依赖关系,你永远不会直接调用new关键字。

由于你提到了StructureMap,下面是一个如何工作的例子:

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>();
            });
        }
    }
}

答案 2 :(得分:0)

简言之:

您的模型依赖IRepository(IRepository的实现可以是任何东西,Dapper,EF,ADO.Net等)来持久化数据,进行查询等。您的模型具有业务规则。

您的视图(控制台,WPF,Web)依赖于视图和模型之间的层,presenter(MVP),controller(MVC)或viewmodel(MVVM) )。

该中间层与Model一起使用以保存数据。

您可以使用依赖点来使用DI。

单元测试可以应用于任何图层,但请确保专门涵盖业务规则。

IUserDetailRepository看起来像一个存储库,你的模型应该使用它。这样就可以将数据库实现分离到接口的抽象之后,如前所述,真正的实现可能是EF,dapper等。

在MVC模型中,控制器调用Model来应用业务规则并保留数据。