Fluent NHibernate:一对多映射到抽象类中存在外键的子类
我是 NHibernate/Fluent NHibernate 的新手,我正在尝试弄清楚如何将它与现有的数据库结构一起使用。如果可能的话,我想让它在不改变数据库结构的情况下工作。
我尝试松散映射的数据库结构类似于:
Forms
----
FormId
CompletedBy
Records
-------
RecordId
RecordTypeId
FormId
EducationRecords
----------------
RecordId
SchoolName
DateAttendedFrom
DateAttendedTo
我的实体:
public class Form
{
public virtual int Id { get; private set; }
public virtual string CompletedBy { get; set; }
public virtual IList<Entities.EducationRecord> EducationRecords { get; set; }
public Form()
{
this.EducationRecords = new List<EducationRecord>();
}
}
public abstract class Record
{
public virtual int Id { get; set; }
public virtual int RecordTypeId { get; set; }
public virtual Form Parent { get; set; }
}
public class EducationRecord : Record
{
public virtual string SchoolName { get; set; }
public virtual DateTime DateAttendedFrom { get; set; }
public virtual DateTime DateAttendedTo { get; set; }
}
我的映射:
public class FormMap : ClassMap<Entities.Form>
{
public FormMap()
{
Table("Forms");
Id(x => x.Id, "FormId");
Map(x => x.CompletedBy);
HasMany(x => x.EducationRecords);
}
}
public class RecordMap : ClassMap<Entities.Record>
{
public RecordMap()
{
Table("Records");
Id(x => x.Id, "RecordId");
Map(x => x.RecordTypeId);
References(x => x.Parent, "FormId");
}
}
public class EducationRecordMap : SubclassMap<Entities.EducationRecord>
{
public EducationRecordMap()
{
Table("EducationRecords");
KeyColumn("RecordId");
Map(x => x.SchoolName);
Map(x => x.DateAttendedFrom);
Map(x => x.DateAttendedTo);
}
}
按照当前的设置方式,在尝试访问 Form 的 EducationRecords 属性时出现以下异常:
[SqlException (0x80131904): Invalid column name 'FormId'.
Invalid column name 'FormId'.]
看起来底层 SQL 查询正在尝试查询 EducationRecords 表中的“FormId”列,但该列不存在。我花了很多时间尝试使用我的地图类配置的不同变体,但没有运气。
所以我的问题是:如何告诉 Fluent NHibernate 在检索教育记录时使用记录表中的“FormId”列,或者这是否可能?
Update:
My problem seems to essentially be the same as the one stated here (which, unfortunately, the question was never resolved):
Fluent NHibernate inheritance mapping problem
Update 2:
按照建议,我对 FormMap 进行了以下更改:
HasMany(x => x.EducationRecords).Inverse();
但仍然出现同样的问题。
这是源错误:
Line 14:
Line 15: <div>
Line 16: <% if (Model.EducationRecords.Any()) { %>
Line 17:
Line 18: <table>
模型的类型为表单。
以下是堆栈跟踪:
[SqlException (0x80131904): Invalid column name 'FormId'.
Invalid column name 'FormId'.]
System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection) +2030802
System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection) +5009584
System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning() +234
System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) +2275
System.Data.SqlClient.SqlDataReader.ConsumeMetaData() +33
System.Data.SqlClient.SqlDataReader.get_MetaData() +86
System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString) +311
System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async) +987
System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, DbAsyncResult result) +162
System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method) +32
System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior, String method) +141
System.Data.SqlClient.SqlCommand.ExecuteDbDataReader(CommandBehavior behavior) +12
System.Data.Common.DbCommand.System.Data.IDbCommand.ExecuteReader() +12
NHibernate.AdoNet.AbstractBatcher.ExecuteReader(IDbCommand cmd) +278
NHibernate.Loader.Loader.GetResultSet(IDbCommand st, Boolean autoDiscoverTypes, Boolean callable, RowSelection selection, ISessionImplementor session) +264
NHibernate.Loader.Loader.DoQuery(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies) +186
NHibernate.Loader.Loader.DoQueryAndInitializeNonLazyCollections(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies) +70
NHibernate.Loader.Loader.LoadCollection(ISessionImplementor session, Object id, IType type) +226
[GenericADOException: could not initialize a collection: [Sample.NHibernate.Entities.Form.EducationRecords#1][SQL: SELECT educationr0_.FormId as FormId1_, educationr0_.RecordId as RecordId1_, educationr0_.RecordId as RecordId1_0_, educationr0_1_.RecordTypeId as RecordTy2_1_0_, educationr0_1_.FormId as FormId1_0_, educationr0_.SchoolName as SchoolName2_0_, educationr0_.DateAttendedFrom as DateAtte3_2_0_, educationr0_.DateAttendedTo as DateAtte4_2_0_, educationr0_.Degree as Degree2_0_, educationr0_.DateDegreeAwarded as DateDegr6_2_0_ FROM dbo.EducationRecords educationr0_ inner join dbo.Records educationr0_1_ on educationr0_.RecordId=educationr0_1_.RecordId WHERE educationr0_.FormId=?]]
NHibernate.Loader.Loader.LoadCollection(ISessionImplementor session, Object id, IType type) +345
NHibernate.Loader.Collection.CollectionLoader.Initialize(Object id, ISessionImplementor session) +27
NHibernate.Persister.Collection.AbstractCollectionPersister.Initialize(Object key, ISessionImplementor session) +29
NHibernate.Event.Default.DefaultInitializeCollectionEventListener.OnInitializeCollection(InitializeCollectionEvent event) +349
NHibernate.Impl.SessionImpl.InitializeCollection(IPersistentCollection collection, Boolean writing) +431
NHibernate.Collection.AbstractPersistentCollection.Initialize(Boolean writing) +47
NHibernate.Collection.Generic.PersistentGenericBag`1.System.Collections.Generic.IEnumerable<T>.GetEnumerator() +16
System.Linq.Enumerable.Any(IEnumerable`1 source) +71
ASP.views_form_index_aspx.__RenderContent2(HtmlTextWriter __w, Control parameterContainer) in c:\Dev\Sandbox\NHibernateSample\Sample.Web\Views\Form\Index.aspx:16
System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +109
System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) +8
System.Web.UI.Control.Render(HtmlTextWriter writer) +10
System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +27
System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +100
System.Web.UI.Control.RenderControl(HtmlTextWriter writer) +25
System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +208
System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) +8
System.Web.UI.Control.Render(HtmlTextWriter writer) +10
System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +27
System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +100
System.Web.UI.Control.RenderControl(HtmlTextWriter writer) +25
System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +208
System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) +8
System.Web.Mvc.ViewPage.Render(HtmlTextWriter writer) +55
System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +27
System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +100
System.Web.UI.Control.RenderControl(HtmlTextWriter writer) +25
System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +3060
在 GenericADOException 中显示的生成的 SQL 查询中,WHERE 子句指定:
WHERE educationr0_.FormId=?
但它需要是:
WHERE educationr0_1_.FormId=?
I'm new to using NHibernate/Fluent NHibernate and am trying to figure out how to use it with an existing database structure. I would like to get it working without changing the DB structure, if possible.
The database structure I'm trying to map loosely resembles:
Forms
----
FormId
CompletedBy
Records
-------
RecordId
RecordTypeId
FormId
EducationRecords
----------------
RecordId
SchoolName
DateAttendedFrom
DateAttendedTo
My entities:
public class Form
{
public virtual int Id { get; private set; }
public virtual string CompletedBy { get; set; }
public virtual IList<Entities.EducationRecord> EducationRecords { get; set; }
public Form()
{
this.EducationRecords = new List<EducationRecord>();
}
}
public abstract class Record
{
public virtual int Id { get; set; }
public virtual int RecordTypeId { get; set; }
public virtual Form Parent { get; set; }
}
public class EducationRecord : Record
{
public virtual string SchoolName { get; set; }
public virtual DateTime DateAttendedFrom { get; set; }
public virtual DateTime DateAttendedTo { get; set; }
}
My mappings:
public class FormMap : ClassMap<Entities.Form>
{
public FormMap()
{
Table("Forms");
Id(x => x.Id, "FormId");
Map(x => x.CompletedBy);
HasMany(x => x.EducationRecords);
}
}
public class RecordMap : ClassMap<Entities.Record>
{
public RecordMap()
{
Table("Records");
Id(x => x.Id, "RecordId");
Map(x => x.RecordTypeId);
References(x => x.Parent, "FormId");
}
}
public class EducationRecordMap : SubclassMap<Entities.EducationRecord>
{
public EducationRecordMap()
{
Table("EducationRecords");
KeyColumn("RecordId");
Map(x => x.SchoolName);
Map(x => x.DateAttendedFrom);
Map(x => x.DateAttendedTo);
}
}
With the way it is currently setup, I get the following exception when trying to access the EducationRecords property of Form:
[SqlException (0x80131904): Invalid column name 'FormId'.
Invalid column name 'FormId'.]
It looks like the underlying SQL query is trying to query against a 'FormId' column on the EducationRecords table, but that column does not exist there. I have spent a lot of time trying different variations of configurations with my map classes and have had no luck.
So my question is: How do I tell Fluent NHibernate to use the 'FormId' column in the Records table when retrieving Education records, or is this even possible?
Update:
My problem seems to essentially be the same as the one stated here (which, unfortunately, the question was never resolved):
Fluent NHibernate inheritance mapping problem
Update 2:
As suggested, I made the following change to FormMap:
HasMany(x => x.EducationRecords).Inverse();
But the same issue still occurs.
Here is the Source Error:
Line 14:
Line 15: <div>
Line 16: <% if (Model.EducationRecords.Any()) { %>
Line 17:
Line 18: <table>
The Model is of type Form.
Here is the stack trace:
[SqlException (0x80131904): Invalid column name 'FormId'.
Invalid column name 'FormId'.]
System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection) +2030802
System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection) +5009584
System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning() +234
System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) +2275
System.Data.SqlClient.SqlDataReader.ConsumeMetaData() +33
System.Data.SqlClient.SqlDataReader.get_MetaData() +86
System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString) +311
System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async) +987
System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, DbAsyncResult result) +162
System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method) +32
System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior, String method) +141
System.Data.SqlClient.SqlCommand.ExecuteDbDataReader(CommandBehavior behavior) +12
System.Data.Common.DbCommand.System.Data.IDbCommand.ExecuteReader() +12
NHibernate.AdoNet.AbstractBatcher.ExecuteReader(IDbCommand cmd) +278
NHibernate.Loader.Loader.GetResultSet(IDbCommand st, Boolean autoDiscoverTypes, Boolean callable, RowSelection selection, ISessionImplementor session) +264
NHibernate.Loader.Loader.DoQuery(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies) +186
NHibernate.Loader.Loader.DoQueryAndInitializeNonLazyCollections(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies) +70
NHibernate.Loader.Loader.LoadCollection(ISessionImplementor session, Object id, IType type) +226
[GenericADOException: could not initialize a collection: [Sample.NHibernate.Entities.Form.EducationRecords#1][SQL: SELECT educationr0_.FormId as FormId1_, educationr0_.RecordId as RecordId1_, educationr0_.RecordId as RecordId1_0_, educationr0_1_.RecordTypeId as RecordTy2_1_0_, educationr0_1_.FormId as FormId1_0_, educationr0_.SchoolName as SchoolName2_0_, educationr0_.DateAttendedFrom as DateAtte3_2_0_, educationr0_.DateAttendedTo as DateAtte4_2_0_, educationr0_.Degree as Degree2_0_, educationr0_.DateDegreeAwarded as DateDegr6_2_0_ FROM dbo.EducationRecords educationr0_ inner join dbo.Records educationr0_1_ on educationr0_.RecordId=educationr0_1_.RecordId WHERE educationr0_.FormId=?]]
NHibernate.Loader.Loader.LoadCollection(ISessionImplementor session, Object id, IType type) +345
NHibernate.Loader.Collection.CollectionLoader.Initialize(Object id, ISessionImplementor session) +27
NHibernate.Persister.Collection.AbstractCollectionPersister.Initialize(Object key, ISessionImplementor session) +29
NHibernate.Event.Default.DefaultInitializeCollectionEventListener.OnInitializeCollection(InitializeCollectionEvent event) +349
NHibernate.Impl.SessionImpl.InitializeCollection(IPersistentCollection collection, Boolean writing) +431
NHibernate.Collection.AbstractPersistentCollection.Initialize(Boolean writing) +47
NHibernate.Collection.Generic.PersistentGenericBag`1.System.Collections.Generic.IEnumerable<T>.GetEnumerator() +16
System.Linq.Enumerable.Any(IEnumerable`1 source) +71
ASP.views_form_index_aspx.__RenderContent2(HtmlTextWriter __w, Control parameterContainer) in c:\Dev\Sandbox\NHibernateSample\Sample.Web\Views\Form\Index.aspx:16
System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +109
System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) +8
System.Web.UI.Control.Render(HtmlTextWriter writer) +10
System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +27
System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +100
System.Web.UI.Control.RenderControl(HtmlTextWriter writer) +25
System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +208
System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) +8
System.Web.UI.Control.Render(HtmlTextWriter writer) +10
System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +27
System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +100
System.Web.UI.Control.RenderControl(HtmlTextWriter writer) +25
System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +208
System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) +8
System.Web.Mvc.ViewPage.Render(HtmlTextWriter writer) +55
System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +27
System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +100
System.Web.UI.Control.RenderControl(HtmlTextWriter writer) +25
System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +3060
In the generated SQL query shown in the GenericADOException, the WHERE clause specifies:
WHERE educationr0_.FormId=?
But it needs to be:
WHERE educationr0_1_.FormId=?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
我花了一段时间才重新审视这一点。在发布这个问题后不久,我就停止使用 Fluent NHibernate。
回想起来,我认为我想要完成的事情可能存在根本性的错误。只需将
EducationRecords
列表替换为Records
即可解决我的问题。如果需要,可以从中检索特定类型的记录。我不记得为什么我最初认为我需要
Forms
中的EducationRecords
列表。我怀疑这要么是我对 ORM 缺乏经验,要么是数据库设计中存在未正确表示的约束。It has taken me a while to revisit this. I stopped playing with Fluent NHibernate shortly after posting this question.
Looking back, I think there may have been something fundamentally wrong with what I was trying to accomplish. My problem could be resolved simply by replacing the list of
EducationRecords
withRecords
. If needed, specific types of records could be retrieved from that.I don't remember why I originally thought I needed a list of
EducationRecords
inForms
. I suspect it was either inexperience with ORMs on my part or there may have been a constraint that was not represented properly in the database design.解决方案确实非常简单。您需要为所有子类映射指定,这是一个抽象
基类
The solution really is very simple. You need to specify for all subclassmap, that is an abstract
base class
尝试改变:
通过
这种方式,你告诉NH,只有这段关系中的孩子负责储蓄。
您可以在此处阅读有关多余更新的更多信息:
http://nhprof.com/Learn/Alerts/SuperfluousManyToOneUpdate
Try to change:
to
this way you are telling NH that only the child in this relationship is in charge of the saving.
You can read more about superfluous updates here:
http://nhprof.com/Learn/Alerts/SuperfluousManyToOneUpdate
您应该检查生成的 XML 映射。也许它生成子类映射而不是连接子类。
http://knol.google.com /k/fabio-maulo/nhibernate-chapter-8-inheritance-mapping/1nr4enxv3dpeq/11
You should check the resulting XML mappings. Maybe its generating subclass mapping instead of joined-subclass.
http://knol.google.com/k/fabio-maulo/nhibernate-chapter-8-inheritance-mapping/1nr4enxv3dpeq/11
尝试这个映射:
并使类 Record 不再抽象。这对我有用。也许“form0_1_”不是正确的列名称。您可以在生成的 SQL 中找到正确的 SQL。
Try this mapping:
And make class Record not abstract. This works for me. Maybe 'form0_1_' is not the correct column name. You can find the right one in the generated SQL.