EF4代码优先+ SQL Server CE:自动保存双向引用

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

[关于我的模型,映射和我到达的完整细节在折叠后。]

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();
} 

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

输入TransactionScope ...

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

...但是在第一次调用context.SaveChanges()时出现这个错误:

连接对象不能在事务范围内注册。

这个问题和这个MSDN文章建议我明确登记交易...

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

        [...same as above...]
        tt.Complete();
    }
}

...同样的错误:

连接对象不能在事务范围内注册。

死胡同在这里...让我们去寻找一种不同的方法 - 使用明确的交易。

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

仍然没有运气。 这一次,错误信息是:

BeginTransaction需要一个开放且可用的Connection。 连接的当前状态为关闭。

我该如何解决?


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; }
}

我想将此映射保存到数据库(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();
}

这在我的脸上发生了这样的错误:

“无法确定依赖操作的有效排序。由于外键约束,模型要求或商店生成的值,可能会存在依存关系。”

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

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

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

    public class TransferItem: Operation
    {
        public Nullable<long> TransferId { get; set; }
        [etc]
    }
    

    另外,这里是映射。 Morteza 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)
                ;
    

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


    为了使这项工作,我不得不添加更流畅的映射,明确创建的可选映射TransferItemTransfer类,以及使在该FK可空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();
             }
    
          }
       }
    

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

    而这个输出:

    在这里输入图像描述


    这可能是因为两次调用SaveChanges导致连接打开和关闭两次。 使用某些版本的SQL Server时,这会导致事务被提升为分布式事务,如果服务未运行,该事务将失败。

    最简单的解决方案是在创建TransactionScope之前通过显式打开它来手动管理连接。 然后EF不会尝试打开或关闭连接本身,直到它关闭它时处理上下文。

    查看代码示例以及此博客文章的答案。

    链接地址: http://www.djcxy.com/p/63065.html

    上一篇: EF4 Code First + SQL Server CE: save bidirectional reference atomically

    下一篇: Display relative time in hour, day, month and year