CDR12
CDR12

Reputation: 491

fluent nhibernate mapping issue: many-to-one...could not insert: [Entity]

I have a one-to-many relationship between Foo and Bar and I cannot perform an insert (I've only tried select and insert so far...select seems to be working just fine, insert fails every time).

Table Definitions:

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

Class Definitions:

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

Mappings:

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

Generated 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>

Exception:

could not insert: [MyApp.Domain.Model.Foo][SQL: INSERT INTO [Foo] (DateField, BoolField, StringField, GorpId, BarId) VALUES (?, ?, ?, ?, ?); select SCOPE_IDENTITY()]

What am I missing/misunderstanding?

Upvotes: 1

Views: 4040

Answers (2)

CDR12
CDR12

Reputation: 491

Thank you, Dave, for pointing me in the right direction. One of the Date fields was nullable in the database, the other was not. I changed my class and my map to reflect that. Voila! It worked!

Too bad the original error message didn't tell me there was an issue with a datetime field, it would've saved me some headaches.

I made the following changes:

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

Upvotes: 1

Dave Rael
Dave Rael

Reputation: 1759

if a property is not initialized explicitly when creating a new object, it will receive the default value. for datetime, that default value is datetime.minvalue.

datetime.minvalue is an older date than what sql server allows. this is an abominiation in that .net and sql server don't support the same datetime range, but it is how it is. when you try to insert datetime.minvalue to sql server, you get the exception you are seeing.

the fix is to always give new objects a value for that property, create a default value in the database for that column, or make the datatime property nullable (DateTime?).

this really doesn't have anything to do with the relationship of your classes, just with the non-initialized nonnullable datetime property using the default value.

Upvotes: 0

Related Questions