none
UPDATEで主キーの一部も更新する場合の設定方法は? RRS feed

  • 質問

  • UPDATEで主キーの一部も更新する場合に、DetailsViewとSqlDataSourceをどう設定すれば
    よりコーディングレスで実現できるか知りたいと思っています。

    以下のような、主キーが複数列で構成されているテーブルを作りました。

    CREATE TABLE [dbo].[Table1](
     [id1] [int] NOT NULL,
     [id2] [int] NOT NULL,
     [value1] [nvarchar](50) NULL,
     CONSTRAINT [PK_Table1] PRIMARY KEY CLUSTERED
    (
     [id1] ASC,
     [id2] ASC
    ) /* 以降省略 */

    UPDATEの要件は、value1の値はそのまま、id2を振り直したいというものです。
    つまり、

    id1 id2 value1
    --------------
    1   1   元いち
    1   2   元に

    という状態があったとき、

    id1 id2 value1
    --------------
    1   1   元いち
    1   3   元に

    としたいのですが、
    「id2=2のレコードをDELETEしてからid2=3をINSERT」で実現させるのではなく、
    UPDATE一回で実現させたい、ということです。
    # SQL発行を一回で済ませたいという要求は、そんなに特殊じゃないですよね?

    DetailsView.DataKeyNamesの値は「id1,id2」です(デフォルト設定のまま)。
    自動生成させるとDetailsViewのid2はテキストボックスにならない(主キーの為)ので、
    ReadOnlyをFalseにしてテキストボックスに変更しました。

    ここから2つの方法を試しました。

    その1(本命):
    SqlDataSource.UpdateQueryは自動生成したものを書き直しました。
     UPDATE [Table1] SET [value1] = @value1
          , [id2] = @id2 /* この行を追加 */
     WHERE [id1] = @id1 AND [id2] = @id2

    これで試したのですが、全く更新されません。
    DetailsViewのItemUpdatingイベントで止めてe.OldValuesとe.NewValuesを見たのですが、
    e.NewValuesはCountが2でテキストボックスへの入力値が渡っているのですが、
    e.OldValuesはCountが1で、value1テキストボックスの値しか渡っておらず予想外でした。

    その2(動くモノ):
    SqlDataSource.UpdateQueryの書き直しに手を入れました。
     UPDATE [Table1] SET [value1] = @value1
          , [id2] = @id2_new /* パラメータ名をWHEREと別にした */
     WHERE [id1] = @id1 AND [id2] = @id2

    さらに、DetailsViewのItemUpdatingイベントに
     SqlDataSource1.UpdateParameters.Item("id2_new").DefaultValue = e.NewValues("id2")
    と書いて、コントロールと結びついていないid2_newに値を渡しました。

    これは思った通りに更新されました。しかし、
    e.NewValuesはCountが2でテキストボックスへの入力値が渡っているのですが、
    e.OldValuesはCountが1で、value1テキストボックスの値しか渡っていないという状況は
    変わりませんでした(予想としては、DefaultValueに代入したらOldValues.Countが
    増えるかと思っていました)。

    一応、その2で出来たので良いのですが、
    その1のように、UpdateQueryのパラメータを増やさないまま、デザイン上で
    実現させる方法はあるのでしょうか?
    どなたかご存知でしたら是非ご教授下さい。

    2009年8月4日 14:45

回答

  • > e.Keys.Countと無関係に、e.NewValues.Countとe.OldValues.Countが同じになるような設定方法か、
    > e.NewValuesとe.OldValuesの内部事情が判れば有難いと思っています。

    主キーを更新するのは想定外で、それを更新できるように書き換えると(ReadOnly="True" を削除したりす
    ると)、OldValues がどうなるかは未定義ということではないでしょうか。

    まあ、趣味の問題ということで、いろいろ調べるのはいいと思いますが、それに関する Microsoft の公式
    文書が見つけられないと(自分は見つけることができませんでした)、あまり深く追求しても得られるものは
    少なそうな気がします。

    パラメータを増やさないことにこだわらなければ、OldValues, NewValues は使わないで、以下のような方
    法もあります。

    (1) クエリを以下のように書き換え。

    UPDATE [Table1] SET [Value1] = @Value1, [Id2] = @Id2 WHERE [Id1] = @original_Id1 AND [Id2] = @original_Id2

    (2) SqlDataSource のプロパティを下記のように設定。

    ConflictDetection="CompareAllValues"
    OldValuesParameterFormatString="original_{0}"

    自動生成されたコードを書き換えてどのような副作用があるかは検証できていませんので、ご参考まで。

    • 回答としてマーク aafxaa 2009年8月6日 13:55
    2009年8月5日 15:02

すべての返信

  • > その1のように、UpdateQueryのパラメータを増やさないまま、デザイン上で
    > 実現させる方法はあるのでしょうか?

    そのような方法はないと思います。

    「1   2   元に」を「1   3   元に」のように UPDATE するのですよね?

    その1のクエリ、

    >  UPDATE [Table1] SET [value1] = @value1
    >       , [id2] = @id2 /* この行を追加 */
    >  WHERE [id1] = @id1 AND [id2] = @id2

    では WHERE 句の条件を満たす(即ち id1 = 1 AND id2 = 2)行の value を「元に」に id2 を「2」にセットする
    ということになるわけですから、結果は「1   2   元に」になる(即ち、更新されない)のは当然と思います。

    その2の方法が適当かどうかは分かりませんが、少なくともクエリはその2のようにしないことには id2 を更新
    できないと思います。

    2009年8月5日 4:33
  • ご回答ありがとうございます。

    > WHERE 句の条件を満たす(即ち id1 = 1 AND id2 = 2)行の value を「元に」に id2 を「2」にセットする
    > ということになるわけですから、結果は「1   2   元に」になる(即ち、更新されない)のは当然と思います。

    確かにクエリだけ見るとSETでもWHEREでもid2 = @id2と書いているのでご指摘通りです。

    しかし気になることがありまして…
    主キーでない項目value1は何もしなくてもe.OldValuesとe.NewValuesに更新前後の値が入るのに、
    主キーの一部項目id2は何故e.NewValuesしかセットされないのかということです。
    id2のReadOnlyをFalseにしてテキストボックスにしたのだから、
    value1のテキストボックスと同じ土俵に立ったじゃないか~という言い分です(笑)

    ItemUpdatingイベントで止めるとeの値は以下のようになります。
    その1もその2も一緒です。

    e.Keys.Count = 2
    e.Keys(0) = 1
    e.Keys(1) = 2
    e.OldValues.Count = 1
    e.OldValues(0) = "元に"
    e.NewValues.Count = 2
    e.NewValues(0) = 3
    e.NewValues(1) = "元に"

    ちなみに、その2のクエリを以下のように変えてみましたが、やはりeの状態は変わりませんでした。
    その3:
     UPDATE [Table1] SET [id2] = @id2_new /* value1の更新を止めた */
     WHERE [id1] = @id1 AND [id2] = @id2

    その4:
     UPDATE [Table1] SET [id2] = @id2_new
     WHERE [id1] = @id1 /* キーの片方だけ指定 */

    DataKeyNamesを「id1,id2」→「id1」に変更すると、以下のようになりました。

    e.Keys.Count = 1 /* 減った */
    e.Keys(0) = 1
    e.OldValues.Count = 2 /* 増えた */
    e.OldValues(0) = 2 /* id2の値 */
    e.OldValues(1) = "元に"
    e.NewValues.Count = 2
    e.NewValues(0) = 3
    e.NewValues(1) = "元に"

    以上のことから、このように推測しました。
    推測1:e.KeysはDataKeyNamesで指定した数だけ設定される
          (その4の結果から、WHEREに指定した引数の数ではない。
           DetailsViewUpdateEventArgsの説明「Keysプロパティにはキーフィールドが格納され」と合っている)
    推測2:e.NewValuesはReadOnly=Falseの数だけ設定される
          (id2がReadOnlyの時はe.NewValues.Count = 1だった為。また、
           その3の結果から、SETに指定した引数の数ではない。
           DetailsViewUpdateEventArgs.NewValuesの説明「キーフィールドは格納されません」とあるが、
           id2の値もReadOnly=Falseにしたら格納された)
    推測3:e.OldValuesは「全カラム数-e.Keys.Count」だけ設定される
          (DataKeyNamesを変更したら増えた為。
           DetailsViewUpdateEventArgsの説明「元のキーフィールド以外の値にアクセスする
           必要がある場合は、 OldValues」と合っている)

    自分としては、
    e.Keys.Countと無関係に、e.NewValues.Countとe.OldValues.Countが同じになるような設定が判れば、
    e.Keys.Count = 2
    e.Keys(0) = 1
    e.Keys(1) = 2
    e.OldValues.Count = 2
    e.OldValues(0) = 2
    e.OldValues(1) = "元に"
    e.NewValues.Count = 2
    e.NewValues(0) = 3
    e.NewValues(1) = "元に"
    となって、その1のクエリのままでも更新が出来るのではないかと期待していました。
    (WHEREのid2 = @id2はOldValuesで、SETのid2 = @id2はNewValuesで解釈してくれる等)

    e.Keys.Countと無関係に、e.NewValues.Countとe.OldValues.Countが同じになるような設定方法か、
    e.NewValuesとe.OldValuesの内部事情が判れば有難いと思っています。

    2009年8月5日 13:15
  • > e.Keys.Countと無関係に、e.NewValues.Countとe.OldValues.Countが同じになるような設定方法か、
    > e.NewValuesとe.OldValuesの内部事情が判れば有難いと思っています。

    主キーを更新するのは想定外で、それを更新できるように書き換えると(ReadOnly="True" を削除したりす
    ると)、OldValues がどうなるかは未定義ということではないでしょうか。

    まあ、趣味の問題ということで、いろいろ調べるのはいいと思いますが、それに関する Microsoft の公式
    文書が見つけられないと(自分は見つけることができませんでした)、あまり深く追求しても得られるものは
    少なそうな気がします。

    パラメータを増やさないことにこだわらなければ、OldValues, NewValues は使わないで、以下のような方
    法もあります。

    (1) クエリを以下のように書き換え。

    UPDATE [Table1] SET [Value1] = @Value1, [Id2] = @Id2 WHERE [Id1] = @original_Id1 AND [Id2] = @original_Id2

    (2) SqlDataSource のプロパティを下記のように設定。

    ConflictDetection="CompareAllValues"
    OldValuesParameterFormatString="original_{0}"

    自動生成されたコードを書き換えてどのような副作用があるかは検証できていませんので、ご参考まで。

    • 回答としてマーク aafxaa 2009年8月6日 13:55
    2009年8月5日 15:02
  • ご回答ありがとうございます。

    > パラメータを増やさないことにこだわらなければ、OldValues, NewValues は使わないで、以下のような方
    > 法もあります。
    > (略)

    なるほど、「オプティミスティック同時実行制御」で自動生成されたSQLについては
    不勉強でした。早速検索したら同様の回答も見つけました。

     Re[4] GridViewでの編集に関する制限
     http://bbs.wankuma.com/index.cgi?mode=al2&namber=21486&KLOG=41

    自動生成された直後のSQLが、
     UPDATE [Table1] SET [value1] = @value1
     WHERE [id1] = @original_id1 AND [id2] = @original_id2
       AND (   ([value1] = @original_value1)
            OR ([value1] IS NULL AND @original_value1 IS NULL))
    となっていて、同じ[value1]に対してSETでは@value1、WHEREでは@original_id1にしてありますね。
    ここからも、その1のクエリは駄目な(.Netの実装はともかく、普通にクエリ単体で
    きちんと動くものを書けばいい)ことが判りました。

    ちなみに今回の方法を使ってもeの状態はその1~4と変わりませんでした。
    e.Keys.Count = 2
    e.Keys(0) = 1
    e.Keys(1) = 2
    e.OldValues.Count = 1
    e.OldValues(0) = "元に"
    e.NewValues.Count = 2
    e.NewValues(0) = 3
    e.NewValues(1) = "元に"

    そうなるとご指摘通り、

    > 主キーを更新するのは想定外で、それを更新できるように書き換えると(ReadOnly="True" を削除したりす
    > ると)、OldValues がどうなるかは未定義ということではないでしょうか。

    ということなのでしょうね。

    自動生成したものを書き換えるというイレギュラーな手段ですが、
    今回教わったものと自分のもの、二つのやり方で実現は出来たので、
    この件についてはこれで終わりにしたいと思っています。
    お付き合いくださいましてありがとうございました。

    2009年8月6日 13:53