2009-11-26 11 views
6

背景NHibernateのメタデータから、クラスのフィールド名とテーブルのカラム名を取得

私は醜いコーナーのすべての種類のレガシーデータベースを使用しています。 1ビットは監査です。監査証跡を持つべきフィールドのテーブル名/フィールドの組み合わせを列挙したテーブルがあります。たとえば、テーブル名に「WORKORDER」、フィールド名に「STATUS」を持つ行がある場合、アプリケーションでWorkorder.Statusプロパティが変更されるたびに監査テーブルに行を追加する必要があります。私はNHのイベントやインターセプタというアプローチを知っていますが、私がその段階に入る前に把握しなければならない問題があります。私が知っている必要があり何

質問

は、(a)において、データベースのフィールド名と、(b)は、関連するプロパティ名を含む単一の永続クラスのためのキー/値ペアのリストを取得する方法ですクラス。したがって、私の例では、ワークオーダーと呼ばれるクラスがWORKORDERと呼ばれるテーブルに関連付けられています。私はそのWorkorderクラスのCurrentStatusというプロパティを持っています。 WORKORDER表の一致プロパティーはSTATUSです。プロパティ名とテーブルの列名が一致していないことに注目してください。監査の前後のデータにアクセスするには、プロパティ名を知る必要があります。しかし、私は愚かなレガシー "AuditTheseColumns"テーブルを照会できるように、バッキング列名を知る必要もあります。

は、私は自分のアプリケーションに

を試してみた私は、 "TS" と "IP" からWorkorder.CurrentStatusを変更します。監査追跡テーブルを見て、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); 
      } 
     } 

しかし同時にではない

私の知る限り、あなたはクラスについての情報を得ることができます。何か案は?私は次にどこを見なければならないのか分かりません。

+0

これが直接あなたの質問に関連していないが、それはアイデアかもしれません - あなたはなぜデータベース上のオブジェクトレベルの変更を監査しないのですか?エンドユーザーはUIレベルの.NETオブジェクトを処理しています。また、.NETデータオブジェクトはデータベースオブジェクト(テーブル、ビューなど)を1:1に反映しているだけでなく、.NETオブジェクトのイベントをデータベースもの。どう思いますか? – Leo

+0

もしそれが私のものだったら、私はそのタイプのアプローチを間違いなく見ています。残念ながら、私はよく確立された製品の背後にあるデータベースを使って作業しており、監査の方法を迂回することはできません。そう、はい、私は欲しい!しかし、残念なことに、このプロジェクトの実行可能なアプローチではありません。 – Dylan

+0

私のために;私はテーブル名とキー列名を格納する定数を使うことにしました。その後、nhibernateマッピングファイルとraw tsqlの定数を使用します – kite

答えて

1

今私はここに正しく理解していれば、あなたが何ができるかです....

一つの方法は、前に埋め込まれたかのNHibernateのセッションの工場であっても後にされているDLLからXMLマッピングファイルを読み込み、解析するだろうビルド。この方法で、XMLファイルから必要な情報をすべて取得し(列はどのプロパティに対応するか)、エンティティの名前を保持するカスタムオブジェクトのグローバルな(おそらく静的な)コレクションを作成し、キーにはプロパティ名と値列名(またはその逆)。

このグローバルコレクションにアクセスして、説明したようにSaveOrUpdate()を呼び出した直後に必要な情報を取得できます。 このアプローチの欠点は、独自のXML解析ロジックを作成して、必要な情報をXMLマッピングファイルから取得する必要があることです。

代わりに、各プロパティに対応する列名を取得するために、エンティティの各プロパティを装飾するカスタム属性を作成することもできます。 例は次のようになります。

[ColumnName("MyColumn")] 
public string Status { get; set; } 

用いた反射あなたは簡単にプロパティ名と、このプロパティがマッピングされる属性から列名を取得することができます。

データベーススキーマが更新されるとき、この方法の欠点は、列名を属性値と同期させなければならないことです。

+0

私はこれを今週早く検討していましたが、他のスタックオーバーフローの回答についても同様の提案がありました。あなたが提案した理由(重複、物事を同期させておくことを忘れる)のためにアプローチを破棄しましたが、あなたが正しいと思います。私はちょうどそれを吸う必要があり、NHibernateの内部ではもはや垣間見るよりもむしろ属性を使用する必要があります。入力いただきありがとうございます。 – Dylan

11

どのように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); 
+0

甘い。応答してくれてありがとう。 – Dylan

+0

ええと、私はこれがコンポーネントのないマッピングされたエンティティでのみ機能することに気付きました。指数関数的に難しくなり、コンポーネントのデータベース列/フィールド名とクラスプロパティ名のキー値ペアを取り出すために、NHibernateのプライベートフィールドを反映する必要があります。私がそれに慣れれば、それを修正したものを投稿しますが、コンポーネントを使用しない場合でもこれはまだ機能します。実際、columnNameArrayには実際にはすべてのデータベースの列/フィールド名が含まれているため、コンポーネントのクラス/エンティティのプロパティ名のコレクションに簡単にアクセスできません。 –

+0

さて、コンポーネントを使ってマップされたエンティティで動作するように更新しました! –

関連する問題