双方向の関係 (両端のナビゲーション プロパティ) でいくつかのエンティティを保存したいと考えています。これは、 への 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();
}
すべて問題ありませんが、 への 2 つの呼び出しの間に落雷が発生した場合に、データベースに矛盾が生じないようにするにはどうすればよい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 詳細
これが私の簡略化されたモデルです。トランザクションを参照する 2 つの操作 (TransferItem) を参照するトランザクションです。これは、 Transfer とその 2 つのアイテムそれぞれの間の 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();
}
これはエラーで私の顔に吹きます:
「依存操作の有効な順序を決定できません。外部キー制約、モデル要件、またはストアで生成された値が原因で、依存関係が存在する可能性があります。」
これはニワトリが先か卵が先かの問題です。Null 非許容の外部キーを使用してオブジェクトを保存することはできませんが、外部キーを設定するには、最初にオブジェクトを保存する必要があります。
この質問を見ると、モデルをリラックスさせる必要があるようです。
- リレーションシップの少なくとも一方の側に null 許容の FK がある
- 最初にそれらのオブジェクトを保存します
- 関係を設定する
- もう一度保存します。
このような:
public class TransferItem: Operation
{
public Nullable<long> TransferId { get; set; }
[etc]
}
また、ここにマッピングがあります。EF の 1 対 1 の関係に関するMorteza Manavi の記事は非常に役に立ちました。基本的に、指定された FK 列との一対多の関係を作成する必要があります。「CascadeOnDelete(false)」は、複数のカスケード パスに関するエラーを処理します。(DB は、関係ごとに 1 回ずつ、合計 2 回 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)
;
エンティティを保存するための更新されたコードは、質問の冒頭にあります。