EF6(代码优先)、MVC、Unity 和没有存储库的服务层

Posted

技术标签:

【中文标题】EF6(代码优先)、MVC、Unity 和没有存储库的服务层【英文标题】:EF6 (code first), MVC, Unity, and a service layer without a repository 【发布时间】:2014-03-17 01:02:24 【问题描述】:

我的应用程序正在使用 SQL Server 2012、EF6、MVC 和 Web API。

它还使用存储库和各种文件,例如:

DatabaseFactory.cs
Disposable.cs
IDatabaseFactory.cs
IRepository.cs
IUnitOfWork.cs
RepositoryBase.cs
UnitOfWork.cs

我们已经在控制器和存储库之间使用了一个服务层 对于一些复杂的业务逻辑。我们没有计划改变到不同的数据库,它已经被指出 对我来说,最近的想法是 EF6 是一个存储库,所以为什么要构建 上面还有另一个存储库,为什么上面有我所有的文件

我开始认为这是一种明智的做法。

有没有人知道任何没有实现 EF6 的示例 存储库,带有服务层。我在网上的搜索发现了很多 复杂的代码示例似乎无缘无故地过于复杂。

我的问题也是在使用服务层时,我该放在哪里:

context = new EFDbContext()

在控制器、服务层还是两者兼而有之?我读到我可以通过依赖注入来做到这一点。我已经将 Unity 用作 IOC,但我不知道该怎么做。

【问题讨论】:

将上下文放在控制器上会违背首先拥有服务层的目的。 @EfrainReyes - 但是对于非常简单的任务,例如简单的 PUT、POST 或 DELETE(这些主要是 Web API 控制器),情况又如何呢?这些不应该在控制器中处理。如果我将这些推送到服务层,这似乎需要大量额外的编码。我希望您对此提出建议。谢谢 代码不多。您所要做的就是将这些简单的任务放在服务层而不是控制器本身上,并让控制器处理服务。就像存储库层是您更改数据库引擎时的保障一样,服务层是您更改 Web 服务框架时的保障。也许您的雇主/客户决定他们想要切换到 RESTSharp、WCF 或 ServiceStack。如果您的任务在控制器本身上,您将不得不重写它们。如果他们在服务层,你只需要调用相应的方法。 【参考方案1】:

实体框架IS 已经是工作单元模式实现以及通用存储库实现(DbContext 是 UoW,DbSet 是通用存储库)。而且我同意在大多数应用程序中在它们之上设计另一个 UoW 或 Generic Repository 是一种矫枉过正的做法(此外,GenericRepsitory 被某些人认为是一种反模式)。

服务层可以充当具体的存储库,它在封装特定于您的业务需求的数据逻辑方面有很多好处。如果使用它,则几乎不需要在其上构建存储库(除非您希望能够更改后端服务技术,例如从 WCF 到 WebApi 或其他......)

我会将您的所有数据访问权限放在您的服务层中。不要在控制器中进行数据访问。这会将您的数据层泄漏到您的 UI 层中,这只是糟糕的设计。它违反了许多核心 SOLID 概念。

但在大多数情况下,您不需要额外的 UnitOfWork 或除此之外的其他层,除非您的应用非常复杂并且旨在在多种环境中工作...

【讨论】:

确实,实体框架是工作单元模式的实现,但实现是内部的。没有什么可以阻止外部对象使用单独的 DbContext 上下文。如果多个服务使用单独的上下文,它们的操作应该在单个事务中注册,Unit of Work 模式的值就会丢失。可以从控制器传递一个具体实例,在所有服务中启动和处理请求,但在我看来这不是那么干净。 我提高了你对我的回答的评论,因为我倾向于同意你的大部分观点,但显然并非总是如此:) @ChrisHardie - 使用 IOC 创建上下文怎么样?我读过很多人都在谈论这个,但还没有找到一个很好的例子。 @SamanthaJ DbContext 没有为其最有用的方法实现接口,所以我认为 Unity 容器不会提供太多价值。即使可以,您也需要确保容器在请求过程中注入相同的实例,但每个实例在请求之间是隔离的。 @ChrisHardie - 大多数 IoC 容器也可以绑定到具体实例,如果你真的想的话,你可以很容易地为你自己的 DbContext 添加一个接口。 IE。 MyContext : DbContext, IMyContext 所以这不是一个真正的问题。将容器配置为使用 PerWebRequest 对象的生命周期也非常简单,而且通常采用这种方式。【参考方案2】:

如果您安装 Unity.Mvc*Unity.WebAPI* Nuget 包并将其添加到您的项目,那么为 ASP.NET MVC 和 WebAPI 设置 Unity 非常容易。 (* 是版本号,例如 3、4 或 5。为您的项目寻找合适的版本。例如链接 to the Unity.Mvc 5 package 和 to the Untity.WebAPI 5 package。)

this blog post 中解释了这些包的用法。

构建块大致是这样的:

您构建一个统一容器并在其中注册所有依赖项,尤其是 EF 上下文:

private static IUnityContainer BuildContainer()

    var container = new UnityContainer();

    container.RegisterType<MyContext>(new HierarchicalLifetimeManager());

    container.RegisterType<IOrderService, OrderService>();
    container.RegisterType<ICustomerService, CustomerService>();
    container.RegisterType<IEmailMessenger, EmailMessenger>();
    // etc., etc.

    return container;

MyContext 是您的派生 DbContext 类。使用HierarchicalLifetimeManager 注册上下文非常重要,因为它将确保每个 Web 请求的新上下文将在每个请求结束时由容器实例化和释放。

如果您的服务没有接口而只有具体的类,您可以删除上面注册接口的行。如果需要将服务注入控制器,Unity 只会创建具体服务类的实例。

一旦你构建了容器,你就可以在Application_Startglobal.asax中将它注册为MVC和WebAPI的依赖解析器:

protected void Application_Start()

    var container = ...BuildContainer();

    // MVC
    DependencyResolver.SetResolver(
        new Unity.MvcX.UnityDependencyResolver(container));

    // WebAPI
    GlobalConfiguration.Configuration.DependencyResolver =
        new Unity.WebApiX.UnityDependencyResolver(container);

一旦设置了DependencyResolvers,如果参数可以用注册的类型解析,框架就能够实例化在其构造函数中接受参数的控制器。例如,您现在可以创建一个CustomerController,然后注入一个CustomerService 和一个EmailMessenger

public class CustomerController : Controller

    private readonly ICustomerService _customerService;
    private readonly IEmailMessenger _emailMessenger;

    public CustomerController(
        ICustomerService customerService,
        IEmailMessenger emailMessenger)
    
        _customerService = customerService;
        _emailMessenger = emailMessenger;
    

    // now you can interact with _customerService and _emailMessenger
    // in your controller actions

这同样适用于 WebAPI 的派生 ApiControllers。

服务可以依赖上下文实例与实体框架交互,如下所示:

public class CustomerService // : ICustomerService

    private readonly MyContext _myContext;

    public CustomerService(MyContext myContext)
    
        _myContext = myContext;
    

    // now you can interact with _myContext in your service methods

当 MVC/WebAPI 框架实例化一个控制器时,它将注入注册的服务实例并解析它们自己的依赖关系,即将注册的上下文注入服务构造函数。您将注入控制器的所有服务都将在单个请求期间收到相同的上下文实例。

使用此设置,您通常不需要context = new MyContext()context.Dispose(),因为 IOC 容器将管理上下文生命周期。

【讨论】:

【参考方案3】:

如果您不使用存储库,那么我假设您将有一些地方来编写您的服务操作将使用的逻辑/处理。我将在该逻辑/流程类方法中创建一个新的 Context 实例并直接使用它的方法。最后,可能在“使用”下使用后立即将其丢弃。

处理方法最终会将返回/处理后的数据转换为服务返回给控制器的数据/消息契约。

保持数据逻辑与控制器完全分离。还要将视图模型与数据协定分开。

【讨论】:

【参考方案4】:

如果您继续使用此架构,您将与您的服务或控制器紧密耦合实体框架。存储库抽象为您提供了一些东西:

1) 您可以在未来轻松更换数据访问技术

2) 您可以模拟您的数据存储,让您可以轻松地对数据访问代码进行单元测试

您想知道将 EF 上下文放在哪里。使用实体框架的好处之一是其上的所有操作都注册到事务中。您需要确保任何数据访问代码使用相同的上下文才能享受此好处。

解决该问题的设计模式是工作单元模式,从外观上看,您已经在使用它。我强烈建议继续使用它。否则,您将需要在控制器中初始化上下文,将其传递给您的服务,该服务需要将其传递给与之交互的任何其他服务。

查看您列出的对象,使用企业架构最佳实践构建此应用程序似乎是一种周到的尝试。虽然抽象确实会带来复杂性,但它们提供的好处是毋庸置疑的。

【讨论】:

大多数人不太可能改变他们的数据访问技术,而且很容易模拟 EF(尤其是 6+,它为此提供了特殊支持),只需从接口以及数据库上下文。即使他们可能会改变他们的数据访问技术,也需要付出很多努力来避免将耦合引入技术,即使在使用存储库时(通用存储库是这里的罪魁祸首,因为它们通常直接传递 linq 查询,而这些通常非常紧密耦合)

以上是关于EF6(代码优先)、MVC、Unity 和没有存储库的服务层的主要内容,如果未能解决你的问题,请参考以下文章

构建ASP.NET MVC5+EF6+EasyUI 1.4.3+Unity4.x注入的后台管理系统(57)-插件---ueditor使用

构建ASP.NET MVC5+EF6+EasyUI 1.4.3+Unity4.x注入的后台管理系统(55)-工作流设计-表单布局

构建ASP.NET MVC5+EF6+EasyUI 1.4.3+Unity4.x注入的后台管理系统(61)-如何使用框架来开发?

构建ASP.NET MVC5+EF6+EasyUI 1.4.3+Unity4.x注入的后台管理系统(54)-工作流设计-所有流程监控

构建ASP.NET MVC5+EF6+EasyUI 1.4.3+Unity4.x注入的后台管理系统--工作流演示截图

构建ASP.NET MVC5+EF6+EasyUI 1.4.3+Unity4.x注入的后台管理系统(63)-Excel导入和导出