MySQL では整数同士を加算するとすべて64ビットになるみたい
いま携わってるプロジェクトは MySQL & VB.NET (一部 C#)で開発してるのですが、先日来 WPF の ComboBox で発生してた BindingExpression の原因やっとわかりました。
ComboBox には DataTable バインドさせてるのですが、まずクエリはこんな感じ。
SELECT CAST(COALESCE(id, -1) AS SIGNED) AS id, COALESCE(value, '') AS name FROM prefecture ORDER BY order_no;
ViewModel で PrefectureId プロパティを実装して・・・
''' <summary>都道府県ID を取得または設定します。</summary> Public Property PrefectureId() As Integer Get Return m_prefectureId End Get Set(ByVal value As Integer) m_prefectureId = value OnPropertyChanged("PrefectureId") End Set End Property
XAML で ComboBox の ViewModel にバインドさせてます。
<ComboBox DisplayMemberPath="name" SelectedValuePath="id" ItemSource="{Binding Path=Prefectures}" SelectedValue="{Binding Path=PrefectureId}" />
この内容で実行しコンボボックスのアイテムをユーザーが手動で変更すると、BindingExpression が発生します。
System.Windows.Data Error: 7 : ConvertBack cannot convert value '17' (type 'UInt32'). BindingExpression:Path=PrefectureId; DataItem='Hogehoge' (HashCode=51609094); target element is 'ComboBox' (Name='comboPref'); target property is 'SelectedValue' (type 'Object') NotSupportedException:'System.NotSupportedException: Int32Converter を System.UInt32 から変換できません。
COALESCE 関数の仕様
このエラー最初理解不能だったのですが、バインドされてる DataTable 調べたらすぐ原因判りました。id 列が Int64 型になってる!>< Int32 のプロパティに Int64 の数値バインドさせようとしてるんだから例外発生するのは当然の話ですね(汗
prefecture テーブルの id 列は int 型 32ビットだったのですが、COALESCE 関数を通したら自動的に64ビットになっていました。これかなり落とし穴。
SELECT id, -- これは32ビットだが COALESCE(id, -1) AS id -- こちらは64ビットになる FROM prefecture ORDER BY order_no;
どうやらこれ MySQL の仕様みたいですね。こちらで調査した限りでは 64ビットに昇格した値を 32ビットに戻す術もなさそうです。
SELECT CAST(COALESCE(id, -1) AS SIGNED INT) AS id -- INT でキャストしても64ビットになる FROM prefecture ORDER BY order_no;
MySQL の演算の仕様
またこれも結構落とし穴。クエリ内で整数同士を加算しても 64ビットの値が返却されるようです。
SELECT id, -- これは32ビットだが id + 0 AS id -- こちらは64ビットになる FROM prefecture ORDER BY order_no;
関連記事:MySQL 5.1 リファレンスマニュアル - 算術演算子
WinForm の場合は特に意識せずともエラーなく動いていたのですが、WPF のデータバインディングはかなり型に厳密なようで、WPF の ComboBox とバインド試みてたら初めてこの問題を知りました。次のリリース直前に気付いたのですが、危ないとこであった(大汗
回避策(2011/08/09 15:50 追記)
さらに色々調べていたら、他のxxxDataAdapter はどうか知りませんが、MySqlDataAdapter.Fill( DataTable ) メソッドならスキーマを設定した DataTable を渡せることが判明。先に Column を生成して DataType を Int32 に指定してから MySqlDataAdapter.Fill に渡してみる。
Dim table As New DataTable Using con As New MySqlConnection("接続文字列") Dim sql As New System.Text.StringBuilder() With sql .AppendLine("SELECT ") .AppendLine(" CAST(COALESCE(id, -1) AS SIGNED) AS id, -- そのままだと64ビットになる ") .AppendLine(" COALESCE(value, '') AS name ") .AppendLine("FROM prefecture ") .AppendLine("ORDER BY order_no; ") End With Dim command As New MySqlCommand(sql.ToString(), con) Dim adapter As New MySqlDataAdapter(command) table.Columns.Add("id", GetType(Int32)) ' ここで Int32 を指定 table.Columns.Add("name", GetType(String)) adapter.Fill(table) End Using
Fill メソッド後に調べたら、id 列の DataType は Int32 のままで、データはしっかり抽出されていました。(^^)v