我想保存一些具有双向关系的实体(两端的导航属性)。这是通过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 question和this 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。
我该如何解决这个问题?
这是我的简化模型:一个引用两个引用事务的操作(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似乎我必须放松我的模型,并且:
像这样:
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)
;
用于保存实体的更新代码位于问题的开头。
答案 0 :(得分:4)
为了完成这项工作,我必须添加更多流畅的映射,以显式创建TransferItem
类Transfer
的可选映射,以及使{{1}上的FK可为空}}
一旦修复映射,我在单个TransactionScope中将这一切都包装完毕没有问题。
以下是整个控制台应用:
TransferItem
当run生成这个数据库时:
这个输出:
答案 1 :(得分:0)
这可能是因为对SaveChanges
的两次调用导致连接被打开和关闭两次。对于某些版本的SQL Server,这会导致事务被提升为分布式事务,如果服务未运行,该事务将失败。
最简单的解决方案是通过在创建TransactionScope
之前显式打开连接来手动管理连接。然后,EF将不会尝试打开或关闭连接本身,直到它在处理上下文时关闭它。
有关代码示例以及this answer和this博文,请参阅this。