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();
}
这在我的脸上发生了这样的错误:
“无法确定依赖操作的有效排序。由于外键约束,模型要求或商店生成的值,可能会存在依存关系。”
这是一个鸡与鸡蛋的问题。 我无法使用不可为空的外键保存对象,但为了填充外键,我需要先保存对象。
看着这个问题,我似乎必须放松我的模型,并且:
喜欢这个:
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)
;
用于保存实体的更新代码位于问题的开头。
为了使这项工作,我不得不添加更流畅的映射,明确创建的可选映射TransferItem
上Transfer
类,以及使在该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