【问题标题】:Need Help in applying SOLID principles [closed]在应用 SOLID 原则时需要帮助 [关闭]
【发布时间】:2013-07-27 18:13:48
【问题描述】:

Juile Lerman 的“EF in Enterprise”课程给我留下了深刻的印象,并决定构建我的演示应用程序。

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

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

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

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 解决方案: 这一层包含我的域类(或 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 解决方案:该层包含对我上面两层的 dll 的引用。 这一层有我的存储库类,如下所示。现在,我将 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 交换。我如何做到这一点?

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

【问题讨论】:

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


【解决方案1】:

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

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

a) 这是正确的做法吗?

正确的做法叫做“松耦合”,看来你的设计就是选择这种方式。

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

是的,您可以使用依赖关系解析器。这样,不需要引用具体的类型,你将有一个只基于抽象的代码

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

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

d) 我如何在这个项目中安装我的 DI 工具?应该在哪一层?

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

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

您想拔下您的 Dependency Resolver 以插入另一个?没问题,只需在对 DR 的配置进行编码时进行预测,以使与您的应用程序的耦合最小。在某些情况下有一些限制耦合的技巧……在我目前正在从事的项目中,我们首先有一个 MVC 应用程序和一个服务层、业务层、数据访问层和基础设施层。我们使用 Ninject 作为 DR,基础设施和 Web UI 层是唯一对 Ninject 有参考的层。拔掉它很容易,我们已经用这种方式尝试过 Unity。

还有一点,您不应该与 UserDetail 签订合同。没有必要,在无状态类上使用依赖注入,而不是像 DTO 这样的所有类。

【讨论】:

  • 哇..很好的解释。我现在需要更多关于 1 的信息。任何指向使用 Dependency Resolver 的链接? 2.最后你说“不应该有UserDetail的合同”。你指的是哪一层?感谢您的时间和详细的回答。
  • 我指的是您的域层中的 UserDetail 类及其合同 IUserDetail。对我来说,为业务对象创建契约(或者你可能有多个实现)已经超出了架构。
  • 您希望通用链接使用依赖解析器?我只有使用 Ninject 的链接 :) 但是从示例中,您将了解使用 DR 的整体架构...试试这个stefanoricciardi.com/2011/01/21/ninject-mini-tutorial-part-1
  • 你的基础设施层有什么?
  • 基础设施层将包含所有关于...
【解决方案2】:

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

例子:

1) 允许编译器确定类型

var repo = new UserDetailRepository();

2) 由类引用确定的类型

UserDetailRepository repo = new UserDetailRepository();

3) 由接口决定的类型

IUserDetailRepository repo = new UserDetailRepository();

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

此外,您处于称为控制反转 (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>();
            });
        }
    }
}

【讨论】:

  • 实际上,我所做的与您在回答中提到的完全相同。我已经更新了我的原始帖子以反映相同的情况。我有几个问题。我知道 StructureMap 并希望在这个项目中使用它,以便将来我应该能够将它与 Ninject 交换。我如何做到这一点?
  • 再次感谢安德鲁。我很抱歉混淆了。我应该更清楚。我知道“如何”使用 StructureMap。您的示例代码是我可以从 UI 层/在 MVC 应用程序的控制器中使用它的最简单方法。但是,我想知道如何将它抽象出来/松散地与 UI/Controller 耦合,以便在需要时将其与 Ninject 交换。
  • 我不是 DI 容器方面的完整专家,但根据我的经验,DI 容器是与应用程序紧密耦合的一个部分,以使其有效。考虑到它们在实际实现中的差异,尝试抽象 DI 容器很可能会带来更多麻烦。
【解决方案3】:

简单地说:

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

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

该中间层与模型一起工作以持久化数据。

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

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

IUserDetailRepository 看起来像一个存储库,您的模型应该使用它。通过这种方式,您可以将数据库实现与接口抽象分开,如前所述,真正的实现可以是 EF、dapper 等任何东西。

在 MVC 模型中,控制器调用模型来应用业务规则并持久化数据。

【讨论】:

  • 我需要更多说明。你说我的模型依赖于 IRepository。您是指上述代码中的 WESDataLayer 解决方案项目吗? 2. 如果是 MVC 应用程序,我的控制器应该如何查询数据?我应该在上面的代码中在 Controller 和 WESDataLayer 解决方案之间再添加一层吗?其实我很困惑你上面说的这个“模型”到底是什么。从我的应用程序的角度来看,这里的“模型”是什么?感谢您的宝贵时间。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-09-15
  • 2021-11-12
  • 2014-09-22
  • 2014-08-31
  • 2018-02-15
  • 2012-07-12
  • 1970-01-01
相关资源
最近更新 更多