NHibernateメタデータからクラスフィールド名とテーブル列名を取得する
-
05-07-2019 - |
質問
背景
私は、あらゆる種類のいコーナーを持つレガシーデータベースを使用しています。 1つは監査です。監査証跡が必要なフィールドのテーブル名/フィールドの組み合わせをリストするテーブルがあります。たとえば、" WORKORDER"を持つ行がある場合テーブル名と" STATUS"フィールド名については、アプリケーションでWorkorder.Statusプロパティが変更されるたびに、監査テーブルに行を追加する必要があります。 NHイベントまたはインターセプターというアプローチを知っていますが、その段階に到達する前に問題を見つけなければなりません。
質問
知っておくべきことは、(a)データベースフィールド名と(b)クラスの関連するプロパティ名を含む単一の永続クラスのキー/値ペアのリストを取得する方法です。したがって、私の例では、WORKORDERという(驚くことではない)テーブルに関連付けられたWorkorderというクラスがあります。 CurrentStatusというWorkorderクラスのプロパティがあります。 WORKORDERテーブルの一致するプロパティはSTATUSです。プロパティ名とテーブル列名の不一致に注意してください。監査の前後のデータにアクセスするには、プロパティ名を知る必要があります。しかし、バッキングカラムの名前も知っておく必要があります。そうすれば、愚かなレガシーの「AuditTheseColumns」をクエリできます。テーブル。
試したこと
私のアプリケーションでは、Workorder.CurrentStatusを" TS"から変更します。 「IP」へ。監査追跡テーブルを見ると、WORKORDER.STATUS列が追跡されていることがわかります。したがって、Session.SaveOrUpdate(workorder)を呼び出した後、STATUS列に関連付けられたWorkorderプロパティを見つけて、古い(" TS")と新しい(" IP")値を伝えるSession.Save(auditRecord)を実行する必要があります。
私が知る限り、クラスに関する情報を取得できます:
var fieldNames = new List<string>();
IClassMetadata classMetadata = SessionFactory(Resources.CityworksDatasource).GetClassMetadata(typeof(T));
int propertyCount = 0;
foreach (IType propertyType in classMetadata.PropertyTypes)
{
if (propertyType.IsComponentType)
{
var cp = (ComponentType)propertyType;
foreach (string propertyName in cp.PropertyNames)
{
fieldNames.Add(propertyName);
}
}
else if(!propertyType.IsCollectionType)
{
fieldNames.Add(classMetadata.PropertyNames[propertyCount + 1]);
}
propertyCount++;
}
テーブルに関する情報:
var columnNames = new List<string>();
PersistentClass mappingMeta = ConfigureCityworks().GetClassMapping(typeof(T));
foreach (Property property in mappingMeta.PropertyIterator)
{
foreach (Column selectable in property.ColumnIterator)
{
if (columnNames.Contains(selectable.Name)) continue;
columnNames.Add(selectable.Name);
}
}
しかし、同時にではありません。何か案は?次はどこに行くか迷っています。
解決
さて、ここで正しく理解できれば、あなたにできることは...
1つの方法は、NHibernateセッションファクトリがビルドされる前またはビルドされた後に埋め込まれたdllからXMLマッピングファイルを読み取り、解析することです。この方法では、XMLファイルから必要なすべての情報を取得し(列はどのプロパティに対応するか)、エンティティ名とプロパティ名と値をキーに持つ辞書を保持するカスタムオブジェクトのグローバル(おそらく静的)コレクションを作成できます列名(またはその逆)。
このグローバルコレクションにアクセスすると、説明したSaveOrUpdate()の呼び出し直後に必要な情報を取得できます。 このアプローチの欠点は、XMLマッピングファイルから必要な情報を取得するために、独自のXML解析ロジックを記述する必要があることです。
別の方法は、エンティティの各プロパティを装飾するカスタム属性を作成して、各プロパティに対応する列名を取得することです。 例は次のとおりです。
[ColumnName("MyColumn")]
public string Status { get; set; }
リフレクションを使用すると、プロパティ名と、このプロパティがマップされている列名を属性から簡単に取得できます。
このアプローチの欠点は、データベーススキーマが更新されたときに列名と属性値を同期させる必要があることです。
他のヒント
NHibernateによってマップされたエンティティのデータベース列/フィールド名およびクラスプロパティ名を取得する方法:
using System;
using System.Collections.Generic;
using System.Reflection;
using NHibernate;
using NHibernate.Persister.Entity;
namespace Stackoverflow.Example
{
/// <summary>
/// NHibernate helper class
/// </summary>
/// <remarks>
/// Assumes you are using NHibernate version 3.1.0.4000 or greater (Not tested on previous versions)
/// </remarks>
public class NHibernateHelper
{
/// <summary>
/// Creates a dictionary of property and database column/field name given an
/// NHibernate mapped entity
/// </summary>
/// <remarks>
/// This method uses reflection to obtain an NHibernate internal private dictionary.
/// This is the easiest method I know that will also work with entitys that have mapped components.
/// </remarks>
/// <param name="sessionFactory">NHibernate SessionFactory</param>
/// <param name="entity">An mapped entity</param>
/// <returns>Entity Property/Database column dictionary</returns>
public static Dictionary<string, string> GetPropertyAndColumnNames(ISessionFactory sessionFactory, object entity)
{
// Get the objects type
Type type = entity.GetType();
// Get the entity's NHibernate metadata
var metaData = sessionFactory.GetClassMetadata(type.ToString());
// Gets the entity's persister
var persister = (AbstractEntityPersister)metaData;
// Creating our own Dictionary<Entity property name, Database column/filed name>()
var d = new Dictionary<string, string>();
// Get the entity's identifier
string entityIdentifier = metaData.IdentifierPropertyName;
// Get the database identifier
// Note: We are only getting the first key column.
// Adjust this code to your needs if you are using composite keys!
string databaseIdentifier = persister.KeyColumnNames[0];
// Adding the identifier as the first entry
d.Add(entityIdentifier, databaseIdentifier);
// Using reflection to get a private field on the AbstractEntityPersister class
var fieldInfo = typeof(AbstractEntityPersister)
.GetField("subclassPropertyColumnNames", BindingFlags.NonPublic | BindingFlags.Instance);
// This internal NHibernate dictionary contains the entity property name as a key and
// database column/field name as the value
var pairs = (Dictionary<string, string[]>)fieldInfo.GetValue(persister);
foreach (var pair in pairs)
{
if (pair.Value.Length > 0)
{
// The database identifier typically appears more than once in the NHibernate dictionary
// so we are just filtering it out since we have already added it to our own dictionary
if (pair.Value[0] == databaseIdentifier)
break;
d.Add(pair.Key, pair.Value[0]);
}
}
return d;
}
}
}
使用法:
// Get your NHiberate SessionFactory wherever that is in your application
var sessionFactory = NHibernateHelper.SessionFactory;
// Get an entity that you know is mapped by NHibernate
var customer = new Customer();
// Get a dictionary of the database column / field names and their corresponding entity property names
var propertyAndColumnNamesDictionary =
Stackoverflow.Example.NHibernateHelper.GetPropertyAndColumnNames(sessionFactory, customer);