如何使用 .NET Core 解析 class 库中的 DI class?

How to resolve a DI class in a class library with .NET Core?

我了解 .NET Core 中 DI 的基础知识,但我无法弄清楚如何将它用于多个项目。想象一下,我正在 ASP.NET Core:

的 Startup class 中设置数据库上下文
public void ConfigureServices(IServiceCollection services)
{
  services.AddDbContext<GalleryDb>();
}

我知道如何在 API 控制器中访问该上下文:

public class AlbumController : Microsoft.AspNetCore.Mvc.Controller
{
  private GalleryDb _ctx;

  public AlbumController(GalleryDb ctx)
  {
    _ctx = ctx;
  }
}

但是当 API 控制器和数据访问 class 之间有许多层和功能时,该怎么办?最终代码到达我的存储库 class,这是实际需要上下文的存储库。它看起来像这样:

public class AlbumRepository
{
  private GalleryDb _ctx;
  public AlbumRepository(GalleryDb ctx)
  {
    _ctx = ctx;
  }

  public void Save(AlbumEntity entity)
  {
    // Use _ctx to persist to DB.
  }
}

我知道我可以从 API 入口点一直向下传递上下文,但这似乎是一种反模式,因为这意味着将它作为参数传递给多个 class对它不感兴趣的es和函数。

相反,我想在调用存储库时做这样的事情 class:

public void Save(AlbumEntity album)
{
  var ctx = DependencyResolver.GetInstance<GalleryDb>();
  var repo = new AlbumRepository(ctx);
  repo.Save(album);
}

我相信一些 DI 框架有类似的东西,但我正在尝试弄清楚如何使用原生 .NET Core 2.0 来做到这一点。这可能吗?最佳做法是什么?我发现一个线程 (ASP.NET Core DependencyResolver) 谈论使用 IServiceProvider 但暗示这不是一个理想的解决方案。

我希望无论解决方案是什么,我都可以将其扩展以应用于其他 DI classes,例如 ASP.NET Identity 的 RoleManager 和 SignInManager。

I understand that I could pass the context from the API entry point all the way down, but that seems like an anti-pattern because it means passing it as a parameter through multiple classes and functions that have no interest in it.

不,那不是反模式。这就是您应该的方式。但是,关于 "classes and functions that have no interest in it" 的部分毫无意义。

简单地说,如果您正在使用类似于包装 DbContext 的存储库之类的东西(顺便说一下,这是一个可怕的想法,但我们会在其中插入一个图钉),那么您不应该永远直接处理那个 DbContext。相反,您应该将您的存储库注入到您的控制器中,然后简单地将上下文注入到其中:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<GalleryDb>();
    services.AddScoped<AlbumRepository>();
}

因为 ASP.NET Core 知道如何注入 GalleryDb,并且 AlbumRepositoryGalleryDb 作为构造函数参数,您只需将 AlbumRepository 注册为注入很好(使用 "scoped" 或请求生命周期)。

现在,您可以像当前注入上下文一样注入 AlbumRepository

public class AlbumController : Microsoft.AspNetCore.Mvc.Controller
{
    private AlbumRepository _repo;

    public AlbumController(AlbumRepository repo)
    {
        _repo = repo;
    }
}

当您有很多存储库时,这开始变得棘手,尤其是当您有需要与多个存储库交互的控制器时。最终,您的代码将成为服务配置和注入样板的老鼠窝。但是,到那时,您实际上也应该使用工作单元模式,将所有存储库封装在一个 class 中,您可以改为注入。但是等等,哦,是的,这就是 DbContext 已经 。它是封装多个存储库或 DbSets 的工作单元。这就是为什么您不应该将存储库模式与 Entity Framework 结合使用。这是一个毫无意义的抽象,除了给你的代码添加额外的和不必要的熵之外什么都不做。

如果你想抽象DbContext,那么你应该使用服务层模式(不要与微软称为"service pattern"的RPC牛粪混淆)或CQRS (命令查询责任分离)模式。存储库模式是为了一件事:抽象出原始 SQL。如果您没有原始 SQL,则不应实施该模式。

chris-pratt 帮助我理解的关键突破是,唯一可行的方法是通过 所有 层使用 DI。例如,在数据层下方,我通过 DI 获得了一个数据库上下文:

public class AlbumRepository
{
  private GalleryDb _ctx;
  public AlbumRepository(GalleryDb ctx)
  {
    _ctx = ctx;
  }
}

在业务层我使用DI来获取对数据层的引用:

public class Album
{
  private AlbumRepository _repo;
  public Album(AlbumRepository repo)
  {
    _repo = repo;
  }
}

然后,在web层,我使用DI获取业务层的引用class:

[Route("api/[controller]")]
public class AlbumController : Microsoft.AspNetCore.Mvc.Controller
{
  private Album _album;
  public AlbumController (Album album)
  {
    _album = album;
  }
}

通过在每一层使用 DI,DI 系统能够在需要的地方构建所有必要的 classes。

此要求对应用程序的体系结构产生了深远的影响,我现在意识到,我最初希望调整现有的非 DI 应用程序以开始将 DI 用于 DB 上下文是一项艰巨的任务。