Fluent NHibernate:一对多映射到抽象类中存在外键的子类

发布于 2024-09-30 16:37:17 字数 8541 浏览 1 评论 0原文

我是 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

箜明 2024-10-07 16:37:17

我花了一段时间才重新审视这一点。在发布这个问题后不久,我就停止使用 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 with Records. 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 in Forms. 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.

梦罢 2024-10-07 16:37:17

解决方案确实非常简单。您需要为所有子类映射指定,这是一个抽象
基类

public class EducationRecordMap : SubclassMap<Entities.EducationRecord>
{
   public EducationRecordMap()
   {
    Table("EducationRecords");

    Abstract(); // because Record base class is abstract

    KeyColumn("RecordId");
    Map(x => x.SchoolName);
    Map(x => x.DateAttendedFrom);
    Map(x => x.DateAttendedTo);
  }
 }

The solution really is very simple. You need to specify for all subclassmap, that is an abstract
base class

public class EducationRecordMap : SubclassMap<Entities.EducationRecord>
{
   public EducationRecordMap()
   {
    Table("EducationRecords");

    Abstract(); // because Record base class is abstract

    KeyColumn("RecordId");
    Map(x => x.SchoolName);
    Map(x => x.DateAttendedFrom);
    Map(x => x.DateAttendedTo);
  }
 }
☆獨立☆ 2024-10-07 16:37:17

尝试改变:

HasMany(x => x.EducationRecords);

通过

HasMany(x => x.EducationRecords).Inverse();

这种方式,你告诉NH,只有这段关系中的孩子负责储蓄。

您可以在此处阅读有关多余更新的更多信息:
http://nhprof.com/Learn/Alerts/SuperfluousManyToOneUpdate

Try to change:

HasMany(x => x.EducationRecords);

to

HasMany(x => x.EducationRecords).Inverse();

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

信愁 2024-10-07 16:37:17

您应该检查生成的 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

花落人断肠 2024-10-07 16:37:17

尝试这个映射:

public class FormMap : ClassMap<Entities.Form>
{
    public FormMap()
    {
        Table("Forms");
        Id(x => x.Id, "FormId");
        Map(x => x.CompletedBy);

        HasMany<Record>(x => x.EducationRecords).Where("form0_1_.EducationRecordId is not null");
    }
}

并使类 Record 不再抽象。这对我有用。也许“form0_1_”不是正确的列名称。您可以在生成的 SQL 中找到正确的 SQL。

Try this mapping:

public class FormMap : ClassMap<Entities.Form>
{
    public FormMap()
    {
        Table("Forms");
        Id(x => x.Id, "FormId");
        Map(x => x.CompletedBy);

        HasMany<Record>(x => x.EducationRecords).Where("form0_1_.EducationRecordId is not null");
    }
}

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文