@IT会議室は、ITエンジニアに特化した質問・回答コミュニティ「QA@IT」に生まれ変わりました。ぜひご利用ください。
- PR -

CLR UDTのDecimal型のプロパティの小数部が丸められる

1
投稿者投稿内容
KI
大ベテラン
会議室デビュー日: 2007/01/10
投稿数: 239
投稿日時: 2007-02-21 13:32
SQL Server 2005の CLRユーザー定義型の振る舞いについて質問させて頂きます。

VS2005(VB)でビルドしたCLRユーザー定義型を含むアセンブリを
SQL Server上に配置しています。
このユーザー定義型にはDecimal型の読み取り専用のプロパティが存在します。
この型を含むテーブルに対するSQLのSELECT句で
[カラム名].[プロパティ名]のように書けば、そのプロパティの値が取得できるのですが、
Decimal型のプロパティに関しては、取得時に小数点以下が勝手に四捨五入されて
整数が取得されてしまいます。
.NET上でこの型のオブジェクトを生成して同じプロパティにアクセスしてみると、
ちゃんと小数部まで含んだDecimal型で返ってきます。

.NET上では現状通りDecimalで値が取得できないと困りますし、
一方でSELECT句でも[カラム名].[プロパティ名]で小数部まで取得できるようにしたいのです。
原因または良い対処法がわかる方いらっしゃいましたら教えて下さい。
よろしくお願いします。


再現可能なソースとして、実際のソースは載せられませんので、
MSDNの下記URLのサンプルにあるPoint型を元に改造して、PointF 型を作成してみました。

http://msdn2.microsoft.com/ja-jp/library/ms131106.aspx

上記サンプルのInt32をSingleに変更してPointFという名前とし、
X, YをそれぞれDecimalにキャストして返すプロパティXDecimal, YDecimalを追加したものです。
サーバー上で実行されるとき、[カラム名].Xとか[カラム名.Y]は小数部も取得されますが、
[カラム名].XDecimalとか[カラム名.YDecimal]は整数で取得されます。

コード:
Imports System
Imports System.Data.SqlTypes
Imports Microsoft.SqlServer.Server
Imports System.Text

<Serializable(), SqlUserDefinedTypeAttribute(Format.Native, _
  IsByteOrdered:=True, _
  ValidationMethodName:="ValidatePointF")> _
  Public Structure PointF
    Implements INullable

    Private is_Null As Boolean
    Private _x As Single
    Private _y As Single

    Public ReadOnly Property IsNull() As Boolean _
       Implements INullable.IsNull
        Get
            Return (is_Null)
        End Get
    End Property

    Public Shared ReadOnly Property Null() As PointF
        Get
            Dim pt As New PointF
            pt.is_Null = True
            Return (pt)
        End Get
    End Property

    ' Use StringBuilder to provide string representation of UDT.
    Public Overrides Function ToString() As String
        ' Since InvokeIfReceiverIsNull defaults to 'true'
        ' this test is unneccesary if Point is only being called
        ' from SQL.
        If Me.IsNull Then
            Return "NULL"
        Else
            Dim builder As StringBuilder = New StringBuilder
            builder.Append(_x)
            builder.Append(",")
            builder.Append(_y)
            Return builder.ToString
        End If
    End Function

    <SqlMethod(OnNullCall:=False)> _
    Public Shared Function Parse(ByVal s As SqlString) As PointF
        ' With OnNullCall=False, this check is unnecessary if
        ' Point only being called from SQL.
        If s.IsNull Then
            Return Null
        End If

        ' Parse input string here to separate out points.
        Dim pt As New PointF()
        Dim xy() As String = s.Value.Split(",".ToCharArray())
        pt.X = Single.Parse(xy(0))
        pt.Y = Single.Parse(xy(1))

        ' Call ValidatePoint to enforce validation
        ' for string conversions.
        If Not pt.ValidatePointF() Then
            Throw New ArgumentException("Invalid XY coordinate values.")
        End If
        Return pt
    End Function

    ' X and Y coordinates are exposed as properties.
    Public Property X() As Single
        Get
            Return (Me._x)
        End Get

        Set(ByVal Value As Single)
            Dim temp As Single = _x
            _x = Value
            If Not ValidatePointF() Then
                _x = temp
                Throw New ArgumentException("Invalid X coordinate value.")
            End If
        End Set
    End Property

    Public Property Y() As Single
        Get
            Return (Me._y)
        End Get

        Set(ByVal Value As Single)
            Dim temp As Single = _y
            _y = Value
            If Not ValidatePointF() Then
                _y = temp
                Throw New ArgumentException("Invalid Y coordinate value.")
            End If
        End Set
    End Property

    ' XをDecimal型で返す
    Public ReadOnly Property XDecimal() As Decimal
        Get
            Return CType(Me._x, Decimal)
        End Get
    End Property

    ' YをDecimal型で返す
    Public ReadOnly Property YDecimal() As Decimal
        Get
            Return CType(Me._y, Decimal)
        End Get
    End Property

    ' Validation method to enforce valid X and Y values.
    Private Function ValidatePointF() As Boolean
        ' Allow only zero or positive integers for X and Y coordinates.
        If (_x >= 0) And (_y >= 0) Then
            Return True
        Else
            Return False
        End If
    End Function

    ' Distance from 0 to Point method.
    <SqlMethod(OnNullCall:=False)> _
  Public Function Distance() As Double
        Return DistanceFromXY(0, 0)
    End Function

    ' Distance from Point to the specified point method.
    <SqlMethod(OnNullCall:=False)> _
    Public Function DistanceFrom(ByVal pFrom As Point) As Double
        Return DistanceFromXY(pFrom.X, pFrom.Y)
    End Function

    ' Distance from Point to the specified x and y values method.
    <SqlMethod(OnNullCall:=False)> _
    Public Function DistanceFromXY(ByVal ix As Single, ByVal iy As Single) _
        As Double
        Return Math.Sqrt(Math.Pow(ix - _x, 2.0) + Math.Pow(iy - _y, 2.0))
    End Function
End Structure

KI
大ベテラン
会議室デビュー日: 2007/01/10
投稿数: 239
投稿日時: 2007-02-23 14:50
プロパティにSqlFacet属性を指定することで自己解決できましたので報告します。

Precision:有効桁数
Scale:小数点以下の桁数

コード:

' XをDecimal型で返す
<SqlFacet(Precision:=4, Scale:=1)> _
Public ReadOnly Property XDecimal() As Decimal
    Get
        Return CType(Me._x, Decimal)
    End Get
End Property

' YをDecimal型で返す
<SqlFacet(Precision:=4, Scale:=1)> _
Public ReadOnly Property YDecimal() As Decimal
    Get
        Return CType(Me._y, Decimal)
    End Get
End Property

1

スキルアップ/キャリアアップ(JOB@IT)