自定义连接开启/关闭DbContext

7

默认情况下,EF Core 对于每个查询都会打开和关闭一个 DbConnection,除非您传入已经打开的连接。

我有许多小查询,所以我想在每次重复使用该连接的同时,将连接保持打开状态,每次保持五秒钟。(上面链接的问题解决方案将连接保持打开状态直到整个DBContext的生命周期结束)

忽略锁定/并发问题,在DbContext 中我可以在哪里注入自定义的连接解析/打开逻辑?类似于以下内容:

before executing query:
   if connection is not open
      open
      set timer to fire close request in five seconds
   take lock on connection (to prevent closing)
      execute query
   release lock

1
我认为底层的数据库驱动程序通常提供连接池,这应该可以实现重用连接。此外,请查看EF Core中的上下文池:https://neelbhatt.com/2018/02/27/use-dbcontextpooling-to-improve-the-performance-net-core-2-1-feature/amp/ - weichch
肯定有一种简单的方法来控制EF Core的连接池 - 假设这是你想要的。 (或者你想要为每个查询使用不同的连接吗?) - timur
抱歉,原来的问题描述有误。我的意思是询问关于基于计时器保持连接“打开”以进行多个查询,但不是在整个上下文的生命周期内保持连接。 - Arithmomaniac
我想知道是否可以使用DBConnecitonInterceptor... - Arithmomaniac
1
检查你的修改,对我来说看起来你可以从信号量和内部计时器中获益。如果我们仔细思考一下,这与连接池非常相似。你考虑过连接池吗? - Cleptus
我正在寻找在计时器上保持单个连接“打开”的方法;连接池不能保证这一点。 - Arithmomaniac
2个回答

8

这是标准的交易方式。 您可以将多个查询组合在一起。

using (var context = new SchoolContext())
{
    var std = new Student()
    {
        FirstName = "Bill",
        LastName = "Gates"
    };
    context.Students.Add(std);

    // or
    // context.Add<Student>(std);

    context.SaveChanges();

   std = context.Students.First<Student>(); 
    std.FirstName = "Steve";
    context.SaveChanges();
}

EF Core 可以使用相同的连接、不同的连接或基于连接池。EF Core 具有连接和断开模式的事务处理。我认为这适合您。

在断开的情况下保存数据与连接的情况下略有不同。在断开的情况下,DbContext 不知道断开的实体,因为实体是在当前 DbContext 实例的范围之外添加或修改的。所以,您需要将断开的实体附加到具有适当 EntityState 的上下文中,以便执行数据库的增删改操作。

以下图示说明了断开情况下的 CUD(Create、Update、Delete)操作:

根据上图,未被 DbContext 跟踪的断开实体(即实体)需要用适当的 EntityState 附加到 DbContext 上。例如,对于新实体,使用添加状态;对于已编辑的实体,使用修改状态;对于已删除的实体,使用已删除状态。当调用 SaveChanges() 方法时,将在数据库中生成 INSERT、UPDATE 或 DELETE 命令。

以下步骤必须按顺序执行,才能在 EF Core 中使用断开场景插入、更新或删除记录:

使用适当的 EntityState(例如 Added、Modified 或 Deleted)将实体附加到 DbContext 调用 SaveChanges() 方法 以下示例演示使用上述步骤向数据库插入新记录:

//Disconnected entity
var std = new Student(){ Name = "Bill" };

using (var context = new SchoolContext())
{
    //1. Attach an entity to context with Added EntityState
    context.Add<Student>(std);

    //or the followings are also valid
    // context.Students.Add(std);
    // context.Entry<Student>(std).State = EntityState.Added;
    // context.Attach<Student>(std);

    //2. Calling SaveChanges to insert a new record into Students table
    context.SaveChanges();
}

在上面的例子中,std是一个已断开连接的Student实体实例。context.Add() 方法将一个Student实体附加到具有Added状态的上下文中。SaveChanges()方法构建并执行以下INSERT语句:
exec sp_executesql N'SET NOCOUNT ON; https://www.entityframeworktutorial.net/efcore/saving-data-in-disconnected-scenario-in-ef-core.aspx 这些都是重要的方法。
public DbContext(DbConnection existingConnection, bool contextOwnsConnection)
public DbContext(DbConnection existingConnection, DbCompiledModel model, bool contextOwnsConnection)

EF6和未来版本中的行为

对于EF6和未来版本,我们采取的方法是,如果调用代码选择通过调用context.Database.Connection.Open()打开连接,则它肯定有一个充分的理由这样做。因此框架将假定调用代码希望控制连接的开启和关闭,并且不再自动关闭连接。

注意

这可能会导致连接长时间处于打开状态,因此请谨慎使用。

我们还更新了代码,使得ObjectContext.Connection.State正确地跟踪底层连接的状态。

  using System;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Core.EntityClient;
using System.Data.Entity.Infrastructure;

namespace ConnectionManagementExamples
{
    internal class DatabaseOpenConnectionBehaviorEF6
    {
        public static void DatabaseOpenConnectionBehavior()
        {
            using (var context = new BloggingContext())
            {
                // At this point the underlying store connection is closed

                context.Database.Connection.Open();

                // Now the underlying store connection is open and the
                // ObjectContext.Connection.State correctly reports open too

                var blog = new Blog { /* Blog’s properties */ };
                context.Blogs.Add(blog);
                context.SaveChanges();

                // The underlying store connection remains open for the next operation  

                blog = new Blog { /* Blog’s properties */ };
                context.Blogs.Add(blog);
                context.SaveChanges();

                // The underlying store connection is still open

           } // The context is disposed – so now the underlying store connection is closed
        }
    }
}

https://learn.microsoft.com/en-us/ef/ef6/fundamentals/connection-management?redirectedfrom=MSDN


2
我认为 OP 是在寻找 EF Core 的解决方案,对吗? - timur
1
@timur,我相信我在一开始就写了他可以结合使用方法。我进行了编辑以使其更有意义。 - Jin Thakur
这可能会导致连接长时间开放,因此请小心使用。这就是我试图通过定时打开和关闭来解决的问题。抱歉我的原始帖子措辞不当。 - Arithmomaniac
这不是我需要的解决方案;请查看我对问题的更正。但我已经点赞了它。 - Arithmomaniac
https://dev59.com/Rmw15IYBdhLWcg3wO5IH - Zubaer Naseem
@ZubaerNaseem 是的,但我不想它在上下文的生命周期内一直开着。 - Arithmomaniac

1
在ADO.NET中,您可以配置连接池以满足您的要求。
为每个唯一的连接字符串创建一个连接池。创建池时,多个连接对象将被创建并添加到池中,以满足最小池大小的要求。在需要时,连接将被添加到池中,最多可达到指定的最大池大小(默认值为100)。当连接关闭或释放时,它们将被释放回池中。
连接池程序通过重新分配连接来满足连接请求,因为它们被释放回池中。如果已达到最大池大小并且没有可用的连接,则请求将被排队。然后,连接池程序尝试重新获取任何连接,直到超时时间到达(默认为15秒)。如果连接池程序无法在连接超时之前满足请求,则会抛出异常。
连接闲置约4-8分钟后,连接池程序将从池中删除连接,或者如果连接池程序检测到与服务器的连接已断开,则也会将其删除。请注意,只有在尝试与服务器通信后才能检测到已断开的连接。如果发现不再连接到服务器的连接,则将其标记为无效。仅当关闭或重新获取时,才会从连接池中删除无效连接。
如果SqlConnection超出范围,它将不会关闭。因此,必须通过调用Close或Dispose来显式关闭连接。Close和Dispose在功能上是等效的。如果Pooling连接池值设置为true或yes,则底层连接将返回到连接池中。另一方面,如果Pooling设置为false或no,则实际上会关闭与服务器的底层连接。
  using (SqlConnection connection = new SqlConnection("Integrated Security=SSPI;Initial Catalog=Northwind"))  
    {  
        connection.Open();
        // Pool A is created.  
    }  

using (SqlConnection connection = new SqlConnection("Integrated Security=SSPI;Initial Catalog=pubs"))  
    {  
        connection.Open();
        // Pool B is created because the connection strings differ.  
    }  

using (SqlConnection connection = new SqlConnection("Integrated Security=SSPI;Initial Catalog=Northwind"))  
    {  
        connection.Open();
        // The connection string matches pool A.  
    } 

您可以在this文章中阅读更多详细信息。 EF.Core和连接池this

相关的SO问题。Entity Framework和连接池


1
DBContextPooling是在不同请求之间重用DbContexts;我想在单个DbContext中重用_connections_。 - Arithmomaniac
一个 DbContext 只能在已打开的连接上运行。 - Support Ukraine
此外,自己实现连接池可能会导致性能问题,例如在 SqlServer 上耗尽连接。 - Support Ukraine
这(ADO连接池)是大多数人的正确解决方案:将其留给提供者,如果您确实需要,则可以调整连接池设置。编写自己的池和计时器通常既浪费时间又容易出错。使用数据库的监视工具查看何时真正打开和关闭连接,通常您会发现EF尽可能地重用db连接。 - Rory

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接