2009-11-26 5 views
6

ContexteObtenir les noms des champs de classe et les noms de colonnes de table à partir des métadonnées NHibernate

J'utilise une base de données existante avec toutes sortes de coins laids. Un bit est l'audit. Il existe un tableau répertoriant les combinaisons nom de table/champ des champs qui doivent avoir une trace d'audit. Par exemple, si une ligne contient "WORKORDER" pour le nom de table et "STATUS" pour le nom de champ, j'ai besoin d'ajouter des lignes à la table d'audit lorsque la propriété Workorder.Status change dans l'application. Je connais l'approche: les événements NH ou les intercepteurs, mais j'ai un problème à résoudre avant d'arriver à ce stade.

Question

Ce que je dois savoir est comment obtenir une liste de paires clé/valeur pour une seule classe persistante contenant (a) le nom du champ de base de données et (b) le nom de propriété associé au classe. Donc pour mon exemple, j'ai une classe appelée Workorder associée à une table appelée (pas de surprise) WORKORDER. J'ai une propriété sur cette classe Workorder appelée CurrentStatus. La propriété correspondante dans la table WORKORDER est STATUS. Notez l'incompatibilité entre le nom de la propriété et le nom de la colonne de la table? J'ai besoin de connaître le nom de la propriété pour accéder aux données avant et après pour l'audit. Mais j'ai aussi besoin de connaître le nom de la colonne de sauvegarde pour que je puisse interroger le stupide tableau "AuditTheseColumns".

Ce que j'ai essayé

dans ma demande que je change la Workorder.CurrentStatus de "TS" à "IP". Je regarde dans ma table de suivi d'audit et vois que la colonne WORKORDER.STATUS est suivie. Donc après avoir appelé Session.SaveOrUpdate (workorder), je dois trouver la propriété Workorder associée à la colonne STATUS et faire un Session.Save (auditRecord) en lui disant les anciennes ("TS") et nouvelles ("IP") valeurs.

Pour autant que je peux dire, vous pouvez obtenir des informations sur la classe:

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

et des informations sur la table:

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

Mais pas en même temps. Des idées? Je ne sais plus où regarder.

+0

Ce n'est pas directement lié à votre question, mais cela pourrait être une idée - pourquoi ne pas auditer les changements au niveau de l'objet au lieu de la base de données? Les utilisateurs finaux traitent l'objet .NET au niveau de l'interface utilisateur et parce que les objets de données .NET ne reflètent pas uniquement les objets de la base de données (tableaux, vues ...), il est peut-être plus précis d'auditer les événements sur les objets. ceux Qu'est-ce que tu penses? – Leo

+0

Si cela ne tenait qu'à moi, je verrais certainement ce type d'approche. Malheureusement, je travaille avec une base de données qui se cache derrière un produit bien établi et je ne peux pas contourner leur approche de l'audit. Alors oui, je souhaite! Mais hélas, pas une approche viable pour ce projet. – Dylan

+0

Pour moi; J'ai décidé d'utiliser des constantes pour stocker les noms des tables et le nom de la colonne clé; puis j'utilise la constante dans les fichiers de mappage nhibernate et tsql – kite

Répondre

1

Maintenant, si je comprends bien, voici ce que vous pourriez faire ....

Une façon serait de lire et d'analyser les fichiers de mappage XML de la dll qui sont incorporés avant ou même après l'usine de la session NHibernate est construire. De cette façon, vous pouvez obtenir toutes les informations dont vous avez besoin des fichiers XML (avec la colonne correspond à quelle propriété) et remplir une collection globale (probablement statique) d'objets personnalisés qui contiendront le nom de l'entité et un dictionnaire avec la clé. le nom de la colonne (ou l'inverse).

Vous pouvez ensuite accéder à cette collection globale pour obtenir les informations dont vous avez besoin juste après l'appel à SaveOrUpdate() tel que vous l'avez décrit. L'inconvénient de cette approche est que vous devez écrire votre propre logique d'analyse XML pour récupérer les informations dont vous avez besoin à partir des fichiers de mappage XML.

Une alternative serait de créer un attribut personnalisé pour décorer chaque propriété de vos entités afin d'obtenir le nom de colonne qui correspond à chaque propriété. Un exemple serait:

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

réflexion en utilisant vous pouvez facilement obtenir le nom de la propriété et de l'attribut le nom de colonne que cette propriété est mise en correspondance. L'inconvénient de cette approche serait de devoir synchroniser vos noms de colonne avec les valeurs d'attribut lors de la mise à jour du schéma de la base de données.

+0

brut Je considérais cela plus tôt dans la semaine et trouvé des suggestions similaires sur d'autres réponses Stack Overflow. J'ai rejeté l'approche en raison de la raison que vous avez suggérée (duplication, en oubliant de garder les choses synchronisées), mais je pense que vous avez raison. J'ai juste besoin de l'aspirer et d'utiliser des attributs plutôt que de me vautrer dans les internes de NHibernate. Merci pour la contribution. – Dylan

11

Comment obtenir la colonne de base de données/noms de champs et les noms de propriétés de classe pour une entité cartographiée par 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; 
     } 
    } 
} 

Utilisation:

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

Sweet. Merci beaucoup d'avoir répondu. – Dylan

+0

Hmm, je viens de réaliser que cela ne fonctionnera que pour les entités mappées sans composants. Il semble que cela devienne exponentiellement plus difficile et nécessite de réfléchir sur les champs privés dans NHibernate pour extraire les paires valeur/clé des noms de colonne/champ de base de données et de propriété de classe pour les composants. Si j'y arrive, je posterai un correctif pour cela mais pour l'instant cela fonctionnera encore si vous n'utilisez pas de composants. Pour info, columnNameArray DOES contient en fait tous les noms de colonne/champ de base de données, c'est juste que nous n'avons pas facilement accès à la collection de noms de propriétés de classe/entité d'un composant. –

+0

D'accord, je l'ai mis à jour pour travailler avec des entités mappées avec des composants! –