避免将具体的数据库上下文类注入控制器

时间:2019-04-02 00:11:05

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

我正在将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. 如果是的话,我怎么能注入一个接口而不是注入一个具体的实现?

谢谢

2 个答案:

答案 0 :(得分:2)

  

在这种情况下注入具体实现是错误的吗?

依赖。如果您正在编写具有不同关注点(业务逻辑,验证,授权)的多层应用程序,或者您正在编写一个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实现什么?您实际上是要编写真实的单元测试吗?老实说,您是否看到自己正在更改或具有多个安装/租户的接口实例?

答案 1 :(得分:1)

我不建议抽象出Entity Framework,因为它本身已经是一个抽象(您可以在不了解使用类的情况下,将SQL Server的数据库提供程序从SQL Server换成PostgreSQL)。我也不建议将上下文插入控制器。

我想做的是,我建议您亲自研究一下是否合适,以利用Command Pattern,并利用Jimmy Bogard令人惊叹的AutoMapper和{{ 3}}。

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

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

我真的很喜欢这样,因为现在您的控制器充当业务逻辑的路由器。他们幸福地没有意识到引擎盖下到底发生了什么(或怎么回事)。

了解您的问题的重点:

  

在这种情况下注入具体的实现是错误的吗?

对此的回答有些固执己见,但许多人认为,这不仅是可以接受的选择,而且是正确的选择。在某个时间点,为了进行实际的数据库查询,您希望了解您的DBContext。

通过抽象它,您将通过编写数百种不同的方法以各种方式查询数据,执行各种selectsincludes来使自己感到痛苦。要么,要么您将拥有“笨重”的方法,这些方法返回的数据远远超出您的实际需要。

通过对DBContext有具体的了解(如果使用命令模式,可能在请求处理程序中),可以完全控制数据的获取方式,从而实现查询优化以及强制分离问题。

免责声明:我提出了上述库,因为它们很棒而且很有帮助,但是我知道很多人都看不起他们将它们包含在答案中。我只想明确地说,它们绝对不是执行命令模式的要求,但是如果您要采用它,为什么还要重新发明轮子呢?