投稿者 魔界の仮面弁士  (社会人) 投稿日時 2017/8/1 19:13:46
最初の質問では WHERE ITEM_ID = ~ だったのに
今回のコードは WHERE NUMBER = ~ ですね。
どちらが正しいのでしょうか。


> 1行しか削除されませんでした。
それは、CurrentCell や CurrentRow を使っているからですね。


> 複数行でも削除できるようにするにはfor文などを使用するのが正しいですか? 
DataGridView 上の複数行を取得する方法は、既に具体例付きで回答しています。
すなわち
 【行全体が選択されているものを対象とする場合】
 【現在のカーソル行とは関係なく、選択されているセルの行を対象とする場合】
の 2 つのコードの事です。For Each のループ処理が書かれていましたよね?


> 複数行選択された状態で削除ボタンが押下された場合、
そもそも「選択された行」と「現在の行」は無関係です。
たとえば下記の図の場合、このような状態を意味します。
 CurrentCell   は 1 件。[行1列0]
 CurrentRow    は 1 件。[行1]
 SelectedRows  は 2 件。[行3],[行4]
 SelectedCells は 5 件。[行3列0],[行3列1],[行4列0],[行4列1],[行6列1]



さてこの場合、削除したいのは以下のいずれでしょうか?
 (A) 1 件削除。行1 のみ。CurrentRow を使う。(現状がこれですよね)
 (B) 2 件削除。行3 と 行4。SelectedRows を使います。
 (C) 3 件削除。行3 と 行4 と 行6。SelectedCells を集約してから使います。

これらの 3 パターンの行取得の手順は、先の投稿を参照してください。

A ならば【現在のカーソル行を対象とする場合】の手法です。
B ならば【行全体が選択されているものを対象とする場合】です。
C ならば【選択されているセルの行を対象とする場合】です。

SelectionMode に FullRowSelect を指定している場合は、
C は使わないので B 案になるでしょう。


先の例においては、削除のために「Delete メソッド」を呼び出していたわけですが、
今回は Delete メソッドを呼ぶ代わりに、ソッケルさんの「DELETE SQL を呼ぶ」コードを
使うことで、ひとまずの目的を達することができるでしょう。
(B あるいは C の For Each の中の処理を書き換える、ということです)


もう少し書いておくと……ソッケルさんの
> DataGridView1.CurrentRow.Cells("注文番号").Value
の場合、「DataGridView1.CurrentRow」が DataGridViewRow 型を表しますよね。

一方、先の例の
> For Each gridRow As DataGridViewRow In DataGridView1.SelectedRows
の場合、「gridRow」が DataGridViewRow 型で得られますので、
 gridRow.Cells("注文番号").Value
のようにして、各行の注文番号を得られることになるわけです。
あとは同じように書き換えられますよね。



さて、これで複数件の削除はひとまず行えるのですが、
さらに追記しておくべきことがあります。

> Dim delete As String = "DELETE FROM T_ITEM WHERE NUMBER =  '" & DataGridView1.CurrentRow.Cells("注文番号").Value & "'"

残念ながら、このコードには 2 つの問題があります。

一つは、データがゼロ件だった場合などのように、
CurrentRow が Nothing を返してくる場合の対処が漏れていること。

もう一つは、SQL インジェクションが考慮されていない点です。
後者は特に危険なので、是正するべきです。

たとえば提示頂いた例で言えば、注文番号欄に
「'--」あるいは「' OR ''='」が入力されることで
T_ITEM 上の全データが削除される危険性を含んでしまいます。


どうするべきかというと、値を埋め込んだ SQL コマンドを直接作るのではなく、
前回の最後に書いた【DELETE 文を手動で生成する場合】のサンプルコードのように、
パラメーター付きの SQL にするということです。

MySQL の場合は、「WHERE NUMBER = ?」の無名パラメーターと
「WHERE NUMBER = @PARAM1」などの名前付きパラメーターの
両方の構文に対応しています。あとは、そのパラメーターに対応させて
MySqlParameter オブジェクトを用意して渡すようにすれば、
『'』が含まれるような文字列であっても、安全に引き渡すことができます。

もし、どうしても値を直接 SQL に埋め込まなくてはならないのなら、
データ内容が安全かを事前にチェックするコードを設けておくか、
あるいは、必ずサニタイジングを施す必要があります。たとえば、
文字列中の「'」や「"」を、Replace メソッドを使うなどして
「\'」や「\"」に置き換えるといった手法です。
https://dev.mysql.com/doc/refman/5.6/ja/string-literals.html


とはいえ、このような手動サニタイズは作業漏れを引き起こしやすいので、
可能な限り、MySqlParameter あるいは MySqlDataAdapter を
使うことを心がけておいたほうが安全です。


> Dim mycomand As New MySqlCommand(delete, conn)
mycomand ではなく
mycommand かな…。


> DataGridView1.Rows.RemoveAt(DataGridView1.CurrentCell.RowIndex)
わざわざ行番号に戻さずとも、RemoveAt の代わりに Remove を使うようにすれば
 DataGridView1.Rows.Remove(DataGridView1.CurrentRow)
のように、行オブジェクトをそのまま渡せますよ。


> Catch ex As Exception
>   MsgBox(ex.Message)
確認では MessageBox.Show を用いていたのに、
エラー表示は MsgBox なんですね。
何か理由があって使い分けているのでしょうか?



> '合計の表示
> sum()
以下、蛇足情報として。

上記がどのような処理で実装されているか分かりませんが、
現在表示されている内容の合計を求めるのであれば、
DataTable の Compute メソッドを使うと、
ループ処理を使わずに、合計を簡単に求められます。

あるいは VB の Aggregate 句を使う方法などもあります。


蛇足ついでにもう一つ。ちょっと難しい内容かもしれませんが、
データ量が多くなってきた場合に必要となる情報を紹介しておきます。

[Windows フォーム DataGridView コントロールを拡張するための推奨される手順]
https://msdn.microsoft.com/ja-jp/library/ha5xt0d9.aspx