Свободный NHibernate: сопоставление «один ко многим» с подклассом, в котором существует внешний ключ в абстрактном классе - PullRequest
1 голос
/ 03 ноября 2010

Я новичок в использовании 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);
    }
}

При текущей настройке я получаю следующее исключение при попытке доступа к свойству EducationRecords формы:

[SqlException (0x80131904): Invalid column name 'FormId'.
Invalid column name 'FormId'.]

Похоже, что базовый запрос SQL пытается выполнить запрос к столбцу «FormId» в таблице EducationRecords, но этот столбец там не существует. Я потратил много времени, пробуя различные варианты конфигураций с моими классами карт, и мне не повезло.

Итак, мой вопрос: как мне сказать Fluent NHibernate использовать столбец «FormId» в таблице «Записи» при получении записей об образовании или это вообще возможно?

<ч /> Обновление:
Кажется, моя проблема, по сути, та же, что и заявленная здесь (которая, к сожалению, вопрос так и не был решен):
Свободная проблема отображения наследования NHibernate

<ч /> Обновление 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

В сгенерированном запросе SQL, показанном в GenericADOException, предложение WHERE указывает:

WHERE educationr0_.FormId=?

Но это должно быть:

WHERE educationr0_1_.FormId=?

Ответы [ 5 ]

1 голос
/ 22 августа 2014

Решение действительно очень простое.Необходимо указать для всей карты подклассов, то есть абстрактный базовый класс

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);
  }
 }
1 голос
/ 17 ноября 2011

Мне потребовалось время, чтобы вернуться к этому. Я перестал играть с Fluent NHibernate вскоре после публикации этого вопроса.

Оглядываясь назад, я думаю, что, возможно, что-то в корне неправильно с тем, что я пытался выполнить. Мою проблему можно решить, просто заменив список EducationRecords на Records. При необходимости из этого можно извлечь определенные типы записей.

Я не помню, почему я изначально думал, что мне нужен список EducationRecords в Forms. Я подозреваю, что это была либо неопытность с ORM с моей стороны, либо, возможно, было ограничение, которое не было должным образом представлено в структуре базы данных.

0 голосов
/ 28 февраля 2011

Попробуйте это сопоставление:

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

И сделайте запись класса не абстрактной.Это работает для меня.Возможно, 'form0_1_' не является правильным именем столбца.Вы можете найти правильный в сгенерированном SQL.

0 голосов
/ 01 марта 2011

Вы должны проверить полученные XML-отображения. Возможно его генерирующее отображение подкласса вместо присоединенного подкласса. http://knol.google.com/k/fabio-maulo/nhibernate-chapter-8-inheritance-mapping/1nr4enxv3dpeq/11

0 голосов
/ 17 ноября 2010

Попробуйте изменить:

HasMany(x => x.EducationRecords);

до

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

так вы говорите NH, что только ребенок в этих отношениях отвечает за сбережения.

Подробнее о лишних обновлениях вы можете прочитать здесь: http://nhprof.com/Learn/Alerts/SuperfluousManyToOneUpdate

...