• Smartphone saga

    Did I mention my Smartphone was broken? Oh, yes I did.

    Well even though it was still in warranty when I sent it off, unfortunately Optus took a couple of weeks to decide that they don’t fix that model anymore. I’m told that the company that does handle repairs wouldn’t do it under warranty then…

    So finally I rang out our IT department to find out what’s going on, and they said that it is fixed. Great. Except that they said what fixed it was upgrading the firmware to the latest version. Hmm, hadn’t I already done that a while ago? Skeptical, but not one to argue with the fact that the audio was actually working, I got my phone back last week.

    And so it was actually working again..

    Until today.

    Yep, the same problem again. What I suspect probably happened is that because of the various trips the phone has made, both in our internal mail, and also interstate once (possibly twice), it unstuck the headphone socket. But now there’s no audio again, so I’m pretty sure the cause is infact a physical problem, that the audio socket is stuck in the ‘plug is still in, so don’t allow sound through the external speaker’ mode.

    Looking ahead, wouldn’t one of these be a nice replacement.

    One interesting side-note. While my rw6828 was away on its little holiday, I was given a plain old Nokia phone (a 3530?). Nothing remarkable, except that the battery would last for days. We live in a pretty poor reception area, which seems to suck other phone batteries dry in a day, so I was surprised that the Nokia would go for 3-4 days without requiring a recharge.

  • Adelaide Geek Dinner

    Last night I was honoured to be one of the attendees at the inaugural Adelaide Geek Dinner. A most enjoyable evening was had by all.

    Thanks to Jason Stangroome for organising the evening. The good news is he’s planning to do it again in January.

    One non .NET thing I did learn - Darren Neimke plays cricket (a bit of a bowler by all accounts)

  • Using Enum types with NHibernate (implementing IUserType)

    NHibernate is a really nice ORM framework, and one of its features is that you can customise the mapping between the business entity’s properties and database columns.

    For example, you might have a ‘GENDER’ column in a database table that has the values “M” or “F”.

    Rather than your entity class have a String or Char property, wouldn’t it be nicer to use a custom Enum like this?

    EntityObj.Gender = Sex.Male

    In order to implement this functionality, there are a few steps that need to be completed.

    StringValueAttribute

    We need some way of recording the string equivalent for each enum value. One way to do this is to use a custom attribute like this:

    Public NotInheritable Class StringValueAttribute
    
        Inherits System.Attribute
    
        Private \_value As String
    
        Public Sub New(ByVal value As String)
    
            \_value = value
    
        End Sub
    
        Public ReadOnly Property Value() As String
    
            Get
    
                Return \_value
    
            End Get
    
        End Property
    
    End Class
    

    You apply this to each value of the enumerated type:

    Public Enum Sex
    
        <StringValue("M")> Male
    
        <StringValue("F")> Female
    
    End Enum
    

    TypeConverters

    Next, we need a nice way to read those values and convert between the enumerated type and the string value. A custom TypeConverter can do this.

    Here’s a generic TypeConverter class:

    Imports System.ComponentModel
    
    Imports System.Globalization
    
    ''' <summary>
    
    ''' A generic base class for implementing type converters for enumerations.
    
    ''' </summary>
    
    ''' <typeparam name="t"></typeparam>
    
    ''' <remarks>The Enumerations must use the <see cref="StringValueAttribute">StringValueAttribute</see> attribute
    
    ''' to map the string/character value with each enumerated value.</remarks>
    
    Public Class EnumConverter(Of t As Structure)
    
        Inherits EnumConverter
    
        Public Sub New()
    
            MyBase.New(GetType(t))
    
        End Sub
    
        ''' <summary>
    
        ''' We can convert from String or Char
    
        ''' </summary>
    
        ''' <param name="context"></param>
    
        ''' <param name="sourceType"></param>
    
        ''' <returns></returns>
    
        ''' <remarks></remarks>
    
        Public Overrides Function CanConvertFrom(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal sourceType As System.Type) As Boolean
    
            If sourceType Is GetType(String) OrElse sourceType Is GetType(Char) Then
    
                Return True
    
            Else
    
                Return False
    
            End If
    
            'Return MyBase.CanConvertFrom(context, sourceType)
    
        End Function
    
        ''' <summary>
    
        ''' Convert from String and Char
    
        ''' </summary>
    
        ''' <param name="context"></param>
    
        ''' <param name="culture"></param>
    
        ''' <param name="value"></param>
    
        ''' <returns></returns>
    
        ''' <remarks>If it is a comma-separated list, then will combine values</remarks>
    
        Public Overrides Function ConvertFrom(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal culture As System.Globalization.CultureInfo, ByVal value As Object) As Object
    
            If TypeOf value Is String OrElse TypeOf value Is Char Then
    
                'Dim x As t
    
                Dim rv As Integer
    
                Dim strValue As String = CStr(value).ToUpper(CultureInfo.CurrentCulture)
    
                For Each ch As String In strValue.Split(","c)
    
                    ch = ch.Trim()
    
                    Dim FieldInfos() As System.Reflection.FieldInfo = GetType(t).GetFields(Reflection.BindingFlags.Static Or Reflection.BindingFlags.Public) ' ft.GetType().GetField(value.ToString())
    
                    For i As Integer = 0 To FieldInfos.Length - 1
    
                        Dim fi As Reflection.FieldInfo = FieldInfos(i)
    
                        Dim attributes() As StringValueAttribute = CType(fi.GetCustomAttributes(GetType(StringValueAttribute), False), StringValueAttribute())
    
                        If attributes.Length > 0 AndAlso attributes(0).Value = ch Then
    
                            Dim newValue As Integer = CInt(System.Enum.Parse(GetType(t), fi.Name))
    
                            rv = rv Or newValue
    
                        End If
    
                    Next
    
                Next
    
                ' a bit messy, but we cast to an object then back to the t type
    
                Return CType(CType(rv, Object), t)
    
            Else
    
                Throw New NotSupportedException()
    
            End If
    
        End Function
    
        ''' <summary>
    
        ''' We can convert to String and Char
    
        ''' </summary>
    
        ''' <param name="context"></param>
    
        ''' <param name="destinationType"></param>
    
        ''' <returns></returns>
    
        ''' <remarks></remarks>
    
        Public Overrides Function CanConvertTo(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal destinationType As System.Type) As Boolean
    
            If destinationType Is GetType(String) OrElse destinationType Is GetType(Char) Then
    
                Return True
    
            Else
    
                Throw New NotSupportedException()
    
            End If
    
        End Function
    
        ''' <summary>
    
        ''' Convert to String or Char
    
        ''' </summary>
    
        ''' <param name="context"></param>
    
        ''' <param name="culture"></param>
    
        ''' <param name="value"></param>
    
        ''' <param name="destinationType"></param>
    
        ''' <returns></returns>
    
        ''' <remarks></remarks>
    
        Public Overrides Function ConvertTo(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal culture As System.Globalization.CultureInfo, ByVal value As Object, ByVal destinationType As System.Type) As Object
    
            If value Is Nothing Then
    
                Throw New ArgumentNullException("value")
    
            End If
    
            Dim ch As String = String.Empty
    
            Dim ft As t = CType(value, t)
    
            Dim fi As System.Reflection.FieldInfo = ft.GetType().GetField(value.ToString())
    
            Dim attributes() As StringValueAttribute = CType(fi.GetCustomAttributes(GetType(StringValueAttribute), False), StringValueAttribute())
    
            If attributes.Length > 0 Then
    
                If destinationType Is GetType(String) Then
    
                    Return attributes(0).Value
    
                ElseIf destinationType Is GetType(Char) Then
    
                    Return CChar(attributes(0).Value)
    
                Else
    
                    'Return MyBase.ConvertTo(context, culture, value, destinationType)
    
                    Throw New NotSupportedException()
    
                End If
    
            Else
    
                Throw New ArgumentException("StringValue attribute not found for this value")
    
            End If
    
        End Function
    
    End Class
    

    You apply this to the enumerated type:

    Imports System.ComponentModel
    
    <TypeConverter(GetType(EnumConverter(Of Sex)))> \_
    
    Public Enum Sex
    
        <StringValue("M")> Male
    
        <StringValue("F")> Female
    
    End Enum
    

    Implementing IUserType

    You now need a class that implements IUserType. This class is used by NHibernate to do the mapping/conversion to and from the database to the property on the class.

    Here’s a generic version, that you can then inherit from for your specific type:

    Imports NHibernate
    
    Imports NHibernate.UserTypes
    
    ''' <summary>
    
    ''' Generic class for implementing NHibernate's <see cref="IUserType">IUserType</see> for a value type.
    
    ''' </summary>
    
    ''' <typeparam name="T">Value type</typeparam>
    
    ''' <remarks>The value type's <see cref="System.ComponentModel.TypeConverter">TypeConverter</see> is used for conversion</remarks>
    
    Public MustInherit Class ValueConverterUserType(Of T As Structure)
    
        Implements IUserType
    
        Protected Converter As ComponentModel.TypeConverter = ComponentModel.TypeDescriptor.GetConverter(GetType(T))
    
        ''' <summary>
    
        ''' Reconstruct an object from the cacheable representation.
    
        ''' </summary>
    
        ''' <param name="cached"></param>
    
        ''' <param name="owner"></param>
    
        ''' <returns></returns>
    
        ''' <remarks>At the very least this method should perform a deep copy if the type is mutable. (optional operation)</remarks>
    
        Public Function Assemble(ByVal cached As Object, ByVal owner As Object) As Object Implements NHibernate.UserTypes.IUserType.Assemble
    
            Return DeepCopy(cached)
    
        End Function
    
        ''' <summary>
    
        ''' Return a deep copy of the persistent state, stopping at entities and at collections.
    
        ''' </summary>
    
        ''' <param name="value"></param>
    
        ''' <returns></returns>
    
        ''' <remarks></remarks>
    
        Public Function DeepCopy(ByVal value As Object) As Object Implements NHibernate.UserTypes.IUserType.DeepCopy
    
            Return value
    
        End Function
    
        ''' <summary>
    
        ''' Transform the object into its cacheable representation.
    
        ''' </summary>
    
        ''' <param name="value"></param>
    
        ''' <returns></returns>
    
        ''' <remarks>At the very least this method should perform a deep copy if the type is mutable.
    
        ''' That may not be enough for some implementations, however; for example, associations must be cached as identifier values. (optional operation)</remarks>
    
        Public Function Disassemble(ByVal value As Object) As Object Implements NHibernate.UserTypes.IUserType.Disassemble
    
            Return DeepCopy(value)
    
        End Function
    
        ''' <summary>
    
        ''' Compare two instances of the class mapped by this type for persistent "equality" ie. equality of persistent state
    
        ''' </summary>
    
        ''' <param name="x"></param>
    
        ''' <param name="y"></param>
    
        ''' <returns></returns>
    
        ''' <remarks></remarks>
    
        Public Shadows Function Equals(ByVal x As Object, ByVal y As Object) As Boolean Implements NHibernate.UserTypes.IUserType.Equals
    
            Return x.Equals(y)
    
        End Function
    
        ''' <summary>
    
        ''' Get a hashcode for the instance, consistent with persistence "equality"
    
        ''' </summary>
    
        ''' <param name="x"></param>
    
        ''' <returns></returns>
    
        ''' <remarks></remarks>
    
        Public Shadows Function GetHashCode(ByVal x As Object) As Integer Implements NHibernate.UserTypes.IUserType.GetHashCode
    
            Return x.GetHashCode()
    
        End Function
    
        ''' <summary>
    
        ''' Are objects of this type mutable?
    
        ''' </summary>
    
        ''' <value></value>
    
        ''' <returns></returns>
    
        ''' <remarks></remarks>
    
        Public ReadOnly Property IsMutable() As Boolean Implements NHibernate.UserTypes.IUserType.IsMutable
    
            Get
    
                Return False
    
            End Get
    
        End Property
    
        ''' <summary>
    
        ''' Retrieve an instance of the mapped class from an <see cref="IDataReader">IDataReader</see>.
    
        ''' </summary>
    
        ''' <param name="rs">IDataReader</param>
    
        ''' <param name="names">Column names</param>
    
        ''' <param name="owner">The containing entity</param>
    
        ''' <returns></returns>
    
        ''' <remarks></remarks>
    
        Public Function NullSafeGet(ByVal rs As System.Data.IDataReader, ByVal names() As String, ByVal owner As Object) As Object Implements NHibernate.UserTypes.IUserType.NullSafeGet
    
            Dim obj As Object = NHibernateUtil.String.NullSafeGet(rs, names)
    
            Dim s As String = CStr(obj)
    
            Return Converter.ConvertFromString(s)
    
        End Function
    
        ''' <summary>
    
        ''' Write an instance of the mapped class to a prepared statement.
    
        ''' </summary>
    
        ''' <param name="cmd">an IDbCommand</param>
    
        ''' <param name="value">the object to write</param>
    
        ''' <param name="index">command parameter index</param>
    
        ''' <remarks>Implementors should handle possibility of null values.  A multi-column type should be written to parameters starting from index.</remarks>
    
        Public Sub NullSafeSet(ByVal cmd As System.Data.IDbCommand, ByVal value As Object, ByVal index As Integer) Implements NHibernate.UserTypes.IUserType.NullSafeSet
    
            Dim NativeValue As String = Converter.ConvertToString(value)
    
            NHibernateUtil.String.NullSafeSet(cmd, NativeValue, index)
    
        End Sub
    
        ''' <summary>
    
        ''' During merge, replace the existing (target) value in the entity we are merging to with a new (original) value from the detached entity we are merging.
    
        ''' </summary>
    
        ''' <param name="original">the value from the detached entity being merged</param>
    
        ''' <param name="target">the value in the managed entity</param>
    
        ''' <param name="owner">the managed entity</param>
    
        ''' <returns>the value to be merged</returns>
    
        ''' <remarks>For immutable objects, or null values, it is safe to simply return the first parameter.
    
        ''' For mutable objects, it is safe to return a copy of the first parameter.
    
        ''' For objects with component values, it might make sense to recursively replace component values.</remarks>
    
        Public Function Replace(ByVal original As Object, ByVal target As Object, ByVal owner As Object) As Object Implements NHibernate.UserTypes.IUserType.Replace
    
            Return original
    
        End Function
    
        ''' <summary>
    
        ''' The type returned by NullSafeGet()
    
        ''' </summary>
    
        ''' <value></value>
    
        ''' <returns></returns>
    
        ''' <remarks></remarks>
    
        Public ReadOnly Property ReturnedType() As System.Type Implements NHibernate.UserTypes.IUserType.ReturnedType
    
            Get
    
                Return GetType(T)
    
            End Get
    
        End Property
    
        ''' <summary>
    
        ''' The SQL types for the columns mapped by this type.
    
        ''' </summary>
    
        ''' <value></value>
    
        ''' <returns></returns>
    
        ''' <remarks></remarks>
    
        Public MustOverride ReadOnly Property SqlTypes() As NHibernate.SqlTypes.SqlType() Implements NHibernate.UserTypes.IUserType.SqlTypes
    
    End Class
    

    Note that you’ll need to override the SqlTypes() property. This tells NHibernate the type of the database column. So in the following example, as the table column is probably a char(1), we use GetString(1).

    Imports NHibernate.SqlTypes
    
    Public Class SexTypeUserType
    
        Inherits ValueConverterUserType(Of Sex)
    
        Public Overrides ReadOnly Property SqlTypes() As NHibernate.SqlTypes.SqlType()
    
            Get
    
                Dim a() As SqlType = {SqlTypeFactory.GetString(1)}
    
                Return a
    
            End Get
    
        End Property
    
    End Class
    

    Mapping file

    Now in the hbm.xml mapping file, you tell NHibernate to use your IUserType class:

        <property name\="Gender" access\="property" type\="MyNameSpace.SexTypeUserType,MyAssemblyName"\>
    
          <column name\="PERSON\_GENDER" not-null\="true" sql-type\="char(1)" />
    
        </property\>
    

    ActiveWriter users

    If you use ActiveWriter to generate your classes and hbm.xml mapping files, please be aware that it is currently unable to resolve your custom user type. Hopefully this will be resolved in future releases.

    The workaround for now is to generate with ActiveWriter, then hand-edit the appropriate hbm and code files. Just remember if you regenerate, you’ll have to reapply your edits!