3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

NHibernateでデータベースに永続化する際、レコードの作成日や更新日を自動的に挿入する

3
Last updated at Posted at 2016-06-24

概要

データベースのレコードにCreatedAt(作成日)やUpdatedAt(更新日)などのフィールドを持たせることがあるかと思います。

NHibernateにはレコードの挿入や更新などのイベント発生時に指定した処理をさせる機構があるので、それを活用することでCreatedAtやUpdatedAtなどのフィールドを自動的に更新させることができます。

実装方針

挿入・更新などのイベントハンドラは、IPreInsertEventListenerIPreUpdateEventListenerを継承したクラス内に記述します。
そのイベントハンドラクラスのインスタンスをNHibernateConfiguration.EventListeners
PreInsertEventListenersPreUpdateEventListenersに登録することで、イベントハンドラが呼ばれるようになります。

作成日や更新日を保存するのに利用するプロパティは独自のCustom Attributeを設定することで指定します。

実際のコード

Custom Attribute

挿入・更新時に自動的に値を設定するフィールドをマークアップするのに利用するCustom Attributeです。

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
public class AutofillCurrentDateTimeOnInsertAttribute : Attribute
{
}

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
public class AutofillCurrentDateTimeOnUpdateAttribute : Attribute
{
}

イベントハンドラ

上で定義したAutofillCurrentDateTimeOnInsertAttribute AutofillCurrentDateTimeOnUpdateAttributeが設定されているプロパティで、かつ、DateTimeOffset型のプロパティだったとき、それらのプロパティの値を自動的に設定するように記述しています。

public class AutofillCurrentDateTimeEventListener : IPreUpdateEventListener, IPreInsertEventListener
{
	public void Register(Configuration configuration)
	{
	    configuration.SetListener(ListenerType.PreInsert, this);
	    configuration.SetListener(ListenerType.PreUpdate, this);
	}

	public bool OnPreInsert(PreInsertEvent @event)
	{
	    var now = DateTimeOffset.Now;

	    @event.Entity.GetType().GetProperties()
	        .Where(_ => Attribute.IsDefined(_, typeof(AutofillCurrentDateTimeOnInsertAttribute)) && _.PropertyType == typeof(DateTimeOffset))
	        .ToList()
	        .ForEach(prop => {
	            Set(@event.Persister, @event.State, prop.Name, now);
	            prop.SetValue(@event.Entity, now);
	        });
	    

	    return false;
	}

	public bool OnPreUpdate(PreUpdateEvent @event)
	{
	    var now = DateTimeOffset.Now;

	    @event.Entity.GetType().GetProperties()
	        .Where(_ => Attribute.IsDefined(_, typeof(AutofillCurrentDateTimeOnUpdateAttribute)) && _.PropertyType == typeof(DateTimeOffset))
	        .ToList()
	        .ForEach(prop => {
	            Set(@event.Persister, @event.State, prop.Name, now);
	            prop.SetValue(@event.Entity, now);
	        });

	    return false;
	}

	//ref: http://stackoverflow.com/a/24908880
	private void Set(IEntityPersister persister, object[] state, string propertyName, object value)
	{
	    var index = Array.IndexOf(persister.PropertyNames, propertyName);
	    if (index == -1)
	        return;
	    state[index] = value;
	}
}

イベントハンドラの登録

Fluent NHibernateの場合、次のような形で登録することになると思います。


sessionFactory = Fluently.Configure()
	.Database(
	    SQLiteConfiguration.Standard
	        .InMemory()
	        .ShowSql()
	    )
	.Mappings(m =>
	{
	    m.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly());
	})
	.ExposeConfiguration(cfg => {
	    cfg.EventListeners.PreInsertEventListeners = new IPreInsertEventListener[] {
	        new AuditEventListener()
	    };
	    cfg.EventListeners.PreUpdateEventListeners = new IPreUpdateEventListener[] {
	        new AuditEventListener()
	    };
	    configuration = cfg;
	})
	.BuildSessionFactory();

モデル側で自動設定対象となるプロパティにAttributeでマークアップする

モデル側では上で定義したAttributeを付与するだけです。
例えば、以下の例では、CreatedAtはレコードが作成されたとき、UpdatedAtはレコード作成と更新があったときにNHibernateによって自動的に現在時刻が設定されるようになります。

public class User
{
	public virtual UserId UserId { get; protected internal set; }

	[AutofillCurrentDateTimeOnInsert]
	public virtual DateTimeOffset CreatedAt { get; protected internal set; }

	[AutofillCurrentDateTimeOnInsert, AutofillCurrentDateTimeOnUpdate]
	public virtual DateTimeOffset UpdatedAt { get; protected internal set; }
}

備考

今回はCustom Attributeでマークアップすることで対象のプロパティを特定していましたが、『ITimestampableなど独自インターフェイスを実装しているクラスだけ対象にする』『CreatedAtという名前のプロパティを対象にする』というような実装も可能です。

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?