Search code examples
c#nhibernatenhibernate-mappingnhibernate-mapping-by-code

Nhibernate OneToMany (Inverse) Sets FK to 0, why?


I've got a Problem wiht my nHibernate Mapping-By-Code.

Table Content has multiple Entries from Table Content_Texts referenced by FK Column "ContentId".

If I'm adding a new ContentEntry with one Text in the collection i get the first Query to the Content Table executed but the second one (to Content_Texts) fails because NHibernate is trying to insert an Content_Text Entity with ContentId = 0, that doesn't exist for sure in the Content Table.

Here is my Mapping for the Content Table:

public class ContentMap : ClassMapping<TContent>
{
    #region Constructors

    public ContentMap()
    {
        Table("nfcms_Content");
        Schema("dbo");
        Lazy(false);
        Id<int>(x => x.Id, map => {

            map.Generator(Generators.Identity);

        });
        //Property(x => x.Id, map => map.NotNullable(true));

        Property(x => x.Cid, map =>
        {
            map.NotNullable(false);

        });
        Property(x => x.CreatorPKID, map => map.NotNullable(false));
        Property(x => x.Locked, map => {

            map.NotNullable(true);

            map.Type(NHibernateUtil.Boolean);

        });
        Property(x => x.RatingGroupID);
        Property(x => x.CDate);

        Property(x => x.ContentType, map => map.NotNullable(true));

        Property(x => x.ContentRef);
        Property(x => x.CreatorSpecialName);

        Bag(x => x.Texts, mapping =>
        {
            mapping.Lazy(CollectionLazy.NoLazy);
            mapping.Key(k =>
            {
                k.Column("ContentId");
                
            });
            mapping.Inverse(true);
            mapping.Cascade(Cascade.All);
            
            mapping.Fetch(CollectionFetchMode.Select);
           
        },
           r => r.OneToMany());

        ManyToOne(x => x.User, m =>
        {

            m.Column(c => c.Name("CreatorPKID"));
            m.Fetch(FetchKind.Select);
            m.Cascade(Cascade.None);
            m.Insert(false);
            m.Update(false);
            m.Lazy(LazyRelation.NoLazy);

        });

        ManyToOne(x => x.Category, m =>
        {

            m.Column(c => c.Name("CID"));
            m.Fetch(FetchKind.Select);
            m.Cascade(Cascade.None);
            m.Insert(false);
            m.Update(false);
            m.Lazy(LazyRelation.NoLazy);

        });

        //OneToOne(x => x.Category, map =>
        //{
        //    map.PropertyReference(
        //    map.Column("CID");
        //    map.NotFound(NotFoundMode.Ignore);
        //    map.Cascade(Cascade.None);
        //});

        //ManyToOne(x => x.User, map =>
        //{
        //    map.PropertyRef("PKID");
        //    map.Column("CreatorPKID");

        //    map.Cascade(Cascade.None);
        //});
    }

    #endregion Constructors
}

And Here for the Content_Text Table

public class ContentTextMap : ClassMapping<Persistence.Domain.ContentText>
{
    #region Constructors

    public ContentTextMap()
    {
        Table(Ren.CMS.CORE.Config.RenConfig.DB.Prefix +"Content_Text");
        Schema("dbo");
        Lazy(false);
        Id<int>(x => x.Id, map => {

            map.Generator(Generators.Identity);

        });
        Property(x => x.ContentId, map => map.NotNullable(true));
        Property(x => x.LangCode, map => map.NotNullable(true));

        //Property(x => x.Id, map => map.NotNullable(true));
        Property(x => x.Title, map => map.NotNullable(true));

        Property(x => x.Seoname);

        Property(x => x.MetaKeyWords);
        Property(x => x.MetaDescription);
        Property(x => x.PreviewText);

        Property(x => x.LongText, x => x.Type(NHibernateUtil.StringClob));


        //OneToOne(x => x.Category, map =>
        //{
        //    map.PropertyReference(
        //    map.Column("CID");
        //    map.NotFound(NotFoundMode.Ignore);
        //    map.Cascade(Cascade.None);
        //});

        //ManyToOne(x => x.User, map =>
        //{
        //    map.PropertyRef("PKID");
        //    map.Column("CreatorPKID");

        //    map.Cascade(Cascade.None);
        //});
    }

    #endregion Constructors
}

This are the executed queries from NHibernate:

NHibernate: INSERT INTO dbo.nfcms_Content (CDate, Cid, ContentRef, ContentType, CreatorPKID, CreatorSpecialName, Locked, RatingGroupID) VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7); select SCOPE_IDENTITY();@p0 = 27.10.2014 13:51:58 [Type: DateTime (0)], @p1 = NULL [Type: Guid (0)], @p2 = 0 [Type: Int32 (0)], @p3 = 'eNews' [Type: String (4000)], @p4 = NULL [Type: Guid (0)], @p5 = '' [Type: String (4000)], @p6 = False [Type: Boolean (0)], @p7 = 0 [Type: Int32 (0)]

NHibernate: INSERT INTO dbo.nfcms_Content_Text (ContentId, LangCode, LongText, MetaDescription, MetaKeyWords, PreviewText, Seoname, Title) VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7); select SCOPE_IDENTITY();@p0 = 0 [Type: Int32 (0)], @p1 = 'de-DE' [Type: String (4000)], @p2 = 'Test' [Type: String (1073741823)], @p3 = 'test' [Type: String (4000)], @p4 = 'test' [Type: String (4000)], @p5 = 'test' [Type: String (4000)], @p6 = 'TEST123455' [Type: String (4000)], @p7 = 'TEST' [Type: String (4000)]

Can anyone tell me what's wrong? I searched and searched and got to no soulution...


Solution

  • Because we do have one-to-many relation end mapped as a Bag with inverse setting (which is absolutely ok) like this:

    public ContentMap()
    {
        ...
        Bag(x => x.Texts, mapping =>
        {
            mapping.Lazy(CollectionLazy.NoLazy);
            mapping.Key(k =>
            {
                k.Column("ContentId");
    
            });
            ...
    

    We need the other end - the many-to-one as well. In our case it must be represented by reference Content (and could be also expressed as readonly int ContentId value)

    public ContentTextMap()
    {
        ManyToOne(x => x.Content, "ContentId");
        Property(x => x.ContentId, map => {
            map.Column("ContentId");
            map.Update(true);
            map.Insert(true);
            map.NotNullable(true)
        });
        ...
    

    Expecting POCOs like this:

    public class Content
    {
       public virtual IList<ContentText> Texts { get; set; }
       ...
    
    public class ContentText
    {
       public virtual Content Content { get; set; }
       public virtual int ContentId { get; set; }
       ...
    

    And this must be the assignment (both ends must be assigned)

    var content = ...;
    var contentText = ...;
    content.Texts.Add(contentText);// both ends must be assigned
    contentText.Content = content; // both ends must be assigned