none
BindingSoure.AddNewで追加した行の参照でエラーが発生する RRS feed

  • 質問

  • [VB2005、MSSQL2005]

    ■エラー内容
     BindingSource.AddNewメソッドを実行します。
     その後に発生するCurrentChangedイベントにて、
     カレント行のdenno列を参照しようとすると、値がDBNullとなっており、
     InvalidCastExceptionのエラーが発生します。
     尚、カレント行の取得自体は問題ありませんでした。

    ■カレント行の取得に使ったコード
      Dim drow As Ds1.TestUriRow
      drow = CType(CType(Bs1.Current, DataRowView).Row, Ds1.TestUriRow)

    ■エラー箇所(Ds1.Designer.vb)
      Partial Public Class TestUriRow
          Public Property denno() As String
              Get
                  Try
                      Return CType(Me(Me.tableTestUri.dennoColumn),String)

    ■知りたいこと
      BindingSourceのAddingNewイベントハンドラにて、
      適切な値を設定したなんらかのインスタンスを
      e.NewObjectに返せばよいのかと思いますが、それが何なのかが分かりません。
      どなたかアドバイスをよろしくお願いいたします。
      尚、私が何か考え違いをしてるかも知れませんので、
      長くなり申し訳ありませんが、念のため実行環境も提示いたします。

    ■実行環境
    DBにTestUri(テーブル)を作成
      denno    VARCHAR(8)
      shncode  VARCHAR(12)
      shnname  VARCHAR(50)
      suryo    INT
      tanka    INT
      kingk    INT
      gyono    INT
      datano   INT  PrimaryKey  Identity

      ※denno~shncodeはNull許可、及び規定値に("")(0)を設定。
      (Null不許可だとAddNewすらできないので、やむをえず...)

    Ds1(データセット)に以下の物を作成
      TestUri(データテーブル、DB側と同一レイアウト)
      TestUriTableAdapter(テーブルアダプタ)

    フォームに以下のコントロールを配置
      Ds1、TestUriTableAdapter、Bs1(バインディングソース)

      Dgv1(データグリッドビュー)
       … TestUriの全列を設定。
       … データソースにBs1を設定。
       … 文字型の列のDefaultCellStyle.NullValue及び
         DataSourceNullValueに空文字("")を設定。

      Dgv1のカレント行に対応するTextBox
       … TxtDenno,TxtShncode ~ TxtDatano
         ※これらはバインドさせず、コードで対応するように処理。

      ButtonAddNew … Bs1.AddNewを実行。

      ButtonUpdate … Bs1.Updateを実行。

    2006年9月16日 15:28

回答

  • 諸農です。

     Deer327 さんからの引用

    私の環境では
    Table1BindingSource_CurrentChangedの
     If r(0) Is DBNull.Value Or で、キャストエラーになります。
    うーん、変ですねえ。
    テキストボックスへのバインドも設定して試したんですが...

    ~~ 中略 ~~

        Private Sub Bs1_CurrentChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles Bs1.CurrentChanged
            Dim drow As Ds1.TestUriRow
            drow = CType(CType(Bs1.Current, DataRowView).Row, Ds1.TestUriRow)
            With drow
                If .denno Is DBNull.Value Then  <= ここでエラーが発生
                    .denno = ""
                    .shncode = ""
                    .shnname = ""
                    .suryo = 0
                    .tanka = 0
                    .kingk = 0
                    .gyono = 0
                End If
            End With
        End Sub
    End Class

    私が投稿したコードのとの違いは、型付きで参照している(Deer327さんの場合)か、汎用DataRowViewにインデックスで参照している(諸農の場合)か、ですね。型付きで参照するように変更し、ブレークポイントを設定してDataRowView.Rowを型付きRowにキャスト後、ビジュアライザで確認すると各フィールドが「DBNullから変換できません」のエラー状態でした。汎用DataRowViewなら大丈夫です。

     

    2006年9月17日 10:40

すべての返信

  • 諸農です。

     Deer327 さんからの引用

     その後に発生するCurrentChangedイベントにて、
     カレント行のdenno列を参照しようとすると、値がDBNullとなっており、
     InvalidCastExceptionのエラーが発生します。

    ■エラー箇所(Ds1.Designer.vb)
      Partial Public Class TestUriRow
          Public Property denno() As String
              Get
                  Try
                      Return CType(Me(Me.tableTestUri.dennoColumn),String)

    キャストする前にカラムフィールドがDBNullかどうかを判定しないといけないのでは?

     Deer327 さんからの引用

    ■知りたいこと
      BindingSourceのAddingNewイベントハンドラにて、
      適切な値を設定したなんらかのインスタンスを
      e.NewObjectに返せばよいのかと思いますが、それが何なのかが分かりません。

    なにを問題視しているのかちょっとよく判らなかったのですが。。。
    とりあえず、テーブルをバインドしているんですよね?であれば、MSDNのBindingSource.AddingNew イベントに「型が異なる場合、例外が発生します。DataView または DataTable にバインドされている場合は、新しい DataRowView をリストに追加できないため、NewObject プロパティを設定することはできません。」と書かれているので、無理なのでは?

    で、最終的にどうしたいのかよく判らないのですが、CurrentChangedイベント内に次のようなコードを書いたら、新規行に移動する度にグリッドビューには同じデータばっかり表示されてしまいますね(^^;

         private void testBindingSource_CurrentChanged(object sender, EventArgs e)
        {
            DataRowView r = (DataRowView)testBindingSource.Current;
            if (r[0] == DBNull.Value)
            {
                r[0] = "EEE";
                r[1] = "PPP";
                r[2] = "TTT";
                r[3] = 10;
                r[4] = 50;
            }
        }

    2006年9月16日 19:23
  • 諸農さん、こんにちは。レスありがとうございます。

     諸農 さんからの引用

    で、最終的にどうしたいのかよく判らないのですが、CurrentChangedイベント内に次のようなコードを書いたら、新規行に移動する度にグリッドビューには同じデータばっかり表示されてしまいますね(^^;

    フォーム上部に売上データを一覧表示するDataGridViewを配置します。
    フォーム下部にTextBoxを配置します。
    このTextBoxにはDataGridViewのカレント行の内容が表示されるように
    BindingSource.CurrentChangedイベントなどにおいてコードで制御します。
    従ってBindingNavigaterは使いません。
    また、DataGridViewはReadOnlyとし、一覧表示と行選択のみに使います。

    行追加ボタンで一旦新規行を追加し、TexiBoxにコード、数量、単価等を入力します。
    行確定ボタンで、TextBoxの入力値をカレント行にセットすると共に、
    DBにも反映させます。

    イメージとしては以下のような画面から、BindingNavigaterを削除したものです。
    新グリッド・コントロールの豊富な機能と高い表現力


    新規行を追加する際、そこに""(空文字)や0をセットできれば、
    新規行の列参照時に発生するキャストエラーを防げると思ったわけです。
    この制御はデータが書き込まれる直前に行う必要がありますので、
    BindingSource.AddingNewイベントが使えるかな、と判断しました。

     諸農 さんからの引用

    キャストする前にカラムフィールドがDBNullかどうかを判定しないといけないのでは?

    『エラー箇所(Ds1.Designer.vb)』と明示してある通り、
    このコードはデータセットデザイナが自動生成したものです。
    この部分に手を加える事はできません。

    BindingSource.CurrentChangedイベントにおいて、drow(カレント行)を取得後
    『If drow.denno = ~』と書いたとたんにTestUriRowクラス内でエラーとなる為、
    判定すらできません。

     諸農 さんからの引用

    とりあえず、テーブルをバインドしているんですよね?であれば、MSDNのBindingSource.AddingNew イベントに「型が異なる場合、例外が発生します。DataView または DataTable にバインドされている場合は、新しい DataRowView をリストに追加できないため、NewObject プロパティを設定することはできません。」と書かれているので、無理なのでは?

    なるほど、最初に読んだ時は気がつかなかったんですが、
    DataTableをバインドした場合、このイベントは使えない(に等しい)わけですか...
    バインドに配列を使うか、もしくはTextBoxを直にバインドさせる標準的な方法を
    使うしかなさそうですね。orz

    #それにしても、DB側で規定値を指定してあるのに、
    #なぜ新規行の初期値がDBNullになってしまうのか。
    #ここは改善してほしいところです。>Microsoftさん

    2006年9月17日 1:45
  • 自己レスです。

    カレント行の参照で例外を拾い、規定値を強制的にセットするようにしました。
    これでなんとか動作しましたが、非常にいやな方法なので、
    業務アプリで使うかどうかは検討中です。

    2006年9月17日 2:44
  • 諸農です。

     Deer327 さんからの引用

    フォーム上部に売上データを一覧表示するDataGridViewを配置します。
    フォーム下部にTextBoxを配置します。
    このTextBoxにはDataGridViewのカレント行の内容が表示されるように
    BindingSource.CurrentChangedイベントなどにおいてコードで制御します。
    従ってBindingNavigaterは使いません。
    また、DataGridViewはReadOnlyとし、一覧表示と行選択のみに使います。

    CurrentChangedイベントを利用するのは何か理由があるのでしょうか。
    例えばカラムの値をそのまま表示したくないとか。であればBinding.FormatとかBindin.Parseとか使えないのでしょうか?

     Deer327 さんからの引用

    行追加ボタンで一旦新規行を追加し、TexiBoxにコード、数量、単価等を入力します。
    行確定ボタンで、TextBoxの入力値をカレント行にセットすると共に、
    DBにも反映させます。

    グリッドは読み取り専用でグリッド上で新規行の追加と削除が出来ない状態です。
    新規追加ボタンがクリックされると新しい行が追加されますが、テキストボックスからの入力しか受け付けません。
    確定ボタンが押されたときにテキストボックスの値をカラムの値として反映し、なおかつデータベースへもUpdateします。
    新規行の入力途中でグリッド上で行位置が変更された場合は、NULL値入力を許さないメッセージを表示します(新規行もなくなる)

         Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            DataGridView1.AllowUserToAddRows = False
            DataGridView1.AllowUserToDeleteRows = False
            DataGridView1.ReadOnly = True

            TextBox1.DataBindings.Add("Text", Table1BindingSource, "A", True)
            TextBox2.DataBindings.Add("Text", Table1BindingSource, "B", True)
            TextBox3.DataBindings.Add("Text", Table1BindingSource, "C", True)
            TextBox4.DataBindings.Add("Text", Table1BindingSource, "D", True)
            TextBox5.DataBindings.Add("Text", Table1BindingSource, "E", True)


            'TODO: このコード行はデータを 'Database1DataSet.Table1' テーブルに読み込みます。必要に応じて移動、または削除をしてください。
            Me.Table1TableAdapter.Fill(Me.Database1DataSet.Table1)

        End Sub

        '新規追加
        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            Table1BindingSource.AddNew()
        End Sub

        '確定、DB更新
        Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
            Table1BindingSource.EndEdit()
            Table1TableAdapter.Update(Database1DataSet.Table1)
        End Sub

        Private Sub Table1BindingSource_CurrentChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Table1BindingSource.CurrentChanged
            Dim r As DataRowView = CType(Table1BindingSource.Current, DataRowView)
            If r(0) Is DBNull.Value Or r(1) Is DBNull.Value Or r(2) Is DBNull.Value Or r(3) Is DBNull.Value Or r(4) Is DBNull.Value Then
            Else
                Table1BindingSource.EndEdit()
                Table1TableAdapter.Update(Database1DataSet.Table1)
            End If
        End Sub

        Private Sub DataGridView1_DataError(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DataGridViewDataErrorEventArgs) Handles DataGridView1.DataError
            Dim r As DataRowView = CType(Table1BindingSource.Current, DataRowView)
            If r(0) Is DBNull.Value Or r(1) Is DBNull.Value Or r(2) Is DBNull.Value Or r(3) Is DBNull.Value Or r(4) Is DBNull.Value Then
                MessageBox.Show("追加行にNULLフィールドが含まれています")
            Else
                MessageBox.Show(e.Exception.Message)
            End If
        End Sub

    上記コードでうまく新規レコードの作成や更新ができるのですが、以下のように言われているキャストエラーなどの現象の追認が出来ませんでした。
    どうやれば再現できるんでしょうか?

     Deer327 さんからの引用

    新規行を追加する際、そこに""(空文字)や0をセットできれば、
    新規行の列参照時に発生するキャストエラーを防げると思ったわけです。
    この制御はデータが書き込まれる直前に行う必要がありますので、
    BindingSource.AddingNewイベントが使えるかな、と判断しました。

     諸農 さんからの引用

    キャストする前にカラムフィールドがDBNullかどうかを判定しないといけないのでは?

    『エラー箇所(Ds1.Designer.vb)』と明示してある通り、
    このコードはデータセットデザイナが自動生成したものです。
    この部分に手を加える事はできません。

    BindingSource.CurrentChangedイベントにおいて、drow(カレント行)を取得後
    『If drow.denno = ~』と書いたとたんにTestUriRowクラス内でエラーとなる為、
    判定すらできません。


    2006年9月17日 4:33
  • 諸農さん、こんにちは。

     諸農 さんからの引用

    CurrentChangedイベントを利用するのは何か理由があるのでしょうか。

    ユーザーが入力した変更を、行毎に反映させる為です。
    いくつかの行を修正した後にEndEdit & UpDateを行い、
    続けていくつかの行を修正した時に、CancelEditすると、
    ユーザーは変更が確定した行がどこまでなのか混乱してしまいます。
    また、データセットとDB側の不一致状態が長くなるのが望ましくありません。
    (私はそう思いますが、この辺は評価がわかれるかもしれません。)
    従って、行単位で追加・更新を確定させたいとの事から
    TextBoxへのバインド、DataGridView上での入力、BindingNavigaterの使用などは
    しないようにしています。

     引用元の名前 さんからの引用

    上記コードでうまく新規レコードの作成や更新ができるのですが、以下のように言われているキャストエラーなどの現象の追認が出来ませんでした。
    どうやれば再現できるんでしょうか?

    私の環境では
    Table1BindingSource_CurrentChangedの
     If r(0) Is DBNull.Value Or で、キャストエラーになります。
    うーん、変ですねえ。
    テキストボックスへのバインドも設定して試したんですが...

    ■環境
    Microsoft Visual Studio 2005
    Version 8.0.50727.42  (RTM.050727-4200)
    Microsoft .NET Framework
    Version 2.0.50727

    インストールされている Edition: Enterprise

    Microsoft Visual Basic 2005   77952-113-3000004-41643
    Microsoft Visual Basic 2005

    ~~ 中略 ~~

    Microsoft Visual Studio 2005 Team Edition for Software Developers
     - 日本語 用の Hotfix (KB912019)  

    ■今回テストしたコード
    Public Class Form3
        Private Sub Form3_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            'データグリッドビューの初期設定
            Dim dgvcol As DataGridViewColumn
            For Each dgvcol In Dgv1.Columns
                dgvcol.ReadOnly = True
                dgvcol.SortMode = DataGridViewColumnSortMode.NotSortable
            Next
            Dgv1.AllowUserToAddRows = False
            Dgv1.SelectionMode = DataGridViewSelectionMode.FullRowSelect

            'テキストボックスのバインド
            TxtDenno.DataBindings.Add("Text", Bs1, "denno", True)
            TxtShncode.DataBindings.Add("Text", Bs1, "shncode", True)
            TxtShnname.DataBindings.Add("Text", Bs1, "shnname", True)
            TxtSuryo.DataBindings.Add("Text", Bs1, "suryo", True)
            TxtTanka.DataBindings.Add("Text", Bs1, "tanka", True)
            TxtKingk.DataBindings.Add("Text", Bs1, "kingk", True)
            TxtGyono.DataBindings.Add("Text", Bs1, "gyono", True)

            Me.TestUriTableAdapter.Fill(Me.Ds1.TestUri)
        End Sub

        Private Sub Form3_Shown(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Shown
            '初回は新規行入力状態とする
            Dgv1.Enabled = False
            Bs1.AddNew()
        End Sub

    ~~ 中略 ~~

        Private Sub Bs1_CurrentChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles Bs1.CurrentChanged
            Dim drow As Ds1.TestUriRow
            drow = CType(CType(Bs1.Current, DataRowView).Row, Ds1.TestUriRow)
            With drow
                If .denno Is DBNull.Value Then  <= ここでエラーが発生
                    .denno = ""
                    .shncode = ""
                    .shnname = ""
                    .suryo = 0
                    .tanka = 0
                    .kingk = 0
                    .gyono = 0
                End If
            End With
        End Sub
    End Class

    2006年9月17日 7:46
  • 諸農です。

     Deer327 さんからの引用

    私の環境では
    Table1BindingSource_CurrentChangedの
     If r(0) Is DBNull.Value Or で、キャストエラーになります。
    うーん、変ですねえ。
    テキストボックスへのバインドも設定して試したんですが...

    ~~ 中略 ~~

        Private Sub Bs1_CurrentChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles Bs1.CurrentChanged
            Dim drow As Ds1.TestUriRow
            drow = CType(CType(Bs1.Current, DataRowView).Row, Ds1.TestUriRow)
            With drow
                If .denno Is DBNull.Value Then  <= ここでエラーが発生
                    .denno = ""
                    .shncode = ""
                    .shnname = ""
                    .suryo = 0
                    .tanka = 0
                    .kingk = 0
                    .gyono = 0
                End If
            End With
        End Sub
    End Class

    私が投稿したコードのとの違いは、型付きで参照している(Deer327さんの場合)か、汎用DataRowViewにインデックスで参照している(諸農の場合)か、ですね。型付きで参照するように変更し、ブレークポイントを設定してDataRowView.Rowを型付きRowにキャスト後、ビジュアライザで確認すると各フィールドが「DBNullから変換できません」のエラー状態でした。汎用DataRowViewなら大丈夫です。

     

    2006年9月17日 10:40
  •  諸農 さんからの引用

    汎用DataRowViewなら大丈夫です。

    上記の動作確認、当方でもとれました。
    諸農さん、最後までおつきあい下さり、ありがとうございました。
    お礼申し上げます。

    #型付きデータセットが使えないのは痛いですが、
    #選択肢が増えたと考える事にします。
    #時期バージョンに期待しつつ...

    2006年9月17日 12:45
  • 自己レスで追加情報です。

    型付きデータセットのデータテーブルを作成した際、
    各列のDefaultValueはDB側の規定値設定にかかわらず、
    一律<DBNull>となってしまいます。
    従って、ここに適正な値(空文字や0など)を設定すれば、
    BindingSource.AddNew直後の列参照で
    キャストエラーが発生するのを防止できます。

    よくよく調べてみれば非常に単純な事でしたが、
    DB側の設定が引き継がれる、と思いこんでいた私には盲点でした。
    これが仕様なのは分かりますが、
    フィードバックフォーラムに要望として投稿致しました。

    2006年9月19日 13:16