为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

C#
拉风的咖菲猫 2022-11-21 21:38:41
我正在使用 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;}只是个小疑问,在这种情况下注入具体实现是错误的吗?如果是,我怎么能只注入一个接口而不是注入一个具体的实现呢?
查看完整描述

2 回答

?
POPMUISE

TA贡献1765条经验 获得超5个赞

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


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


我已经为小型的、主要是只读的应用程序完成了这些,因为只有很小的变化,我不需要过度设计和构建解决方案。


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


当然,但是在某些时候,一个类将需要一个 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 实现什么?您真的要编写真正的单元测试吗?您真的看到自己为多个安装/租户更改或拥有多个接口实例吗?


查看完整回答
反对 回复 2022-11-21
?
30秒到达战场

TA贡献1828条经验 获得超6个赞

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


我喜欢做的是采用Command Pattern,利用令人惊叹的 Jimmy Bogard 库AutoMapper和MediatR,自己研究一下它是否合适。


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


[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。


通过将其抽象出来,您只会通过编写数百种不同的方法来以不同的方式查询您的数据、执行各种操作而给自己带来selects痛苦includes。否则,您将拥有“笨拙”的方法,这些方法返回的数据比您实际需要的多得多。


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


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


查看完整回答
反对 回复 2022-11-21
  • 2 回答
  • 0 关注
  • 59 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信