【问题标题】:Dapper with .NET Core - injected SqlConnection lifetime/scopeDapper 与 .NET Core - 注入 SqlConnection 生命周期/范围
【发布时间】:2017-03-21 20:54:12
【问题描述】:

我在应用程序启动期间使用 .NET Core 依赖注入来实例化 SqlConnection 对象,然后我计划将其注入到我的存储库中。 Dapper 将使用这个SqlConnection 从我的存储库实现中的数据库读取/写入数据。我将使用 async 调用 Dapper。

问题是:我应该将SqlConnection 注入为瞬态还是单例?考虑到我想使用 async 的事实,我的想法是使用瞬态,除非 Dapper 在内部实现了一些隔离容器,并且我的单例范围仍将被包装在 Dapper 在内部使用的任何范围内。

在使用 Dapper 时,是否有关于 SqlConnection 对象生命周期的任何建议/最佳实践?有什么我可能遗漏的警告吗?

提前致谢。

【问题讨论】:

    标签: c# dependency-injection asp.net-core dapper


    【解决方案1】:

    如果您以单例方式提供 SQL 连接,除非您启用 MARS,否则您将无法同时处理多个请求,这也有其局限性。最佳做法是使用瞬时 SQL 连接并确保它被正确处理。

    在我的应用程序中,我将自定义 IDbConnectionFactory 传递给存储库,用于在 using 语句中创建连接。在这种情况下,存储库本身可以是单例的,以减少堆上的分配。

    【讨论】:

    • 我喜欢工厂方法,但是如何处理单个应用程序的多个数据源(数据库)的场景?为每个数据库创建一个子ISomeDbConnectionFactory 并将附加参数传递给工厂以确定需要创建哪个连接?或者更优雅的东西?
    • 我想同一个存储库将使用连接到单个数据源。在这种情况下,我将采用数据源特定连接工厂的方法。工厂的接口可以相同,只是DB特定的实现。
    • 即使启用 MARS,您也无法使用相同的 SqlConnection 处理多个并发请求。根据MSDN:“MARS 操作不是线程安全的”。
    • 好点@Steven!它支持并行执行。我认为可以同时使用相同的连接。但仍然可以异步启动多个操作。这是我尝试过的,也是 MARS 支持的。
    • 我不确定我是否遗漏了什么,你说,“在这种情况下,存储库本身可以是单例的,以减少堆上的分配”。但是,如果将瞬态服务注入单例服务,该单例服务会将瞬态服务提升为单例,不是吗?这样做,您将失去该临时服务的生命周期约束。简单来说,当你创建一个单例服务时,所有的依赖只为那个服务创建一次,然后那个服务被固定在内存中。因此,依赖的临时服务不会按预期重新创建。
    【解决方案2】:

    我同意@Andrii Litvinov 的回答和评论。

    在这种情况下,我会采用数据源特定连接工厂的方法。

    使用相同的方法,我提到不同的方法 - UnitOfWork。

    参考this 答案中的DalSessionUnitOfWork。这处理连接。
    参考BaseDal 来自this 的答案。这是我对Repository(实际上是BaseRepository)的实现。

    • UnitOfWork 作为瞬态注入。
    • 可以通过为每个数据源创建单独的DalSession 来处理多个数据源。
    • UnitOfWork 被注入到 BaseDal 中。

    在使用 Dapper 时,是否有关于 SqlConnection 对象生命周期的任何建议/最佳实践?

    大多数开发人员同意的一件事是,连接应该尽可能短。我在这里看到两种方法:

    1. 每个操作的连接。
      这当然是最短的连接寿命。您将每个操作的连接附在using 块中。只要您不想对操作进行分组,这是一种很好的方法。即使您想对操作进行分组,大多数情况下也可以使用事务。
      问题是当您想跨多个类/方法对操作进行分组时。您不能在此处使用 using 块。解决方案是 UnitOfWork,如下所示。
    2. 每个工作单元的连接。
      定义你的工作单元。每个应用程序都会有所不同。在 Web 应用程序中,“按请求连接”是一种广泛使用的方法。
      这更有意义,因为通常(大多数时候)我们想要作为一个整体执行一组动作。我在上面提供的两个链接中对此进行了解释。
      这种方法的另一个优点是,应用程序(使用 DAL)可以更好地控制连接的使用方式。在我的理解中,应用程序比 DAL 更了解应该如何使用连接。

    【讨论】:

      【解决方案3】:

      很好的问题,已经有两个很好的答案。起初我对此感到困惑,并想出了以下解决方案来解决问题,它将存储库封装在一个管理器中。管理器本身负责提取连接字符串并将其注入到存储库中。

      我发现这种方法可以单独测试存储库,比如在模拟控制台应用程序中,要简单得多,而且我很幸运在几个大型项目中遵循这种模式。尽管我承认我不是测试、依赖注入或其他方面的专家!

      我要问自己的主要问题是 DbService 是否应该是单例。我的理由是,不断创建和销毁封装在DbService 中的各种存储库并没有多大意义,而且由于它们都是无状态的,我认为允许它们“生存”没有太大问题。虽然这可能是完全无效的逻辑。

      编辑:如果您想要一个现成的解决方案,请查看我在 GitHub 上的 Dapper 存储库实现

      存储库管理器的结构如下:

      /*
       * Db Service
       */
      public interface IDbService
      {
          ISomeRepo SomeRepo { get; }
      }
      
      public class DbService : IDbService
      {
          readonly string connStr;
          ISomeRepo someRepo;
      
          public DbService(string connStr)
          {
              this.connStr = connStr;
          }
      
          public ISomeRepo SomeRepo
          {
              get
              {
                  if (someRepo == null)
                  {
                      someRepo = new SomeRepo(this.connStr);
                  }
      
                  return someRepo;
              }
          }
      }
      

      示例存储库的结构如下:

      /*
       * Mock Repo
       */
      public interface ISomeRepo
      {
          IEnumerable<SomeModel> List();
      }
      
      public class SomeRepo : ISomeRepo
      {
          readonly string connStr;
      
          public SomeRepo(string connStr)
          {
              this.connStr = connStr;
          }
      
          public IEnumerable<SomeModel> List()
          {
              //work to return list of SomeModel 
          }
      }
      

      连接起来:

      /*
       * Startup.cs
       */
      public IConfigurationRoot Configuration { get; }
      
      public void ConfigureServices(IServiceCollection services)
      {
          //...rest of services
      
          services.AddSingleton<IDbService, DbService>();
      
          //...rest of services
      }
      

      最后,使用它:

      public SomeController : Controller 
      {
          IDbService dbService;
      
          public SomeController(IDbService dbService)
          {
              this.dbService = dbService;
          }
      
          public IActionResult Index()
          {
              return View(dbService.SomeRepo.List());
          }
      }
      

      【讨论】:

      • 你是如何将连接字符串发送到服务的?
      • @VahidFarahmandian 您可以在使用函数注册时设置它。
      猜你喜欢
      • 2018-12-12
      • 1970-01-01
      • 2012-06-23
      • 1970-01-01
      • 2016-09-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多