3

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

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

我在这个演示应用程序中使用了 DB first 方法。它只包含一个名为 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 解决方案: 这个层包含对我上面2层的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 交换。我如何做到这一点?

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

4

3 回答 3

3

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

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

a) 这是正确的做法吗?

正确的做法称为“松散耦合”,看来您的设计正在选择这种方式。

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

是的,您可以使用 Dependency Resolver。这样,无需引用具体类型,您将拥有仅基于抽象的代码

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

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

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

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

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

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

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

于 2013-07-28T15:36:18.327 回答
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>();
            });
        }
    }
}
于 2013-07-27T16:09:55.230 回答
0

简要地:

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

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

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

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

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

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

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

于 2013-07-27T14:56:59.927 回答