简体   繁体   English

带有 .NET Core 的 Dapper - 注入的 SqlConnection 生命周期/范围

[英]Dapper with .NET Core - injected SqlConnection lifetime/scope

I'm using .NET Core Dependency Injection to instantiate a SqlConnection object during the application startup, which I'm then planning to inject in my repository.我正在使用 .NET Core Dependency Injection 在应用程序启动期间实例化SqlConnection对象,然后我计划将其注入到我的存储库中。 This SqlConnection will be used by Dapper to read/write data from the database within my repository implementation. Dapper 将使用此SqlConnection从我的存储库实现中的数据库读取/写入数据。 I am going to use async calls with Dapper.我将使用 Dapper 的async调用。

The question is: should I inject the SqlConnection as transient or as a singleton?问题是:我应该将SqlConnection作为瞬态注入还是作为单例注入? Considering the fact that I want to use async my thought would be to use transient unless Dapper implements some isolation containers internally and my singleton's scope will still be wrapped within whatever the scope Dapper uses internally.考虑到我想使用async这一事实,我的想法是使用瞬态,除非 Dapper 在内部实现了一些隔离容器,并且我的单例范围仍将被包裹在 Dapper 内部使用的任何范围内。

Are there any recommendations/best practices regarding the lifetime of the SqlConnection object when working with Dapper?使用 Dapper 时,是否有关于 SqlConnection 对象生命周期的任何建议/最佳实践? Are there any caveats I might be missing?我可能会遗漏任何注意事项吗?

Thanks in advance.提前致谢。

If you provide SQL connection as singleton you won't be able to serve multiple requests at the same time unless you enable MARS, which also has it's limitations.如果您提供 SQL 连接作为单例,除非您启用 MARS,否则您将无法同时处理多个请求,这也有其局限性。 Best practice is to use transient SQL connection and ensure it is properly disposed.最佳做法是使用瞬态 SQL 连接并确保正确处置它。

In my applications I pass custom IDbConnectionFactory to repositories which is used to create connection inside using statement.在我的应用程序中,我将自定义IDbConnectionFactory传递给存储库,该存储库用于在using语句中创建连接。 In this case repository itself can be singleton to reduce allocations on heap.在这种情况下,存储库本身可以是单例的,以减少堆上的分配。

I agree with @Andrii Litvinov, both answer and comment.我同意@Andrii Litvinov 的回答和评论。

In this case I would go with approach of data-source specific connection factory.在这种情况下,我将采用数据源特定连接工厂的方法。

With same approach, I am mentioning different way - UnitOfWork.使用相同的方法,我提到了不同的方式 - UnitOfWork。

Refer DalSession and UnitOfWork from this answer.这个答案中参考DalSessionUnitOfWork This handles connection.这处理连接。
Refer BaseDal from this answer.这个答案中参考BaseDal This is my implementation of Repository (actually BaseRepository ).这是我的Repository实现(实际上是BaseRepository )。

  • UnitOfWork is injected as transient. UnitOfWork作为瞬态注入。
  • Multiple data sources could be handled by creating separate DalSession for each data source.可以通过为每个数据源创建单独的DalSession来处理多个数据源。
  • UnitOfWork is injected in BaseDal . UnitOfWork注入BaseDal

Are there any recommendations/best practices regarding the lifetime of the SqlConnection object when working with Dapper?使用 Dapper 时,是否有关于 SqlConnection 对象生命周期的任何建议/最佳实践?

One thing most of developers agree is that, connection should be as short lived as possible.大多数开发人员同意的一件事是,连接应该尽可能短。 I see two approaches here:我在这里看到两种方法:

  1. Connection per action.每个动作的连接。
    This of-course will be shortest life span of connection.这当然是最短的连接寿命。 You enclose connection in using block for each action.您在每个操作的using块中附上连接。 This is good approach as long as you do not want to group the actions.只要您不想将操作分组,这是一种很好的方法。 Even when you want to group the actions, you can use transaction in most of the cases.即使您想对操作进行分组,在大多数情况下也可以使用事务。
    Problem is when you want to group actions across multiple classes/methods.问题是当您想要跨多个类/方法对操作进行分组时。 You cannot use using block here.您不能在此处使用using块。 Solution is UnitOfWork as below.解决方案是 UnitOfWork,如下所示。
  2. Connection per Unit Of Work.每个工作单元的连接。
    Define your unit of work.定义您的工作单元。 This will be different per application.这将因应用程序而异。 In web application, "connection per request" is widely used approach.在 Web 应用程序中,“按请求连接”是广泛使用的方法。
    This makes more sense because generally there are (most of the time) group of actions we want to perform as a whole.这更有意义,因为通常(大多数情况下)我们希望作为一个整体执行一组操作。 This is explained in two links I provided above.我在上面提供的两个链接中对此进行了解释。
    Another advantage of this approach is that, application (that uses DAL) gets more control on how connection should be used.这种方法的另一个优点是,应用程序(使用 DAL)可以更好地控制如何使用连接。 And in my understanding, application knows better than DAL how connection should be used.在我的理解中,应用程序比 DAL 更了解应该如何使用连接。

Great question, and already two great answers.很好的问题,已经有两个很好的答案。 I was puzzled by this at first, and came up with the following solution to solve the problem, which encapsulates the repositories in a manager.一开始对这个很疑惑,想出了如下方案来解决这个问题,将repositories封装在一个manager中。 The manager itself is responsible for extracting the connection string and injecting it into the repositories.管理器本身负责提取连接字符串并将其注入存储库。

I've found this approach to make testing the repositories individually, say in a mock console app, much simpler, and I've have much luck following this pattern on several larger-scale project.我发现这种方法可以单独测试存储库,比如在模拟控制台应用程序中,更简单,而且我在几个大型项目中遵循这种模式很幸运。 Though I am admittedly not an expert at testing, dependency injection, or well anything really!虽然我承认我不是测试、依赖注入或任何其他方面的专家!

The main question I'm left asking myself, is whether the DbService should be a singleton or not.我要问自己的主要问题是 DbService 是否应该是单例。 My rationale was that, there wasn't much point constantly creating and destroying the various repositories encapsulated in DbService and since they are all stateless I didn't see much problem in allowing them to "live".我的理由是,不断创建和销毁封装在DbService的各种存储库并没有多大意义,而且由于它们都是无状态的,因此我认为允许它们“存活”并没有太大问题。 Though this could be entirely invalid logic.虽然这可能是完全无效的逻辑。

EDIT: Should you want a ready made solution check out my Dapper repository implementation on GitHub编辑:如果你想要一个现成的解决方案,请查看我在GitHub 上的Dapper 存储库实现

The repository manager is structured as follows:存储库管理器的结构如下:

/*
 * 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;
        }
    }
}

A sample repository would be structured as follows:示例存储库的结构如下:

/*
 * 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 
    }
}

Wiring it all up:全部接线:

/*
 * Startup.cs
 */
public IConfigurationRoot Configuration { get; }

public void ConfigureServices(IServiceCollection services)
{
    //...rest of services

    services.AddSingleton<IDbService, DbService>();

    //...rest of services
}

And finally, using it:最后,使用它:

public SomeController : Controller 
{
    IDbService dbService;

    public SomeController(IDbService dbService)
    {
        this.dbService = dbService;
    }

    public IActionResult Index()
    {
        return View(dbService.SomeRepo.List());
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM