none
データベースの修正の方法について RRS feed

  • 質問

  • データベースを1レコード読み込んで修正したいのですが
    私はいつも、一度データセット(XSD)をつくってコードを書いていました。

    ですが、データセットをつくらなくてもできるんではないかと思って質問させていただきます。
    データセットですと、テーブルを少しでも、変更するたびに修正が必要になりますし、できれば作らないで
    修正が出来る方法があればと思い質問させていただきました。

    読み込みまではできたのですが、
    修正ができません。どのようにしたら、修正ができるようになるんでしょうか?
    よろしくお願いします。

    <%@ Page Language="VB" %>
    <%@ Import Namespace="System.Data" %>
    <%@ Import Namespace="System.Data.SqlClient" %>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <script runat="server">
    
        Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs)
            Dim strSQL As String = ConfigurationManager.ConnectionStrings("project_jobConnectionString2").ConnectionString
            Dim cnn As SqlConnection = New SqlConnection(strSQL)
            Dim SqlcmdSelect As SqlCommand = New SqlCommand()
            Dim adpt As SqlDataAdapter = New SqlDataAdapter()
            Dim dsDataSet As DataSet = New DataSet
    
            SqlcmdSelect.Connection = cnn
            SqlcmdSelect.CommandTimeout = 15
    
            strSQL = "SELECT * FROM 名前 WHERE ID='" & TextBox1.Text & "';"
            SqlcmdSelect.CommandText = strSQL
            adpt.SelectCommand = SqlcmdSelect
            
            Try
                adpt.Fill(dsDataSet)
            Catch ex As Exception
                cnn.Close()
                MsgBox("データベースアクセスに失敗しました。")
            End Try
            If dsDataSet.Tables(0).Rows.Count > 0 Then
                With dsDataSet.Tables(0).Rows(0)
                    TextBox1.Text = .Item("ID")
                    TextBox2.Text = .Item("名前")
                    TextBox3.Text = .Item("性別")
                    TextBox4.Text = .Item("血液型")
                    Session("Original_tb") = Items
                End With
            Else
                MsgBox("商品情報がありません。")
            End If
        End Sub
    
        Protected Sub Button2_Click(ByVal sender As Object, ByVal e As System.EventArgs)
            Dim strSQL As String = ConfigurationManager.ConnectionStrings("project_jobConnectionString2").ConnectionString
            Dim cnn As SqlConnection = New SqlConnection(strSQL)
            Dim SqlcmdSelect As SqlCommand = New SqlCommand()
            Dim adpt As SqlDataAdapter = New SqlDataAdapter()
            Dim dsDataSet As DataSet = New DataSet
    
            Items = Session("Original_tb")
            Try
                Items(0).名前 = TextBox2.Text
                Items(0).性別 = TextBox3.Text
                Items(0).血液型 = TextBox4.Text
            Catch ex As Exception
                Return
            End Try
            Try
                ?.Update(?)
            Catch exdb As DBConcurrencyException
                lblMessage.Text = "他のユーザーによって更新編集操作をやり直してください。"
                Return
            Catch ex As Exception
                lblMessage.Text = ex.GetBaseException.Message
                Return
            End Try
        End Sub
        Protected Sub TextBox1_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs)
        End Sub
        Protected Sub TextBox2_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs)
        End Sub
        Protected Sub TextBox3_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs)
        End Sub
        Protected Sub TextBox4_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs)
        End Sub
    </script>
    
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>無題のページ</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
        
            ID<asp:TextBox ID="TextBox1" runat="server" Width="119px" 
                ontextchanged="TextBox1_TextChanged"></asp:TextBox>
            <asp:Button ID="Button1" runat="server" onclick="Button1_Click" Text="情報読込" />
            <br />
            -------------------------------------------<br />
            名前<asp:TextBox ID="TextBox2" runat="server" 
                ontextchanged="TextBox2_TextChanged"></asp:TextBox>
        
            <br />
            性別<asp:TextBox ID="TextBox3" runat="server" 
                ontextchanged="TextBox3_TextChanged"></asp:TextBox>
            <br />
            血液型<asp:TextBox ID="TextBox4" runat="server" 
                ontextchanged="TextBox4_TextChanged"></asp:TextBox>
        
            <asp:Button ID="Button2" runat="server" onclick="Button2_Click" Text="修正" />
        
            <asp:Label ID="lblMessage" runat="server" Text="Label"></asp:Label>
        
        </div>
        </form>
    </body>
    </html>
    2009年3月29日 15:43

回答

  • データベースにSQL文を発行することによって、データベースから値を取得したり更新を行います。これが基本です。
    基本的な形は以下のようになります。

    ADO.NetでSQLServerに接続@VB.Net
    http://www.cocoaliz.com/vb.net/index/30/

    レコードを更新するのであれば、SQLのUpdate文を発行します。where句のID値などは文字列連結せずにパラメータを使って下さい。つまり、SQL文は文字列連結で作成しないようにして下さい。SQLインジェクションの原因になります。AddWithValueで検索すれば例が見つかると思います。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/
    • 回答としてマーク sk7474 2009年4月16日 2:56
    2009年3月30日 0:24
    モデレータ
  • Update文の作り方がわからないので、修正のコードの説明はないでしょうか?
    以下に参考になるコードがあります。SQL文は大丈夫ですか?

    SqlParameterCollection.AddWithValue メソッド
    http://msdn.microsoft.com/ja-jp/library/system.data.sqlclient.sqlparametercollection.addwithvalue.aspx
    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/
    • 回答としてマーク sk7474 2009年4月16日 2:56
    2009年3月30日 12:44
    モデレータ
  • SqlDataSource と DetailsView または FormView を使って、ウィザードベース
    で(ほとんどコードを書かないで)目的の Web アプリケーションを作れますが、
    それでは何か不足ですか?

    それとも、自分の勉強のためなどの理由で、ADO.NET ライブラリ(質問の例で
    は DataSet と SqlDataAdapter)を使って自分でコードを書きたいということ
    ですか? そうであれば、質問のコードに SqlDataAdapter の InsertCommand,
    UpdateCommand, DeleteCommand の設定を追加する必要があります。代わりに
    SqlCommandBuilder を使って自動生成するという手もあります。それらをキー
    ワードに検索して、参考になる例を探してください。

    • 回答としてマーク sk7474 2009年4月16日 2:56
    2009年3月30日 13:04
  • 本題と関係ないところに突っ込み。

    Try
    
        adpt.Fill(dsDataSet)
    
    Catch ex As Exception
    
        cnn.Close()
    
        MsgBox("データベースアクセスに失敗しました。")
    
    End Try
    
    

    これは、ダメです。

    まず、これだと、切断がエラーが発生したときだけしかされません。何度も通るうちに、データベースと接続できなくなります。

    次に、MsgBox です。おそらく、開発中は、メッセージボックスが表示されていると思います。しかし、本番環境では表示されません。MsgBox は、サーバーで表示されます。開発中は、クライアントがサーバーを兼ねているので問題ないように見えます。


    Try
        adpt.Fill(dsDataSet)
    Catch
        Me.RegisterClientScriptBlock("DBERROR", _
            "<script>alert('データベースアクセスに失敗しました。');</script>")
    Finally
        cnn.Close()
    End Try
    

    こんな感じ。


    Jitta@わんくま同盟
    • 回答としてマーク sk7474 2009年4月16日 2:56
    2009年4月1日 13:46
  • 本題にも突っ込み。

    データセットですと、テーブルを少しでも、変更するたびに修正が必要になりますし、できれば作らないで修正が出来る方法があればと思い質問させていただきました。

    ここに提示されているコードならば、テーブルを変更しても“DataSet は”変更しなくて良いはずです。

    Dim dsDataSet As DataSet = New Data
    adpt.Fill(dsDataSet)
    

    このように、型なし DataSet を使用されています。この場合、データベースから取得したスキーマにあわせて適切な型が宣言されるため、DataSet の変更は必要ありません。XSD を作られているそうですが、このコードでは、その XSD は使われていません。

    また、DataSet を使わないようにしたとしても、列とユーザーが変更するための UI は変更せねばならず、その連携も当然変更しなければなりません。そのあたりのことは、どのようにしようとお考えなのでしょうか。


    Jitta@わんくま同盟
    • 回答としてマーク sk7474 2009年4月16日 2:56
    2009年4月7日 12:46

すべての返信

  • データベースにSQL文を発行することによって、データベースから値を取得したり更新を行います。これが基本です。
    基本的な形は以下のようになります。

    ADO.NetでSQLServerに接続@VB.Net
    http://www.cocoaliz.com/vb.net/index/30/

    レコードを更新するのであれば、SQLのUpdate文を発行します。where句のID値などは文字列連結せずにパラメータを使って下さい。つまり、SQL文は文字列連結で作成しないようにして下さい。SQLインジェクションの原因になります。AddWithValueで検索すれば例が見つかると思います。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/
    • 回答としてマーク sk7474 2009年4月16日 2:56
    2009年3月30日 0:24
    モデレータ
  • このHPも読み込みの方法ですよね
    http://www.cocoaliz.com/vb.net/index/30/


    Update文の作り方がわからないので、修正のコードの説明はないでしょうか?
    よろしくお願いします。
    2009年3月30日 10:59
  • Update文の作り方がわからないので、修正のコードの説明はないでしょうか?
    以下に参考になるコードがあります。SQL文は大丈夫ですか?

    SqlParameterCollection.AddWithValue メソッド
    http://msdn.microsoft.com/ja-jp/library/system.data.sqlclient.sqlparametercollection.addwithvalue.aspx
    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/
    • 回答としてマーク sk7474 2009年4月16日 2:56
    2009年3月30日 12:44
    モデレータ
  • SqlDataSource と DetailsView または FormView を使って、ウィザードベース
    で(ほとんどコードを書かないで)目的の Web アプリケーションを作れますが、
    それでは何か不足ですか?

    それとも、自分の勉強のためなどの理由で、ADO.NET ライブラリ(質問の例で
    は DataSet と SqlDataAdapter)を使って自分でコードを書きたいということ
    ですか? そうであれば、質問のコードに SqlDataAdapter の InsertCommand,
    UpdateCommand, DeleteCommand の設定を追加する必要があります。代わりに
    SqlCommandBuilder を使って自動生成するという手もあります。それらをキー
    ワードに検索して、参考になる例を探してください。

    • 回答としてマーク sk7474 2009年4月16日 2:56
    2009年3月30日 13:04
  • 本題と関係ないところに突っ込み。

    Try
    
        adpt.Fill(dsDataSet)
    
    Catch ex As Exception
    
        cnn.Close()
    
        MsgBox("データベースアクセスに失敗しました。")
    
    End Try
    
    

    これは、ダメです。

    まず、これだと、切断がエラーが発生したときだけしかされません。何度も通るうちに、データベースと接続できなくなります。

    次に、MsgBox です。おそらく、開発中は、メッセージボックスが表示されていると思います。しかし、本番環境では表示されません。MsgBox は、サーバーで表示されます。開発中は、クライアントがサーバーを兼ねているので問題ないように見えます。


    Try
        adpt.Fill(dsDataSet)
    Catch
        Me.RegisterClientScriptBlock("DBERROR", _
            "<script>alert('データベースアクセスに失敗しました。');</script>")
    Finally
        cnn.Close()
    End Try
    

    こんな感じ。


    Jitta@わんくま同盟
    • 回答としてマーク sk7474 2009年4月16日 2:56
    2009年4月1日 13:46
  • 本題にも突っ込み。

    データセットですと、テーブルを少しでも、変更するたびに修正が必要になりますし、できれば作らないで修正が出来る方法があればと思い質問させていただきました。

    ここに提示されているコードならば、テーブルを変更しても“DataSet は”変更しなくて良いはずです。

    Dim dsDataSet As DataSet = New Data
    adpt.Fill(dsDataSet)
    

    このように、型なし DataSet を使用されています。この場合、データベースから取得したスキーマにあわせて適切な型が宣言されるため、DataSet の変更は必要ありません。XSD を作られているそうですが、このコードでは、その XSD は使われていません。

    また、DataSet を使わないようにしたとしても、列とユーザーが変更するための UI は変更せねばならず、その連携も当然変更しなければなりません。そのあたりのことは、どのようにしようとお考えなのでしょうか。


    Jitta@わんくま同盟
    • 回答としてマーク sk7474 2009年4月16日 2:56
    2009年4月7日 12:46
  • こんにちは。中川俊輔です。

    皆様、回答ありがとうございます。

    komi1さん、フォーラムのご利用ありがとうございます。
    その後いかがでしょうか?疑問は解決しましたか?

    勝手ながら、有用な情報と思われる回答へ回答マークをつけさせていただきました。

    今後ともフォーラムをよろしくお願いします。
    それでは!
    マイクロソフト株式会社 フォーラム オペレータ 中川 俊輔
    2009年4月16日 3:00
  • 遅くなりました申し訳ありません。
    更新ですが

        Protected Sub Button2_Click(ByVal sender As Object, ByVal e As System.EventArgs)
            Dim connStr As String = ConfigurationManager.ConnectionStrings("project_jobConnectionString2").ConnectionString
            Dim selectStr As String = "SELECT * FROM 名前"
    
            Dim conn As New SqlConnection()
            conn.ConnectionString = connStr
            Dim selectCmd As New SqlCommand()
            selectCmd.Connection = conn
            selectCmd.CommandText = selectStr
            Dim da As New SqlDataAdapter()
            da.SelectCommand = selectCmd
            Dim ds As New DataSet()
            da.Fill(ds, "名前")
            Dim dt As DataTable = ds.Tables("名前")
            dt.PrimaryKey = New DataColumn() {dt.Columns("ID")}
            Dim targetRow As DataRow
            Try
                targetRow = dt.Rows.Find(TextBox1.Text)
                targetRow("名前") = TextBox2.Text
                targetRow("性別") = TextBox3.Text
                targetRow("血液型") = TextBox4.Text
            Catch ex As Exception
                MsgBox("データ更新に失敗しました。")
            End Try
                Dim cb As New SqlCommandBuilder(da)
                da.Update(ds, "名前")
    
        End Sub



    で、できるようになりました。
    ですが、エラー処理に問題はあるのでしょうか?
    コメントで「何度も通るうちに、データベースと接続できなくなります。」とありましたが
    そういったことにならないようなエラー処理がしたいのですが。

    よろしくお願いします。

    2009年4月17日 15:21
  • TextBox1.Textはユーザーがキーを入力するエリアでしょうか? もしそうであればTryを使うべきではありません。dt.Rows.Find(TextBox1.Text)がnullの結果引き起こされる例外をTryで捕まえようとされています(nullなのにTextBox2.Textを代入できない)。しかし、dt.Rows.Find(TextBox1.Text)がnullの時点で更新するレコードが無いことがわかりますので、nullかどうかで判断してユーザーに「更新するレコードがありません。」のようなメッセージを出すのが良いでしょう。
    さて、なぜ私がここでTryを使うべきではないと書いた理由については、以下を読んでみてください。

    .NETの例外処理 Part.1
    http://blogs.msdn.com/nakama/archive/2008/12/29/net-part-1.aspx

    また、da.Updateの結果は、RowUpdatedイベントでRecordsAffectedプロパティでチェックするようにして下さい。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/

    2009年4月17日 16:53
    モデレータ
  • > ですが、エラー処理に問題はあるのでしょうか?

    TextBox はユーザー入力だと思いますが、そうであればユーザーが入力している過程で検証コントロ
    ール(RequiredFieldValidator, RegularExpressionValidator など)を利用してチェックするという
    ことも検討したほうがいいと思います。

    > コメントで「何度も通るうちに、データベースと接続できなくなります。」とありましたが
    > そういったことにならないようなエラー処理がしたいのですが。

    そこのところは、接続を Close しないとコネクションリークが発生して、何度もやっているうちにコ
    ネクションプールの中のコネクションがなくなってしまうということです。

    ただし、DataSet と SqlDataAdapter を利用したデータアクセスで、SqlDataAdapter.Fill メソッドや
    SqlDataAdapter.Update メソッドを利用している限りは、自動的にコネクションの Open、Close が行
    われるのでその心配はないです。

    コネクションリークについては以下のサイトを読んでください。trapemiya さんが紹介されたいたサイ
    トの記事の Part-2 です。

    http://blogs.msdn.com/nakama/archive/2009/01/02/net-part-2.aspx

    2009年4月18日 2:32
  • TextBox1をDropDownList1に変えRequiredFieldValidatorを使用し入力間違えのないものにしました。
    また、データの修正で空白が入ってしまっているのですがどのように回避したらいいのでしょうか?

    データの情報で現在
    ID     nchar(10)
    名前  nchar(10)
    性別  nchar(10)
    血液型  nchar(10)

    で作っているのですが
    データが
    【1         】
    【相沢        】
    【女         】
    【O         】という具合に10文字にするように空白が勝手にできてしまいます。
    また、情報の更新で【O         】を全て空白にしても【          】というようになってしまうのですが
    この空白はどうのように回避できるのでしょうか?
    2009年4月18日 14:49
  • nchar に替えて nvarchar を使って試してみてください。

    #質問の内容とスレッドのタイトル(もともとの質問)が異なってきた場合は、新たに
     別のスレッドを立てて質問するのがよいと思います。(他のユーザーのことも考え
     て)

    2009年4月18日 15:10
  • 申し訳ありません。もう少し、接続のことで質問させてください。

    データの内容が

    ID     10
    名前  戸田
    性別  null
    血液型  null

    だと

        Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs)
            Dim connStr As String = ConfigurationManager.ConnectionStrings("project_jobConnectionString2").ConnectionString
            Dim sqlStr As String = "SELECT * FROM 名前 WHERE ID='" & DropDownList1.Text & "';"
            Dim conn As New SqlConnection(connStr)
            Dim cmd As New SqlCommand(sqlStr, conn)
            conn.Open()
            Dim dr As SqlDataReader = cmd.ExecuteReader()
            dr.Read()
            Try
                DropDownList1.Text = dr("ID")
                TextBox2.Text = dr("名前")
                TextBox3.Text = dr("性別")
                TextBox4.Text = dr("血液型")
            Catch ex As Exception
                MsgBox("情報がありません。")
            End Try
            dr.Close()
            conn.Close()
        End Sub
    


    NULLでデータが読み込まれず
    情報がありませんとなってしまいます。
    もし、NULLでも、更新ボタンを押すことによって、情報が書き換えられるようなものにしたいのですが
    どのように対処できるのでしょうか?

    2009年4月19日 3:35
  • 質問の内容を完全に理解していないのですが、データが読み込まれない場合はTextBox2などを空白にされたいということなのでしょうか? であれば、dr.Read()がtrueかfalseで判断できます。というか、読めたか読めなかったかを必ずこのように判断することをお勧めします。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/
    2009年4月19日 8:48
    モデレータ
  • > NULLでデータが読み込まれず
    > 情報がありませんとなってしまいます。

    何が NULL なのでしょう? ひょっとして ID ですか? そのあたりが分からないと、対応策
    が提案できないです。

    それから、質問とは関係ないですが、例外処置がうまくないです。毎回言っていることです
    が、もっとレスを注意深く読んでください。せっかくレスしても、それでは意味がないです。

    2009年4月19日 9:16
  • 説明不足で申し訳ありません。

    ---------------------
    ID     Null不可
    名前  Null不可
    性別  Null許可
    血液型  Null許可
    ----------------------
    でテーブルを作成しています。

    ID     10
    名前  戸田
    しか最初の入力では情報がわからず
    性別  Null
    血液型  Null
    でデータをいれ、
    情報がわかったら読み込みをし、情報を更新させたいのですが
    TextBox3.Text = dr("性別")の部分で 型 'DBNull' から型 'String' への変換は無効です。 という
    エラーになります。
    Nullの場合はTextBoxに空白で表示されるようにしたいのですが
    どのようにしたらいいのでしょうか
    2009年4月19日 14:08
  • IsDBNullで判断して、Nullなら空白をセットするようにして下さい。もしくはSQL文でNullの場合には空白を返すようにすることもできます。SQL Serverでしょうか?
    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/
    2009年4月19日 14:33
    モデレータ
  • 性別 NULL、血液型 NULL  の処置は trapemiya さんが言われるとおりですが・・・

    > 情報がわかったら読み込みをし、情報を更新させたいのですが

    何故わざわざ TextBox と SqlDataReader を使うというような面倒なことをしているの
    でしょうか? 

    1レコードだけ DB から抽出して表示/更新するなら SqlDataSource と DetailsView を
    使ってはいかがですか? NULL の処置に悩まなくてすみますし、今のコードではできな
    い更新もできるようになりますけど。

    それから、例外処理は結局どうなったんですか?

    2009年4月19日 15:24
  • ありがとうございます。

                If IsDBNull(dr("性別")) = False Then
                    TextBox3.Text = dr("性別")
                Else
                    TextBox3.Text = ""
                End If

    にすることで思うような処理ができるようになりました。

    SqlDataSource と DetailsView のコードを書かなくていいものでなくADO.netを使っているのは
    今回このテーブルで、各textBoxと違いがあった場合は履歴テーブルに
    更新情報、更新時間を記録させ履歴がのこせるようにしたいと思ったからです。

    SQL ServerでSQL文でNullの場合には空白を返すようにすることもできますということですが
    どのようにしたらできるのでしょうか?

    エラー処理はTextBox1をDropDownList1に変えRequiredFieldValidatorを使用し入力間違えのないものにしました。
    また、文字制限を越えないようにMaxLengthで文字数制限をいれました。

    2009年4月20日 14:29
  • SQL ServerでSQL文でNullの場合には空白を返すようにすることもできますということですが
    どのようにしたらできるのでしょうか?
    ISNULLを使用します。

    ISNULL (Transact-SQL)
    http://technet.microsoft.com/ja-jp/library/ms184325.aspx
    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/
    2009年4月21日 0:04
    モデレータ
  • > SqlDataSource と DetailsView のコードを書かなくていいものでなくADO.netを使っているのは
    > 今回このテーブルで、各textBoxと違いがあった場合は履歴テーブルに
    > 更新情報、更新時間を記録させ履歴がのこせるようにしたいと思ったからです。

    違いの判定と更新記録を残す部分をは自力でコードを書く必要があるかもしれませんが、その他の主要
    部分(DB からのデータの抽出、表示、修正、DB の更新)は SqlDataSource と DetailsView で可能では
    ないかと思うのですが・・・

    TextBox と ADO.NET ライブラリを使うのでは生産性が低いし、例外処置も面倒だし、バグの入る余地も
    増えますし。まぁ、趣味の問題ということなら話は別ですが。

    > SQL ServerでSQL文でNullの場合には空白を返すようにすることもできますということですが
    > どのようにしたらできるのでしょうか?

    その点は trapemiya さんのレスを参照してください。

    > エラー処理はTextBox1をDropDownList1に変えRequiredFieldValidatorを使用し入力間違えのないものにしました。
    > また、文字制限を越えないようにMaxLengthで文字数制限をいれました。

    ユーザーの入力エラー処理のことではなく、コネクションリークが問題ということです。

    先のコードは DataSet と SqlDataAdapter を利用した非接続型データアクセスだったので、コネクション
    リークの問題はなかったのですが、今回のように SqlDataReader を使っての接続型データアクセスでは、
    アップされたコードでは問題ありです。

    詳しくは、先に紹介したサイトの記事 (Part-2) をよく読んでください。

    http://blogs.msdn.com/nakama/archive/2009/01/02/net-part-2.aspx

    2009年4月21日 13:01
  • エラー処理、ISNULLの先に更新方法について質問させてください。

    >違いの判定と更新記録を残す部分をは自力でコードを書く必要があるかもしれませんが、その他の主要
    >部分(DB からのデータの抽出、表示、修正、DB の更新)は SqlDataSource と DetailsView で可能では
    >ないかと思うのですが・・・
    >TextBox と ADO.NET ライブラリを使うのでは生産性が低いし、例外処置も面倒だし、バグの入る余地も
    >増えます

    私のやろうとしていることはADO.netを使わなくてもできることなんでしょうか?
    やりたいこととしては下記の動作です。

    できればバグが少なくすむものを作りたいです。
    ご教授お願いします。

        Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs)
            Dim connStr As String = ConfigurationManager.ConnectionStrings("project_jobConnectionString2").ConnectionString
            Dim sqlStr As String = "SELECT * FROM 名前 WHERE ID='" & DropDownList1.Text & "';"
            Dim conn As New SqlConnection(connStr)
            Dim cmd As New SqlCommand(sqlStr, conn)
            conn.Open()
            Dim dr As SqlDataReader = cmd.ExecuteReader()
            dr.Read()
            Try
                DropDownList1.Text = dr("ID")
                TextBox2.Text = dr("名前")
                If IsDBNull(dr("性別")) = False Then
                    TextBox3.Text = dr("性別")
                Else
                    TextBox3.Text = ""
                End If
            
                If IsDBNull(dr("血液型")) = False Then
                    TextBox4.Text = dr("血液型")
                Else
                    TextBox4.Text = ""
                End If
    
            Catch ex As Exception
                MsgBox("情報がありません。")
            End Try
            dr.Close()
            conn.Close()
            Session("名") = TextBox2.Text
            Session("性") = TextBox3.Text
            Session("血") = TextBox4.Text
        End Sub
    
        Protected Sub Button2_Click(ByVal sender As Object, ByVal e As System.EventArgs)
            Dim 旧 As String = 0
            Dim 新 As String = 0
            Dim connStr As String = ConfigurationManager.ConnectionStrings("project_jobConnectionString2").ConnectionString
            Dim selectStr As String = "SELECT * FROM 履歴"
            Dim conn As New SqlConnection()
            conn.ConnectionString = connStr
            Dim selectCmd As New SqlCommand()
            selectCmd.Connection = conn
            selectCmd.CommandText = selectStr
            Dim da As New SqlDataAdapter()
            da.SelectCommand = selectCmd
            Dim ds As New DataSet()
            da.Fill(ds, "履歴")
            Dim dt As DataTable = ds.Tables("履歴")
            
            
            For i = 0 To 2
                Select Case i
                    Case 0
                        旧 = Session("名")
                        新 = TextBox2.Text
                    Case 1
                        旧 = Session("性")
                        新 = TextBox3.Text
                    Case 2
                        旧 = Session("血")
                        新 = TextBox4.Text
                End Select
                If 旧 <> 新 Then
                    Dim newRow As DataRow = dt.NewRow()
                    Try
                        newRow("ID") = TextBox2.Text
                        newRow("変更内容") = TextBox3.Text
                        newRow("変更時間") = TextBox4.Text
                        dt.Rows.Add(newRow)
                    Catch ex As Exception
                        MsgBox("データ更新に失敗しました。")
                    End Try
                End If
            Next
            Dim selectStr2 As String = "SELECT * FROM 名前"
            Dim conn2 As New SqlConnection()
            conn2.ConnectionString = connStr
            Dim selectCmd2 As New SqlCommand()
            selectCmd2.Connection = conn2
            selectCmd2.CommandText = selectStr2
            Dim da2 As New SqlDataAdapter()
            da2.SelectCommand = selectCmd2
            Dim ds2 As New DataSet()
            da2.Fill(ds2, "名前")
            Dim dt2 As DataTable = ds2.Tables("名前")
            dt2.PrimaryKey = New DataColumn() {dt2.Columns("ID")}
            Dim targetRow2 As DataRow
            Try
                targetRow2 = dt2.Rows.Find(DropDownList1.Text)
                targetRow2("名前") = TextBox2.Text
                targetRow2("性別") = TextBox3.Text
                targetRow2("血液型") = TextBox4.Text
            Catch ex As Exception
                MsgBox("データ更新に失敗しました。")
            End Try
            Dim cb As New SqlCommandBuilder(da)
            da.Update(ds, "履歴")
            Dim cb2 As New SqlCommandBuilder(da2)
            da2.Update(ds2, "名前")
        End Sub
    2009年4月22日 15:00
  • > 私のやろうとしていることはADO.netを使わなくてもできることなんでしょうか?
    > やりたいこととしては下記の動作です。

    SqlDataSource もその内部で SqlDataReader などの ADO.NET ライブラリを使っていると思います
    が、SqlDataSource と DetailsView を使えば SqlConnection, SqlCommand, SqlDataReader な
    どを直接操作しなくても目的は果たせるということです。

    例えば今回のケースでは、DB からのデータの抽出、表示、修正、DB の更新は SqlDataSource と
    DetailsView を使ってウィザードベースで作り、違いの判定は SqlDataSource.Updating イベント
    と SqlDataSource.Updated イベントでデータを取得して比較し、違っていたら更新記録を DB に
    書き込むというコードを追加することはできると思います。

    2009年4月23日 15:25
  • 私のやろうとしていることはADO.netを使わなくてもできることなんでしょうか?
    やりたいこととしては下記の動作です。


    データベースに係る操作はADO.NETを必ず使いますと言い切っても良いでしょう。
    簡単に整理しましょう。ADO.NETは非接続でデータベースを操作できるようになっています。例えばデータベースから10件抽出してきたとします。この10件をローカルのデータテーブルに保存し、このデータテーブルに対してプログラムであれやこれや操作して、後でまとめてこのデータテーブルの状態をデータベースに書き戻します。データテーブルでレコードの更新や削除や挿入もあるでしょう。それらをまとめて一度にデータベースに返すのです。この仕組みを簡単にしているのがDataAdapterです。データテーブルに対してい更新・削除・登録の操作を行うと、それぞれのレコードにどうような操作をしたかの印が付いています。(データテーブル上で削除しても実際には削除マークが付いているだけです)
    DataAdapterのUpdateメソッドを呼び出すと、それぞれのレコードのこの印にしたがって、データベースに対して更新やら削除やらの命令を出します。(例えばSQLのUpdate文やDelete文です)
    データテーブルはちょうどWindowsのブリーフケースみたいなものです。非接続とはこのようにデータベースから切断されたテーブル、つまり非接続状態にあるデータテーブルで作業し、それを一気にデータベースに書き戻すものです。

    さて、Button1_Clickイベントプロシージャでselect文を発行してデータを取得されています。1件ですのでselect文を使う方法が常套手段です。その後、Button2_Clickイベントプロシージャでこのレコードの変更の書き戻し、および履歴テーブルへの追加処理をされています。いずれもDataAdapterのUpdateメソッドを利用されています。これでも目的を達することはできるでしょう。しかし、冗長です。性質が違うので名前テーブルと履歴テーブルを分けて考えます。

    まず名前テーブルですが、select文で読んだ後、ユーザーが更新をし、その結果をデータベースへ書き戻しています。

    select文
      ↓
    ユーザー編集
      ↓
    DataAdapterのUpdateメソッド

    これは普通に考えれば以下のようになります。

    select文
      ↓
    ユーザー編集
      ↓
    Update文

    ただし、同時実行制御、つまりこのユーザーが編集している間に他の人がそのレコードに編集を加えているかもしれませんので、それを考慮しなければなりません。SQL ServerであればTIMESTAMP型を用意して行います。これが面倒ということであれば、DataAdapterに同時実行制御のオプションがありますので、それにチェックを付ければ自動で同時実行制御を行ってくれるようになります。この場合は、

    DataAdapterのFillメソッド
      ↓
    ユーザー編集
      ↓
    DataAdapterのUpdateメソッド

    になります。しかし、簡単にできる分、無駄はどうしても多くなります。(不必要にデータテーブルを使うなど)

    SqlDataSourceを使ってもできますが、データバウンドコントロール(GridViewとか)を使うことがメインになっているため、TextBoxに対して使う分にはやや不向きです。

    次に履歴テーブルです。これはこのテーブルに対して追加に行くだけですから、非接続は全く考える必要はありません。単に、SQLのInsert文を実行するだけで済みます。データベースの操作の基本はSQL文です。抽出、更新、登録、削除のSQL文は最低限書けるようになって下さい。SqlCommandBuilderによるSQL文の自動生成は便利かもしれませんが、本質がわかっていなければ全く応用が利きません。Insert文もSqlCommandで実行できますので、少なくとも履歴テーブルはそれを使って更新してみて下さい。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/
    2009年4月24日 6:47
    モデレータ
  • 一つ言うのを忘れていました。Web アプリケーションでユーザーにメッ
    セージを伝えるのに MsgBox は使えません。

    Web アプリケーションで MsgBox というメソッドがどこで動いているか
    理解しているでしょうか?

    サーバーで動いています。従って、メッセージはサーバー側で表示され
    ます(サーバーにモニターがあれば)。

    実環境ではサーバーとユーザーの PC は異なりますから、ユーザーはメ
    ッセージを見ることはできません。

    開発環境ではサーバーとユーザーの PC が一緒だから MsgBox のメ
    ッセージがユーザーに表示されるように見えるだけです。

    2009年4月24日 13:49
  • 2009年4月7日 19:46:12 の指摘は間違ってますね。「型なし DataSet を使うように修正したのだけれど、更新が期待通りにできない。どうすればよいのか?」という質問ですね。


    で、2009年4月7日 19:46:12 の突っ込みを、もう一度。

    “個人”のデータ管理だと思いますが、今、管理されるデータは“名前”、“性別”、“血液型”の3つです。ではここに、“星座”も加えなければならなくなった場合、どうしましょう?DataSet は、変更の必要がありませんが、星座を入力するためのコントロールを、画面に追加しなければなりません。そのあたりは、どのようにお考えなのでしょう?私は、画面も変更しなければならないのだから、型付き DataSet の方がよいと思います。あるいは、SurferOnWwwさんがおっしゃるように、「SqlDataSource と DetailsView を使ってはいかがですか?」履歴を取るのが目的なら、データベース側でトリガーを使うという手もあります。


    TextBox と ADO.NET ライブラリを使うのでは生産性が低いし、例外処置も面倒だし、バグの入る余地も増えますし。まぁ、趣味の問題ということなら話は別ですが。

    SqlDataSource と DetailsView を使えば、ほぼ追加コードなしに実現できます。コードを書くということは、バグを作り込む可能性があるということです。提示されているコードには、たくさんのバグがあります。2009年4月22日 15:00:11 のコードを添削します。

    
        Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs)
            Dim connStr As String = ConfigurationManager.ConnectionStrings("project_jobConnectionString2").ConnectionString
            ↑この文字列が変わったら、2カ所以上、修正が必要になります。
            Dim sqlStr As String = "SELECT * FROM 名前 WHERE ID='" & DropDownList1.Text & "';"
            ↑ここに、SQL Injection が潜んでいます。
            Dim conn As New SqlConnection(connStr)
            Dim cmd As New SqlCommand(sqlStr, conn)
            conn.Open()
            Dim dr As SqlDataReader = cmd.ExecuteReader()
            dr.Read()
            ↑ここに例外が発生する可能性があります。すると、conn と dr がクローズできません。
            Try
                DropDownList1.Text = dr("ID")
                TextBox2.Text = dr("名前")
                If IsDBNull(dr("性別")) = False Then
                    TextBox3.Text = dr("性別")
                Else
                    TextBox3.Text = ""
                End If
    
                If IsDBNull(dr("血液型")) = False Then
                    TextBox4.Text = dr("血液型")
                Else
                    TextBox4.Text = ""
                End If
                ↑この Try ブロックで例外が送出される可能性は、ほとんどありません。
    
            Catch ex As Exception
                MsgBox("情報がありません。")
                ↑開発中は実行できているかもしれませんが、本番環境では使用できません。
            End Try
            dr.Close()
            conn.Close()
            Session("名") = TextBox2.Text
            Session("性") = TextBox3.Text
            Session("血") = TextBox4.Text
        End Sub
    
        Protected Sub Button2_Click(ByVal sender As Object, ByVal e As System.EventArgs)
            Dim 旧 As String = 0
            Dim 新 As String = 0
            Dim connStr As String = ConfigurationManager.ConnectionStrings("project_jobConnectionString2").ConnectionString
            ↑この文字列が変わったら、2カ所以上、修正が必要になります。
            Dim selectStr As String = "SELECT * FROM 履歴"
            Dim conn As New SqlConnection()
            conn.ConnectionString = connStr
            Dim selectCmd As New SqlCommand()
            selectCmd.Connection = conn
            selectCmd.CommandText = selectStr
            Dim da As New SqlDataAdapter()
            da.SelectCommand = selectCmd
            Dim ds As New DataSet()
            da.Fill(ds, "履歴")
            ↑Fill メソッドが例外を送出する可能性があります。
            Dim dt As DataTable = ds.Tables("履歴")
    
    
            For i = 0 To 2
                Select Case i
                    Case 0
                        旧 = Session("名")
                        新 = TextBox2.Text
                    Case 1
                        旧 = Session("性")
                        新 = TextBox3.Text
                    Case 2
                        旧 = Session("血")
                        新 = TextBox4.Text
                End Select
                If 旧 <> 新 Then
                    Dim newRow As DataRow = dt.NewRow()
                    Try
                        newRow("ID") = TextBox2.Text
                        newRow("変更内容") = TextBox3.Text
                        newRow("変更時間") = TextBox4.Text
                        dt.Rows.Add(newRow)
                        ↑この Try ブロックで例外が送出される可能性は、ほとんどありません。
                    Catch ex As Exception
                        MsgBox("データ更新に失敗しました。")
                        ↑データは、まだ更新されていません。
                        ↑開発中は実行できているかもしれませんが、本番環境では使用できません。
                    End Try
                End If
            Next
            Dim selectStr2 As String = "SELECT * FROM 名前"
            Dim conn2 As New SqlConnection()
            conn2.ConnectionString = connStr
            Dim selectCmd2 As New SqlCommand()
            selectCmd2.Connection = conn2
            selectCmd2.CommandText = selectStr2
            Dim da2 As New SqlDataAdapter()
            da2.SelectCommand = selectCmd2
            Dim ds2 As New DataSet()
            da2.Fill(ds2, "名前")
            ↑Fill メソッドが例外を送出する可能性があります。
            Dim dt2 As DataTable = ds2.Tables("名前")
            dt2.PrimaryKey = New DataColumn() {dt2.Columns("ID")}
            Dim targetRow2 As DataRow
            Try
                targetRow2 = dt2.Rows.Find(DropDownList1.Text)
                targetRow2("名前") = TextBox2.Text
                targetRow2("性別") = TextBox3.Text
                targetRow2("血液型") = TextBox4.Text
                ↑この Try ブロックで例外が送出される可能性は、ほとんどありません。
            Catch ex As Exception
                MsgBox("データ更新に失敗しました。")
                ↑データは、まだ更新されていません。
                ↑開発中は実行できているかもしれませんが、本番環境では使用できません。
            End Try
            Dim cb As New SqlCommandBuilder(da)
            da.Update(ds, "履歴")
            Dim cb2 As New SqlCommandBuilder(da2)
            da2.Update(ds2, "名前")
            ↑データが更新されるのはこの Update メソッドで、ここで例外が送出される可能性があります。
        End Sub
    

    例外処理例

    
    Dim dr As SqlDataReader
    Try
        conn.Open()
        Try
            dr = cmd.ExecuteReader()
            dr.Read()
        Catch ex As DbException
            ' データベースに関わるエラー処理
            ' ユーザーへの通知は、JavaScript などを使う
        Finally
            conn.Close()
        End Try
    Catch ex As DbException
        ' データベースに関わるエラー処理
        ' ユーザーへの通知は、JavaScript などを使う
    Finally
        dr.Close()
    End Try
    DropDownList1.Text = dr("ID")
    TextBox2.Text = dr("名前")
    TextBox3.Text = dr.IsDbNull(2) ? "" : dr("性別")
    TextBox4.Text = dr.IsDbNull(3) ? "" : dr("血液型")
    

    Jitta@わんくま同盟
    2009年4月24日 15:42
  • コードをどうやって書いたらいいのか
    何か参考になるHPはないでしょうか?

    よろしくお願いします。

    2009年4月24日 17:23
  • > コードをどうやって書いたらいいのか
    > 何か参考になるHPはないでしょうか?

    まずは以下のページのサンプルコードのうち「DetailsView コントロールを使用してレコードを追加、削除、
    および編集する方法のコード例を次に示します。」を参考に、DB からのデータの抽出、表示、修正、DB の
    更新をするコードを実装してください。イベントハンドラの部分以外は、ほとんどウィザードベースでコー
    ドを書かずにできるはずです。

    DetailsView クラス
    http://msdn.microsoft.com/ja-jp/library/system.web.ui.webcontrols.detailsview.aspx

    まずは DB からのデータの抽出、表示、修正、DB の更新が問題なくできるようにコードを試験して完成させ
    てください。更新記録を DB に書き込むコードを追加するのはその後で。

    次に、DetailsView.ItemUpdated イベントのハンドラ(上記のサンプルでは CustomerDetail_ItemUpdated
    メソッド)の中で DetailsViewUpdatedEventArgs.OldValues と DetailsViewUpdatedEventArgs.NewValues
    プロパティを利用して、更新前後のレコードの値を取得します。その例は以下のページが参考になると思います。

    DetailsViewUpdatedEventArgs.OldValues プロパティ
    http://msdn.microsoft.com/ja-jp/library/system.web.ui.webcontrols.detailsviewupdatedeventargs.oldvalues.aspx

    NULL の扱いに注意してください。上記のサンプルでは NULL があると e.OldValues(i).ToString() または
    e.NewValues(i).ToString() で例外がスローされます。

    更新前後の値が取得できれば、後は、それを比較して違っていたら更新記録を残すコードを実装すれば求め
    るものになるはずです。それはできますよね?

    なお、更新記録テーブルにレコードを Insert するのに DataSet と SqlDataAdapter を利用した非接続型
    データアクセスを使うのは、無駄が多いので考え直したほうがいいと思います。

    2009年4月25日 4:38
  • Insert文もSqlCommandで実行できますので、少なくとも履歴テーブルはそれを使って更新してみて下さい。
    書き忘れたことがありました。あまり一度にいろんなことを言うと混乱されるかもしれませんが、本来、名前テーブルの更新と履歴テーブルへの追加はトランザクションを切らなければなりません。単純な履歴追加であれば、場合によってはトリガを使って履歴テーブルに追加しても良いでしょう。
    とりあえず次のステップで考えてみて下さい。
    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/
    2009年4月25日 6:51
    モデレータ
  • OldValues、NewValues教えていただきありがとうございます。
    newRow("変更項目") = "☆変更レコード☆"
    に変更のあったテーブルの項目を表示したいのですが私では
    この項目分の配列要素をつくってiの数で識別するなど、
    長いコードを書くようになるんですが、便利なコードがあるのでしょうか?

    >なお、更新記録テーブルにレコードを Insert するのに DataSet と SqlDataAdapter を利用した非接続型
    >データアクセスを使うのは、無駄が多いので考え直したほうがいいと思います。

    ↓この変更の接続の仕方では無駄が多いのでしょうか?

        Sub CustomerDetail_ItemUpdated(ByVal sender As Object, _
          ByVal e As DetailsViewUpdatedEventArgs)
            Dim connStr As String = ConfigurationManager.ConnectionStrings("project_jobConnectionString2").ConnectionString
            Dim selectStr As String = "SELECT * FROM 履歴"
            Dim conn As New SqlConnection()
            conn.ConnectionString = connStr
            Dim selectCmd As New SqlCommand()
            selectCmd.Connection = conn
            selectCmd.CommandText = selectStr
            Dim da As New SqlDataAdapter()
            da.SelectCommand = selectCmd
            Dim ds As New DataSet()
            da.Fill(ds, "履歴")
            Dim dt As DataTable = ds.Tables("履歴")
            Dim i As Integer
            For i = 0 To e.OldValues.Count - 1
                If e.OldValues(i).ToString() <> e.NewValues(i).ToString() Then
                    Dim newRow As DataRow = dt.NewRow()
                    newRow("ID") = "1"
                    newRow("変更項目") = "☆変更レコード☆"
                    newRow("変更内容") = e.NewValues(i).ToString()
                    newRow("変更時間") = Now
                    dt.Rows.Add(newRow)
                End If
            Next
            Dim cb As New SqlCommandBuilder(da)
            da.Update(ds, "履歴")        
            CustomersView.DataBind()
        End Sub
    2009年4月27日 15:29
  • ↓この変更の接続の仕方では無駄が多いのでしょうか?


    一点確認。この処理の中で、履歴テーブルは INSERT するだけですよね?そうであるなら、SELECT する必要はありません。しかし、型付きデータセットを作りたくないため、SqlCommandBuilder を使いたいが為に、SELECT しているのだと思います。
    ここで、考えていただきたいのですが、この履歴テーブルは、どれくらいのデータ量(行数)になる予定なのでしょう?ここに使われている SELECT 文は、全てのデータを引っ張ってきます。このとき、データ量が多くなればそれだけ処理が遅くなりますし、メモリが足りなくなるという事態が発生するかもしれません。そのあたり、どのようにお考えでしょう?私は、1行1行の無駄より、こっちの無駄の方が大きいと思います。
    Jitta@わんくま同盟
    2009年4月28日 3:01
  • > newRow("変更項目") = "☆変更レコード☆"
    > に変更のあったテーブルの項目を表示したいのですが私では
    > この項目分の配列要素をつくってiの数で識別するなど、
    > 長いコードを書くようになるんですが、便利なコードがあるのでしょうか?

    質問の意味が不明確です。「変更のあったテーブルの項目」の「項目」と
    は更新されたフィールド名(最初の質問の「名前」とか「性別」)のこと
    ですか? 

    そうであれば、紹介した web ページ(後者の方)に "更新されたレコー
    ドの元のフィールドの名前/値ペアを格納しているディクショナリを取得
    します。" と書いてありますように OldValues, NewValues プロパティか
    ら取得できます。

    > ↓この変更の接続の仕方では無駄が多いのでしょうか?

    その前に、1フィールドごとでなく1回の更新でまとめて「履歴」テーブ
    ルに1レコード INSERT する等、簡略化の余地はないのでしょうか?

    DB の更新で無駄な部分は、DataSet と SqlDataAdapter を使っていると
    ころです。trapemiya さん、Jitta さんと同意見です。

    2009年4月28日 15:20
  • >質問の意味が不明確です。「変更のあったテーブルの項目」の「項目」と
    >は更新されたフィールド名(最初の質問の「名前」とか「性別」)のこと
    >ですか? 

    説明不足で申し訳ありません。
    変更項目にはその変更内容にあたる項目
     【名前】【性別】【血液型】のどれかがはいるようにしたいのですが
                    newRow("ID") = "1"
                    newRow("変更項目") = 【名前】OR【性別】OR【血液型】
                    newRow("変更内容") = e.NewValues(i).ToString()
                    newRow("変更時間") = Now

    紹介していただいたページでは 
    Old Value=前 , New Value=後
    と変更内容に入れたい値がでるのですが
    それが
     【名前】【性別】【血液型】のどの列のものなのか区別する項目が欲しいのですが

    INSERTのほうですが
    http://msdn.microsoft.com/ja-jp/library/ms174335.aspxを見てみたのですが
    使い方がよく理解できません。
    サンプルコードなどはないでしょうか?


    2009年4月29日 14:21
  • > 【名前】【性別】【血液型】のどの列のものなのか区別する項目が欲しいのですが

    OldValues, NewValues の中にその情報は入っています。

    > サンプルコードなどはないでしょうか?

    SQL Server, ADO.NET, INSERT などをキーワードにして検索すれば見つかると思います。

    2009年4月29日 15:02
  • ローカルを見て
    OldValues,NewValuesの中の情報を見てみました。

    NewValues,OldValuesはフィールドの数分Countがあるのですが
    KeysのCountは1なので読み込むことはできません。

    newRow("変更内容")=e.NewValues(i).ToString() ←期待通りの値が取れます
    newRow("変更項目")=e.Keys(i).ToString() ←Countが1なのでエラーになります。

    教えていただいたHPに

    Dim records(e.NewValues.Count) As DictionaryEntry
    e.NewValues.CopyTo(records,0)
    newRow("変更項目")=records(i).Key

    というコードが乗っていたのでそれで実行させましたが
    この場合recordsにKey,Valueがフィールドの数分設定されますが
    フィールド順ではなくフィールドの順番がバラバラなので、records(i)としても思ったタイトルが得られません。

    また、Insertのほうですが
    INSERTの方法というのがあったのでやってみたのですが
    SQL Serverで
            Insert into 履歴
            (ID,変更項目,変更内容,変更時間)
            values(01,名前,伊崎,2009/04/27 23:51:42)
    で、SQLの実行をするとINSERTできるのですが、VWDeveloper上でこれを行うということでしょうか?
    すみません。やり方がよくわかりません。できるのでしょうか??

    2009年4月30日 13:18
  • > NewValues,OldValuesはフィールドの数分Countがあるのですが
    > KeysのCountは1なので読み込むことはできません。

    e.Keys で取得できるのは主キーです。それは今回関係ありません。

    もっとスマートな方法があるかもしれませんが、とりあえず以下のような感じで取得できます。

    System.Collections.Generic.List<string> fieldNames = new System.Collections.Generic.List<string>();
    System.Collections.Generic.List<string> oldValues = new System.Collections.Generic.List<string>();
    System.Collections.Generic.List<string> newValues = new System.Collections.Generic.List<string>();

    foreach (DictionaryEntry entry in e.OldValues)
    {
        fieldNames.Add((string)entry.Key);
        oldValues.Add((string)entry.Value);
    }

    foreach (string name in fieldNames)
    {
        newValues.Add((string)e.NewValues[name]);
    }

    > SQL Serverで
    >         Insert into 履歴
    >         (ID,変更項目,変更内容,変更時間)
    >         values(01,名前,伊崎,2009/04/27 23:51:42)
    > で、SQLの実行をするとINSERTできるのですが、VWDeveloper上でこれを行うということでしょうか?
    > すみません。やり方がよくわかりません。できるのでしょうか??

    できます。自分でも SELECT クエリを ADO.NET を介して発行しているではないですか。INSERT も同じように
    できます。 レスを読んでますか? SQL Server, ADO.NET, INSERT をキーワードにして Google で検索して
    みましたか?

    2009年4月30日 13:55
  • INSERTできました。ありがとうございます。
        Sub CustomerDetail_ItemUpdated(ByVal sender As Object, _
          ByVal e As DetailsViewUpdatedEventArgs)
    
            Dim i As Integer
            Dim fieldNames As New System.Collections.Generic.List(Of String)()
            Dim oldValues As New System.Collections.Generic.List(Of String)()
            Dim newValues As New System.Collections.Generic.List(Of String)()
            For Each entry As DictionaryEntry In e.OldValues
                fieldNames.Add(DirectCast(entry.Key,String))
                oldValues.Add(DirectCast(entry.Value, String))
            Next
            For Each name As String In fieldNames
                newValues.Add(DirectCast(e.NewValues(name), String))
            Next
            For i = 0 To oldValues.Count - 1
                If oldValues(i) <> newValues(i) Then
                    Dim DBConn As SqlConnection
                    Dim DBAdd As New SqlCommand
                    Try
                        DBConn = New SqlConnection("Data Source=XXXX\SQLEXPRESS;Initial Catalog=project_job;Integrated Security=True;")
                        DBAdd.CommandText = "Insert Into 履歴(" & "変更項目,変更内容,変更時間" & ") values('" & fieldNames(i) & "'," & "'" & newValues(i) & "'," & "'" & Now & "'" & ")"
                        DBAdd.Connection = DBConn
                        DBAdd.Connection.Open()
                        DBAdd.ExecuteNonQuery()
                    Catch ex As DbException
                    Finally
                    End Try
                End If
            Next
            CustomersView.DataBind()
        End Sub
    これで、履歴テーブルができ、空白の場合もエラーがでずに更新、Null許可以外の情報をいれれば新規追加もできました。

    ですが、今のままでは
    GreidViewのテーブル情報が変更されても履歴テーブルが追加されない場合もあるなどのエラーも出る状態ですよね?
    これを両方正しく処理できなければ
    エラーが出るようにしたいのですがどのようにしたらいいのでしょうか?
    2009年5月2日 16:16
  • 気になった点をいくつか挙げます。

    1.NewValuesとOldValuesを比較するだけならListを使う必要はなく、そのまま比較すれば良い。

    2.SqlConnectionのインスタンスの作成とその設定、およびOpen()は一度行えば良いのでForループの外に出す。

    3.SqlConnectionはOpenしたらFinallyで必ずCloseすること。もしくはUsingを利用して自動的にCloseさせても良い。

    4.SQL文の変数の部分は絶対に文字列連結しないでパラメータを使うこと。SQLインジェクションの原因になります。
    "Insert Into 履歴(" & "変更項目,変更内容,変更時間" & ") values('" & fieldNames(i) & "'," & "'" & newValues(i) & "'," & "'" & Now & "'" & ")"

    (参考)
    上記3と4に関して、以下に例があります。

    【第56号】VB.NETデータベースプログラミング奮闘記
    http://archive.mag2.com/0000128094/20070821093000000.html

    5.DetailsViewの更新および履歴テーブルの更新はトランザクションを切る。

    一般の業務アプリケーションではトランザクションおよび同時実行制御が絡むことが多々あります。この2つについては常に意識するようにして下さい。したがって質の高い業務アプリケーションを作成するにはADO.NETの知識が必須ですので、しっかり基本を習得されると良いと思います。SQLインジェクションへの対策も忘れてはいけません。
    しかし、今回はとりあえず5の件に関しては後回しにしましょう。一度に実現しようとして問題が複雑化してもいけませんので。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/
    2009年5月3日 2:44
    モデレータ
  • > これを両方正しく処理できなければ
    > エラーが出るようにしたいのですがどのようにしたらいいのでしょうか?

    エラーが出るようにするより、途中で失敗したら巻き戻すようにする(要するに、すべての SQL 処理をなか
    ったことにする)のが普通です。

    同じ connection で SQL 処理が行われているのであれば、SqlTransaction クラスを利用してトランザクショ
    ン制御ができます。

    SqlTransaction クラス
    http://msdn.microsoft.com/ja-jp/library/system.data.sqlclient.sqltransaction.aspx

    今回の場合、SqlDataSource よる SQL 処理(「名前」テーブルの UPDATE)と今回追加した SQL 処理(「履歴」
    テーブルへの INSERT)を1つのトランザクションに束ねる必要があり、ちょっと工夫が必要です。以下のページ
    に紹介されているサンプルコードの2つ目が参考になると思います。

    SqlDataSource.Update メソッド
    http://msdn.microsoft.com/ja-jp/library/system.web.ui.webcontrols.sqldatasource.update.aspx

    その前に、trapemiya さんのレスの 1 ~ 4 の件にきちんと対処するのが先だと思いますが。(1 については
    もう少しスマートな方法はありますが、特に対処しなくても致命的ではないので、とりあえずはそのままでよい
    と思います。個人的には、2, 3, 4 を先に対処することをお勧めします)

    2009年5月3日 5:52
  • 2,3をしてみました。4のパラメータの使い方がよくわからないのですが
    DBAdd.CommandText = "Insert Into 履歴(" & "変更項目,変更内容,変更時間" & ") values('" & fieldNames(i) & "'," & "'" & newValues(i) &・・・・
    をDBAdd.Parameters.Addにするということでしょうか???CommandTextはどのようにしたらいいのでしょうか???
    また、SqlDataSource.Update メソッドのトランザクションのサンプルを実行できるようにし、今までのコードに使いたい部分を貼り付けてみましたがe.Commandで
    メンバではないというエラーが出てしまいました。
    これはサンプルではSqlDataSourceでだったからですよね?どのように対処したらいいのでしょうか?
        Sub CustomerDetail_ItemUpdated(ByVal sender As Object, _
          ByVal e As DetailsViewUpdatedEventArgs)
            Dim i As Integer
            Dim fieldNames As New System.Collections.Generic.List(Of String)()
            Dim oldValues As New System.Collections.Generic.List(Of String)()
            Dim newValues As New System.Collections.Generic.List(Of String)()
            For Each entry As DictionaryEntry In e.OldValues
                fieldNames.Add(DirectCast(entry.Key, String))
                oldValues.Add(DirectCast(entry.Value, String))
            Next
            For Each name As String In fieldNames
                newValues.Add(DirectCast(e.NewValues(name), String))
            Next
            Try
                Using DBConn As New SqlConnection("Data Source=EPKO\SQLEXPRESS;Initial Catalog=project_job;Integrated Security=True;")
                    Dim DBAdd As New SqlCommand
                    DBAdd.Connection = DBConn
                    DBAdd.Connection.Open()
                    For i = 0 To oldValues.Count - 1
                        If oldValues(i) <> newValues(i) Then
                            DBAdd.CommandText = "Insert Into 履歴(" & "変更項目,変更内容,変更時間" & ") values('" & fieldNames(i) & "'," & "'" & newValues(i) & "'," & "'" & Now & "'" & ")"
                            DBAdd.ExecuteNonQuery()
                        End If
                    Next
                End Using
            Catch ex As Exception
                Label2.Text = "例外発生"
            End Try
            '-------------
            Dim command As DbCommand
            Dim transaction As DbTransaction
            command = e.Command
            transaction = command.Transaction
    
            Dim OtherProcessSucceeded As Boolean = True
    
            If (OtherProcessSucceeded) Then
                transaction.Commit()
                Label2.Text = "更新しました!"
            Else
                transaction.Rollback()
                Label2.Text = "更新できませんでした"
            End If
            '-------------
            CustomersView.DataBind()
        End Sub
    
        Sub CustomerDetail_ItemUpdating(ByVal sender As Object, _
          ByVal e As DetailsViewUpdateEventArgs)
            '-------------             
            Dim command As DbCommand
            Dim connection As DbConnection
            Dim transaction As DbTransaction
    
            command = e.Command
            connection = command.Connection
            connection.Open()
            transaction = connection.BeginTransaction()
            command.Transaction = transaction
            '-------------        
            For i As Integer = 0 To e.NewValues.Count - 1
                If e.NewValues(i) IsNot Nothing Then
                    e.NewValues(i) = Server.HtmlEncode(e.NewValues(i).ToString())
                End If
            Next
        End Sub

    2009年5月3日 18:28
  • > 4のパラメータの使い方がよくわからないのですが
    > DBAdd.CommandText = "Insert Into 履歴(" & "変更項目,変更内容,変更時間" & ") values('" & fieldNames(i) & "'," & "'" & newValues(i) &・・・・
    > をDBAdd.Parameters.Addにするということでしょうか???

    そうです。変更項目、変更内容、変更時間のそれぞれについて、合計3つ For ループの外に記述します。

    パラメータへの値の代入は For ループの中で、DBAdd.Parameters("変更項目").Value = fieldNames(i) というように行います。
    これも変更項目、変更内容、変更時間のそれぞれについて3つ必要です。

    NULL(DetailsView では空白で表示されるはず) の扱いに注意してください。NULL があるなら「履歴」テーブルの該当フィー
    ルドを NULL 許容にするのはもちろん、上記の式で代入する値が Nothing の場合それに換えて DBNull.Value を代入するように
    してください。

    > CommandTextはどのようにしたらいいのでしょうか???

    以下のようにします。これも For ループの外に記述してください。

    DBAdd.CommandText = "Insert Into 履歴 (変更項目, 変更内容, 変更時間) values (@変更項目, @変更内容, @変更時間)"

    > また、SqlDataSource.Update メソッドのトランザクションのサンプルを実行できるようにし、今までのコードに使いたい部分を
    > 貼り付けてみましたがe.Commandでメンバではないというエラーが出てしまいました。
    > これはサンプルではSqlDataSourceでだったからですよね?どのように対処したらいいのでしょうか?

    何も考えずに単純にサンプルコードをコピペしているようですが、それでできるほど甘くはないです。自分の目的に合わせて、
    よく考えて、自分のコードとサンプルコードの整合を取って、書き換えてください。

    ポイントは以下の通りです。

    (1) DetailsView の DataSource に設定している SqlDataSource の Updating イベントのハンドラでトランザクションを開始
      し、DbCommand.Transaction にトランザクションを設定する。(これはサンプルコードの通り)

    (2) 同じ SqlDataSource の Updated イベントのハンドラで、その DbConnection と DbCommand.Transaction を取得。(これ
      もサンプルコードの通り)

    (3) 「履歴」テーブルへの INSERT は、上記 (2) で取得した DbConnection を利用する(新たに New SqlConnection しない)。

    (4) 「履歴」テーブルへの INSERT のための SqlCommand(アップされたコードでは DBAdd)の Transaction プロパティに、
      上記 (2) で取得した DbCommand.Transaction を設定する。

    (5) すべての SQL 処置が終わったら Commit する。どこかで失敗したら(例外を捕捉したら) Rollback する。

    #上記とは関係ないことですが一つ言い忘れました。接続文字列をコードの中に埋め込むのはやめて、Web.config に設定して
     そこから取得するのをお勧めします。

    2009年5月4日 1:56
  • パラメータの部分で質問させてください。
    >パラメータへの値の代入は For ループの中で、DBAdd.Parameters("変更項目").Value = fieldNames(i) というように行います。
    >これも変更項目、変更内容、変更時間のそれぞれについて3つ必要です。
    >以下のようにします。これも For ループの外に記述してください。
    >DBAdd.CommandText = "Insert Into 履歴 (変更項目, 変更内容, 変更時間) values (@変更項目, @変更内容, @変更時間)"

            For i = 0 To oldValues.Count - 1
                If oldValues(i) <> newValues(i) Then
                    DBAdd.Parameters.Add("@変更項目", SqlDbType.VarChar, 20).Value = fieldNames(i)
                    DBAdd.Parameters.Add("@変更内容", SqlDbType.VarChar, 20).Value = newValues(i)
                    DBAdd.Parameters.Add("@変更時間", SqlDbType.DateTime, 20).Value = Now
                End If
            Next
            DBAdd.CommandText = "Insert Into 履歴 (変更項目, 変更内容, 変更時間) values (@変更項目, @変更内容, @変更時間)"
            DBAdd.ExecuteNonQuery()

    ↑このように修正したのですがこれでは1項目の更新なら処理できるのですが(ParametersのCount=3)、2つ以上の更新があった場合(ParametersのCount=6以上)、
    変数名 '@変更項目' は既に宣言されています。変数名は、クエリ バッチまたはストアド プロシージャ内で一意にしてください。というエラーがでてしまいます。

    2009年5月4日 5:34
  • 相変わらずレスをきちんと読んでないようですね。DBAdd.Parameters.Add は For ループの外だと言ったでしょ。
    中ではループが回るたびパラメータが追加されて '変数名 '@変更項目' は既に宣言されています ' というエラ
    ーになります。

    ただし、Value を設定するのは For ループの中です。ExecuteNonQuery も中ですよ!  以下のように書き直し
    てください。

    DBAdd.CommandText = "Insert Into 履歴 (変更項目, 変更内容, 変更時間) values (@変更項目, @変更内容, @変更時間)"

    DBAdd.Parameters.Add("@変更項目", SqlDbType.VarChar)
    DBAdd.Parameters.Add("@変更内容", SqlDbType.VarChar)
    DBAdd.Parameters.Add("@変更時間", SqlDbType.DateTime)

    For i = 0 To oldValues.Count - 1
        If oldValues(i) <> newValues(i) Then
            DBAdd.Parameters.Add("@変更項目").Value = fieldNames(i)
            DBAdd.Parameters.Add("@変更内容").Value = newValues(i)
            DBAdd.Parameters.Add("@変更時間").Value = Now
            DBAdd.ExecuteNonQuery()
        End If
    Next

    前にも言いましたが、newValues(i) が Nothing(DetailsView で空白にするとそうなるはず)だと例外がスロー
    されるので注意!

    2009年5月4日 9:44
  • ありがとうございます。
    Parametersの設定、newValues(i) のNothing処理がうまくいきました。
    トランザクションの方ですが(1) (2) (3) をやってみましたが
    (4) 「履歴」テーブルへの INSERT のための SqlCommand(アップされたコードでは DBAdd)の Transaction プロパティに、
      上記 (2) で取得した DbCommand.Transaction を設定する。

    (5) すべての SQL 処置が終わったら Commit する。どこかで失敗したら(例外を捕捉したら) Rollback する。

    の処理の仕方がどうやってやったらいいのかわかりません。

        Sub CustomerDetail_ItemUpdating(ByVal sender As Object, _
          ByVal e As DetailsViewUpdateEventArgs)
            For Each entry As DictionaryEntry In e.OldValues
                fieldNames.Add(DirectCast(entry.Key, String))
                oldValues.Add(DirectCast(entry.Value, String))
            Next
            For Each name As String In fieldNames
                newValues.Add(DirectCast(e.NewValues(name), String))
            Next
        End Sub
    
        Protected Sub On_Sql_Updating(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.SqlDataSourceCommandEventArgs)
            '-------------             
            Dim command As DbCommand
            Dim connection As DbConnection
            Dim transaction As DbTransaction
            command = e.Command
            connection = command.Connection
            connection.Open()
            transaction = connection.BeginTransaction()
            command.Transaction = transaction
            '-------------        
            Try
                Using connection
                    Dim DBAdd As New SqlCommand
                    DBAdd.Connection = connection
                    DBAdd.Connection.Open()
                    DBAdd.CommandText = "Insert Into 履歴 (変更項目, 変更内容, 変更時間) values (@変更項目, @変更内容, @変更時間)"
                    DBAdd.Parameters.Add("@変更項目", SqlDbType.VarChar)
                    DBAdd.Parameters.Add("@変更内容", SqlDbType.VarChar)
                    DBAdd.Parameters.Add("@変更時間", SqlDbType.DateTime)
                    Dim i As Integer
                    For i = 0 To oldValues.Count - 1
                        If oldValues(i) <> newValues(i) Then
                            DBAdd.Parameters("@変更項目").Value = fieldNames(i)
                            If newValues(i) = Nothing Then
                                DBAdd.Parameters("@変更内容").Value = ""
                            Else
                                DBAdd.Parameters("@変更内容").Value = newValues(i)
                            End If
                            DBAdd.Parameters("@変更時間").Value = Now
                            DBAdd.ExecuteNonQuery()
                        End If
                    Next
                End Using
            Catch ex As Exception
                Label2.Text = "例外発生"
            End Try
        End Sub
        
        Protected Sub On_Sql_Updated(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.SqlDataSourceStatusEventArgs)
            Dim command As DbCommand
            Dim transaction As DbTransaction
            command = e.Command
            transaction = command.Transaction
            Dim OtherProcessSucceeded As Boolean = True
            If (OtherProcessSucceeded) Then
                transaction.Commit()
                Label2.Text = "更新しました!"
            Else
                transaction.Rollback()
                Label2.Text = "更新できませんでした"
            End If
            CustomersView.DataBind()
        End Sub


    2009年5月4日 14:21
  • > の処理の仕方がどうやってやったらいいのかわかりません。

    レスをきちんと読んで、よく考えれば分かると思いますけど。

    十分な情報はすでに書いたつもりです。参考になる web サイトも紹介しました。不足する分は MSDN ラ
    イブラリや google などで検索すれば得られるはずです。

    以前も同じことを言ったと思いますが、そもそもレスをきちんと読んでいないように見えます。その点
    を改善してください。

    2009年5月5日 4:24
  • >今回の場合、SqlDataSource よる SQL 処理(「名前」テーブルの UPDATE)と今回追加した SQL 処理(「履歴」
    >テーブルへの INSERT)を1つのトランザクションに束ねる必要があり、ちょっと工夫が必要です。以下のページ
    >に紹介されているサンプルコードの2つ目が参考になると思います。SqlDataSource.Update メソッド
    >http://msdn.microsoft.com/ja-jp/library/system.web.ui.webcontrols.sqldatasource.update.aspx

    教えてください。トランザクション処理がよくわからず2つ目のサンプルコードを調べてみていたのですが

    トランザクション処理というのはデータベースへの接続にロックをかける
    →処理の成功の場合commitする(ロック解除、トランザクションの開始からの処理を実行)
    →処理の失敗の場合Rollbackする(トランザクションの開始からの処理を実行しない)ですよね

    ですが、この例で
        If (OtherProcessSucceeded) Then
            transaction.Commit()
                Label2.Text = "更新しました!"
    のIf (OtherProcessSucceeded) Thenの部分で処理を停止させデータベースをみると
    Commitしていないのに変更しようとおもって入れた値がすでに反映されてしまいました。

    2009年5月5日 15:19
  • > Commitしていないのに変更しようとおもって入れた値がすでに反映されてしまいました。

    VWD のデバッガはそういう仕様(?)になっているようです。

    [デバッグの停止]でも実行は止まらない?
    http://social.msdn.microsoft.com/forums/ja-JP/vsgeneralja/thread/e3c22b97-da56-4781-b520-4af607c73ab0/

    2009年5月6日 0:32
  • すみません。いろいろ検索してみたのですが4がどうしたらいいのかよくわかりません。

    (4) 「履歴」テーブルへの INSERT のための SqlCommand(アップされたコードでは DBAdd)の Transaction プロパティに、
      上記 (2) で取得した DbCommand.Transaction を設定する。

    これは
    現在のトランザクションのほかに
    ☆の部分に新しくトランザクション2 DBAdd)の開始というコードを追加すればいいということでしょうか??

    そうなると
    トランザクション1開始→トランザクション2開始→トランザクション1終了→トランザクション2終了というように
    トランザクションを分けることで名前テーブルを変更しても履歴テーブルは変更されないなどの不具合が発生するのでは???
    全く見当違いな質問をしているのでしょうか?すみません。もう少し考えるヒントをいただけないでしょうか?

            Dim command As DbCommand
            Dim connection As DbConnection
            Dim transaction As DbTransaction
            command = e.Command
            connection = command.Connection
            connection.Open()
            transaction = connection.BeginTransaction()
            command.Transaction = transaction
            '-------------       
            Try
                Using DBAdd As SqlCommand = connection.CreateCommand
                    DBAdd.CommandText = "Insert Into 履歴 (変更項目, 変更内容, 変更時間) values (@変更項目, @変更内容, @変更時間)"
    ☆トランザクション2の設定☆☆☆☆
                    DBAdd.Parameters.Add("@変更項目", SqlDbType.VarChar)
                    DBAdd.Parameters.Add("@変更内容", SqlDbType.VarChar)
                    DBAdd.Parameters.Add("@変更時間", SqlDbType.DateTime)
                    Dim i As Integer
                    For i = 0 To oldValues.Count - 1
                        If oldValues(i) <> newValues(i) Then
                            DBAdd.Parameters("@変更項目").Value = fieldNames(i)
                            If newValues(i) = Nothing Then
                                DBAdd.Parameters("@変更内容").Value = ""
                            Else
                                DBAdd.Parameters("@変更内容").Value = newValues(i)
                            End If
                            DBAdd.Parameters("@変更時間").Value = Now
                            DBAdd.ExecuteNonQuery()
                        End If
                    Next
                End Using
            Catch ex As Exception
                Label2.Text = "例外発生"
            End Try

    2009年5月7日 15:10
  • > これは
    > 現在のトランザクションのほかに
    > ☆の部分に新しくトランザクション2 DBAdd)の開始というコード
    > を追加すればいいということでしょうか??

    違います。

    先のレスで「SqlDataSource よる SQL 処理(名前テーブルの UPDATE)と今回追加し
    た SQL 処理(履歴テーブルへの INSERT)を1つのトランザクションに束ねる」と書
    いたとおりです。レスを読んでください。

    > もう少し考えるヒントをいただけないでしょうか?

    依然として「履歴」テーブルへの INSERT のための SQL 処理を SqlDataSource.Updating
    のイベントのハンドラで行っているなら、まずそれが間違いです。

    Updating と Updated イベントの違いを調べて、どの時点で行うべきかよく考えてく
    ださい。

    そこが理解できれば、すべての疑問は解けるのではないかと思います。

    2009年5月8日 13:42
  • Updating更新操作の前に発生
    Updated 更新操作が終了したときに発生
    ということはUpdateCommandに追加コードを書き込めばいいということでしょうか?

    UpdateCommand="UPDATE 名前 SET 名前 = @名前, 性別 = @性別, 血液型 = @血液型 WHERE ID = @ID" +
                        "INSERT INTO 履歴 (変更項目, 変更内容, 変更時間) VALUES (@変更項目, @変更内容, @変更時間)"
            onupdated="On_Sql_Updated" onupdating="On_Sql_Updating">

    としていれてみたのですがエラーがでてしまい、実行できない状態です。
    どちらかのテーブル作業条件を消せば実行できるのですが2つにするとエラーになります。
    1つのCommandで複数の条件さらには履歴の場合は
    変更があるのみ履歴テーブルを追加としますがそんなに長く指定することができるのでしょうか?

    2009年5月9日 16:13
  • > ということはUpdateCommandに追加コードを書き込めばいいということでしょうか?

    違います。SqlDataSource よる SQL 処理のコードに手を加えてはいけません。

    履歴テーブルに INSERT するための SQL 処理のコードを、Updating イベントのハンドラに記述す
    るのは間違いで、Updated イベントのハンドラに記述すると書いたつもりですが、理解できなかっ
    たでしょうか?

    そして、その SQL 処理を一つのトランザクションにまとめると書きましたが、そこのところは理
    解できているでしょうか?

    もう少し具体的に書くと、先に紹介した以下のページのサンプルコード(2つ目のもの)で、

    SqlDataSource.Update メソッド
    http://msdn.microsoft.com/ja-jp/library/system.web.ui.webcontrols.sqldatasource.update.aspx

    (1) OnSqlUpdating メソッドの中のコードには一切手を加えないでそのままコピペして使用。

    (2) OnSqlUpdated メソッドの中のコードで、コメントの部分を履歴テーブルに INSERT するための
      SQL 処理のコードに書き換える。SqlConnection と SqlCommand.Transaction の設定に注意。
      サンプルコードの DbTransaction tx = command.Transaction; に続けて以下のようにします。

            DbConnection connection = command.Connection;
            string insertQuery = "Insert Into 履歴 (変更項目, 変更内容, 変更時間) " +
                                 "values (@変更項目, @変更内容, @変更時間)";
            SqlCommand sqlCom = new SqlCommand(insertQuery, (SqlConnection)connection);
            sqlCom.Transaction = (SqlTransaction)tx;

    (3) 処理が成功したら Commit、失敗したら RollBack するよう bool OtherProcessSucceeded = true;
      以下のコードを書き換える。

    2009年5月10日 2:58
  • ありがとうございます。
    思った操作ができるようになりました。
    これで、名前テーブルが変更されて履歴テーブルに追加されないということがおきなくなりましたよね。
    その他起こり得る、エラー処理を施したく文字数制限をつける、名前のみNULL不許可にするようにしました。

    他にオプティミスティック同時実行制御を付けたいのですが
    この場合、サンプルの場合カスタムSQLステートメントまたはストアドプロシージャを指定するにしているので
    同時実行制御のチェックボタンは付けられませんよね?

    この場合どうしたらいいのでしょうか?

        Protected Sub On_Sql_Updated(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.SqlDataSourceStatusEventArgs)
            Dim command As DbCommand
            Dim transaction As DbTransaction
            command = e.Command
            transaction = command.Transaction
            Dim connection As DbConnection = Command.Connection
            Dim insertQuery As String = "Insert Into 履歴 (変更項目, 変更内容, 変更時間) " & "values (@変更項目, @変更内容, @変更時間)"
            Dim sqlCom As New SqlCommand(insertQuery, DirectCast(connection, SqlConnection))
            sqlCom.Transaction = DirectCast(transaction, SqlTransaction)
            sqlCom.Parameters.Add("@変更項目", SqlDbType.VarChar)
            sqlCom.Parameters.Add("@変更内容", SqlDbType.VarChar)
            sqlCom.Parameters.Add("@変更時間", SqlDbType.DateTime)
    
            Dim OtherProcessSucceeded As Boolean = True
            If (OtherProcessSucceeded) Then
                Dim i As Integer
                For i = 0 To oldValues.Count - 1
                    If oldValues(i) <> newValues(i) Then
                        sqlCom.Parameters("@変更項目").Value = fieldNames(i)
                        If newValues(i) = Nothing Then
                            sqlCom.Parameters("@変更内容").Value = ""
                        Else
                            sqlCom.Parameters("@変更内容").Value = newValues(i)
                        End If
                        sqlCom.Parameters("@変更時間").Value = Now
                        sqlCom.ExecuteNonQuery()
                    End If
                Next
                transaction.Commit()
                Label2.Text = "更新しました!"
            Else
                transaction.Rollback()
                Label2.Text = "更新できませんでした"
            End If
            CustomersView.DataBind()
        End Sub



    2009年5月10日 7:44
  • > これで、名前テーブルが変更されて履歴テーブルに追加されないということがおきなくなりましたよね。

    コードがぜんぜんダメですよ。Dim OtherProcessSucceeded As Boolean = True なんてしたら
    INSERT に失敗しても True で、RollBack はかからないことが理解できませんか?

    > 他にオプティミスティック同時実行制御を付けたいのですが

    楽観的同時実行制御なんて考える前に、もっと勉強してコードをきちんと書けるようになるの
    が先です。

    前にも書きましたが、ある程度知識がないと言葉が通じないので、掲示板のやり取りでは無理
    です。

    2009年5月10日 9:38
  • トランザクションの処理で2テーブル行う処理例を見てみたのですが下記コードのようなものでした。
    このコードの動きは日本語で書いてみたのですが理解できました。

    ですが今回Updating(更新操作の前に発生)→SQLのUPDATE→Updated(更新操作が終了したときに発生)と分かれて書いているため
    どこで、どのようにしたらいいのか理解できない状態です。

    下記コードでは'【データを書き換える・追加する】のところで、ExecuteNonQuery処理をしているのがわかるのですが、
    今回のUPDATEが失敗した場合、コードはどのようにどこに書いたらいいのかわかりません。

    アドバイスいただけないでしょうか?よろしくお願いします。

            '名前と履歴テーブルへのINSERT・UPDATEステートメント配列				
            Dim insOrderSql() As String = _				
                        {"UPDATE 名前 SET 名前 = @名前, 性別 = @性別, 血液型 = @血液型 WHERE ID = @ID;", _				
                         "Insert Into 履歴 (変更項目, 変更内容, 変更時間) " & "values (@変更項目, @変更内容, @変更時間)"}				
        ’トランザクション処理をするに当たって処理の成功の確認		
            Dim TransactResult As Boolean = True		
        ’トランザクション処理の開始 Using scope As New System.Transactions.TransactionScope(Transactions.TransactionScopeOption.RequiresNew)
    ’接続先

    Using con As New SqlConnection(ConnectionString) con.Open() Using cmd1 As SqlCommand = con.CreateCommand() cmd1.CommandType = CommandType.Text Try '名前テーブルデータ変更用のコマンドオブジェクトの用意 With cmd1 .Parameters.Add("@名前", SqlDbType.VarChar) .Parameters.Add("@性別", SqlDbType.VarChar) .Parameters.Add("@血液型", SqlDbType.VarChar) End With '履歴テーブルデータ挿入用のコマンドオブジェクトの用意 Using cmd2 As SqlCommand = con.CreateCommand() cmd2.CommandType = CommandType.Text With cmd2 .Parameters.Add("@変更項目", SqlDbType.VarChar) .Parameters.Add("@変更内容", SqlDbType.VarChar) .Parameters.Add("@変更時間", SqlDbType.DateTime) End With 'データを書き換える・追加する cmd1.CommandText = insOrderSql(0) cmd1.ExecuteNonQuery() cmd2.CommandText = insOrderSql(1) cmd2.ExecuteNonQuery() End Using
                 ’実行する scope.Complete() Catch er As Exception
                 ’例外が発生した場合Falseにします TransactResult = False Finally con.Close() End Try End Using End Using End Using
         ’Completeを実行しない場合はトランザクション前の状態に戻ります。 Return TransactResult


     

    2009年5月18日 15:09
  • まず、一つ前のコードに戻してください(今回は TransactionScope は使わない
    前提で話をしています)。

    > 今回のUPDATEが失敗した場合、コードはどのようにどこに書いたらいいのか
    > わかりません。

    「どこに」は Updated メソッドの中、「どのように」は Updated メソッドの引数 e
    から例外および更新された行数を取得して UPDATE が成功したか否かを判断で
    きます。失敗していたら RollBack して return、成功していたら履歴テーブルへ
    の INSERT に進めばよいでしょう。

    ちなみに、履歴テーブルへの INSERT は、先のコードに try - catch ブロックを
    追加し(サンプルコードにあった if - else ではなく)、try ブロックで例外を補足し
    たら catch で RollBack、成功したら Commit する(try ブロックの中の一番最
    後に Commit を記述)ことでうまくいくと思います。

    その他、RollBack する前の SqlTransaction の確認、connection の Close
    のコード等も適宜追加してください。

    2009年5月19日 13:21
  • 1つ前のコードに戻してみました。動きがよくわからないので自分なりの日本語訳をかいてみたのですが
    動きがCommitの方はうまくいっているのですがRollBackの方があやふやな感じです。

    また、更新された行数を取得するということですが、このコードの書き方がよくわからないのですが
    どのようにして取得できるのでしょうか?

        Protected Sub On_Sql_Updating(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.SqlDataSourceCommandEventArgs)
            Dim command As DbCommand
            Dim connection As DbConnection
            Dim transaction As DbTransaction
            'UPDATEコマンドUpdateCommand="UPDATE 名前 SET 名前 = @名前, 性別 = @性別, 血液型 = @血液型 WHERE ID = @ID"
            command = e.Command
            '接続の宣言
            connection = command.Connection
            '接続を開く
            connection.Open()
            '開いた接続にトランザクションをする宣言
            transaction = connection.BeginTransaction()
            command.Transaction = transaction
        End Sub
        
        Protected Sub On_Sql_Updated(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.SqlDataSourceStatusEventArgs)
            Dim command As DbCommand
            Dim connection As DbConnection
            Dim transaction As DbTransaction
            Dim OtherProcessSucceeded As Boolean = True
            'UPDATEコマンドUpdateCommand="UPDATE 名前 SET 名前 = @名前, 性別 = @性別, 血液型 = @血液型 WHERE ID = @ID"
            command = e.Command
            connection = command.Connection
            'トランザクションをする宣言
            transaction = command.Transaction
            Dim rowCount As Integer
            Try
                With command
                    .CommandType = CommandType.Text
                    '更新した行のカウントがしたいのですがどのようにしたらいいのかわりません
                    .CommandText = "SELECT Count(*) FROM 名前 WHERE ID = @ID"               
                End With
                '名前テーブルのIDをカウントする
                rowCount = Convert.ToInt32(command.ExecuteScalar())
               
                If rowCount <> 0 Then
                    Dim insertQuery As String = "Insert Into 履歴 (変更項目, 変更内容, 変更時間) " & "values (@変更項目, @変更内容, @変更時間)"
                    Dim sqlCom As New SqlCommand(insertQuery, DirectCast(connection, SqlConnection))
                    With sqlCom
                        .Transaction = DirectCast(transaction, SqlTransaction)
                        .Parameters.Add("@変更項目", SqlDbType.VarChar)
                        .Parameters.Add("@変更内容", SqlDbType.VarChar)
                        .Parameters.Add("@変更時間", SqlDbType.DateTime)
                    End With
           
                    Dim i As Integer
                    For i = 0 To oldValues.Count - 1
                        If oldValues(i) <> newValues(i) Then
                            sqlCom.Parameters("@変更項目").Value = fieldNames(i)
                            If newValues(i) = Nothing Then
                                sqlCom.Parameters("@変更内容").Value = ""
                            Else
                                sqlCom.Parameters("@変更内容").Value = newValues(i)
                            End If
                            sqlCom.Parameters("@変更時間").Value = Now
                            sqlCom.ExecuteNonQuery()
                        End If
                    Next
                Else
                    OtherProcessSucceeded = False
                End If
            Catch ex As Exception
                OtherProcessSucceeded = False
            End Try
           
                If (OtherProcessSucceeded) Then
                    transaction.Commit()
                    Label2.Text = "更新しました!"
                Else
                transaction.Rollback()
                Label2.Text = "更新できませんでした"
                Return
            End If
            connection.Close()
            CustomersView.DataBind()
        End Sub
    2009年5月19日 16:27
  • > 更新された行数を取得するということですが、このコードの書き方がよくわからないのですが
    > どのようにして取得できるのでしょうか?

    相変わらずレスをきちんと読んでないようですね。何から取得できるかは一つ前のレスに書き
    ましたけど。

    せっかく書いても読んでもらえないのならレスする意味がないのでやめます。読んでも分から
    ないなら、どこが分からないか書いてください。でないと、話にならないです。

    2009年5月20日 14:41
  • すみません。いつも、レスを読んでいるのですがきちんと対応できていない、理解できないことが多くて後からああ!ということの連続です。
    いつもご回答いただき本当にありがとうございます。
    ご指摘のとおりeを実行中みてみましたらUpdateRowSourceをみつけました。
    Dim rowCount As Integer~If rowCount <> 0 Thenまでを以下のようにかえてみたのですが
    うまく実行でき、If rowCount = 0 Thenとしてみたら、更新も追加もされていなくうまくトランザクション処理が実行できていました。

            Dim rowCount As Integer
            Try
                rowCount = UpdateRowSource.Both
                If rowCount <> 0 Then

    ですが、Bothの使い方がよくわかりません。
    どの行を変更しても必ずrowCount=3となりますし
    もし、UpdateRowSource.Bothで変更が無かった場合rowCountは0となりIf rowCount <> 0 Thenが
    ちゃんと機能するのでしょうか?
    both{3}という意味が理解できずHPで検索してみたのですが説明がみつからず、使い方がよくわかりません。
    参考になるHPがありましたら教えてください。
    また、現在のコードで何か問題等はないでしょうか?
    よろしくお願いします。

    2009年5月20日 16:24
  • > ご指摘のとおりeを実行中みてみましたらUpdateRowSourceをみつけました。

    どこをどう探すと UpdateRowSource が見つかるのか不思議です。「実行中」ということですから、
    デバッガで e の中身を覗いてみたのでしょうか? 何にせよ見当はずれの方向に進んでいるよう
    です。

    VS2008 のヘルプや Web のMSDN ライブラリを見てください。それで、引数 e から何が取得
    できるかを調べるのです。

    引数 e は、ByVal e As System.Web.UI.WebControls.SqlDataSourceStatusEventArgs
    となっていることから分かるように、SqlDataSourceStatusEventArgs 型(クラス)です。それを
    キーワードにして検索すれば分かるはずです。

    SqlDataSourceStatusEventArgs メンバ
    http://msdn.microsoft.com/ja-jp/library/system.web.ui.webcontrols.sqldatasourcestatuseventargs_members.aspx

    ちなみに UpdateRowSource も、VS2008 Document や MSDN ライブラリを調べれば説明は
    見つかります。その説明を読めば、UpdateRowSource を使うのは見当違いであることは分かる
    はずです。

    Web 上での MSDN ライブラリでの検索は手間と時間がかかるので、MSDN Library for Visual
    Studio 2008 SP1 をダウンロードしてオフラインで使うことをお勧めします(2GB 超とサイズが大き
    いですが)。

    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年5月21日 13:51
  • 調べ方について質問させてください。
    Studio 2008 SP1 をダウンロードして、デスクトップにJPNMSDNX1530336.ISOというファイルが作成されました。
    これを開くとどのアプリケーションで開くかということをきかれるのですが、どう実行できるのでしょうか?

    http://msdn.microsoft.com/ja-jp/library/system.web.ui.webcontrols.sqldatasourcestatuseventargs_members.aspx
    を見てみたのですが更新された行数を取得するということですので
    AffectedRows(データベース操作の影響を受けた行の数を取得。)をつかうということでしょうか?
    規定値は-1となっていましたので

            Dim rowCount As Integer
            Try
                rowCount = AffectedRows
                If rowCount <> -1 Then
    に設定したのですが、何も変更せずに更新ボタンを押しても、AffectedRowsが1とされデータが履歴テーブルに追加されてしまいます。

    2009年5月23日 14:23
  • > Studio 2008 SP1 をダウンロードして、デスクトップにJPNMSDNX1530336.ISOというファイルが作成されました。
    > これを開くとどのアプリケーションで開くかということをきかれるのですが、どう実行できるのでしょうか?

    それはパソコン初心者の質問ですよ。ちょっとそこまではお付き合いできないって感じなんですけど。

    答えは紹介したダウンロードサイトに書いてありますし、それで分からなければ「iso イメージ」でググって調
    べればわかります。

    >             rowCount = AffectedRows
    >             If rowCount <> -1 Then
    > に設定したのですが、何も変更せずに更新ボタンを押しても、AffectedRowsが1とされデータが履歴テーブルに
    > 追加されてしまいます。

    実際は rowCount = e.AffectedRows としてませんか? rowCount = AffectedRows ではコンパイラも通
    らないはずです。

    正しく rowCount = e.AffectedRows となっているとして、DetailsView で何も変更せずに更新ボタンを押して
    rowCount が 1 になるのは正しいです。なぜなら、同じデータで上書きされ、データベース操作の影響を受けた
    行の数は 1 になるからです。

    同じデータでも履歴テーブルに追加されてしまうのは、If oldValues(i) <> newValues(i) Then のところがうま
    く動いていないからではないですか?

    2009年5月24日 3:04
  • ありがとうございます。
    ご指摘のとおりIf oldValues(i) <> newValues(i) Then のところがうまく動作していなかったようでした。

    これで、思うような操作ができるようになったのですが
    DetailsViewのテンプレートの中にPANELやボタンを使うことは不可能なのでしょうか?
    DetailsViewの中に
    panel1.Visible=falseなどとしてもpanel1に波線がでて実行できません。

    2009年5月25日 13:18
  • > DetailsViewのテンプレートの中にPANELやボタンを使うことは不可能なのでしょうか?

    可能です。でも、表題とは違うことなので、別のスレッドを新たに立てて質問してください。


    それより、先のコードでは例外処置がきちんとできてませんよ。紹介した Web Site をよく読
    んで、自助努力でコードを完成させてください。

    このスレッドは MSG が多くなりすぎました。この辺で自分がレスするのは終わりにしたいと
    思います。

    2009年5月25日 14:16
  • 例外処置を試してみます。
    いつも、ありがとうございます。

    2009年5月25日 15:25