Fluent NHibernate多表共享数据模型查询结果异常问题排查
问题分析与解决
问题背景
你的数据库存在多个结构完全一致的表,尝试用单个通用实体类GenericTable映射这些表,并通过原生SQL查询获取数据,但所有查询结果都指向同一张表(MyTable),即使查询的是DifferentTable,返回的依然是MyTable的数据。
数据库示例结构:
CREATE TABLE MyTable ( Id INT NOT NULL, Title VARCHAR(255) NOT NULL ) INSERT INTO MyTable (Id, Title) VALUE (1, 'foo') INSERT INTO MyTable (Id, Title) VALUE (2, 'bar') CREATE TABLE DifferentTable ( Id INT NOT NULL, Title VARCHAR(255) NOT NULL ) INSERT INTO DifferentTable (Id, Title) VALUE (1, 'foobar')
通用实体与映射代码:
public class GenericTable { public virtual int Id { get; set; } public virtual string Title { get; set; } } public class GenericTableMap : ClassMap<GenericTable> { public GenericTableMap() { Id(x => x.Id); Map(x => x.Title); } }
查询代码:
List<GenericTable> result = MyNHibernateSession.CreateSQLQuery("SELECT * FROM MyTable") .AddEntity(typeof(GenericTable)) .List<GenericTable>() .ToList(); List<GenericTable> differentResult = MyNHibernateSession.CreateSQLQuery("SELECT * FROM DifferentTable") .AddEntity(typeof(GenericTable)) .List<GenericTable>() .ToList();
异常现象:
result == [ { 1, "foo" }, { 2, "bar" } ] differentResult == [ { 1, "foo" } ]
核心原因
- NHibernate一级缓存机制:NHibernate的Session一级缓存按「实体类型 + Id」存储对象。第一次查询
MyTable得到Id=1的GenericTable对象后,该对象会被缓存;第二次查询DifferentTable返回Id=1的结果时,NHibernate直接从缓存取已有对象,不会重新映射新结果。 - 实体映射的表关联缺失:你的
GenericTableMap未指定Table()关联具体表,NHibernate会默认用类名推断表名,再加上AddEntity强制使用实体映射规则,导致所有查询都绑定到了默认关联的表(此处恰好是MyTable)。
解决方案
方案1:临时绕过缓存(仅解决当前查询问题)
查询时忽略一级缓存,强制NHibernate重新映射结果集:
List<GenericTable> differentResult = MyNHibernateSession.CreateSQLQuery("SELECT * FROM DifferentTable") .AddEntity(typeof(GenericTable)) .SetCacheMode(CacheMode.Ignore) .List<GenericTable>() .ToList();
缺点:无法从根本上支持多表映射同一个实体,后续CRUD操作仍会有问题。
方案2:使用结果转换器(推荐通用查询场景)
抛弃实体映射,直接用AliasToBean结果转换器将SQL结果映射到GenericTable类,完全绕开NHibernate的实体管理逻辑:
List<GenericTable> result = MyNHibernateSession.CreateSQLQuery("SELECT * FROM MyTable") .SetResultTransformer(Transformers.AliasToBean<GenericTable>()) .List<GenericTable>() .ToList(); List<GenericTable> differentResult = MyNHibernateSession.CreateSQLQuery("SELECT * FROM DifferentTable") .SetResultTransformer(Transformers.AliasToBean<GenericTable>()) .List<GenericTable>() .ToList();
优点:不需要配置ClassMap,只要列名和类的属性名一致就能映射,完美适配多表通用查询的需求。
方案3:继承映射(支持完整CRUD场景)
如果需要NHibernate完整管理这些表的CRUD操作,可以使用「每个具体类一张表」的继承映射:
- 定义基类与子类:
public abstract class BaseTable { public virtual int Id { get; set; } public virtual string Title { get; set; } } public class MyTableEntity : BaseTable { } public class DifferentTableEntity : BaseTable { }
- 配置映射:
public class BaseTableMap : ClassMap<BaseTable> { public BaseTableMap() { Id(x => x.Id); Map(x => x.Title); UseUnionSubclassForInheritanceMapping(); } } public class MyTableMap : SubclassMap<MyTableEntity> { public MyTableMap() { Table("MyTable"); } } public class DifferentTableMap : SubclassMap<DifferentTableEntity> { public DifferentTableMap() { Table("DifferentTable"); } }
- 查询方式:
// 查询MyTable List<MyTableEntity> myTableData = MyNHibernateSession.Query<MyTableEntity>().ToList(); // 查询DifferentTable List<DifferentTableEntity> differentTableData = MyNHibernateSession.Query<DifferentTableEntity>().ToList();
优点:可以利用NHibernate的完整ORM功能,包括缓存、关联、CRUD等,适合需要对这些表进行复杂操作的场景。
内容的提问来源于stack exchange,提问作者TheHvidsten
相关产品推荐
相关产品推荐

