Context.SaveChanges()挂起,似乎是由于实体框架中的嵌套事务和FK约束

Rus*_*ght 2 .net c# entity-framework transactions

我遇到了导致我的应用程序出去吃饭的问题.我已经将这种情况隔离开来,涉及父实体的一个简单的更新(对键或索引没有影响),它包含在TransactionScope中,SaveChanges()在之后的上下文中运行.不久之后,与更新的父项相关的子实体被插入到一个新的上下文实例中,该实例被包装在内部TransactionScope中.为子进程运行SaveChanges()时,线程将一直阻塞,直到达到SqlCommand.CommandTimeout并回滚事务.下面是代码和模型.

我在这里展示了用于演示的体系结构,但外部事务由一个处理队列并旋转各种类的作业管理器启动.内部事务逻辑实际上位于一个帮助程序类中,其目的是允许作业类进行更新,如果外部事务被撤消,则不会回滚.

我的拙劣猜测是内部SaveChanges()无法完成,因为外部SaveChanges()和事务尚未完成.这会使父级处于不确定状态,因此无法验证约束.虽然,我对System.Transaction和EF的理解仍然相当绿色.

希望是避免建筑改变,或至少保持超级小.我们目前正在运行.NET4和EF5.建议?非常感谢提前.

编辑: 修复了标题歧义并添加了SQL诊断输出的屏幕截图.

using System;
using System.Linq;
using System.Transactions;
using SSI.Server.DataContext;
using SSI.Server.DataModel;

namespace MyBox
{
    class MyBox
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Firing up context...");
            var context = new SsiContext();

            if (!context.parent.Any(p => p.Name == "Mom"))
            {
                context.parent.Add(new parent()
                {
                    Name = "Mom"
                });
                context.SaveChanges();
            }

            var tranOpts = new TransactionOptions();
            tranOpts.IsolationLevel = IsolationLevel.ReadCommitted;
            tranOpts.Timeout = TimeSpan.FromSeconds(300);

            using (var outerTran = new TransactionScope(TransactionScopeOption.Required, tranOpts))
            {
                var mom = context.parent.FirstOrDefault(p => p.Name == "Mom");
                if(mom == null) { throw new InvalidOperationException("Where's momma!?"); }

                Console.WriteLine("Setting parent number and saving in first transaction...");
                mom.Number = 1980;
                context.SaveChanges();

                using (var innerTran = new TransactionScope(TransactionScopeOption.RequiresNew, tranOpts))
                {
                    Console.WriteLine("Second transaction create.  Spinning up new context...");
                    var innerContext = new SsiContext();

                    Console.WriteLine("Creating new child, linking to parent, saving, and closing inner transaction...");
                    innerContext.child.Add(new child() { ParentId = mom.ParentId });

                    // Execution hangs here
                    innerContext.SaveChanges();
                    innerTran.Complete();
                }

                Console.WriteLine("Completing outer transaction...");
                outerTran.Complete();
            }

            Console.WriteLine("Done");
            Console.ReadKey();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

父模型

public class parent
{
    public string ParentId { get; set; }
    public string Name { get; set; }
    public int Number { get; set; }
    protected virtual ICollection<child> children { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

父配置

public class parentMap : EntityTypeConfiguration<parent>
{
    public parentMap()
    {
        this.HasKey(t => t.ParentId);
        this.Property(t => t.ParentId).IsRequired().HasMaxLength(40);
        this.Property(t => t.Name).HasMaxLength(20);
        this.Property(t => t.Number);
    }
Run Code Online (Sandbox Code Playgroud)

}

儿童模特

public class child
{
    public string ChildId { get; set; }
    public string ParentId { get; set; }
    public string Name { get; set; }
    public int Number { get; set; }
    protected virtual parent parent { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

儿童配置

public class childMap : EntityTypeConfiguration<child>
{
    public childMap()
    {
        this.HasKey(t => t.ChildId);
        this.Property(t => t.ChildId).IsRequired().HasMaxLength(40);
        this.Property(t => t.ParentId).IsRequired().HasMaxLength(40);
        this.Property(t => t.Name).HasMaxLength(20);
        this.Property(t => t.Nubmer);
    }
}
Run Code Online (Sandbox Code Playgroud)

SQL SPID信息

在此输入图像描述

zmb*_*mbq 6

您遇到了僵局.

您有两个不同的数据库事务(由于该RequiresNew选项,内部事务范围创建了一个新事务.您的第一个事务是锁定第二个事务所需的某些数据库资源.第二个事务被数据库阻止,并且因为第一个事务不能完成直到第二个完成,锁永远不会释放.

SQL服务器可以检测到死锁,但在这种情况下它不能 - 它无法知道第一个事务在完成第二个事务之前不会完成.