Obtendo nomes de campo de classe e nomes de coluna da tabela de NHibernate metadados
-
05-07-2019 - |
Pergunta
Fundo
Eu estou usando um banco de dados legado com todos os tipos de cantos feias. Um bit é a auditoria. Há uma tabela listando as combinações tablename / campo de campos que devem ter uma trilha de auditoria. Por exemplo, se há uma linha que tem "WorkOrder" para o nome da tabela e "STATUS" para o nome do campo, então eu preciso adicionar linha (s) para a tabela de auditoria sempre que os Workorder.Status alterações de propriedade no aplicativo. Eu sei que a abordagem: eventos NH ou interceptadores, mas eu tenho um problema de descobrir antes de eu chegar a essa fase
.Pergunta ??strong>
O que eu preciso saber é como obter uma lista de pares de chave / valor para um único classe persistente que contém (a) o nome do campo de banco de dados e (b) o nome da propriedade associada da classe. Então para o meu exemplo, eu tenho uma classe chamada Ordem de Trabalho associado com uma tabela chamada (nenhuma surpresa) WorkOrder. Eu tenho uma propriedade em que a classe Ordem de Trabalho chamado CurrentStatus. A propriedade correspondente na tabela WorkOrder é STATUS. Observe a incompatibilidade entre o nome da propriedade eo nome da coluna da tabela? Eu preciso saber o nome da propriedade para acessar o antes e depois de dados para a auditoria. Mas eu também preciso saber o nome da coluna de apoio para que eu possa consultar o legado estúpido "AuditTheseColumns" mesa.
O que eu tentei
no meu aplicativo eu mudar o Workorder.CurrentStatus de "TS" para "IP". Eu olho em minha mesa de rastreamento de auditoria e ver que a coluna WORKORDER.STATUS é monitorado. Então, depois de chamar Session.saveOrUpdate (workorder), eu preciso encontrar a propriedade Ordem de Trabalho associado à coluna STATUS e fazer um Session.Save (auditRecord) dizendo que a velha valores novos ( "IP") ( "TS") e.
Tanto quanto eu posso dizer, você pode obter informações sobre a 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++;
}
E informações sobre a tabela:
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);
}
}
Mas não ao mesmo tempo. Alguma ideia? Eu estou em uma perda para onde olhar em seguida.
Solução
Agora, se eu entendi corretamente aqui é o que você poderia fazer ....
Uma forma seria a de ler e analisar os arquivos de mapeamento XML do dll que são incorporados antes ou mesmo após a fábrica de sessão NHibernate é de construção. Desta forma, você pode obter todas as informações que você precisa a partir dos arquivos XML (com corresponde coluna para que a propriedade) e preencher uma coleção global (provavelmente estática) de objetos personalizados que irá manter o nome da entidade e um dicionário com chave o nome propery e valor o nome da coluna (ou o outro em torno de forma).
Você pode acessar esta coleção global para obter a informação que você precisa logo após a chamada para saveOrUpdate (), como você descreveu. A desvantagem dessa abordagem é que você precisa para escrever sua própria lógica de análise XML retrive a informação que você precisa a partir dos arquivos de mapeamento XML.
Uma alternativa seria criar um atributo personalizado para decorar cada propriedade de suas entidades a fim de obter o nome da coluna que corresponde a cada propriedade. Um exemplo seria:
[ColumnName("MyColumn")]
public string Status { get; set; }
Usando reflexão você pode facilmente obter o nome da propriedade ea partir do atributo o nome da coluna que esta propriedade é mapeada para.
A desvantagem dessa abordagem seria ter que manter em sincronia os nomes das colunas com os valores de atributo quando o esquema de banco de dados é atualizado.
Outras dicas
Como chegar a coluna de banco de dados nomes / campo e nomes de propriedade de classe para uma entidade mapeada pelo 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);