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

C# Context.SaveChanges()挂起,似乎是由于实体框架中的嵌套事务和FK约束造成的,c#,.net,entity-framework,transactions,C#,.net,Entity Framework,Transactions,我遇到了一个问题,导致我的申请外出吃午饭。我已经隔离了这种情况,在父实体上进行了一次琐碎的更新(对键或索引没有影响),该更新被包装在TransactionScope中,之后立即在上下文上运行SaveChanges()。不久之后,与更新的父对象相关的子实体被插入到新的上下文实例中,该实例被包装在内部TransactionScope中。为子线程运行SaveChanges()时,线程将阻塞,直到达到SqlCommand.CommandTimeout并回滚事务。下面是代码和模型 我已经在这里展平了表示的

我遇到了一个问题,导致我的申请外出吃午饭。我已经隔离了这种情况,在父实体上进行了一次琐碎的更新(对键或索引没有影响),该更新被包装在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();
        }
    }
}
父模型

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; }
}
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; }
}
子配置

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);
    }
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);
    }
}
public类childMap:EntityTypeConfiguration
{
公共儿童地图()
{
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.numer);
}
}
SQL SPID信息


您遇到了死锁

您有两个不同的数据库事务(由于
RequiresNew
选项,内部事务作用域创建了一个新事务。您的第一个事务正在锁定第二个事务所需的某些数据库资源。第二个事务被数据库阻止,并且由于第一个事务在第二个事务完成之前无法完成,因此锁永远不会解除。


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

我们最终修改了体系结构,将一个空的虚拟方法添加到所有作业类派生的基类中。在作业和主事务完成后,我们触发在第二个事务中,作业的“执行后”方法可能会被重写,从而使作业有机会保留不依赖于第一个事务的数据