【问题标题】:EF6 dbContext SaveChanges: how to catch rollback?EF6 dbContext SaveChanges:如何捕获回滚?
【发布时间】:2014-06-09 06:01:54
【问题描述】:

在我调用 SaveChanges() 后,数据库返回新记录的 ID。每次我将一个新对象传递给我的 Add 方法时,ID 都会增加,所以我知道数据库(Azure)在循环中。但实际上没有任何东西被插入到数据库中,因为有一个回滚。除了做 .Find(newId) 和测试 null,这意味着另一次访问数据库之外,还有什么办法可以捕捉到这个?

发生这种情况是因为我无意中创建了一个需要插入相关表的关系。让我烦恼的是交易默默地失败了。如果我一直在使用 ADO.NET 和参数化存储过程,它会抛出异常并返回一些有用的东西。

我是 EF / Code First 的新手,并且正在从事一个新建项目,因此可以先使用代码构建数据库。到目前为止,这是一次冒险。

更新:根据 Aron 的要求提供代码。此外,更多的背景。我使用的是 NLogMvc,它有自己的上下文将消息写入数据库。 Aron 提到的分布式事务可能存在问题。无论如何,我都对解决方案进行了重组,删除了 NLogMvc,并转移到了 Ninject.Extensions.Loggings.nlog2

我知道数据库上发生的事情是我未能清理一些插入相关表所需的旧代码。 SQL Server 在回滚事务时会抛出错误。

问题的最终更新:我对可能导致问题的所有假设都是错误的。结果我无意中在存储库构造函数中创建了一个 TransactionScope 实例。有关详细信息,请参阅我对我自己的问题的回答(如下)。

代码:

        public int AddUpdateSlideshow(SlideshowDto slideshow)
    {
        if (!_user.IsInRole("Admin")) return -1;
        bool isNewShow = false;
        var now = DateTime.Now;
        try
        {
            using (var context = new ApplicationDbContext())
            {
                context.Database.Log = s => _logger.Info(s);

                int slideshowId;
                if (slideshow.SlideshowId == 0)
                {
                    isNewShow = true;
                    var newSlideshow = new Slideshow
                    {
                        Name = slideshow.Name,
                        Description = slideshow.Description,
                        DateAdded = now,
                        AddedBy = _userName,
                        DateLastUpdated = now,
                        UpdatedBy = _userName,
                        IsActive = slideshow.IsActive
                    };

                    context.Slideshows.Add(newSlideshow);

                    context.SaveChanges();

                    slideshowId = newSlideshow.SlideshowId;
                }
                else
                {
                    Slideshow dbUpdate =
                        context.Slideshows.Find(
                            slideshow.SlideshowId);
                    if (dbUpdate != null)
                    {
                        slideshowId = slideshow.SlideshowId;
                        dbUpdate.Name = slideshow.Name;
                        dbUpdate.Description = slideshow.Description;
                        dbUpdate.IsActive = slideshow.IsActive;
                        dbUpdate.DateLastUpdated = now;
                        dbUpdate.UpdatedBy = _userName;
                        context.SaveChanges();

                        // Number of objects written to the database.
                        // Zero means the update failed.
                        int result = context.SaveChanges();
                        slideshowId = result == 0 ? result : slideshow.SlideshowId;
                    }
                    else
                    {
                        var errorMessage = string.Format(
                            "The {0} method in {1} threw an ArgumentNullException exception. The user was {2}.",
                            MethodBase.GetCurrentMethod().Name, _className, _userName);
                        _logger.Error(errorMessage);

                        throw new ArgumentNullException("slideshow");
                    }
                }

                // The database will return new id value BUT
                // if for some reason the transaction fails the
                // changes will be rolled back and we won't have the
                // new record. So we need to confirm that we do in
                // fact have one.
                if (isNewShow)
                {
                    var newShow = context.Slideshows.Find(slideshowId);
                    if (newShow == null)
                    {
                        slideshowId = 0;
                    }                        
                }

                return slideshowId;
            }
        }
        catch (Exception ex)
        {
            var errorMessage = string.Format("The {0} method in {1} threw an exception. The user was {2}.",
                MethodBase.GetCurrentMethod().Name, _className, _userName);
            _logger.Error(errorMessage, ex);

            return -1;
        }
    }

【问题讨论】:

  • 请输入代码。听起来您正在使用一个事务,它自动升级为分布式事务,但您从未提交过。 sscce.org
  • 无法从我当前的位置访问我的代码,但今晚会添加它。但是,当我知道要访问多个表时,我使用了事务,但在这种情况下没有这样做;我认为 EF 在幕后为我做到了这一点。
  • 不,它没有。 EF 的设计很合理,您认为如果不是这样,它会像现在这样受欢迎吗?答案就在您的源代码中。
  • 引用这个 StackOverflow 答案(272 次赞成)“大多数情况下,使用实体框架 SaveChanges() 就足够了。这会创建一个事务,或加入任何环境事务,并完成所有必要的工作那笔交易。” stackoverflow.com/questions/815586/…
  • 最重要的是它关闭了交易,如果它未能关闭交易,它会在异常情况下通知您。又名理智。

标签: .net entity-framework dbcontext


【解决方案1】:

最终解决了这个问题,方法是构建一个对其他实体按预期工作的存储库,然后与日志文件进行一些 a-b 比较。

在插入失败的情况下,在存储库构造函数中,我为私有成员变量创建了一个 TransactionScope 实例。我打算将事务用于其他用途,但是,正如您从我的代码中看到的那样,我确实没有将它用于失败的插入方法。

我没有使用有效的插入方法在存储库的构造函数中创建 TransactionScope 实例。

我对这两个事务都使用了新的 EF6 日志记录工具。注意 SQL EF 发送到数据库的区别:

插入失败: 插入 [dbo].[Media_Slideshow]([Name], [Description], [DateAdded], [AddedBy], blah blah blah 2014 年 6 月 8 日 12:37:24 PM -05:00 关闭连接 幻灯片 ID 为 3

插入成功: 开始交易于 6/8/2014 12:41:35 PM -05:00 插入 [dbo].[Blog]([Title], [BlogContent], [DateAdded], [AddedBy], blah, blah, blah 在 2014 年 6 月 8 日 12:41:35 PM -05:00 提交的交易 2014 年 6 月 8 日 12:41:35 PM -05:00 关闭连接 博客 ID 为 5

因此,由于我目前不理解并且没有时间调查的原因,在存储库构造函数中实例化 TransactionScope 导致插入失败。

正如我的问题所述: 1. 我试图插入的对象没有导航属性。 2. 我没有(故意)在失败的方法上使用 TransactionScope。 3. 我没有(故意)在成功的方法上使用 TransactionScope。 4. SaveChanges() 返回数据库为两次插入创建的 id,但在第一种情况下,数据库中没有插入任何内容。 5. 没有其他上下文打开。 6、失败的方法没有抛出异常;在控制器尝试使用新 ID 检索记录之前,一切看起来都很美好。我能够诊断此问题的唯一方法是使用记录器。

通过查看日志可以清楚地观察到,EF 确实在幕后创建了一个事务,在插入失败的情况下,我以某种方式通过实例化 TransactionScope 搞砸了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-29
    • 1970-01-01
    相关资源
    最近更新 更多