Search code examples
c#nhibernatenullcomponents

Nhibernate fail to delete Value Object that has null property value


I faced this sort of problem with nhibernate:

when I try to delete a component (object that as no ID) that has null as value of a property, operation fails because condition is translated as field = NULL instead of Field IS NULL

I have the RilevanzaFinding object mapped as component of Finding:

HasMany<RilevanzaFinding>(x => x.Rilevanze)
        .Access.CamelCaseField(Prefix.Underscore)
        .Table("RilevanzaFinding_T045")
        .KeyColumn("int_T045_IdFinding")
        .Cascade.AllDeleteOrphan()
        .AsSet()
        .Component(fee =>
        {
            fee.References<Rating>(x => x.Rating).Column("int_T045_IdRating").Fetch.Join();
            fee.Map(x => x.DataFine)
                .Column("dte_T045_DataFine")                    
                .CustomSqlType("Date");
            fee.Map(x => x.Note)
                .Column("nvc_T045_Note")
                .Length(100000);
        });

public class Finding : BaseObject<Finding, int>
{
    private ICollection<RilevanzaFinding> _rilevanze = new List<RilevanzaFinding>();
    public virtual IEnumerable<RilevanzaFinding> Rilevanze
    {
        get
        {
            return _rilevanze.ToArray();
        }
    }
}

public class RilevanzaFinding : EquatableObject<RilevanzaFinding>
{
    public virtual Rating Rating { get; set; }
    public virtual DateTime? DataFine{ get; set; }
    public virtual string Note { get; set; }
}

where EquatableObject implements this Equals:

public override bool Equals(object obj)
    {
        if (obj == null)
            return false;

        TObject other = obj as TObject;

        return Equals(other);

    }

    public virtual bool Equals(TObject other)
    {
        if (other == null)
            return false;

        Type t = GetType();

        TypeInfo typeInfo = t.GetTypeInfo();            
        IEnumerable<FieldInfo> fields = typeInfo.DeclaredFields.Where(x => x.FieldType.Name != typeof(ICollection<>).Name); 
        foreach (FieldInfo field in fields)
        {
            object value1 = field.GetValue(other);
            object value2 = field.GetValue(this);

            if (value1 == null)
            {
                if (value2 != null)
                    return false;
            }
            else if (!value1.Equals(value2))
                return false;
        }
        return true;
    }

now when I remove a Rilevanza from a collection of a finding Rilevanze this is the SQL generated by nhibernate:

NHibernate: DELETE FROM RilevanzaFinding_T045 WHERE int_T045_IdFinding = 
@p0 AND dte_T045_DataFine = @p1 AND nvc_T045_Note = @p2 AND int_T045_IdRating
 = @p3;@p0 = 201 [Type: Int32 (0:0:0)], @p1 = NULL [Type: DateTime (0:0:0)], @p2 = 'GD675PFN2GTR9EUJ3JHPG7XFX' [Type: String (1073741823:0:0)], 
@p3 = 243 [Type: Int32 (0:0:0)]

which fails due to "dte_T045_DataFine = NULL" condition, since it would be "dte_T045_DataFine IS NULL"

How can I make it write the right condition?


Solution

  • You cannot with a set and NHibernate v5.1 or former.

    From NHibernate reference documentation (v5.1 and below):

    Please note that a composite element mapping doesn't support null-able properties if you're using a <set>. NHibernate has to use each columns value to identify a record when deleting objects (there is no separate primary key column in the composite element table), which is not possible with null values. You have to either use only not-null properties in a composite-element or choose a <list>, <map>, <bag> or <idbag>.

    Better map an entity than a component for this case, and add a primary key to your table, in my opinion. Your SQL will perform better by the way.


    From NHibernate v4.1, null equality semantic support was added to filtering queries with NH-3634. But the delete case was still not supported.

    From NHibernate v5.2 (which should be available in 2018, Q3), the delete case does now support it too (#1170).

    So another solution will be to upgrade to NHibernate v5.2.