从NHibernate元数据获取类字段名和表列名

从NHibernate元数据获取类字段名和表列名,nhibernate,metadata,Nhibernate,Metadata,背景 我使用的是一个遗留数据库,里面有各种各样难看的角落。一点是审计。有一个表列出了应具有审核跟踪的字段的tablename/字段组合。例如,如果有一行的表名为“WORKORDER”,字段名为“STATUS”,则每当应用程序中的WORKORDER.STATUS属性发生更改时,我需要将行添加到审核表中。我知道方法:NH事件或拦截器,但在进入该阶段之前,我有一个问题需要解决 问题 我需要知道的是如何获取单个持久类的键/值对列表,该持久类包含(a)数据库字段名和(b)类中关联的属性名。因此,在我的示例

背景

我使用的是一个遗留数据库,里面有各种各样难看的角落。一点是审计。有一个表列出了应具有审核跟踪的字段的tablename/字段组合。例如,如果有一行的表名为“WORKORDER”,字段名为“STATUS”,则每当应用程序中的WORKORDER.STATUS属性发生更改时,我需要将行添加到审核表中。我知道方法:NH事件或拦截器,但在进入该阶段之前,我有一个问题需要解决

问题

我需要知道的是如何获取单个持久类的键/值对列表,该持久类包含(a)数据库字段名和(b)类中关联的属性名。因此,在我的示例中,我有一个名为Workorder的类与一个名为(毫不奇怪)Workorder的表相关联。我在Workorder类上有一个名为CurrentStatus的属性。WORKORDER表中的匹配属性为STATUS。注意属性名和表列名之间的不匹配吗?我需要知道属性名称才能访问审计前后的数据。但我还需要知道支持列名,以便查询愚蠢的遗留“AuditTheseColumns”表

我尝试过的

在我的应用程序中,我将Workorder.CurrentStatus从“TS”更改为“IP”。我查看审计跟踪表,发现WORKORDER.STATUS列被跟踪。因此,在调用Session.SaveOrUpdate(workorder)之后,我需要找到与STATUS列关联的workorder属性,并执行Session.Save(auditRecord),告诉它旧(“TS”)和新(“IP”)值

据我所知,您可以获得有关该课程的信息:

        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 fieldNames=新列表();
IClassMetadata classMetadata=SessionFactory(Resources.CityworksDatasource).GetClassMetadata(typeof(T));
int propertyCount=0;
foreach(classMetadata.PropertyTypes中的IType propertyType)
{
if(propertyType.IsComponentType)
{
var cp=(ComponentType)propertyType;
foreach(cp.PropertyNames中的字符串propertyName)
{
fieldNames.Add(propertyName);
}
}
如果(!propertyType.IsCollectionType)为else,则为
{
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);
            }
        }
var columnNames=new List();
PersistentClass mappingMeta=ConfigureCityworks();
foreach(mappingMeta.PropertyInterator中的属性)
{
foreach(可在property.ColumnIterator中选择列)
{
如果(columnNames.Contains(可选的.Name))继续;
columnNames.Add(可选的.Name);
}
}

但不是同时。有什么想法吗?我不知道下一步该往哪里看。

现在如果我理解正确,这里是您可以做的

一种方法是从dll中读取和解析XML映射文件,这些文件在构建NHibernate会话工厂之前甚至之后嵌入。通过这种方式,您可以从XML文件(列对应于哪个属性)中获得所需的所有信息,并填充自定义对象的全局(可能是静态)集合,该集合将保存实体名称和一个字典,其中键为propery name,值为column name(或其他方式)

然后,您可以访问此全局集合,以便在调用SaveOrUpdate()后立即获得所需的信息。 这种方法的缺点是,您需要编写自己的XML解析逻辑来从XML映射文件中检索所需的信息

另一种方法是创建一个自定义属性来装饰实体的每个属性,以便获得对应于每个属性的列名。 例如:

[ColumnName("MyColumn")]
public string Status { get; set; }
使用反射,您可以很容易地从属性中获取属性名和该属性映射到的列名


这种方法的缺点是在更新数据库架构时必须使列名与属性值保持同步。

如何获取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;
        }
    }
}

这和您的问题并没有直接关系,但可能是一个想法——为什么不在对象级别而不是数据库级别审核更改?最终用户在UI级别处理.NET对象,因为.NET数据对象不仅仅是数据库对象(表、视图等)的1:1反映,所以在.NET对象上审核事件可能更准确,而不是在数据库对象上审核事件。你觉得怎么样?如果由我决定,我肯定会考虑这种方法。不幸的是,我使用的是一个建立良好的产品背后的数据库,我无法绕过他们的审计方法。是的,我希望如此!但遗憾的是,对于这个项目来说,这不是一个可行的方法;我决定使用常量来存储表名和键列名;然后我在nhibernate映射文件中使用了常量,原始tsqlI在本周早些时候考虑过这个问题,并在其他堆栈溢出问题上找到了一些类似的建议。由于您提出的原因(复制、忘记保持同步),我放弃了这种方法,但我认为您是对的。我只需要接受它并使用属性,而不再沉迷于NHibernate内部。谢谢你的意见。非常感谢您的回复。嗯,我刚刚意识到这只适用于没有组件的映射实体。它看起来越来越困难,需要在NHibernate中的私有字段上进行反射,才能提取数据库的键值对
// 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);