проблема с беглым отображением nhibernate: многие-к-одному не удалось вставить: [Entity]

У меня есть отношение «один ко многим» между Foo и Bar, и я не могу выполнить вставку (до сих пор я только пытался выбрать и вставить... кажется, что выбор работает нормально, вставка каждый раз завершается ошибкой).

Определения таблиц:

CREATE TABLE [Bar](
[Id] [int] IDENTITY(1,1) NOT NULL,
[DateField] [datetime] NULL,
[StringField] [varchar](8000) NULL
CONSTRAINT [PK_Bar] PRIMARY KEY CLUSTERED([Id] ASC)) ON PRIMARY

CREATE TABLE [Foo](
[Id] [int] IDENTITY(1,1) NOT NULL,
[BarId] [int] NOT NULL,
[DateField] [date] NOT NULL,
[StringField] [varchar](100) NOT NULL,
[GorpId] [int] NOT NULL,
[BoolField] [bit] NOT NULL
CONSTRAINT [PK_Foo] PRIMARY KEY CLUSTERED([Id] ASC))

ALTER TABLE [dbo].[Foo]  WITH CHECK ADD  CONSTRAINT [FK_Foo_Bar] FOREIGN KEY([BarId])
REFERENCES [dbo].[Bar] ([Id])
GO

Определения классов:

public class Foo
{
    public virtual int Id { get; set; }
    public virtual Bar Bar{ get; set; }
    public virtual DateTime DateField{ get; set; }
    public virtual string StringField{ get; set; }
    public virtual Gorp Gorp{ get; set; }
    public virtual bool BoolField{ get; set; }
}

public class Bar
{
    public virtual int Id { get; set; }
    public virtual DateTime DateField{ get; set; }
    public virtual string StringField{ get; set; }

    public virtual ICollection<Foo> Foos{ get; set; }

    public Bar()
    {
        Foos= new List<Foo>();
    }

    public virtual void AddFoo(Foo foo)
    {
        foo.Bar = this;
        Foos.Add(foo);
    }
}

Сопоставления:

public class FooMap : ClassMap<Foo>
{
    public FooMap()
    {
        Id(x => x.Id).UnsavedValue(0).GeneratedBy.Identity();
        Map(x => x.DateField);
        Map(x => x.BoolField);
        Map(x => x.StringField);
        References(x => x.Gorp)
          .Column("GorpId")
          .Class<Gorp>();
        References(x => x.Bar)
          .Column("BarId")
          .Not.Nullable();
    }
}

public class BarMap : ClassMap<Bar>
{
    Id(x => x.Id).UnsavedValue(0).GeneratedBy.Identity();
    Map(x => x.DateField);
    Map(x => x.StringField);

    HasMany<Foo>(x => x.Foos)
      .AsBag()
      .Cascade.SaveUpdate()
      .ForeignKeyConstraintName("FK_Foo_Bar")
      .Inverse()
      .KeyColumn("BarId")
      .Not.KeyNullable();
}

Сгенерированный XML:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="property" auto-import="true" default-cascade="none" default-lazy="true">
    <class xmlns="urn:nhibernate-mapping-2.2" mutable="true" name="MyApp.Domain.Model.Bar, MyApp.Domain, Version=3.5.0.0, Culture=neutral, PublicKeyToken=null" table="`Bar`">
        <id name="Id" type="System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" unsaved-value="0">
            <column name="Id" />
            <generator class="identity" />
        </id>
        <property name="DateField" type="System.DateTime, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
            <column name="DateField" />
        </property>
        <property name="StringField" type="System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
            <column name="StringField" />
        </property>
        <bag cascade="save-update" inverse="true" name="Foos" mutable="true">
            <key foreign-key="FK_Foo_Bar" not-null="true">
                <column name="BarId" />
            </key>
            <one-to-many class="MyApp.Domain.Model.Foo, MyApp.Domain, Version=3.5.0.0, Culture=neutral, PublicKeyToken=null" />
        </bag>
    </class>
</hibernate-mapping>

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="property" auto-import="true" default-cascade="none" default-lazy="true">
    <class xmlns="urn:nhibernate-mapping-2.2" mutable="true" name="MyApp.Domain.Model.Foo, MyApp.Domain, Version=3.5.0.0, Culture=neutral, PublicKeyToken=null" table="`Foo`">
        <id name="Id" type="System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" unsaved-value="0">
            <column name="Id" />
            <generator class="identity" />
        </id>
        <property name="DateField" type="System.DateTime, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
            <column name="DateField" />
        </property>
        <property name="BoolField" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
            <column name="BoolField" />
        </property>
        <property name="StringField" type="System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
             <column name="StringField" />
        </property>
        <many-to-one class="MyApp.Domain.Model.Gorp, MyApp.Domain, Version=3.5.0.0, Culture=neutral, PublicKeyToken=null" name="Gorp">
            <column name="GorpId" />
        </many-to-one>
        <many-to-one class="MyApp.Domain.Model.Bar, MyApp.Domain, Version=3.5.0.0, Culture=neutral, PublicKeyToken=null" insert="false" name="Bar" update="false">
            <column name="BarId" not-null="true" />
        </many-to-one>
    </class>
</hibernate-mapping>

Исключение:

не удалось вставить: [MyApp.Domain.Model.Foo][SQL: INSERT INTO [Foo] (DateField, BoolField, StringField, GorpId, BarId) VALUES (?, ?, ?, ?, ?); выберите SCOPE_IDENTITY()]

Что я упускаю/не понимаю?


person CDR12    schedule 29.03.2012    source источник
comment
Если я устанавливаю Cascade.None() с обеих сторон (с целью сохранения экземпляра Bar, установки BarId для объекта Foo, а затем сохранения экземпляра Foo)... я получаю исключение, переполнение SqlDateTime. Должно быть между 01.01.1753 00:00:00 и 31.12.9999 23:59:59, когда я пытаюсь сохранить экземпляр Bar, потому что он все равно пытается каскадировать сохранение (??) и Bar.Foos пуст. Я считаю, что это исключение, потому что Foo.DateField не содержит значения.   -  person CDR12    schedule 29.03.2012


Ответы (2)


Спасибо, Дэйв, за то, что указал мне правильное направление. Одно из полей даты в базе данных допускало значение NULL, другое — нет. Я изменил свой класс и свою карту, чтобы отразить это. Вуаля! Это сработало!

Жаль, что исходное сообщение об ошибке не сообщало мне о проблеме с полем даты и времени, это избавило бы меня от некоторых головных болей.

Я сделал следующие изменения:

public class Foo
{
    public virtual int Id { get; set; }
    public virtual Bar Bar{ get; set; }
    public virtual DateTime DateField{ get; set; }
    public virtual string StringField{ get; set; }
    public virtual Gorp Gorp{ get; set; }
    public virtual bool BoolField{ get; set; }

    public Foo()
    {
        DateField = new DateTime(1753, 1, 1);
    }
}

public class Bar
{
    public virtual int Id { get; set; }
    public virtual DateTime? DateField{ get; set; }
    public virtual string StringField{ get; set; }

    public virtual ICollection<Foo> Foos{ get; set; }

    public Bar()
    {
        DateField = new DateTime(1753, 1, 1);
        Foos= new List<Foo>();
    }

    public virtual void AddFoo(Foo foo)
    {
        foo.Bar = this;
        Foos.Add(foo);
    }
}


public class FooMap : ClassMap<Foo>
{
    public FooMap()
    {
        Id(x => x.Id).UnsavedValue(0).GeneratedBy.Identity();
        Map(x => x.DateField);
        Map(x => x.BoolField);
        Map(x => x.StringField);
        References(x => x.Gorp)
          .Column("GorpId")
          .Class<Gorp>();
        References(x => x.Bar)
          .Column("BarId")
          .Not.Nullable();
    }
}

public class BarMap : ClassMap<Bar>
{
    Id(x => x.Id).UnsavedValue(0).GeneratedBy.Identity();
    Map(x => x.DateField)
          .Nullable();
    Map(x => x.StringField);

    HasMany<Foo>(x => x.Foos)
      .AsBag()
      .Cascade.SaveUpdate()
      .Fetch.Join()
      .ForeignKeyConstraintName("FK_Foo_Bar")
      .Inverse()
      .KeyColumn("BarId")
      .Not.KeyNullable()
      .Table("Foo");
}
person CDR12    schedule 30.03.2012
comment
Поздравляю с исправлением! Когда вы сможете, обязательно пометьте свой ответ как «принятый», чтобы другие увидели, что на ваш вопрос дан ответ, и смогли извлечь уроки из вашего решения. Ура~ - person Andrew Kozak; 30.03.2012

если свойство не инициализируется явно при создании нового объекта, оно получит значение по умолчанию. для даты и времени это значение по умолчанию — datetime.minvalue.

datetime.minvalue - более старая дата, чем позволяет сервер sql. это мерзость в том, что сервер .net и sql не поддерживают один и тот же диапазон дат и времени, но так оно и есть. когда вы пытаетесь вставить datetime.minvalue на сервер sql, вы получаете исключение, которое видите.

исправление состоит в том, чтобы всегда присваивать новым объектам значение этого свойства, создавать значение по умолчанию в базе данных для этого столбца или делать свойство datatime обнуляемым (DateTime?).

это действительно не имеет ничего общего с отношениями ваших классов, просто с неинициализированным ненулевым свойством даты и времени, использующим значение по умолчанию.

person Dave Rael    schedule 29.03.2012
comment
Да, но почему он вообще пытается сохранить объект Foo, когда я вызываю Save() для Bar? Если это не каскад, это даже не должно быть проблемой... или я что-то неправильно понимаю? - person CDR12; 29.03.2012
comment
Я не могу проголосовать за ответ, потому что у меня еще нет 15 представителей, но я бы сделал это, если бы мог. Этот ответ указал мне правильное направление. Спасибо, Дэйв. - person CDR12; 30.03.2012
comment
не видя больше кода, показывающего, что именно вы делаете, я не могу предоставить дополнительную информацию. да, установка каскада none должна предотвращать каскадирование. если вы попытаетесь сохранить еще не сохраненный объект, который ссылается на другой еще не сохраненный объект без отключения каскадирования, вы должны получить исключение, сообщающее, что объект ссылается на объект, о котором сеанс не знает. - person Dave Rael; 30.03.2012