C# DB ConnectionState=Open,但context.SaveChanges抛出;“连接断开”;例外

C# DB ConnectionState=Open,但context.SaveChanges抛出;“连接断开”;例外,c#,database-connection,entity-framework-5,sql-server-2014,C#,Database Connection,Entity Framework 5,Sql Server 2014,在我的服务中,我有一个后台线程,它尽最大努力保存特定实体类型的对象流。代码大致如下: while (AllowRun) { try { using (DbContext context = GetNewDbContext()) { while (AllowRun && context.GetConn

在我的服务中,我有一个后台线程,它尽最大努力保存特定实体类型的对象流。代码大致如下:

        while (AllowRun)
        {
            try
            {
                using (DbContext context = GetNewDbContext())
                {
                    while (AllowRun && context.GetConnection().State == ConnectionState.Open)
                    {
                        TEntity entity = null;
                        try
                        {
                            while (pendingLogs.Count > 0)
                            {
                                lock (pendingLogs)
                                {
                                    entity = null;
                                    if (pendingLogs.Count > 0)
                                    {
                                        entity = pendingLogs[0];
                                        pendingLogs.RemoveAt(0);
                                    }
                                }

                                if (entity != null)
                                {
                                    context.Entities.Add(entity);
                                }
                            }
                            context.SaveChanges();
                        }
                        catch (Exception e)
                        {
                            // (1)
                            // Log exception and continue execution
                        }
                    }
                }
            }
            catch (Exception e)
            {
               // Log context initialization failure and continue execution
            }
        }
(这大部分是实际的代码,我省略了一些不相关的部分,它们试图将弹出的对象保存在内存中,直到我们能够在
(1)
块捕获异常时再次将内容保存到DB)

所以,本质上,有一个无休止的循环,试图从一些列表中读取项目并将其保存到数据库中。如果我们检测到数据库连接由于某种原因失败,它只会尝试重新打开它并继续。问题是,有时(到目前为止我还没有弄清楚如何复制它),调用
context.SaveChanges()
时,上面的代码开始产生以下异常(在
(1)
块中捕获):

错误会被记录,但当执行返回到
context.GetConnection().State==ConnectionState.Open
检查时,它的计算结果为true。因此,当上下文报告其数据库连接已打开时,我们处于一种状态,但我们无法对该上下文运行查询。重新启动服务将消除该问题(以及在调试器中使用
AllowRun
变量来强制重新创建上下文)。所以问题是,因为我不能信任上下文的连接状态,我如何验证我可以对DB运行查询


还有,有没有一种干净的方法来判断连接是否处于“健康”状态?我的意思是,
EntityException
本身并不是我应该重置连接的指示,只有当其内部异常是带有特定消息的
InvalidOperationException
时,才是重置它的时候了。但是,现在我想当ConnectionState指示一切正常,但我无法查询DB时,还会出现其他情况。我能主动抓住它们,而不是等到它开始咬我吗?

日志频率是多少

如果此循环花费的时间超过连接超时,则在执行savechanges时连接将关闭

while (pendingLogs.Count > 0)
{
     lock (pendingLogs)
     {
          entity = null;
          if (pendingLogs.Count > 0)
          {
             entity = pendingLogs[0];
             pendingLogs.RemoveAt(0);
          }
     }

     if (entity != null)
     {
          context.Entities.Add(entity);
     }
}
context.SaveChanges();

根据我在类似服务上的工作经验,直到使用块的结束时才会进行垃圾收集

如果有很多挂起的日志要写,这可能会占用大量内存,但我猜这可能会耗尽dbConnection池

您可以使用RedGate ANTS或类似工具分析内存使用情况,并使用此StackOverflow问题中的以下脚本检查打开的数据库连接:

)

我认为尽可能经常地释放上下文是一种好的做法,以便让GC更改清理,这样您就可以将循环重写为:

while (AllowRun)
    {
        try
        {
            while (pendingLogs.Count > 0)
            {
                using (DbContext context = GetNewDbContext())
                {
                    while (AllowRun && context.GetConnection().State == ConnectionState.Open)
                    {
                        TEntity entity = null;
                        try
                        {

                            lock (pendingLogs)
                            {
                                entity = null;
                                if (pendingLogs.Count > 0)
                                {
                                    entity = pendingLogs[0];
                                    pendingLogs.RemoveAt(0);
                                }
                            }

                            if (entity != null)
                            {
                                context.Entities.Add(entity);
                                context.SaveChanges();
                            }
                        }                        
                        catch (Exception e)
                        {
                            // (1)
                            // Log exception and continue execution
                        }
                    }
                }
            }
        }
        catch (Exception e)
        {
           // Log context initialization failure and continue execution
        }
    }

我建议浏览以下url: 当sql查询运行时间过长时,通常会引发超时过期

听起来好像SQL作业正在运行,备份?这可能是锁定表或重新启动服务


能否提供详细的堆栈跟踪?似乎是某个事务打乱了正在进行的EF连接,或者遇到了连接超时问题(如果应用分布式事务,则可能来自MSDTC)。最有可能的是,您忽略的非相关部分是真正相关的。总之,在该操作的生存期内不需要保持连接。使用连接的唯一方法是SaveChanges调用(如果示例中的pendingLogs为空,则重复调用)。只需在pendingLogs不为空时创建新上下文(连接将最常从池中返回)并在保存更改后进行处置(连接将返回到池)。还要检查您的事务是否升级为分布式以及原因(可能您在一个事务中调用多个数据库等)。为什么您首先会得到一个具有开放连接的上下文?@TetsuyaYamamoto,我会在日志中找到完整的堆栈跟踪,这个问题不会经常发生(我看过两次,包括让我写这篇文章的一个案例)。@GertArnold,这是一个有趣的问题。出于某种原因,我们的上下文创建方法总是执行
ctx=new-DbContext(…);ctx.GetConnection().Open()
。为什么这样做,我只能猜测:代码比我早,但我猜测我们可以在一个地方处理连接故障。“连接超时”是建立连接的时间限制。一旦建立连接,保持连接打开就不会超时。我错了吗?至少我从未见过建立的SQL连接超时(当然是在查询超时之外)。
SELECT 
    DB_NAME(dbid) as DBName, 
    COUNT(dbid) as NumberOfConnections,
    loginame as LoginName
FROM
    sys.sysprocesses
WHERE 
    dbid > 0
GROUP BY 
    dbid, loginame
while (AllowRun)
    {
        try
        {
            while (pendingLogs.Count > 0)
            {
                using (DbContext context = GetNewDbContext())
                {
                    while (AllowRun && context.GetConnection().State == ConnectionState.Open)
                    {
                        TEntity entity = null;
                        try
                        {

                            lock (pendingLogs)
                            {
                                entity = null;
                                if (pendingLogs.Count > 0)
                                {
                                    entity = pendingLogs[0];
                                    pendingLogs.RemoveAt(0);
                                }
                            }

                            if (entity != null)
                            {
                                context.Entities.Add(entity);
                                context.SaveChanges();
                            }
                        }                        
                        catch (Exception e)
                        {
                            // (1)
                            // Log exception and continue execution
                        }
                    }
                }
            }
        }
        catch (Exception e)
        {
           // Log context initialization failure and continue execution
        }
    }