none
主キーが複数あるテーブルのUpdate RRS feed

  • 質問

  • ADO.NETで、主キーが複数あるテーブルのUpdate をしたいと思っています。

    コードは、
                cmd = new SqlCommand(
                    "UPDATE Cards SET " +
                    "CardYear = @CardYear, KindOfCard = @KindOfCard, " + 
                    "SendMonth = @SendMonth, SendDay = @SendDay, " + 
                    "ReceiveMonth = @ReceiveMonth, ReceiveDay = @ReceiveDay, " + 
                    "CardMemo = @CardMemo " +
                    "WHERE ID = @ID", cn);
    
                cmd.Parameters.Add(
                    "@CardYear", SqlDbType.Int, 30, "CardYear");
                cmd.Parameters.Add(
                    "@KindOfCard", SqlDbType.Int, 30, "KindOfCard");
                cmd.Parameters.Add(
                    "@SendMonth", SqlDbType.Int, 30, "SendMonth");
                cmd.Parameters.Add(
                    "@SendDay", SqlDbType.Int, 30, "SendDay");
                cmd.Parameters.Add(
                    "@ReceiveMonth", SqlDbType.Int, 30, "ReceiveMonth");
                cmd.Parameters.Add(
                    "@ReceiveDay", SqlDbType.Int, 30, "ReceiveDay");
                cmd.Parameters.Add(
                    "@CardMemo", SqlDbType.NVarChar, 50, "CardMemo");







                da.UpdateCommand = cmd;
                object[] findVals = new object[3];
                findVals[0] = Convert.ToInt32(idBox.Text);
                findVals[1] = Convert.ToInt32(cardYearBox.Text);
                findVals[2] = Convert.ToInt32(kindOfCardBox.Text);
                da.UpdateCommand = cmd;
                cmd.Parameters.AddWithValue("@ID", findVals[0]);
                cmd.Parameters.AddWithValue("@CardYear", findVals[1]);
                cmd.Parameters.AddWithValue("@KindOfCard", findVals[2]);
                    dataSet.Tables["Cards"].PrimaryKey = new DataColumn[] { 
                            dataSet.Tables["Cards"].Columns["ID"],
                            dataSet.Tables["Cards"].Columns["CardYear"],
                            dataSet.Tables["Cards"].Columns["KindOfCard"]};

                DataRow row = dSet.Tables["Cards"].Rows.Find(findVals);


    なお、KindOfCard は、年賀状と暑中見舞いとの区別のためのフィールドです。

    この次に
                if (cardYearBox.Text != "")
                    row["CardYear"] = cardYearBox.Text;
                else
                    row["CardYear"] = "";
                if (kindOfCardBox.Text != "")
                    row["KindOfCard"] = kindOfCardBox.Text;
                else
                    row["KindOfCard"] = 0;
                if (sendMonthBox.Text != "")
                    row["SendMonth"] = sendMonthBox.Text;
                else
                    row["SendMonth"] = null;
                if (sendDayBox.Text != "")
                    row["SendDay"] = sendDayBox.Text;
                else
                    row["SendDay"] = null;
                if (receiveMonthBox.Text != "")
                    row["ReceiveMonth"] = receiveMonthBox.Text;
                else
                    row["ReceiveMonth"] = DBNull.Value;
                if (receiveDayBox.Text != "")
                    row["ReceiveDay"] = receiveDayBox.Text;
                else
                    row["ReceiveDay"] = DBNull.Value;
                if (cardMemoBox.Text != "")
                    row["CardMemo"] = cardMemoBox.Text;
                else
                    row["CardMemo"] = null;
    
                cn.Open();
                cmd.ExecuteNonQuery();
    とすると、
    変数名'@CardYear' は既に宣言されています。変数名は、クエリ バッチまたはストアドプロシージャ内で一意にしてください。
    というエラーが表示されてしまいます。

    なお、da はデータアダプタ、cn は、SqlConnection です。

    主キーが複数であるのに、その対応が出来ていないためだとは思うのですが、具体的にどうしたらよいのかが分かりません。
    どうか、ご指導お願いします。
    2009年12月26日 12:49

回答

  • エラー?本当に?例外ではなく?

    端的に言うと、CardYear、KindOfCard の2つについて、Parameters.Add と、Parameters.AddWithValue の2回追加しているから、です。
    どうすればいいかというと、1回目の Add を削除するか、2回目の AddWithValue を、Item プロパティ経由で Value プロパティにアクセスするように修正するか、です。

    でも、私としては、理解して使って欲しい。
    説明書を読まずに「洗うものだから」と、洗濯機にネコを放り込んで洗うようなことは、しないで欲しい。
    Jitta@わんくま同盟
    • 回答としてマーク yasheeki 2009年12月29日 21:32
    2009年12月29日 4:05

すべての返信

  • 主キーは、どれとどれでしょう?
    エラーが表示されるのは、どのタイミング(実行時 OR コンパイル時)でしょうか。また、どの行で発生するのでしょうか。
    実行時ならば、発生しているのはエラーでしょうか、それとも例外でしょうか。
    例外ならば、例外の種類は何でしょうか。

    Jitta@わんくま同盟
    2009年12月26日 13:41
  • cmd.Parameters.Add(
    "@CardYear", SqlDbType.Int, 30, "CardYear");
    cmd.Parameters.Add(
    "@KindOfCard", SqlDbType.Int, 30, "KindOfCard");

    (略)
    cmd.Parameters.AddWithValue("@ID", findVals[0]);
    cmd.Parameters.AddWithValue("@CardYear", findVals[1]);
    cmd.Parameters.AddWithValue("@KindOfCard", findVals[2]);
    @CardYearと@KindOfCardを2重に登録してませんか?
    2009年12月26日 16:07
  • 主キーは、ID 、CardYear、KindOfCardの3つの列です。
    cmd.ExecuteNonQuery();
    のところで、エラーが発生します。
    実行時エラーです。
    2009年12月27日 3:20
  • 上の
    cmd.Parameters.Add("@CardYear", SqlDbType.Int, 30, "CardYear");
    cmd.Parameters.Add("@KindOfCard", SqlDbType.Int, 30, "KindOfCard");
    をコードから外すと
    今度は、SqlExceptionエラーとして、
    パラメータ化クエリ '(@SendMonth int,@SendDay int,@ReceiveMonth int,@ReceiveDay int,@' に必要なパラメータ '@SendMonth' が指定されていません。
    とのメッセージが表示されます。

    2009年12月27日 3:26
  • DataRowから値を拾って欲しいなら、cmd.ExecuteNonQuery()ではなくda.Update(new DataRow[]{row})などを使いましょう。

    SqlDataAdapterのUpdateを使えばマッピングされているテーブルから値を拾ってくれます。
    しかしSqlDataAdapterのUpdateを使わずに、SqlCommandで直接Updateをするなら、各SqlParameterには自分で値を入れてやらないといけません。
    "パラメータ'@SendMonth'が指定されていません"のエラーは@SendMonthに値が入っていないから発生しています。
    他の未設定のパラメータも同様です。


    追記
    row["SendMonth"] = null;
    row["SendDay"] = null;
    row["CardMemo"] = null;
    のように書かれていますが入れられました?
    nullは入れられないのでエラーになるはずですが…
    • 編集済み gekkaMVP 2009年12月27日 5:33 追記追加
    2009年12月27日 5:26
  • > 上の
    > cmd.Parameters.Add("@CardYear", SqlDbType.Int, 30, "CardYear");
    > cmd.Parameters.Add("@KindOfCard", SqlDbType.Int, 30, "KindOfCard");
    > をコードから外すと

    一行余分に cmd.Parameters.Add("@SendMonth", SqlDbType.Int, 30, "SendMonth"); も消してし
    まったということはありませんか?

    その他にもいろいろ問題がありそうな感じです。

    SqlCommandBuilder を使わないで自分でコードを実装したいということでしょうか? そうであれば、
    一度、型付 DataSet + TableAdapter を作って、自動生成されるコードを眺めてみてはいかがですか。

    その中の、InitAdapter メソッドでの UpdateCommand.CommandText やパラメーターの追加の仕方、
    Update メソッドでの SqlParameter.Value の設定方法など参考になるのではないかと思います。

    • 編集済み SurferOnWww 2009年12月27日 7:16 誤記訂正
    2009年12月27日 7:12
  • > row["SendMonth"] = null;
    > row["SendDay"] = null;
    > row["CardMemo"] = null;
    > のように書かれていますが入れられました?
    > nullは入れられないのでエラーになるはずですが…

    テーブル定義で NULL 許可になっているとエラーは出ず、Update が実行されると、どこか
    で DBNull.Value に変換されて DB には NULL が設定されるようです。

    NULL 不許可の場合は、Update 実行時に DBNull.Value を使えとのエラーメッセージが出ま
    す。で、言われたとおり DBNull.Value を使うと、NULL 不許可で SqlException がスローさ
    れます。

    何にせよ cmd.ExecuteNonQuery(); としたのでは、DataRow に何を設定しようと関係ない
    と思いますが。

    2009年12月27日 7:26
  • >cmd.Parameters.Add("@SendMonth", SqlDbType.Int, 30, "SendMonth"); も消してし
    まったということは

    、ありません。

    また、InitAdapter メソッドというのは、分かりませんでしたが、
    自動生成されたUpdateCommand を見てみました。
    すると、

    UPDATE [dbo].[Cards] SET [ID] = @ID, [CardYear] = @CardYear, 
    [KindOfCard] = @KindOfCard, [SendMonth] = @SendMonth,
    [SendDay] = @SendDay, [ReceiveMonth] = @ReceiveMonth,
    [ReceiveDay] = @ReceiveDay, [CardMemo] = @CardMemo
    WHERE (([ID] = @Original_ID) AND
    ([CardYear] = @Original_CardYear) AND
    ([KindOfCard] = @Original_KindOfCard) AND
    ((@IsNull_SendMonth = 1 AND [SendMonth] IS NULL) OR
    ([SendMonth] = @Original_SendMonth)) AND
    ((@IsNull_SendDay = 1 AND [SendDay] IS NULL) OR
    ([SendDay] = @Original_SendDay)) AND
    ((@IsNull_ReceiveMonth = 1 AND [ReceiveMonth] IS NULL) OR
    ([ReceiveMonth] = @Original_ReceiveMonth)) AND
    ((@IsNull_ReceiveDay = 1 AND [ReceiveDay] IS NULL) OR
    ([ReceiveDay] = @Original_ReceiveDay)) AND
    ((@IsNull_CardMemo = 1 AND [CardMemo] IS NULL) OR
    ([CardMemo] = @Original_CardMemo)));

    という、非常にながいコードがありました。
    スカラ変数の数が非常に多くなっています。
    このような、コードをUpdateCommand として設定すべきなのでしょうか?

    どうも、迷路に入ってしまい、抜けられません。
    よろしくお願いします。

    なお、
    コードは、下記のように変更しました。
                cmd = new SqlCommand("UPDATE [dbo].[Cards] " +
                    "SET [ID] = @ID, [CardYear] = @CardYear, [KindOfCard] = @KindOfCard, " +
                    "[SendMonth] = @SendMonth, [SendDay] = @SendDay, " +
                    "[ReceiveMonth] = @ReceiveMonth, [ReceiveDay] = @ReceiveDay, " +
                    "[CardMemo] = @CardMemo " +
                    "WHERE (([ID] = @Original_ID) AND " +
                    "([CardYear] = @Original_CardYear) AND " +
                    "([KindOfCard] = @Original_KindOfCard) AND " +
                    "((@IsNull_SendMonth = 1 AND [SendMonth] IS NULL) OR " +
                    "([SendMonth] = @Original_SendMonth)) AND " +
                    "((@IsNull_SendDay = 1 AND [SendDay] IS NULL) OR " +
                    "([SendDay] = @Original_SendDay)) AND " +
                    "((@IsNull_ReceiveMonth = 1 AND [ReceiveMonth] IS NULL) OR " +
                    "([ReceiveMonth] = @Original_ReceiveMonth)) AND " +
                    "((@IsNull_ReceiveDay = 1 AND [ReceiveDay] IS NULL) OR " +
                    "([ReceiveDay] = @Original_ReceiveDay)) AND " +
                    "((@IsNull_CardMemo = 1 AND [CardMemo] IS NULL) OR " +
                    "([CardMemo] = @Original_CardMemo)));", cn);

                da.UpdateCommand = cmd;

                object[] findVals = new object[3];

                findVals[0] = Convert.ToInt32(idBox.Text);
                findVals[1] = Convert.ToInt32(cardYearBox.Text);
                findVals[2] = Convert.ToInt32(kindOfCardBox.Text);

                cmd.Parameters.AddWithValue("@ID", findVals[0]);
                cmd.Parameters.AddWithValue("@CardYear", findVals[1]);
                cmd.Parameters.AddWithValue("@KindOfCard", findVals[2]);

                cmd.Parameters.Add(
                    "@SendMonth", SqlDbType.Int, 30, "SendMonth");
                cmd.Parameters.Add(
                    "@SendDay", SqlDbType.Int, 30, "SendDay");
                cmd.Parameters.Add(
                    "@ReceiveMonth", SqlDbType.Int, 30, "ReceiveMonth");
                cmd.Parameters.Add(
                    "@ReceiveDay", SqlDbType.Int, 30, "ReceiveDay");
                cmd.Parameters.Add(
                    "@CardMemo", SqlDbType.NVarChar, 50, "CardMemo");

                dSet.Tables["Cards"].PrimaryKey = new DataColumn[] 
                  {dSet.Tables["Cards"].Columns["ID"],                         
                  dSet.Tables["Cards"].Columns["CardYear"],                         
                  dSet.Tables["Cards"].Columns["KindOfCard"]};
                DataRow row = dSet.Tables[tableName].Rows.Find(findVals);

                if (cardYearBox.Text != "")
                    row["CardYear"] = cardYearBox.Text;
                else
                    row["CardYear"] = "";
                if (kindOfCardBox.Text != "")
                    row["KindOfCard"] = kindOfCardBox.Text;
                else
                    row["KindOfCard"] = 0;
                if (sendMonthBox.Text != "")
                    row["SendMonth"] = sendMonthBox.Text;
                else
                    row["SendMonth"] = DBNull.Value;
                if (sendDayBox.Text != "")
                    row["SendDay"] = sendDayBox.Text;
                else
                    row["SendDay"] = DBNull.Value;
                if (receiveMonthBox.Text != "")
                    row["ReceiveMonth"] = receiveMonthBox.Text;
                else
                    row["ReceiveMonth"] = DBNull.Value;
                if (receiveDayBox.Text != "")
                    row["ReceiveDay"] = receiveDayBox.Text;
                else
                    row["ReceiveDay"] = DBNull.Value;
                if (cardMemoBox.Text != "")
                    row["CardMemo"] = cardMemoBox.Text;
                else
                    row["CardMemo"] = DBNull.Value;

                cn.Open();
                da.Update(new DataRow[] { row });

    で、ここでは、対応するスカラ変数を宣言していないためか
    スカラ変数 "@Original_ID" を宣言してください
    との、エラーメッセージが表示されます。

    • 編集済み yasheeki 2009年12月27日 9:31 エラーコードの追加
    2009年12月27日 9:18
  • > このような、コードをUpdateCommand として設定すべきなのでしょうか?

    楽観的同時実行制御のため、 WHERE 以下に ((@IsNull_SendMonth = 1 AND [SendMonth] IS NULL) と
    いうようなコードが入っています。それをそのままコピペして DataAdpter の UpdateCommand に設定してもう
    まくいきません。どのようなクエリを書けばよいのか、よく考えてみてください。

    > どうも、迷路に入ってしまい、抜けられません。

    型付 DataSet + TableAdapter を作成したのであれば、その名前が TestDataBaseDataSet とすると、以下
    のようにすればうまくいくはずです。

    object[] findVals = new object[3];
    findVals[0] = XXXXX;
    findVals[1] = XXXXX
    findVals[2] = XXXXX;
    TestDataBaseDataSet.CardsRow row =
          (TestDataBaseDataSet.CardsRow)dataset.Cards.Rows.Find(findVals);
    row.ID = XXXXX;
    row.CardYear = XXXXX;
    row.KindOfCard = XXXXX;
    row.SendMonth = XXXXX;
    row.SendDay = XXXXX;
    row.RecieveMonth = XXXXX;
    row.ReceiveDay = XXXXX;
    row.CardMemo = XXXXX;
    adapter.Update(row);

    ADO.NET の基本的なクラスライブラリを使うのも、慣れていればいいのですが、そうでなければ型付 DataSet
    + TableAdapter などを自動生成して使うのが効率が良いと思います。

    2009年12月27日 14:19
  • エラー?本当に?例外ではなく?

    端的に言うと、CardYear、KindOfCard の2つについて、Parameters.Add と、Parameters.AddWithValue の2回追加しているから、です。
    どうすればいいかというと、1回目の Add を削除するか、2回目の AddWithValue を、Item プロパティ経由で Value プロパティにアクセスするように修正するか、です。

    でも、私としては、理解して使って欲しい。
    説明書を読まずに「洗うものだから」と、洗濯機にネコを放り込んで洗うようなことは、しないで欲しい。
    Jitta@わんくま同盟
    • 回答としてマーク yasheeki 2009年12月29日 21:32
    2009年12月29日 4:05
  • 久々にこっちのフォーラム覗いたら

     データテーブルのデータをデータベースのテーブルにINSERTするにあたり 

    の話の発展形ですかね?
    こっちで話が続いているとは思いませんでした。

    ADO.NET の基本的なクラスライブラリを使うのも、慣れていればいいのですが、そうでなければ型付 DataSet
    + TableAdapter などを自動生成して使うのが効率が良いと思います。

    私もこの意見に同意です。
    ADO.NET がどうしても理解しにくいのであれば、TableAdapter を使って操作した方がいいのかもしれません。

    #ADO.NET をより使い易くするために用意されたのが TableAdapter のようですが・・・
    2009年12月29日 5:52
    モデレータ
  • お世話になります。

    改めて、コードを眺めながてみると、自分でもおかしいなと思うところが多々あり、修正してみました。
    すると、やっと正しくUpdate メソッドの処理できました。念願でありましたので、とても感激しています。

    皆様のご指導に感謝します。で、何を変えたかを書かずに閉めるのは失礼と思いますので、参考のためにコードを書いておきます。

                cmd = new SqlCommand(
                    "UPDATE Cards SET " +
                    "SendMonth = @SendMonth, SendDay = @SendDay, " +
                    "ReceiveMonth = @ReceiveMonth, ReceiveDay = @ReceiveDay, " +
                    "CardMemo = @CardMemo " +
                    "WHERE ID = @ID AND CardYear = @CardYear AND KindOfCard = @KindOfCard", cn);

                cmd.Parameters.Add(
                    "@SendMonth", SqlDbType.Int, 30, "SendMonth");
                cmd.Parameters.Add(
                    "@SendDay", SqlDbType.Int, 30, "SendDay");
                cmd.Parameters.Add(
                    "@ReceiveMonth", SqlDbType.Int, 30, "ReceiveMonth");
                cmd.Parameters.Add(
                    "@ReceiveDay", SqlDbType.Int, 30, "ReceiveDay");
                cmd.Parameters.Add(
                    "@CardMemo", SqlDbType.NVarChar, 50, "CardMemo");

                da.UpdateCommand = cmd;

                object[] findVals = new object[3];

                findVals[0] = Convert.ToInt32(idBox.Text);
                findVals[1] = Convert.ToInt32(cardYearBox.Text);
                findVals[2] = Convert.ToInt32(kindOfCardBox.Text);

                cmd.Parameters.AddWithValue("@ID", findVals[0]);
                cmd.Parameters.AddWithValue("@CardYear", findVals[1]);
                cmd.Parameters.AddWithValue("@KindOfCard", findVals[2]);

                SetPrimaryKey(dSet, "Cards", "");
                DataRow row = dSet.Tables["Cards"].Rows.Find(findVals);

                if (sendMonthBox.Text != "")
                    row["SendMonth"] = sendMonthBox.Text;
                else
                    row["SendMonth"] = DBNull.Value;
                if (sendDayBox.Text != "")
                    row["SendDay"] = sendDayBox.Text;
                else
                    row["SendDay"] = DBNull.Value;
                if (receiveMonthBox.Text != "")
                    row["ReceiveMonth"] = receiveMonthBox.Text;
                else
                    row["ReceiveMonth"] = DBNull.Value;
                if (receiveDayBox.Text != "")
                    row["ReceiveDay"] = receiveDayBox.Text;
                else
                    row["ReceiveDay"] = DBNull.Value;
                if (cardMemoBox.Text != "")
                    row["CardMemo"] = cardMemoBox.Text;
                else
                    row["CardMemo"] = DBNull.Value;

                cn.Open();
                da.Update(new DataRow[] { row });
                cn.Close();

    私のような素人目には、よく似ているのですが、実際は全然違いました。
    情けないことに、SQL文が間違っていまして、はなはだお恥ずかしい限りです。

    また、未解決の問題も残ります。
    というのは、
    1つには、
                da.Update(new DataRow[] { row });
    の代わりに、
                da.Update(dSet, "Cards");
    でも、同じようにデータの更新が出来るのはなぜだろう。

    また、1つには、
                pr = da.UpdateCommand.Parameters.Add("@ID", SqlDbType.Int);
                pr.SourceColumn = "ID";
                pr.SourceVersion = DataRowVersion.Original;
    という風に、SourceVersion に関する指定をしていないのに、処理できるのはなぜだろう。

    などです。
    冬休みに入りましたので、じっくり考えてみたいと思っています。
    アドバイスありがとうございました。

    • 編集済み yasheeki 2009年12月29日 21:30 間違い訂正
    2009年12月29日 19:34
  • 主キーを変更する必要がなければ、アップされたコードでよさそうです。(ただ、主キーの値を
    TextBox から取得するのは、個人的にはどうかとは思いますが)

    次は INSERT と DELETE の実装でしょうか。

    さらには、DataGridView, BindingSource, BindingNavigator を追加して、それらと作成した
    DataSet, SqlDataAdpter を連携させ、DataGridView 上で編集した結果を、ボタンクリックで
    DB に保存できるようにするところまでコードを拡張できるといいですね。

    ところで、MDSN Library はお持ちでしょうか? Express Edition のヘルプでは情報が十分得
    られないので、もしお持ちでなければ、以下のツールをダウンロードして使ってみてくだい。

    MSDN Library for Visual Studio 2008 SP1 (2008年12月更新版)
    http://www.microsoft.com/downloads/details.aspx?FamilyID=7bbe5eda-5062-4ebb-83c7-d3c5ff92a373&DisplayLang=ja

    たぶん、「未解決の問題」の解決の役に立つと思います。

    2009年12月30日 6:28