You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在(Fluent) NHibernate中将枚举映射为整数外键?

问题:如何在(Fluent) NHibernate中将枚举属性映射为整数外键?

需求:将Communication实体中类型为CommunicationType枚举的Type字段,映射为指向CommunicationType表的整数外键。该枚举在数据库中对应整数字段,同时关联存储枚举有效值的表用于报表统计。

相关代码如下:

public enum CommunicationType {
    General = 1,
    ServicesContactEnquiry = 2,
    ChangePassword = 3,
    OrderDetails = 4,  
}

// 数据库中用于报表统计的枚举对应表实体
public class CommunicationTypeRecord {
    public virtual int ID { get; set; }
    public virtual string Name { get; set; }
    public virtual string Description { get; set; }
}

public class Communication {
    public virtual CommunicationType Type { get; set; }
    // 其他字段省略
}

public class CommunicationMap : ClassMap<Communication> {
    public CommunicationMap() {
        Map(x => x.Type).CustomType<CommunicationType>().Not.Nullable();  // 需要改为外键 -> [CommunicationType].[ID]
        // 其他字段省略
    }
}

public class CommunicationTypeRecordMap : ClassMap<CommunicationTypeRecord> {
    public CommunicationTypeRecordMap () {
        Table("CommunicationType");
        Id(x => x.ID).Column("ID").GeneratedBy.Assigned();
        Map(x => x.Name).Column("Name").Not.Nullable().CustomType("AnsiString");
        Map(x => x.Description).Column("Description").Nullable();
    }
}

注意:也接受非Fluent的NHibernate解决方案。


尝试过的方案及问题

尝试以下配置,能正确生成数据库,但无法完成数据加载/存储:

References<CommunicationTypeRecord>(x => x.CommunicationType).ForeignKey().Not.Nullable();

抛出异常:

[ArgumentException: 你的映射存在问题。可能是尝试将System.ValueType映射到(NHibernate不允许),或者错误地使用了映射到的IDictionary。

值类型(CommunicationType)不能用作IdentityKey。关于值类型装箱拆箱的问题以及为何不能用作IdentityKey,相关讨论有详细说明。(Parameter 'key')]
Data:
StackTrace:
at NHibernate.Util.IdentityMap.VerifyValidKey(Object obj)
at NHibernate.Util.IdentityMap.set_Item(Object key, Object value)
at NHibernate.Engine.StatefulPersistenceContext.AddChildParent(Object child, Object parent)
at NHibernate.Engine.Cascade.CascadeToOne(Object parent, Object child, IType type, CascadeStyle style, Object anything, Boolean isCascadeDeleteEnabled)
at NHibernate.Engine.Cascade.CascadeAssociation(Object parent, Object child, IType type, CascadeStyle style, Object anything, Boolean isCascadeDeleteEnabled)
at NHibernate.Engine.Cascade.CascadeProperty(Object parent, Object child, IType type, CascadeStyle style, String propertyName, Object anything, Boolean isCascadeDeleteEnabled)
at NHibernate.Engine.Cascade.CascadeOn(IEntityPersister persister, Object parent, Object anything)
at NHibernate.Event.Default.AbstractSaveEventListener.CascadeBeforeSave(IEventSource source, IEntityPersister persister, Object entity, Object anything)
at NHibernate.Event.Default.AbstractSaveEventListener.PerformSaveOrReplicate(Object entity, EntityKey key, IEntityPersister persister, Boolean useIdentityColumn, Object anything, IEventSource source, Boolean requiresImmediateIdAccess)
at NHibernate.Event.Default.AbstractSaveEventListener.PerformSave(Object entity, Object id, IEntityPersister persister, Boolean useIdentityColumn, Object anything, IEventSource source, Boolean requiresImmediateIdAccess)
at NHibernate.Event.Default.AbstractSaveEventListener.SaveWithGeneratedId(Object entity, String entityName, Object anything, IEventSource source, Boolean requiresImmediateIdAccess)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.SaveWithGeneratedOrRequestedId(SaveOrUpdateEvent event)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.EntityIsTransient(SaveOrUpdateEvent event)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.PerformSaveOrUpdate(SaveOrUpdateEvent event)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.OnSaveOrUpdate(SaveOrUpdateEvent event)
at NHibernate.Impl.SessionImpl.FireSaveOrUpdate(SaveOrUpdateEvent event)
at NHibernate.Impl.SessionImpl.SaveOrUpdate(Object obj)


解决方案

核心问题:NHibernate不允许直接将值类型(枚举)作为实体引用的映射目标,References仅支持引用类型的实体关联。以下是两种可靠解决方案:

方案1:Fluent NHibernate双属性映射(推荐)

同时维护业务用的枚举属性和NHibernate用的实体引用属性,将两者绑定到同一个数据库字段,既保留枚举的易用性,又维护外键约束:

public class Communication {
    // 业务代码直接使用的枚举属性
    public virtual CommunicationType Type { get; set; }
    // 仅NHibernate使用的实体引用,设为protected避免业务代码直接操作
    protected virtual CommunicationTypeRecord TypeRecord { get; set; }
    // 其他字段省略
}

public class CommunicationMap : ClassMap<Communication> {
    public CommunicationMap() {
        // 映射实体引用作为外键,绑定到Type字段
        References(x => x.TypeRecord)
            .Column("Type")
            .Not.Nullable()
            .ReadOnly() // 枚举属性控制值,无需修改实体引用
            .ForeignKey("FK_Communication_CommunicationType");
        
        // 映射枚举属性,与实体引用共享同一个数据库字段
        Map(x => x.Type)
            .Column("Type")
            .CustomType<CommunicationType>()
            .Not.Insert() // 避免重复插入
            .Not.Update(); // 避免重复更新
    }
}

方案2:原生NHibernate XML映射

如果使用XML配置,可通过many-to-one映射实体引用,再在实体中封装枚举属性:

<class name="Communication" table="Communication">
    <!-- 其他属性配置 -->
    <many-to-one name="TypeRecord" column="Type" class="CommunicationTypeRecord" not-null="true" readonly="true"/>
</class>

<class name="CommunicationTypeRecord" table="CommunicationType">
    <id name="ID" column="ID" type="Int32">
        <generator class="assigned"/>
    </id>
    <property name="Name" column="Name" type="AnsiString" not-null="true"/>
    <property name="Description" column="Description" type="String"/>
</class>

实体代码:

public class Communication {
    private CommunicationType _type;
    public virtual CommunicationType Type {
        get => _type;
        set {
            _type = value;
            TypeRecord = new CommunicationTypeRecord { ID = (int)value };
        }
    }
    public virtual CommunicationTypeRecord TypeRecord { get; protected set; }
    // 其他字段省略
}

关键说明

  • 不可直接用References映射枚举,因为枚举是值类型,NHibernate的实体关联仅支持引用类型。
  • 双属性方案兼顾业务代码的简洁性和数据库外键约束的完整性,是生产环境最常用的方案。
  • 若无需业务层访问CommunicationTypeRecord,可将该属性设为protected或internal,减少不必要的暴露。

内容的提问来源于stack exchange,提问作者Herman Schoenfeld

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 20:15:28