【问题标题】:Injecting DbContext and ILogger into database DAL constructor, best practice将 DbContext 和 ILogger 注入数据库 DAL 构造函数,最佳实践
【发布时间】:2020-07-28 10:04:46
【问题描述】:

我正在使用 Entity Framework 在 ASP.NET Core 3.1 中构建 Web API。

我的数据库上下文在 Startup.cs 中注册为服务:

services.AddDbContext<LocalDbContext>(options =>
{
    options.UseSqlServer(builderLocal.ConnectionString);
});

我正在使用 DI 在我的控制器中检索 DbContext,并在我为每个方法实例化它时将其传递给我的数据库访问类 (DAL)

private readonly LocalDbContext _context;

public HomeController(LocalDbContext context)
{
    _context = context;
}

public IActionResult GetSomeData(int id)
{
    var localDb = new LocalDb(_context);
    return Ok(localDb.GetSomeDataById(id));
}

然后是我的数据库文件:

public class LocalDbContext : DbContext
{
    public LocalDbContext(DbContextOptions<LocalDbContext> options)
        : base (options) { }

    **DbSets for my models**
}

public class LocalDb
{
    private readonly LocalDbContext _context;
    private readonly ILogger<LocalDb> _logger;

    // I would want to avoid having to pass in logger in this contstructor
    public LocalDb(LocalDbContext context) 
    {
        _context = context;
        // Can I retrieve a logger from somewhere else? From the context?
        // _logger = logger; 
    }

    public void AddStudent(Student student)
    {
        _context.Student.Add(student);
        try
        {
          _context.SaveChanges();  
        } 
        catch (Exception ex)
        {
            _logger.LogError("logMessage about exception: " + ex.StackTrace);
            throw;
        }
    }
}

所以我希望 _logger 可以写入,但在我的代码中,我没有将 _logger 设置为任何内容。我知道ILogger 存在于 DbContext 中,并且有很多指南解释了如何为 Context 和 EF 添加和配置记录器。但是当我想使用我的LocalDb 类时,是否每次调用var localDb = new LocalDb(_context, _logger) 时都必须传入一个ILogger 实例

我觉得每次都必须传入一个记录器实例有点奇怪,必须有更好的解决方案。是否可以将 LocalDb 类添加为服务,然后将依赖项注入上下文并记录到该类中?最佳做法是什么?

【问题讨论】:

    标签: c# dependency-injection entity-framework-core dbcontext


    【解决方案1】:

    请注意,HomeController 不直接使用 LocalDbContext,而只是使用它将其传递给它的真实依赖关系,LocalDb。因此,不应将LocalDbContext 注入HomeController 的构造函数,而应直接注入LocalDb。这很好地解决了你的问题,因为现在LocalDb 可以在 DI 容器中注册,它可以为你解决它可能具有的任何依赖关系。

    这是一个直接依赖于LocalDbHomeController 示例。

    public class HomeController : Controller
    {
        private readonly LocalDb _db;
    
        public HomeController(LocalDb db)
        {
            _db = db;
        }
    
        public IActionResult GetSomeData(int id)
        {
            return Ok(_db.GetSomeDataById(id));
        }
    }
    

    因为LocalDb被注入到构造函数中,所以必须在DI Container中注册:

    services.AddTransient<LocalDb>();
    services.AddDbContext<LocalDbContext>(options =>
    {
        options.UseSqlServer(builderLocal.ConnectionString);
    });
    

    但是由于LocalDb是由DI Container组成的,所以可以扩展任何依赖,比如ILogger依赖:

    public class LocalDb
    {
        private readonly LocalDbContext _context;
        private readonly ILogger<LocalDb> _logger;
    
        public LocalDb(LocalDbContext context, ILogger<LocalDb> _logger) 
        {
            _context = context;
            _logger = logger; 
        }
        ...
    }
    

    提示:防止在记录和重新抛出的代码库中散布catch 语句。相反,更喜欢有一些全球基础设施来记录任何失败的请求。如果我没记错的话,ASP.NET Core 会为您开箱即用。如果没有,只需几行代码即可启用。这使代码(例如您的 LocalDb.AddStudent 更简单,并限制了类所具有的依赖项的数量。

    【讨论】:

    • 谢谢,这是我正在寻找的确认答案!
    • 使用 DI 要记住的一件事:它一直是依赖关系。依赖关系图可以变得非常深,这是正常的和预期的。这降低了应用程序中各个类的复杂性。
    • 遇到了一个挑战:我的 UnitTesting 类正在使用旧的构造函数来注入一个模拟集作为上下文。我现在如何在期望ILogger 的同时将模拟集注入数据库文件?传入 null 作为参数并在数据库类中进行检查?
    • 尝试将您的LocalDb 隐藏在抽象之后,让控制器依赖于该抽象。这简化了测试,因为控制器不再依赖于具体类型。
    猜你喜欢
    • 1970-01-01
    • 2021-02-13
    • 2019-02-18
    • 2011-04-17
    • 1970-01-01
    • 2011-02-22
    • 2017-01-09
    • 2022-08-22
    • 1970-01-01
    相关资源
    最近更新 更多