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

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


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


我在 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;

}

只是个小疑问,


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

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


拉风的咖菲猫
浏览 60回答 2
2回答

POPMUISE

在这种情况下注入具体实现是错误的吗?这取决于。如果您正在编写一个具有分离关注点(业务逻辑、验证、授权)的多层应用程序,或者您正在编写一个 5 页的应用程序来为有限数量的用户显示杂项信息?我已经为小型的、主要是只读的应用程序完成了这些,因为只有很小的变化,我不需要过度设计和构建解决方案。如果是,我怎么能只注入一个接口而不是注入一个具体的实现呢?当然,但是在某些时候,一个类将需要一个 dbcontext,它可以由需要它的对象进行 DI 或创建。如果您确实将数据访问抽象到一个单独的类/接口中,我不会推荐 DI(将上下文注入具体的数据访问),因为该具体类型将与 EF 紧密耦合。这意味着如果您决定切换到 Dapper、NHibernate 或其他一些 ORM,您无论如何都必须重写所有方法。一种快速而肮脏的方法(对于小型/学习经验,我永远不会向百万用户系统推荐这种方法)来抽象掉它DbContext很简单:public interface IUserDA{&nbsp; IQueryable<User> Users { get; }}public class MyDbContext : IUserDA{&nbsp; public DbSet<User> Users { get; set; }&nbsp; IQueryable<User> IUserDA.Users&nbsp; {&nbsp; &nbsp; &nbsp;get&nbsp; &nbsp; &nbsp;{&nbsp; &nbsp; &nbsp; &nbsp; return Users;&nbsp; // references the DbSet&nbsp; &nbsp; &nbsp;}&nbsp; }}现在您的 MyDbContext 实现了一个未耦合到实体框架的接口。但是如果我们注入一个具体的类,它会破坏 DI 的目的吗?取决于你的目的。您究竟希望通过使用 DI 实现什么?您真的要编写真正的单元测试吗?您真的看到自己为多个安装/租户更改或拥有多个接口实例吗?

30秒到达战场

我不建议将 Entity Framework 抽象出来,因为它本身已经是一个抽象(您可以在您的消费类不知情的情况下将数据库提供程序从 SQL Server 换成 PostgreSQL)。我也不建议将您的上下文注入您的控制器。我喜欢做的是采用Command Pattern,利用令人惊叹的 Jimmy Bogard 库AutoMapper和MediatR,自己研究一下它是否合适。为您提供一个基本示例,说明使用此类架构设计选择时控制器的外观:[Route("api/[controller]")][ApiController]public class SomeTypeController : ControllerBase{&nbsp; &nbsp; private readonly IMediator mediator;&nbsp; &nbsp; private readonly IMapper mapper;&nbsp; &nbsp; public SomeTypeController(IMediator mediator, IMapper mapper)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; this.mediator = mediator;&nbsp; &nbsp; &nbsp; &nbsp; this.mapper = mapper;&nbsp; &nbsp; }&nbsp; &nbsp; [HttpGet]&nbsp; &nbsp; public async Task<ActionResult<IEnumerable<SomeTypeContract>>> GetAll(CancellationToken cancellationToken)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; var result = await mediator.Send(new GetAllSomeTypeRequest(), cancellationToken);&nbsp; &nbsp; &nbsp; &nbsp; return Ok(mapper.Map<IEnumerable<SomeTypeContract>>(result));&nbsp; &nbsp; }}我真的很喜欢这个,因为现在您的控制器充当业务逻辑的路由器。他们幸福地不知道引擎盖下究竟发生了什么(或者它是如何发生的)。要了解问题的核心:在这种情况下注入具体实现是错误的吗?对此的答案有点固执己见,但许多人认为这不仅是一个可以接受的选择,而且是正确的选择。在某个时间点,为了进行实际的数据库查询,您需要了解您的 DBContext。通过将其抽象出来,您只会通过编写数百种不同的方法来以不同的方式查询您的数据、执行各种操作而给自己带来selects痛苦includes。否则,您将拥有“笨拙”的方法,这些方法返回的数据比您实际需要的多得多。通过具体了解您的 DBContext(如果您使用命令模式,可能在您的请求处理程序中),您可以完全控制数据的获取方式,允许查询优化以及强制关注点分离。免责声明:我提出上述库是因为它们很棒且很有帮助,但我知道很多人看不起将它们包含在答案中。我只想说清楚,它们绝不是执行命令模式的必要条件,但如果您要采用它,为什么要重新发明轮子?
打开App,查看更多内容
随时随地看视频慕课网APP