【问题标题】:Avoid injecting concrete database context class to controller [closed]避免将具体的数据库上下文类注入控制器[关闭]
【发布时间】:2019-08-23 05:08:48
【问题描述】:

我正在使用 Entity Framework Core for MySql 和数据库优先方法。

搭建好数据库后,生成上下文类:sakilaContext.cs(sakila是MySql内置的数据库/方案,我用它来玩玩)。

我在 Startup.cs 中注入了我的数据库

public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        var connectionString = @"server=localhost;port=1123;user=root;password=xxx;database=sakila";
        services.AddDbContext<sakilaContext>(options => options.UseMySql(connectionString));
    }

在我的 api 控制器中,我这样做:

private sakilaContext dbCtx;

public SakilaController(sakilaContext dbContext)
{
    dbCtx = dbContext;
}

只是想知道,

  1. 这种情况下注入具体的实现是不是错了??
  2. 如果是,我怎么能只注入一个接口而不是注入一个具体的实现?

谢谢

【问题讨论】:

  • 1.我认为不,这没有什么问题,因为通用 dbcontext 不会包含您的属性,因此您无法使用它
  • @KienChu 呵呵,我有点同意你的看法。但是如果我们注入一个具体的类,它会破坏 DI 的目的吗?
  • DI 的好处不仅仅是连接抽象和实现,它有助于降低依赖图的复杂性,帮助您自动控制对象的生命周期等
  • 我不认为注入一个具体的实现是错误的,特别是在这种情况下它是数据库提供者。我的意思是为数据库提供者提供 DI 和接口很好,但是你/你会在项目中多久切换一次数据库提供者?通常会选择一种数据库提供程序,并在项目的整个生命周期内持续使用。现在为了避免代码中到处都有具体的类,你可以编写一个工厂来生成 dbContext 并注入它的接口,或者使用提到的 MediatR @Darren。
  • @DavidLiang 那么我们将如何测试控制器?因为我们将该控制器与数据库提供程序相结合。

标签: c# dependency-injection asp.net-core-mvc entity-framework-core


【解决方案1】:

这种情况下注入具体的实现是不是错了??

取决于。如果您正在编写一个具有分离关注点(业务逻辑、验证、授权)的多层应用程序,或者您正在编写一个 5 页的应用程序来为有限数量的用户显示杂项信息?

我已经为小型的(主要是只读的)应用程序做到了这一点,因为只有很小的更改,我不需要Over design and architect the solution

如果是,我怎么能只注入一个接口而不是注入一个具体的实现?

当然,但是在某些时候,一个类需要一个 dbcontext,它可以是 DI 或由需要它的对象创建的。如果您确实将数据访问抽象为单独的类/接口,我不建议使用 DI(将上下文注入具体数据访问),因为该具体类型将与 EF 紧密耦合。这意味着如果您决定切换到 Dapper、NHibernate 或其他一些 ORM,则无论如何您都必须重写所有方法。

抽象出DbContext 的一种快速而肮脏的方法(对于小型/学习经验,我绝不会向大型百万用户系统推荐此方法),如下所示:

public interface IUserDA
{
  IQueryable<User> Users { get; }
}

public class MyDbContext : IUserDA
{
  public DbSet<User> Users { get; set; }

  IQueryable<User> IUserDA.Users
  {
     get
     {
        return Users;  // references the DbSet
     }
  }

}

现在您的 MyDbContext 实现了一个不与实体框架耦合的接口。

但是如果我们注入一个具体的类,它会破坏 DI 的目的吗?

取决于你的目的。您究竟希望通过使用 DI 实现什么目标?你真的要写真正的单元测试吗?您是否真的看到自己在为多个安装/租户更改或拥有多个接口实例?

【讨论】:

  • 非常正确,通常一个项目会坚持使用一个选定的数据库。但是当涉及到测试时,我们如何测试一个控制器和一个具体的数据库上下文类呢?
  • 这就是为什么我问你做 DI 的目的是什么。
【解决方案2】:

我不建议将实体框架抽象出来,因为它本身就是一个抽象(你可以在不知道消费类的情况下将数据库提供程序从 SQL Server 换成 PostgreSQL)。我也不建议将你的上下文注入你的控制器。

我喜欢做的,我建议你自己研究一下,看看它是否合适,是采用 Command Pattern,利用令人惊叹的 Jimmy Bogard 的库 AutoMapperMediatR

为您提供一个基本示例,说明使用这种架构设计选择时控制器的外观:

[Route("api/[controller]")]
[ApiController]
public class SomeTypeController : ControllerBase
{
    private readonly IMediator mediator;
    private readonly IMapper mapper;

    public SomeTypeController(IMediator mediator, IMapper mapper)
    {
        this.mediator = mediator;
        this.mapper = mapper;
    }

    [HttpGet]
    public async Task<ActionResult<IEnumerable<SomeTypeContract>>> GetAll(CancellationToken cancellationToken)
    {
        var result = await mediator.Send(new GetAllSomeTypeRequest(), cancellationToken);
        return Ok(mapper.Map<IEnumerable<SomeTypeContract>>(result));
    }
}

我真的很喜欢这个,因为现在您的控制器充当业务逻辑的路由器。他们幸福地不知道引擎盖下到底发生了什么(或者它是如何发生的)。

要抓住问题的核心:

这种情况下注入具体实现是不是错了??

这个问题的答案有点固执己见,但许多人认为这不仅是一个可以接受的选择,而且是正确的选择。在某个时间点,为了进行实际的 DB 查询,您想要了解您的 DBContext。

通过将其抽象出来,您只会通过编写数百种不同的方法来以不同的方式查询您的数据,执行各种selectsincludes,从而给自己带来痛苦。要么这样,要么您将拥有返回比您实际需要的数据多得多的数据的“笨重”方法。

通过具体了解您的 DBContext(如果您使用命令模式,可能在您的请求处理程序中),您可以完全控制数据的获取方式,允许查询优化以及执行关注点分离。

免责声明:我提出上述库是因为它们很棒而且很有帮助,但我知道很多人看不起它们包含在答案中。我想明确一点,它们绝不是执行命令模式的必要条件,但如果你要采用它,为什么要重新发明轮子?

【讨论】:

  • 我也喜欢中介者模式,并且是 Jimmy 的粉丝。但我不认为这是@Franva 所要求的。即使使用中介模式,DbContext 仍然需要稍后在管道中注入,无论是在处理程序中还是在存储库中。
  • 谢谢你,我对自己写的东西太着迷了,以至于忘记了手头的问题。我已经更新了我的帖子!
  • 我认为包含库没有任何问题,只要您披露您是否与它们有任何关联。
  • 这很公平,尽管我不确定每个人都同意这一点。此外,我与 Jimmy Bogard 没有任何关系,就像我希望的那样!
  • 如果您决定这样做,那么您应该知道的另一件事是我将保存我的DBContext 中的更改的责任委托给Action Filter。这意味着我需要的所有处理程序确实是DBSets,他们不必担心保存更改。这是 Jimmy Bogard 在他的 Contoso University Core 项目中概述的内容。
猜你喜欢
  • 1970-01-01
  • 2014-03-11
  • 1970-01-01
  • 2019-11-12
  • 2017-06-11
  • 2016-01-07
  • 1970-01-01
  • 1970-01-01
  • 2015-01-21
相关资源
最近更新 更多