EF4 Code First + SQL Server CE:以原子方式保存双向引用

Cri*_*scu 7 transactions sql-server-ce entity-framework-4 ef-code-first

我想保存一些具有双向关系的实体(两端的导航属性).这是通过2次调用来完成的context.SaveChanges().

[关于我的模型,映射以及我如何到达那里的完整细节都在折叠之后.]

public void Save(){

     var t = new Transfer();
     var ti1 = new TransferItem();
     var ti2 = new TransferItem();

     //deal with the types with nullable FKs first
     t.TransferIncomeItem = ti1;
     t.TransferExpenseItem = ti2;

     context.Transfers.Add(t);
     context.Operations.Add(ti1);
     context.Operations.Add(ti2);

     //save, so all objects get assigned their Ids
     context.SaveChanges();

     //set up the "optional" half of the relationship
     ti1.Transfer = t;
     ti2.Transfer = t;
     context.SaveChanges();
} 
Run Code Online (Sandbox Code Playgroud)

一切都很好,但如果在两次调用之间发生雷击,确保数据库不一致怎么样SaveChanges()

输入TransactionScope...

public void Save(){
    using (var tt = new TransactionScope())
    {
        [...same as above...]
        tt.Complete();
    }
}
Run Code Online (Sandbox Code Playgroud)

...但是在第一次调用context.SaveChanges()此错误时失败:

连接对象不能在事务范围中登记.

这个问题这篇MSDN文章建议我明确地征集交易......

public void Save(){
    using (var tt = new TransactionScope())
    {
        context.Database.Connection.EnlistTransaction(Transaction.Current);

        [...same as above...]
        tt.Complete();
    }
}
Run Code Online (Sandbox Code Playgroud)

......同样的错误:

连接对象不能在事务范围中登记.

死胡同在这里......让我们采取不同的方法 - 使用显式交易.

public void Save(){
    using (var transaction = context.Database.Connection.BeginTransaction())
    {
        try
        {
            [...same as above...]
            transaction.Commit();
        }
        catch
        {
            transaction.Rollback();
            throw;
        }
    }
Run Code Online (Sandbox Code Playgroud)

仍然没有运气.这次,错误消息是:

BeginTransaction需要一个开放且可用的连接.连接的当前状态为Closed.

我该如何解决?


TL; DR细节

这是我的简化模型:一个引用两个引用事务的操作(TransferItem)的事务.这是 Transfer与其两个项目之间的1:1映射.

我想要的是确保在添加新内容时以原子方式保存这些内容Transfer.

这是我走过的路,以及我被卡住的地方.

该模型:

public class Transfer
{
    public long Id { get; set; }
    public long TransferIncomeItemId { get; set; }
    public long TransferExpenseItemId { get; set; }
    public TransferItem TransferIncomeItem { get; set; }
    public TransferItem TransferExpenseItem { get; set; }
}

public class Operation {
    public long Id;
    public decimal Sum { get; set; }
}

public class TransferItem: Operation
{
    public long TransferId { get; set; }
    public Transfer Transfer { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

我想将此映射保存到数据库(SQL CE).

public void Save(){
     var t = new Transfer();
     var ti1 = new TransferItem();
     var ti2 = new TransferItem();
     t.TransferIncomeItem = ti1;
     t.TransferExpenseItem = ti2;

     context.Transfers.Add(t);
     context.Operations.Add(ti1);
     context.Operations.Add(ti2);
     context.SaveChanges();
}
Run Code Online (Sandbox Code Playgroud)

这个错误打击了我的脸:

"无法确定依赖操作的有效排序.由于外键约束,模型要求或存储生成的值,可能存在依赖关系."

Thsi是一个鸡与蛋的问题.我无法使用不可为空的外键保存对象,但为了填充外键,我需要先保存对象.

看看这个问题,似乎我必须放松我的模型,并且:

  • 在关系的至少一方有可空的FK
  • 首先保存这些对象
  • 建立关系
  • 再次保存.

像这样:

public class TransferItem: Operation
{
    public Nullable<long> TransferId { get; set; }
    [etc]
}
Run Code Online (Sandbox Code Playgroud)

此外,这里是映射.莫尔塔扎Manavi的上EF 1条:1间的关系真正有帮助的.基本上,我需要与指定的FK列创建一对多的关系.'CascadeOnDelete(false)'处理有关多个级联路径的错误.(数据库可能会尝试删除转移两次,每次关系一次)

        modelBuilder.Entity<Transfer>()
            .HasRequired<TransferItem>(transfer => transfer.TransferIncomeItem)
            .WithMany()
            .HasForeignKey(x => x.TransferIncomeItemId)
            .WillCascadeOnDelete(false)
            ;

        modelBuilder.Entity<Transfer>()
            .HasRequired<TransferItem>(transfer => transfer.TransferExpenseItem)
            .WithMany()
            .HasForeignKey(x => x.TransferExpenseItemId)
            .WillCascadeOnDelete(false)
            ;
Run Code Online (Sandbox Code Playgroud)

用于保存实体的更新代码位于问题的开头.

Mar*_*eta 4

为了完成这项工作,我必须添加更流畅的映射,以显式创建类TransferItem上的可选映射Transfer,以及使TransferItem.

一旦映射被修复,我将这一切包装在一个 TransactionScope 中就没有问题了。

这是整个控制台应用程序:

  public class Transfer
   {
      public long Id { get; set; }
      public long TransferIncomeItemId { get; set; }
      public long TransferExpenseItemId { get; set; }
      public TransferItem TransferIncomeItem { get; set; }
      public TransferItem TransferExpenseItem { get; set; }
   }

   public class Operation
   {
      public long Id { get; set; }
      public decimal Sum { get; set; }
   }

   public class TransferItem : Operation
   {
      public long? TransferId { get; set; }
      public Transfer Transfer { get; set; }
   }

   public class Model : DbContext
   {

      public DbSet<Transfer> Transfers { get; set; }
      public DbSet<Operation> Operations { get; set; }
      public DbSet<TransferItem> TransferItems { get; set; }

      protected override void OnModelCreating( DbModelBuilder modelBuilder )
      {
         modelBuilder.Entity<Transfer>()
            .HasRequired( t => t.TransferIncomeItem )
            .WithMany()
            .HasForeignKey( x => x.TransferIncomeItemId )
            .WillCascadeOnDelete( false );

         modelBuilder.Entity<Transfer>()
            .HasRequired( t => t.TransferExpenseItem )
             .WithMany()
            .HasForeignKey( x => x.TransferExpenseItemId )
            .WillCascadeOnDelete( false );

         modelBuilder.Entity<TransferItem>()
            .HasOptional( t => t.Transfer )
            .WithMany()
            .HasForeignKey( ti => ti.TransferId );
      }
   }

   class Program
   {
      static void Main( string[] args )
      {

         using( var scope = new TransactionScope() )
         {
            var context = new Model();

            var ti1 = new TransferItem();
            var ti2 = new TransferItem();

            //deal with the types with nullable FKs first
            context.Operations.Add( ti1 );
            context.Operations.Add( ti2 );
            var t = new Transfer();
            context.Transfers.Add( t );
            t.TransferIncomeItem = ti1;
            t.TransferExpenseItem = ti2;
            //save, so all objects get assigned their Ids
            context.SaveChanges();

            //set up the "optional" half of the relationship
            ti1.Transfer = t;
            ti2.Transfer = t;
            context.SaveChanges();
            scope.Complete();
         }

      }
   }
Run Code Online (Sandbox Code Playgroud)

当运行产生这个数据库时:

在此输入图像描述

这个输出:

在此输入图像描述