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

时间:2009-11-26 00:34:24

标签: nhibernate metadata

背景

我正在使用具有各种丑陋角落的遗留数据库。一点是审计。有一个表列出了应具有审计跟踪的字段的表名/字段组合。例如,如果某个行的表名为“WORKORDER”而fieldname为“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”)值。 / p>

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

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

但不是在同一时间。有任何想法吗?我不知道接下来会去哪看。

2 个答案:

答案 0 :(得分:12)

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

用法:

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

答案 1 :(得分:1)

现在如果我理解正确,你可以做什么......

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

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

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

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

使用反射,您可以轻松获取属性名称,并从属性中获取此属性映射到的列名称。

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