2009-11-26 26 views
6

AntecedentesConseguir nombres de los campos de clase y los nombres de columna de la tabla de metadatos NHibernate

Estoy utilizando una base de datos heredada con todo tipo de rincones feos. Un bit es auditar. Hay una tabla que enumera las combinaciones nombre de tabla/campo de campos que deberían tener una pista de auditoría. Por ejemplo, si hay una fila que tiene "WORKORDER" para el nombre de la tabla y "STATUS" para el nombre del campo, entonces necesito agregar fila (s) a la tabla de auditoría siempre que la propiedad Workorder.Status cambie en la aplicación. Conozco el enfoque: eventos NH o interceptores, pero tengo un problema que resolver antes de llegar a esa etapa.

Pregunta

Lo que necesito saber es cómo conseguir una lista de pares clave/valor para una sola clase persistente que contiene (a) el nombre del campo de base de datos y (b) el nombre de la propiedad asociada en el clase. Entonces, para mi ejemplo, tengo una clase llamada Workorder asociada con una tabla llamada (sin sorpresa) WORKORDER. Tengo una propiedad en esa clase Workorder llamada CurrentStatus. La propiedad coincidente en la tabla WORKORDER es STATUS. ¿Observe la discrepancia entre el nombre de la propiedad y el nombre de la columna de la tabla? Necesito saber el nombre de la propiedad para acceder a los datos de antes y después de la auditoría. Pero también necesito saber el nombre de la columna de respaldo para poder consultar la estúpida tabla heredada "AuditTheseColumns".

Lo que he tratado

en mi aplicación puedo cambiar el Workorder.CurrentStatus de "TS" a "IP". Miro en mi tabla de seguimiento de auditoría y veo que se realiza un seguimiento de la columna WORKORDER.STATUS. Entonces, después de llamar a Session.SaveOrUpdate (workorder), necesito encontrar la propiedad Workorder asociada con la columna STATUS y hacer una Session.Save (auditRecord) diciéndole los valores antiguos ("TS") y nuevos ("IP").

Por lo que yo puedo decir, se puede obtener información acerca de la clase:

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

y la información sobre la mesa:

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

Pero no al mismo tiempo. ¿Algunas ideas? No puedo mirar a continuación.

+0

Esto no está directamente relacionada con su pregunta, pero podría ser una idea: ¿por qué no auditas los cambios en el nivel de objeto en lugar de en la base de datos? Los usuarios finales tratan con el objeto .NET en el nivel UI y como los objetos de datos .NET no son solo reflejos 1: 1 de objetos de base de datos (tablas, vistas ...) quizás sea más preciso auditar eventos en objetos .NET en lugar de bases de datos unos. ¿Qué piensas? – Leo

+0

Si dependiera de mí definitivamente estaría buscando ese tipo de enfoque. Desafortunadamente, estoy trabajando con una base de datos que se encuentra detrás de un producto bien establecido y no puedo eludir su enfoque de auditoría. Entonces sí, ¡ojalá! Pero, por desgracia, no es un enfoque viable para este proyecto. – Dylan

+0

Para mí; Decidí usar constantes para almacenar los nombres de las tablas y el nombre de la columna clave; luego utilizo la constante en los archivos de mapeo nhibernate y tsql en bruto – kite

Respuesta

1

Ahora bien, si he entendido bien esto es lo que podría hacer ....

Una forma sería la de leer y analizar los archivos de mapeo XML desde el archivo DLL que se incrusta antes o incluso después de la sesión de NHibernate es la fábrica construir. De esta forma, puede obtener toda la información que necesita de los archivos XML (con la columna correspondiente a cada propiedad) y llenar una colección global (probablemente estática) de objetos personalizados que contendrán el nombre de la entidad y un diccionario con clave, nombre de propiedad y valor el nombre de la columna (o al revés).

A continuación, puede acceder a esta colección global para obtener la información que necesita inmediatamente después de la llamada a SaveOrUpdate() como la describió. La desventaja de este enfoque es que necesita escribir su propia lógica de análisis XML para recuperar la información que necesita de los archivos de asignación XML.

Una alternativa sería crear un atributo personalizado para decorar cada propiedad de sus entidades con el fin de obtener el nombre de columna que corresponde a cada propiedad. Un ejemplo sería:

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

El uso de la reflexión se puede obtener fácilmente el nombre de la propiedad y la del atributo nombre de la columna que esta propiedad está asignado.

La desventaja de este enfoque sería tener que mantener sincronizados los nombres de sus columnas con los valores de los atributos cuando se actualice el esquema de la base de datos.

+0

Estaba considerando esto a principios de semana y encontré algunas sugerencias similares en otras respuestas de desbordamiento de pila. Descarté el enfoque debido a la razón por la que sugirió (duplicación, olvidando mantener las cosas sincronizadas), pero creo que tiene razón. Solo necesito absorberlo y usar atributos en lugar de revolcarme en los internos de NHibernate por más tiempo. Gracias por el aporte. – Dylan

11

Como llegar La columna de base de datos de nombres/campo y los nombres de las propiedades de clase para una entidad asignada por 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; 
     } 
    } 
} 

Uso:

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

Dulce. Muchas gracias por responder. – Dylan

+0

Hmm, me acabo de dar cuenta de que esto solo funcionará para las entidades mapeadas sin componentes. Parece que se vuelve exponencialmente más difícil y requiere que se refleje sobre los campos privados en NHibernate para extraer los pares clave de valor del nombre de la columna/campo de la base de datos y los nombres de las propiedades de la clase para los componentes. Si lo hago, publicaré una solución para eso, pero por ahora esto funcionará si no usas componentes. FYI, el columnNameArray realmente contiene todos los nombres de columna/campo de la base de datos, es solo que no tenemos acceso fácil a la colección de un componente de nombres de propiedad de clase/entidad. –

+0

¡De acuerdo, lo he actualizado para que ahora funcione con entidades mapeadas con componentes! –

Cuestiones relacionadas