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

C# 避免向控制器注入具体的数据库上下文类,c#,dependency-injection,asp.net-core-mvc,entity-framework-core,C#,Dependency Injection,Asp.net Core Mvc,Entity Framework Core,我正在使用EntityFrameworkCoreforMySQL和数据库优先的方法 在搭建数据库之后,它生成上下文类:sakilaContext.cs(sakila是MySql中内置的数据库/方案,我用它来玩) 我已将数据库注入Startup.cs public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(Compatibil

我正在使用EntityFrameworkCoreforMySQL和数据库优先的方法

在搭建数据库之后,它生成上下文类: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));
    }
只是想知道

  • 在这种情况下,注入具体的实现是错误的吗
  • 如果是,我怎么能只注入一个接口而不是注入一个具体的实现呢

  • 谢谢

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

    我想做的是,我建议你自己研究一下,看看它是否合适,就是采用,利用令人惊叹的Jimmy Bogard的库和

    要向您提供一个使用此类体系结构设计选项的控制器外观的基本示例,请执行以下操作:

    [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));
        }
    }
    
    [路由(“api/[控制器]”)]
    [ApiController]
    公共类SomeTypeController:ControllerBase
    {
    专用只读IMediator中介器;
    专用只读IMapper映射器;
    公共SomeTypeController(IMediator中介器、IMapper映射器)
    {
    this.mediator=mediator;
    this.mapper=mapper;
    }
    [HttpGet]
    公共异步任务GetAll(CancellationToken CancellationToken)
    {
    var result=wait mediator.Send(新的GetAllSomeTypeRequest(),cancellationToken);
    返回Ok(mapper.Map(result));
    }
    }
    
    我真的很喜欢这一点,因为现在您的控制器充当业务逻辑的路由器。他们幸福地不知道引擎盖下到底发生了什么(或者它是如何发生的)

    要抓住问题的核心:

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

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

    通过将其抽象出来,您只需编写数百种不同的方法以不同的方式查询数据,执行各种
    选择
    包含
    ,就会给自己带来痛苦。要么这样,要么您将拥有“chunky”方法,这些方法返回的数据远远超过您实际需要的数据

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

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

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

    视情况而定。如果您正在编写一个具有独立关注点(业务逻辑、验证、授权)的多层应用程序,或者您正在编写一个5页的应用程序来为有限数量的用户显示杂项信息

    我已经为小的,大部分是只读的应用程序做到了这一点,因为只有很小的更改,我不需要这样做

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

    当然,但是在某些时候,类将需要一个dbcontext,它可以由需要它的对象进行DI'd或创建。如果将数据访问抽象到单独的类/接口中,我不建议使用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
         }
      }
    
    }
    
    公共接口IUserDA
    {
    IQueryable用户{get;}
    }
    公共类MyDbContext:IUserDA
    {
    公共数据库集用户{get;set;}
    IQueryable IUserDA.Users
    {
    得到
    {
    返回用户;//引用数据库集
    }
    }
    }
    
    现在,MyDbContext实现了一个不耦合到实体框架的接口

    但是,如果我们注入一个具体的类,它是否违背了DI的目的


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

    1。我认为没有问题,因为泛型的
    dbcontext
    不会包含您的属性,所以您不能对it@KienChu嗯,我有点同意你。但是,如果我们注入一个具体的类,它是否违背了DI的目的?DI的好处不仅仅是连接抽象和实现,它有助于降低依赖关系图的复杂性,帮助您自动控制对象的生存期,etcI不认为注入具体实现是错误的,特别是在这种情况下,它是数据库提供程序。我的意思是为数据库提供程序提供一个DI和接口是很好的,但是您/您会在项目中多久切换一次数据库提供程序?通常,将选择一种数据库提供者,并将持续整个项目生命周期。现在,为了避免代码中到处都有具体的类,您可以编写一个工厂来生成dbContext并注入其接口,或者使用前面提到的MediatR@Darren@DavidLia
    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
         }
      }
    
    }