1

HOTELと​​OWNERの2つのテーブルがあります。両方にとIdentity列がありますが、一方にはもう一方のテーブルに必要な外部キーがあります。

両方のレコードを同時にトランザクションで追加する必要がありますが、プライマリテーブルで挿入が失敗した場合は、セカンダリテーブルのレコードを書き込んだトランザクションをロールバックする必要があります。

私が理解している限り、セカンダリテーブルから自動生成されたIDをフェッチするには.SaveChanges()が必要ですが、これもトランザクションをコミットしているようです。

これを行う他の方法はありますか?

public class HOTEL
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Int64 HOTEL_ID { get; set; }

    public string blah1 { get; set; }

    public string blah2 { get; set; }
}

public class OWNER
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Int64 OWNER_ID { get; set; }

    public string blah3 { get; set; }

    public string blah4 { get; set; }

    public Int64 HOTEL_ID { get; set; }

    [ForeignKey("HOTEL_ID")]
    public virtual HOTEL HOTEL { get; set; }
}

..。

public class MyContext : DbContext
{
    public MyContext() : base() { }
    public MyContext(string connectionString) : base(connectionString) { }

    public DbSet<HOTEL> HOTELs { get; set; }
    public DbSet<OWNER> OWNERs { get; set; }

    public ObjectContext ObjectContext
    {
        get
        {
            return (this as IObjectContextAdapter).ObjectContext;
        }
    }
}

..。

Int64 ret = 0;

// Suppress required for DB2.
using (var transaction = new TransactionScope(TransactionScopeOption.Suppress))  
{
    try
    {
        using (var context = new MyContext())
        {
            var secondaryEntity = new HOTEL();
            context.HOTELs.Add(secondaryEntity);

            // This appears to commit the changes in the trasaction.
            context.SaveChanges();

            primaryEntity.HOTEL_ID = secondaryEntity.HOTEL_ID;

            context.OWNERs.Attach(primaryEntity);
            context.Entry(primaryEntity).State = primaryEntity.OWNER_ID == 0 ? EntityState.Added : EntityState.Modified;

            context.SaveChanges();
            ret = primaryEntity.OWNER_ID;
        }
    }
    catch (Exception ex)
    {
        // Deal with errors.
    }

    if (ret != 0)
    {
        transaction.Complete();
    }
}

return ret;
4

1 に答える 1

0

これをやり直した後、本当の問題は、TransactionScopeOption.Suppressを使用していたためであることがわかりました。DB2が他のオプションで動作していないように見えたので、これを使用していました。これは、Client 9.7 Fixpack 4を使用しているためです。他のTransactionScopeOptionを使用すると、次のエラーが返されていました。

ERROR [57016] [IBM][DB2/NT64] SQL0668N  Operation not allowed for reason code "7" on table "DB2ADMIN.HOTEL".

さらに深く掘り下げてみると、DB2がTransactionScopeに参加していないためです。この問題に関する他のいくつかの問題を確認した後、次の手順で解決しました。

  • MSDTCプロパティで有効になっているXAトランザクション
    1. 起動し、「dcomcnfg」を実行して、コンポーネントサービス管理コンソールを起動します。
    2. ツリーを「コンポーネントサービス>コンピューター>マイコンピューター>分散トランザクションコーディネーター>ローカルDTC」に移動します。
    3. 「ローカルDTC」で、右クリックして[プロパティ]を選択します。
    4. 「セキュリティ」タブを選択します。
    5. 「XAトランザクションを有効にする」タブをチェックします。
    6. OKを押します。
  • DB2 9.7 FP4を引き続き使用する場合は、レジストリキーHKLM \ SOFTWARE \ Microsoft \ MSDTC \ XADLLに、名前が「DB2CLI.DLL」、値が「C:\ Program Files \ IBM \ SQLLIB \BIN\」の値を入力する必要があります。 DB2CLI.DLL '

    [HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ MSDTC \ XADLL] "DB2CLI.DLL" = "C:\ Program Files \ IBM \ SQLLIB \ BIN \ db2cli.dll"

  • または、(レジストリー項目から)、少なくともDB2 Client9.7FP6をインストールします。

  • コンピューターを再起動します。
  • TransactionScopeを破棄する前に接続を閉じないでください。

最終的なコードは次のようになりました。

Int64 ret = 0;

using (var transaction = new TransactionScope(TransactionScopeOption.Required))
{
    try
    {
        using (var context = new MyContext())
        {
            var secondaryEntity = new HOTEL();
            context.HOTELs.Add(secondaryEntity);

            // SaveChanges(bool) has been depricated, use SaveOptions.
            // SaveChanges is required to generate the autogenerated Identity HOTEL_ID.
            context.ObjectContext.SaveChanges(SaveOptions.AcceptAllChangesAfterSave);
            primaryEntity.HOTEL_ID = secondaryEntity.HOTEL_ID;

            context.OWNERs.Attach(primaryEntity);
            context.Entry(primaryEntity).State = primaryEntity.OWNER_ID == 0 ? EntityState.Added : EntityState.Modified;

            context.ObjectContext.SaveChanges(SaveOptions.AcceptAllChangesAfterSave);
            ret = primaryEntity.OWNER_ID;

            if (ret != 0)
            {
                transaction.Complete();
                context.ObjectContext.AcceptAllChanges();
            }
        }
    }
    catch (Exception ex)
    {
        // Deal with errors.
    }
}

return ret;

@Pawelからのコメントを読んだ後の代替手段。これにより、secondaryEntityのIDが生成され、値を手動で割り当てる必要なしに、primaryEntityに自動的にアタッチされます。

Int64 ret = 0;

using (var transaction = new TransactionScope(TransactionScopeOption.Required))
{
    try
    {
        using (var context = new MyContext())
        {
            var secondaryEntity = new HOTEL();
            primaryEntity.HOTEL = secondaryEntity;

            context.HOTELs.Add(secondaryEntity);
            context.OWNERs.Attach(primaryEntity);
            context.Entry(primaryEntity).State = primaryEntity.OWNER_ID == 0 ? EntityState.Added : EntityState.Modified;

            context.SaveChanges();
            ret = primaryEntity.OWNER_ID;

            // TransactionScopeOption.Required can still used in case something 
            // goes wrong with additional processing at this point.

            if (ret != 0)
            {
                transaction.Complete();
            }
        }
    }
    catch (Exception ex)
    {
        // Deal with errors.
    }
}

return ret;
于 2013-03-27T22:52:21.987 に答える