7

双方向の関係 (両端のナビゲーション プロパティ) でいくつかのエンティティを保存したいと考えています。これは、 への 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)
            ;

エンティティを保存するための更新されたコードは、質問の冒頭にあります。

4

2 に答える 2

4

TransferItemこれを機能させるには、より流暢なマッピングを追加し、クラスで のオプションのマッピングを明示的に作成し、Transferで FK を null 可能にする必要がありました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();
         }

      }
   }

実行すると、このデータベースが作成されました。

ここに画像の説明を入力

そして、この出力:

ここに画像の説明を入力

于 2012-09-06T01:27:38.703 に答える
0

これは、2 回の呼び出しSaveChangesにより、接続が 2 回開かれ、閉じられたことが原因である可能性があります。SQL Server の一部のバージョンでは、これによりトランザクションが分散トランザクションに昇格され、サービスが実行されていないと失敗します。

最も簡単な解決策は、TransactionScope. 次に、EF は、コンテキストが破棄されたときに接続を閉じるまで、接続自体を開いたり閉じたりしようとしません。

コードサンプルについてはこの回答と、これこのブログ投稿を参照してください。

于 2012-09-12T17:58:51.613 に答える