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