Search code examples
c#nhibernatefluent-nhibernatefluent-nhibernate-mapping

not-null property references a null or transient value - understanding of Inverse and Cascade


This is fragment of my database project, and this is fragment of auto generated DB by Fluent NHibernate

And these are my entities and mapping classes in Fluent NHibernate of above DB project.

Entities:

public class BaseEntity<T> where T : BaseEntity<T> {
    public virtual int Id { get; set; }
...
}

public class Person<T> : BaseEntity<T> where T : BaseEntity<T> {
    public virtual string FirstName { get; set; }
    public virtual string SecondName { get; set; }
    public virtual string LastName { get; set; }
    public virtual string PESEL { get; set; }
    public virtual DateTime BirthDate { get; set; }
    public virtual string City { get; set; }
    public virtual string PostalCode { get; set; }
    public virtual string Street { get; set; }
    public virtual string HouseNr { get; set; }
    public virtual string ApartmentNr { get; set; }
    public virtual string PhoneNr { get; set; }
    public virtual string Email { get; set; }
}

public class DrivingLicense : BaseEntity<DrivingLicense> {
    #region Relations
    public virtual IList<DrivingLicensePermissions> DrivingLicensePermissions { get; set; }
    public virtual Student Student { get; set; }
    #endregion

    public virtual DateTime IssueDate { get; set; }
    public virtual string DrivingLicenseNr { get; set; }
}

public class DrivingLicensePermissions : BaseEntity<DrivingLicensePermissions> {
    #region Relations
    public virtual DrivingLicense DrivingLicense { get; set; }
    #endregion

    public virtual DrivingLicenseCategory Category { get; set; }
}

Mappings:

class StudentMap : ClassMap<Student> {
    public StudentMap() {
        Id(x => x.Id);
        Map(x => x.FirstName).Not.Nullable().Length(25);
        Map(x => x.SecondName).Nullable().Length(25);
        Map(x => x.LastName).Not.Nullable().Length(50);
        Map(x => x.PESEL).Nullable().Length(11);
        Map(x => x.BirthDate).Not.Nullable();
        Map(x => x.City).Not.Nullable().Length(50);
        Map(x => x.PostalCode).Nullable().Length(6);
        Map(x => x.Street).Not.Nullable().Length(50);
        Map(x => x.HouseNr).Not.Nullable().Length(10);
        Map(x => x.ApartmentNr).Nullable().Length(10);
        Map(x => x.PhoneNr).Nullable().Length(20);
        Map(x => x.Email).Nullable().Length(100);

        References(x => x.DrivingLicense).Nullable().Cascade.All();
        References(x => x.User).Nullable().Cascade.All();
        HasMany(x => x.Participants).Cascade.All();
    }
}

class DrivingLicenseMap : ClassMap<DrivingLicense> {
    public DrivingLicenseMap() {
        Id(x => x.Id);
        Map(x => x.IssueDate).Not.Nullable();
        Map(x => x.DrivingLicenseNr).Not.Nullable().Length(20);

        HasMany(x => x.DrivingLicensePermissions).Cascade.All();
        References(x => x.Student).Not.Nullable();
    }
}

class DrivingLicensePermissionsMap : ClassMap<DrivingLicensePermissions> {
    public DrivingLicensePermissionsMap() {
        Id(x => x.Id);
        Map(x => x.Category).Not.Nullable();

        References(x => x.DrivingLicense).Not.Nullable();
    }
}

And my problem is this exception: not-null property references a null or transient value Model.Entities.DrivingLicense.Student, while I'm trying to persist Student object like this

session.Save(student);

which has assigned DrivingLicense object to it's DrivingLicense property.

I assume it's caused because of bad mapping - wrong Cascade or lack of Inverse. I tried many combinations and can't get it work. Also is it correct that Student table has DrivingLicense_id and the opposite, DrivingLicense has Student_id column?!


Solution

  • To solve my problem I had to change Reference-to-Reference to HasOne-to-Reference.

    Now it looks like this.

    Entities have not changed

    Mappings:

    class StudentMap : ClassMap<Student> {
        public StudentMap() {
            Id(x => x.Id);
            Map(x => x.FirstName).Not.Nullable().Length(25);
            Map(x => x.SecondName).Nullable().Length(25);
            Map(x => x.LastName).Not.Nullable().Length(50);
            Map(x => x.PESEL).Nullable().Length(11);
            Map(x => x.BirthDate).Not.Nullable();
            Map(x => x.City).Not.Nullable().Length(50);
            Map(x => x.PostalCode).Nullable().Length(6);
            Map(x => x.Street).Not.Nullable().Length(50);
            Map(x => x.HouseNr).Not.Nullable().Length(10);
            Map(x => x.ApartmentNr).Nullable().Length(10);
            Map(x => x.PhoneNr).Nullable().Length(20);
            Map(x => x.Email).Nullable().Length(100);
    
            HasOne(x => x.DrivingLicense).PropertyRef(x => x.Student).Cascade.All();
            References(x => x.User).Unique().Not.Nullable();
            HasMany(x => x.Participants).Cascade.All();
        }
    }
    
    class DrivingLicenseMap : ClassMap<DrivingLicense> {
        public DrivingLicenseMap() {
            Id(x => x.Id);
            Map(x => x.IssueDate).Not.Nullable();
            Map(x => x.DrivingLicenseNr).Not.Nullable().Length(20);
    
            HasMany(x => x.DrivingLicensePermissions).Cascade.All();
            References(x => x.Student).Unique().Not.Nullable();
        }
    }
    
    class DrivingLicensePermissionsMap : ClassMap<DrivingLicensePermissions> {
        public DrivingLicensePermissionsMap() {
            Id(x => x.Id);
            Map(x => x.Category).Not.Nullable();
    
            References(x => x.DrivingLicense).Nullable();
        }
    }
    

    Thank to this change, circular reference was gone. see it here

    There is no DrivingLicense_id in Students table and now I'm able to persist Student entity along with DrivingLicense and it's permissions just by saving student like I did it before.

    session.Save(student);