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

时间:2012-08-31 09:20:13

标签: entity-framework-4 transactions ef-code-first sql-server-ce

我想保存一些具有双向关系的实体(两端的导航属性)。这是通过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();
} 

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

输入TransactionScope ...

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

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

  

连接对象不能在事务范围内登记。

This questionthis MSDN article建议我明确地加入交易......

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需要一个开放且可用的连接。连接的当前状态为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; }
}

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

这让我感到震惊:

  

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

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

this question似乎我必须放松我的模型,并且:

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

像这样:

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

此外,这是映射。 Morteza Manavi的article on EF 1:1 relationships 非常非常有帮助。基本上,我需要与指定的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)
            ;

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

2 个答案:

答案 0 :(得分:4)

为了完成这项工作,我必须添加更多流畅的映射,以显式创建TransferItemTransfer的可选映射,以及使{{1}上的FK可为空}}

一旦修复映射,我在单个TransactionScope中将这一切都包装完毕没有问题。

以下是整个控制台应用:

TransferItem

当run生成这个数据库时:

enter image description here

这个输出:

enter image description here

答案 1 :(得分:0)

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

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

有关代码示例以及this answerthis博文,请参阅this