我想保存一些具有双向关系的实体(两端的导航属性)。这是通过 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()
出现此错误:
连接对象不能在事务范围内登记。
这个问题和这篇 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 需要一个开放且可用的连接。连接的当前状态是关闭。
我该如何解决?
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)' 处理有关多个级联路径的错误。(数据库可能会尝试删除 Transfer 两次,每个关系一次)
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)
;
用于保存实体的更新代码位于问题的开头。