WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

SQL Server と .NET の decimal 型

by WebSurfer 2023年7月3日 17:47

SQL Server の decimal 型のフィールドから、.NET アプリで ADO.NET を使って .NET の Decimal 型として値を取得する際、SQL Server と .NET では扱える値の大きさが異なる (SQL Server の方が大きい) ことにより、OverflowException が発生することがあるという話を書きます。

OverflowException 発生

.NET の Decimal 型は、Microsoft のドキュメント「Decimal 構造体」によると、128 ビットで構成され、その内 96 ビットが整数部で、残り 32 ビットが正負の符号と小数点の位置を指定するのに使われているそうです。

従い、.NET の Decimal 型の最大値 (MaxValue) は、

2^96 - 1 = 79,228,162,514,264,337,593,543,950,335

ということになります。

一方、SQL Server の decimal 型は、Microsoft のドキュメント「decimal 型と numeric 型 (Transact-SQL)」によると、38 桁まで表すことができるそうです。例えば、decimal(38,0) は小数部の無い 38 桁の整数を表すことができるということになります。

上の画像では、.NET の Decimal.MaxValue より 1 大きい SQL Server の decimal(38,0) 型の値を、.NET のコンソールアプリで ADO.NET + System.Data.SqlClient を使って取得しようとして OverflowException が発生しています。

上のようなケースではオバーフローするのが当たり前と思えるのですが、自分がハマったのは SQL Server の decimal 型のフィールドで小数点が指定してある場合でした。そのことを以下に書きます。

なお、以下の話は System.Data.SqlClient を使った場合ですので注意してください。(Microsoft.Data.SqlClient の場合は後述します)

例えば、decimal(38,26) と言うように小数部の桁数も指定して、上の画像のコード例の内 query2 を以下のように変更したとします。

// 上の画像の query2 で、
// 79228162514264337593543950336 ⇒ 10000000
// decimal(38,0) ⇒ decimal(38,26)
// に変更
var query2 = "select cast(10000000 as decimal(38,26)) val";

この場合も OverflowException がスローされます。上の 10000000 という値は、上の回答の画像の例 MaxValue + 1 よりはるかに小さいのになぜオーバーフローするのでしょうか?

そこが分からなくてハマったのですが、どうやら以下のようなことらしいです。

上のコードの例では、10000000 の後に 26 個の 0 を続けた整数値を表現できなければならず、.NET の Decimal の整数部 96 ビットでは表現できない (MaxValue を超える) のでオバーフローするということのようです。

試しに上のコードの 10000000 を 792 および 793 に代えて実行してみました。

79200000000000000000000000000 < MaxValue < 79300000000000000000000000000

ということで 792 は OK でしたが、793 はオバーフローするという結果になりました。


Microsoft.Data.SqlClient を使った場合は話が違ってきます

System.Data.SqlClient も Microsoft.Data.SqlClient も整数部が 96 ビットというのは同じなので、一番上の画像のコード例のように MaxValue を超える整数を設定した場合はオバーフローするのは同じです。

ただし、Microsoft.Data.SqlClient の場合は、上に書いた例と同様に SQL Server の decimal 型に小数部の桁数を指定してもオーバーフローとはなりませんでした。

System.Data.SqlClient とは実装が変わったようです。ただ、どのように変わったのか、どこまで大丈夫なのかは調べ切れてません。時間があったら調べて、何か分かったら追記します。

Tags: , , ,

ADO.NET

日付と通貨の書式設定

by WebSurfer 2019年2月4日 18:10

ASP.NET MVC5 アプリで日付(DateTime 型)と通貨(Decimal 型)を表示し、編集してデータベースを更新する場合、どのように書式設定を行うのが良いかということについて書きます。

日付と通貨の書式設定

上の画像は DisplayFor と TextBoxFor を上下に並べて表示していますが、その「契約日」と「価格」に示したように、

  1. DisplayFor 等を使った表示の場合のみ書式設定を行い、
  2. テキストボックスに値を表示する場合は書式設定しない。または、"{0:F0}" のような書式設定に留めパースできない文字は含めないようにする。

・・・のがよさそうです。

スキャフォールディング機能を使って CRUD 可能なアプリを作ると Index (レコード一覧), Create, Delete, Edit, Details というアクションメソッドとそれに対応するビューが生成されますが、Index, Delete, Details を上記 (1) で、Create, Edit を上記 (2) で表示するということです。

テキストボックスに表示する文字列も 2019年2月4日 とか ¥1,000 のように表示することはできますが、クライアント側での検証を無効にしてサーバーに POST したとしても、DateTime 型や Decimal 型にパースできないので結局モデルバインディングの際の検証が通りません。

どうしてもテキストボックスに表示する文字列も 2019年2月4日 とか ¥1,000 のようにしたいということであれば、モデルのプロパティも含めて全て String 型として扱い、検証は正規表現のみを使って行うということも考えられますが、データベースの型が datetime とか money でしょうから、サーバー側での取り扱いを考えると現実的ではなさそうです。

それに、ユーザーとしても、編集する際にいちいち ¥ とか , を入力するのは煩わしいはずで、喜んではくれないのでは?

上の画像のように表示するための方法ですが、DisplayFor での表示の書式はモデルのプロパティに DisplayFormatAttribute を付与して設定し、テキストボックスには TextBoxFor を使ってその第 2 引数に "{0:yyyy/MM/dd}"、"{0:F0}" などの書式を設定するのがよさそうです。

EditorFor を使うと書式が自由に設定できないし(DisplayFormatAttribute の設定と同じでよければ可能ですが)、レンダリングされる html ソースの input 要素の type 属性が問題になることがありますので使う場合は要注意です。

具体的には以下のようにします。

Model

public class PurchaseRecord
{
  [Display(Name = "ID")]
  [Required(ErrorMessage = "{0} は必須")]
  [RegularExpression(@"^\d{1,5}$", 
      ErrorMessage = "数字 1 ~ 5 文字")]
  public int ID { get; set; }

  [Display(Name = "契約日")]
  [Required(ErrorMessage = "{0} は必須")]
  [RegularExpression(
      @"^\d{4}/\d{2}/\d{2}( \d{1,2}:\d{2}:\d{2})?$", 
      ErrorMessage = "yyyy/MM/dd 形式")]
  [DisplayFormat(DataFormatString = "{0:yyyy年M月d日}")]
  public DateTime ContractDate { get; set; }

  [Display(Name = "価格")]
  [Required(ErrorMessage = "{0} は必須")]
  [RegularExpression(@"^\d{1,5}$", 
      ErrorMessage = "数字 1 ~ 5 文字")]
  [DisplayFormat(DataFormatString = "{0:C0}")]
  public decimal Price { get; set; }
}

View(一部の抜粋)

<div class="form-group">
  @Html.LabelFor(model => model.ID, 
    htmlAttributes: new { @class = "control-label col-md-2" })
  <div class="col-md-10">
    @Html.DisplayFor(model => model.ID)
    @Html.EditorFor(model => model.ID, 
      new { htmlAttributes = new { @class = "form-control" } })
    @Html.ValidationMessageFor(model => model.ID, 
      "", new { @class = "text-danger" })
  </div>
</div>

<div class="form-group">
  @Html.LabelFor(model => model.ContractDate, 
    htmlAttributes: new { @class = "control-label col-md-2" })
  <div class="col-md-10">
    @Html.DisplayFor(model => model.ContractDate)
    @Html.TextBoxFor(model => model.ContractDate, 
      "{0:yyyy/MM/dd}", new { @class = "form-control" })
    @Html.ValidationMessageFor(model => model.ContractDate, 
      "", new { @class = "text-danger" })
  </div>
</div>

<div class="form-group">
  @Html.LabelFor(model => model.Price, 
    htmlAttributes: new { @class = "control-label col-md-2" })
  <div class="col-md-10">
    @Html.DisplayFor(model => model.Price)
    @Html.TextBoxFor(model => model.Price, 
      "{0:F0}",new { @class = "form-control" })
    @Html.ValidationMessageFor(model => model.Price, 
      "", new { @class = "text-danger" })
  </div>
</div>

あと、オマケの話ですが、ASP.NET Web Forms アプリで使う GridView などで書式設定する際も同様なことはできます。以下の画像を見てください。

GridView で日付と通貨の書式設定

赤枠で囲った部分は[編集]ボタンをクリックして編集モードにしたテキストボックスですが、他の行との違いを見てください。

Tags: , ,

MVC

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar