2009-11-26 2 views
6

HintergrundErste-Klasse-Feldnamen und Tabellenspaltennamen von NHibernate Metadaten

ich mit allen Arten von hässlichen Ecken eines Vermächtnis Datenbank verwenden. Ein bisschen ist Auditing. Es gibt eine Tabelle, die Tabellenname/Feldkombinationen von Feldern auflistet, die einen Audit-Trail haben sollten. Wenn beispielsweise eine Zeile mit dem Namen "WORKORDER" für den Tabellennamen und "STATUS" für den Feldnamen vorhanden ist, muss ich der Überwachungstabelle immer dann Zeilen hinzufügen, wenn sich die Eigenschaft "Workorder.Status" in der Anwendung ändert. Ich kenne den Ansatz: NH-Ereignisse oder Abfangjäger, aber ich habe ein Problem, bevor ich zu diesem Zeitpunkt komme.

Frage

Was ich wissen muss, ist, wie für eine einzige persistente Klasse eine Liste von Schlüssel/Wert-Paare zu erhalten, die (a) die Datenbank Feldnamen und (b) die zugehörige Eigenschaftsnamen in der Klasse. Also für mein Beispiel habe ich eine Klasse namens Workorder, die mit einer Tabelle namens (keine Überraschung) WORKORDER verbunden ist. Ich habe eine Eigenschaft in dieser Workorder-Klasse namens CurrentStatus. Die übereinstimmende Eigenschaft in der Tabelle WORKORDER ist STATUS. Beachten Sie, dass der Name der Eigenschaft und der Name der Tabellenspalte nicht übereinstimmen. Ich muss den Namen der Eigenschaft kennen, um auf die Vorher-Nachher-Daten für die Prüfung zugreifen zu können. Aber ich muss auch den Namen der Backing-Spalte kennen, damit ich die dumme Tabelle "AuditTheseColumns" abfragen kann.

Was ich versucht habe

in meiner Anwendung ändern Sie den Workorder.CurrentStatus von "TS" auf "IP". Ich schaue in meine Überwachungsverfolgungstabelle und sehe, dass die Spalte WORKORDER.STATUS verfolgt wird. Nach dem Aufruf von Session.SaveOrUpdate (Arbeitsauftrag) muss ich die Arbeitsorder-Eigenschaft suchen, die der STATUS-Spalte zugeordnet ist, und eine Session.Save (auditRecord) ausführen, die den alten ("TS") und neuen ("IP") Wert angibt.

 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++; 
     } 

Und Informationen über die Tabelle:

 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); 
      } 
     } 

aber nicht zugleich

Soweit ich das beurteilen kann, können Sie Informationen über die Klasse erhalten. Irgendwelche Ideen? Ich weiß nicht, wo ich als nächstes hinsehen soll.

+0

Diese nicht direkt auf Ihre Frage bezieht sich aber Es könnte eine Idee sein - warum auditieren Sie Änderungen nicht auf Objektebene statt auf Datenbank? Endbenutzer haben mit .NET-Objekten auf UI-Ebene zu tun, und weil .NET-Datenobjekte nicht nur eine 1: 1-Spiegelung von Datenbankobjekten (Tabellen, Ansichten ...) sind, ist es vielleicht genauer, Ereignisse auf .NET-Objekten anstatt auf Datenbank zu überwachen Einsen. Was denken Sie? – Leo

+0

Wenn es nach mir ginge, würde ich definitiv auf diese Art von Ansatz schauen. Leider arbeite ich mit einer Datenbank, die hinter einem gut etablierten Produkt sitzt, und ich kann ihren Ansatz zum Auditing nicht umgehen. Also ja, ich wünsche! Aber leider kein praktikabler Ansatz für dieses Projekt. – Dylan

+0

Für mich; Ich entschied mich, Konstanten zu verwenden, um die Tabellennamen und Schlüsselspaltennamen zu speichern; dann verwende ich die Konstante in den Nhibernate-Mapping-Dateien und rohen tsql – kite

Antwort

1

Nun, wenn ich hier richtig verstehen ist, was man tun könnte ....

Eine Möglichkeit wäre, die XML-Mapping-Dateien aus der DLL zu lesen und zu analysieren, die vor oder auch nach der Fabrik NHibernate Sitzung eingebettet sind, bauen. Auf diese Weise können Sie alle benötigten Informationen aus den XML-Dateien abrufen (wobei die Spalte der jeweiligen Eigenschaft entspricht) und eine globale (wahrscheinlich statische) Sammlung benutzerdefinierter Objekte füllen, die den Namen der Entität und ein Wörterbuch enthalten der Spaltenname (oder umgekehrt).

Sie können dann auf diese globale Sammlung zugreifen, um die Informationen zu erhalten, die Sie direkt nach dem Aufruf von SaveOrUpdate() benötigen, wie Sie es beschrieben haben. Der Nachteil dieses Ansatzes besteht darin, dass Sie Ihre eigene XML-Parsing-Logik schreiben müssen, um die benötigten Informationen aus den XML-Zuordnungsdateien abzurufen.

Eine Alternative wäre, ein benutzerdefiniertes Attribut zu erstellen, um jede Eigenschaft Ihrer Entitäten zu dekorieren, um den Spaltennamen zu erhalten, der jeder Eigenschaft entspricht. Ein Beispiel wäre:

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

Mit Reflexion können Sie bequem die Eigenschaftsnamen und die aus dem Attribute der Spaltennamen erhalten, dass diese Eigenschaft zugeordnet wird.

Der Nachteil dieses Ansatzes wäre, dass Sie Ihre Spaltennamen mit den Attributwerten synchronisieren müssen, wenn das Datenbankschema aktualisiert wird.

+0

Ich dachte dies früher in der Woche und fand einige ähnliche Vorschläge zu anderen Stack Overflow-Antworten. Ich habe den Ansatz aufgrund des von Ihnen vorgeschlagenen Grundes verworfen (Duplikation, vergessen, die Dinge synchron zu halten), aber ich denke, Sie haben Recht. Ich muss es einfach aufheben und Attribute verwenden, anstatt mich in NHibernate Interna zu wälzen. Danke für die Eingabe. – Dylan

11

Wie die Datenbankspalte/Feldnamen und Klasseneigenschaftsnamen für eine Entität von NHibernate zugeordnet bekommen:

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; 
     } 
    } 
} 

Verbrauch:

// 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

Süß. Vielen Dank für Ihre Antwort. – Dylan

+0

Hmm, ich habe gerade festgestellt, dass dies nur für abgebildete Entitäten ohne Komponenten funktioniert. Es sieht so aus, als ob es exponentiell schwieriger wird und erfordert Reflektieren über private Felder in NHibernate, um die Schlüsselwertpaare der Datenbankspalten-/Feldnamen- und Klasseneigenschaftsnamen für Komponenten herauszuziehen. Wenn ich dazu komme, werde ich einen Fix dafür posten, aber für den Moment funktioniert das immer noch, wenn Sie keine Komponenten verwenden. Zu guter Letzt enthält das columnNameArray tatsächlich alle Datenbankspalten-/Feldnamen, nur dass wir nicht einfach Zugriff auf die Auflistung von Klassen-/Entitätseigenschaftsnamen einer Komponente haben. –

+0

Okay, ich habe es aktualisiert, um jetzt mit zugeordneten Entitäten mit Komponenten zu arbeiten! –