回滚事务时出现异常 - 连接已关闭?

14
使用 Entity Framework 6.0.0,当关闭事务时出现异常。 因为表存在并发更改的问题,所以我将其包装在事务中,现在回滚时会出现异常。
代码如下:
public LockInfo getSharedLock(string jobid)
{
    using (var myDbContext = new MyDbContext())
    {
        using (var transaction = myDbContext.Database.BeginTransaction())
        {
            try
            {
                this.logger.log("Attempting to get shared lock for {0}", jobid);

                var mylocks =
                    myDbContext.joblocks.Where(j => j.customerid == this.userContext.customerid)
                        .Where(j => j.jobid == jobid)
                        .Where(j => j.operatorid == this.userContext.operatorid);

                var exclusiveLock = mylocks.FirstOrDefault(
                    j => j.lockstatus == LockInfo.LockState.Exclusive);
                if (exclusiveLock != null)
                {
                    this.logger.log("{0} already had exclusive lock, ignoring", jobid);
                    return LockInfo.populate(exclusiveLock);
                }

                var sharedLock = mylocks.FirstOrDefault(
                    j => j.lockstatus == LockInfo.LockState.Shared);
                if (sharedLock != null)
                {
                    this.logger.log("{0} already had shared lock, ignoring", jobid));
                    sharedLock.lockdt = DateTime.Now;
                    myDbContext.SaveChanges();

                    return LockInfo.populate(sharedLock);
                }

                var joblock = new joblock
                {
                    customerid = this.userContext.customerid,
                    operatorid = this.userContext.operatorid,
                    jobid = jobid,
                    lockstatus = LockInfo.LockState.Shared,
                    sharedLock.lockdt = DateTime.Now
                };

                myDbContext.joblocks.Add(joblock);
                myDbContext.SaveChanges();
                transaction.Commit();

                this.logger.log("Obtained shared lock for {0}", jobid);
                return LockInfo.populate(joblock);
            }
            catch (Exception ex)
            {
                transaction.Rollback();
                this.logger.logException(ex, "Exception in getSharedLock(\"{0}\")", jobid);
                throw;
            }
        }
    }
}

您可以在上面的代码中看到日志记录。我们还在数据库中启用了日志记录。日志跟踪:

===================
NORMAL    TicketLockController.getLock("AK2015818002WL")
===================
SQL    Opened connection at 9/22/2015 2:47:49 PM -05:00
===================
SQL    Started transaction at 9/22/2015 2:47:49 PM -05:00
===================
NORMAL    Attempting to get shared lock for AK2015818002WL
===================
SQL    SELECT TOP (1) [Extent1].[customerid] AS [customerid]
    ,[Extent1].[jobid] AS [jobid]
    ,[Extent1].[lockdtdate] AS [lockdtdate]
    ,[Extent1].[lockdttime] AS [lockdttime]
    ,[Extent1].[operatorid] AS [operatorid]
    ,[Extent1].[lockstatus] AS [lockstatus]
    ,[Extent1].[changes] AS [changes]
FROM [dbo].[joblock] AS [Extent1]
WHERE ([Extent1].[customerid] = 'TESTTK')
    AND ([Extent1].[jobid] = 'AK2015818002WL')
    AND ([Extent1].[operatorid] = 'ADMIN')
    AND (N'Exclusive' = [Extent1].[lockstatus])
===================
SQL    SELECT TOP (1) [Extent1].[customerid] AS [customerid]
    ,[Extent1].[jobid] AS [jobid]
    ,[Extent1].[lockdtdate] AS [lockdtdate]
    ,[Extent1].[lockdttime] AS [lockdttime]
    ,[Extent1].[operatorid] AS [operatorid]
    ,[Extent1].[lockstatus] AS [lockstatus]
    ,[Extent1].[changes] AS [changes]
FROM [dbo].[joblock] AS [Extent1]
WHERE ([Extent1].[customerid] = 'TESTTK')
    AND ([Extent1].[jobid] = 'AK2015818002WL')
    AND ([Extent1].[operatorid] = 'ADMIN')
    AND (N'Shared' = [Extent1].[lockstatus])
===================
SQL    INSERT [dbo].[joblock] (
    [customerid]
    ,[jobid]
    ,[lockdtdate]
    ,[lockdttime]
    ,[operatorid]
    ,[lockstatus]
    ,[changes]
    )
VALUES (
    @0
    ,@1
    ,@2
    ,@3
    ,@4
    ,@5
    ,NULL
    )
===================
SQL    Closed connection at 9/22/2015 2:47:50 PM -05:00
===================
EXCEPTION    Unhandled exception caught: The underlying provider failed on Rollback.
===================
EXCEPTION    Inner Exception: Value cannot be null.
Parameter name: connection

两个select语句都成功,但某些原因导致插入失败并抛出异常。而且由于某些原因,在Rollback()执行之前连接就关闭了。

你有任何想法是我做错了什么吗?

==== 添加堆栈跟踪信息 ====

外部异常的堆栈跟踪信息:

   at System.Data.Entity.Core.EntityClient.EntityTransaction.Rollback()
   at korterra.kt_api.Shared.TicketLockWrangler.getSharedLock(String jobid)
   at korterra.kt_ws.ApiControllers.Shared.TicketLockController.getSharedLock(TicketLockDTO ticketLockDTO)
   at lambda_method(Closure , Object , Object[] )
   at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass10.<GetExecutor>b__9(Object instance, Object[] methodParameters)
   at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.Execute(Object instance, Object[] arguments)
   at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ExecuteAsync(HttpControllerContext controllerContext, IDictionary`2 arguments, CancellationToken cancellationToken)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Web.Http.Tracing.ITraceWriterExtensions.<TraceBeginEndAsyncCore>d__18`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Web.Http.Controllers.ApiControllerActionInvoker.<InvokeActionAsyncCore>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Web.Http.Tracing.ITraceWriterExtensions.<TraceBeginEndAsyncCore>d__18`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Web.Http.Controllers.ActionFilterResult.<ExecuteAsync>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Web.Http.Filters.AuthorizationFilterAttribute.<ExecuteAuthorizationFilterAsyncCore>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Web.Http.Controllers.ExceptionFilterResult.<ExecuteAsync>d__0.MoveNext()

内部异常的堆栈跟踪:

   at System.Data.Entity.Utilities.Check.NotNull[T](T value, String parameterName)
   at System.Data.Entity.Infrastructure.Interception.DbTransactionInterceptionContext.WithConnection(DbConnection connection)
   at System.Data.Entity.Infrastructure.Interception.DbTransactionDispatcher.Rollback(DbTransaction transaction, DbInterceptionContext interceptionContext)
   at System.Data.Entity.Core.EntityClient.EntityTransaction.Rollback()

请发布完整的异常ToString。您的错误日志记录不足。 - usr
另外,在许多情况下,您并没有提交交易。这真的是有意为之吗? - usr
我无法按需重现此问题。这是在我们的 QA 环境中间歇性出现的东西。我从未在开发环境中看到过它。除了我已经发布的信息之外,我所拥有的唯一信息就是堆栈跟踪,它只是指示异常发生在我发布的代码中。 - Jeff Dege
我不会在不改变数据库的执行路径上进行提交。但也许我应该这样做。 - Jeff Dege
好的,我找不到错误。你迫切需要修订你的错误日志,以便它不会丢失太多信息。从堆栈跟踪中,我们可以看到这个问题发生的调用和行号。 - usr
显示剩余6条评论
1个回答

9
在讨论之后,我开始记录异常并尝试回滚 - 这揭示了错误。
事务发生了死锁:
Exception in getSharedLock("ticketnumber123456"): An error occurred while updating the entries. See the inner exception for details.

Inner Exception: An error occurred while updating the entries. See the inner exception for details.

Inner Exception: Transaction (Process ID 139) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

根据我所读的,当你被告知事务已成为死锁受害者时,它已经被回滚了。也许这就是我们遇到异常的原因?

解决方法似乎要么是识别出我们何时发生了死锁,不回滚,要么是不使用事务,在主键冲突时重试。


2
在这段代码中,你不应该调用Rollback。此外,这是一个EF的错误,它不应该崩溃。它应该使用一个干净的异常报告API使用错误或其他问题。你能否在EF存储库中打开一个GitHub问题? - usr
我目前正在尝试不使用事务,而是在基础记录更改时简单地重试 - 乐观锁定。 - Jeff Dege
@usr 能否详细解释一下为什么在这段代码中不应该“调用Rollback” - 这是什么意思?这是否是滥用Rollback功能的表现? - Markus Knappen Johansson
如果服务器检测到两个事务发生了死锁,它将选择其中一个进行终止。在这种情况下,它将回滚事务并向客户端发送错误信息。作为客户端,您不希望在此情况下回滚事务,因为该事务已经被回滚,尝试再次回滚将创建另一个错误。 - Jeff Dege

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