NHibernate で enum のプロパティ/列をマッピングするときのメモです。
普通にマッピングすると
以下の様な enum のプロパティをマッピングすると、"Gender" は 1, "Female" は 2 という感じで enum の中身の整数(int)でマッピングされます。
モデル
public enum Gender
{
Male = 1,
Female = 2
}
public class Student
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Gender Gender { get; set; }
}
テーブル
テーブルのほうはこんな感じですね。
CREATE TABLE [dbo].[Students] (
[Id] INT IDENTITY NOT NULL PRIMARY KEY,
[Name] NVARCHAR (100) NOT NULL,
[Gender] INT NOT NULL,
CHECK ([Gender] = 1 OR [Gender] = 2)
);
マッピング
マッピングはこうです。
<class name="Student" table="Students">
<id name="Id">
<generator class="identity"></generator>
</id>
<property name="Name"></property>
<property name="Gender"></property>
</class>
データ
Id |
Name |
Gender |
1
|
スタン
|
1
|
2
|
カイル
|
1
|
3
|
カートマン
|
1
|
4
|
ケニー
|
1
|
5
|
バターズ
|
1
|
6
|
ウェンディ
|
2
|
文字列でマッピングしたいときは
でも整数ではなく文字列でテーブルに持ちたいときもあります。データをこんな風にしたいときですね。
Id |
Name |
Gender |
1
|
スタン
|
Male
|
2
|
カイル
|
Male
|
3
|
カートマン
|
Male
|
4
|
ケニー
|
Male
|
5
|
バターズ
|
Male
|
6
|
ウェンディ
|
Female
|
そんなときは NHibernate の IUserType インターフェイスを実装してマッピングに利用します。
例えばこんな感じの基底クラスを用意しておいて、
public class EnumStringType<TEnum> : IUserType
{
public bool Equals(object x, object y)
{
if (ReferenceEquals(x, y)) return true;
if (x == null || y == null) return false;
return x.Equals(y);
}
public int GetHashCode(object x)
{
if (x == null) return 0;
return x.GetHashCode();
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
var value = NHibernateUtil.String.NullSafeGet(rs, names);
if (value == null)
return null;
return (TEnum) Enum.Parse(typeof (TEnum), (string) value);
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
string stringValue = null;
if (value != null)
stringValue = ((TEnum) value).ToString();
NHibernateUtil.String.NullSafeSet(cmd, stringValue, index);
}
public object DeepCopy(object value)
{
return value;
}
public object Replace(object original, object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
return cached;
}
public object Disassemble(object value)
{
return value;
}
public SqlType[] SqlTypes
{
get { return new[] {NHibernateUtil.String.SqlType}; }
}
public Type ReturnedType
{
get { return typeof (TEnum); }
}
public bool IsMutable
{
get { return false; }
}
}
これを派生した、目的の enum 用のクラスを作ればOKです。
public class GenderStringType : EnumStringType<Gender>
{
}
テーブル
あとはテーブルの列の型を文字列に変更して...
CREATE TABLE [dbo].[Students] (
[Id] INT IDENTITY NOT NULL PRIMARY KEY,
[Name] NVARCHAR (100) NOT NULL,
[Gender] NVARCHAR (10) NOT NULL,
CHECK ([Gender] = ‘Male‘ OR [Gender] = ‘Female‘)
);
マッピング
マッピングに利用する UserType を指定してあげます。
<class name="Student" table="Students">
<id name="Id">
<generator class="identity"></generator>
</id>
<property name="Name"></property>
<property name="Gender" type="Sample.NH.UserTypes.GenderStringType, Sample.NH"></property>
</class>
これで enum を文字列で永続化できます。
補足
例では簡潔にするために省きましたが、整数/文字列どちらで保存する場合も、
実際にはマスタテーブル(この例だと Genders とか)を作って外部キー制約を指定しておいたほうがいいですね。
CREATE TABLE [dbo].[Students] (
[Id] INT IDENTITY NOT NULL PRIMARY KEY,
[Name] NVARCHAR (100) NOT NULL,
[Gender] NVARCHAR (10) NOT NULL,
FOREIGN KEY (Gender) REFERENCES Genders (Value)
);
CREATE TABLE [dbo].[Genders] (
[Id] INT NOT NULL PRIMARY KEY,
[Value] NVARCHAR (10) NOT NULL,
UNIQUE (Value)
);