Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use NHibernate DiscriminateSubClassesOnColumn and References for the same column

The Setup

I am using FluentNHibernate 1.4.0 for my NHibernate 3.3.3-SP1 mappings in a .NET4.0 library. I'm using the "table-per-inheritance" approach for my type hierarchy as follows:

-- Different process types potentially use 
-- different types of reference values
CREATE TABLE ProcessTypes
(Id INT PRIMARY KEY)

-- Contains reference values for value comparisons
CREATE TABLE ProcessReferenceValues
(Id INT PRIMARY KEY IDENTITY(1,1),
 ProcessTypeId INT FOREIGN KEY REFERENCES ProcessTypes(Id),
 FloatReferencesValue FLOAT NULL,
 IntReferenceValue INT NULL)
// POCOs

class ProcessReferenceValues
{
    public virtual int Id { get; set; }
    public virtual ProcessTypes ProcessType { get; set; }
    public virtual float? FloatReferenceValue { get; set; }
    public virtual int? IntReferenceValue { get; set; }
}

class IntProcessReferenceValues : ProcessReferenceValues { }

class FloatProcessReferenceValues : ProcessReferenceValues { }

enum ProcessTypeName : int
{
    IntProcess = 1,
    FloatProcess = 2
}

class ProcessTypes
{
    public virtual int Id { get; set; }
    public virtual ProcessTypeName Name { get; set; }
}

// FluentNHibernate Mappings

class ProcessReferenceValuesMap 
    : FluentNHibernate.Mapping.ClassMap<ProcessReferenceValues>
{
    public ProcessReferenceValuesMap()
    {
        string processTypeId = "ProcessTypeId";

        this.Id(x => x.Id);
        this.Map(x => x.FloatReferenceValue).Nullable();
        this.Map(x => x.IntReferenceValue).Nullable();

        // Here is the tricky bit
        this.References(x => x.ProcessType, processTypeId);
        this.DiscriminateSubClassesOnColumn(processTypeId);
    }
}

class IntProcessReferenceValuesMap 
    : FluentNHibernate.Mapping.SubclassMap<IntProcessReferenceValues>
{
    public IntProcessReferenceValuesMap()
    {
        this.DiscriminatorValue((int)ProcessTypeName.IntProcess);
    }
}

class FloatProcessReferenceValuesMap 
    : FluentNHibernate.Mapping.SubclassMap<FloatProcessReferenceValues>
{
    public FloatProcessReferenceValuesMap()
    {
        this.DiscriminatorValue((int)ProcessTypeName.FloatProcess);
    }
}

class ProcessPeriodTypesMap : FluentNHibernate.Mapping.ClassMap<ProcessPeriodTypes>
{
    public ProcessPeriodTypesMap()
    {
        this.ReadOnly();
        this.Id(x => x.Id, "id");
        this.Map(x => x.Name, "id").ReadOnly().CustomType<PeriodTypeName>();
    }
}

The Problem

While reading from the database works like a charm - the appropriate sub classes are selected correctly - saving a new process reference value gives me an exception:

// Reading

var processType =
   (from type in session.Query<ProcessTypes>()
    where type.Name == ProcessTypeName.IntProcess
    select type).FirstOrDefault(); // OK, finds the IntProcess

var referenceValues =
   (from val in session.Query<ProcessReferenceValues>()
    select val).ToList(); // OK, finds the appropriate subclasses
// Inserting

var processType = new ProcessTypes
{
    Id = (int)ProcessTypeName.IntProcess
};

var referenceValue = new ProcessReferenceValues
{
    FloatReferenceValue = 0.7f,
    IntReferenceValue = null,
    ProcessType = processType // Needs the appropriate ProcessType
};
session.Save(referenceValue); // <- BOOM!
Error dehydrating property value for ProcessReferenceValues.ProcessType

Invalid Index 2 for OleDbParameterCollection with Count=2.
    bei System.Data.OleDb.OleDbParameterCollection.RangeCheck(Int32 index)
    bei System.Data.OleDb.OleDbParameterCollection.GetParameter(Int32 index)
    bei System.Data.Common.DbParameterCollection.System.Collections.IList.get_Item(Int32 index)
    bei NHibernate.Type.Int32Type.Set(IDbCommand rs, Object value, Int32 index) in p:\nhibernate-core\src\NHibernate\Type\Int32Type.cs:Zeile 60.
    bei NHibernate.Type.NullableType.NullSafeSet(IDbCommand cmd, Object value, Int32 index) in p:\nhibernate-core\src\NHibernate\Type\NullableType.cs:Zeile 182.
    bei NHibernate.Type.NullableType.NullSafeSet(IDbCommand st, Object value, Int32 index, Boolean[] settable, ISessionImplementor session) in p:\nhibernate-core\src\NHibernate\Type\NullableType.cs:Zeile 122.
    bei NHibernate.Type.ManyToOneType.NullSafeSet(IDbCommand st, Object value, Int32 index, Boolean[] settable, ISessionImplementor session) in p:\nhibernate-core\src\NHibernate\Type\ManyToOneType.cs:Zeile 50.
    bei NHibernate.Persister.Entity.AbstractEntityPersister.Dehydrate(Object id, Object[] fields, Object rowId, Boolean[] includeProperty, Boolean[][] includeColumns, Int32 table, IDbCommand statement, ISessionImplementor session, Int32 index) in p:\nhibernate-core\src\NHibernate\Persister\Entity\AbstractEntityPersister.cs:Zeile 2410.

As always in hard times I summoned the gooragle and the problem seems to be that the this.DiscriminateSubClassesOnColumn(processTypeId) adds a conflict with the this.References(x => x.ProcessType, processTypeId) mapping. When I remove the former the insert is successful but I want the subclass mapping AND I also need to be able to set the ProcessReferenceValues.ProcessType when adding new instances of ProcessReferenceValues to distinguish the subclasses.

The Question

Is it possible to discriminate subclasses on a column and at the same time referencing that same column on the same type?

Help very much appreciated, there's got to be a way to do this ...

thx in advance!

like image 374
mfeineis Avatar asked Sep 19 '14 16:09

mfeineis


1 Answers

classes and mappings without the problem

// POCOs

class ProcessValue
{
    public virtual int Id { get; set; }
    public abstract ProcessValueType Type { get; }
}

class IntProcessValue : ProcessValue
{
    public virtual int? Value { get; set; }
    public override ProcessValueType Type { get { return ProcessValueType.Int; } }
}

class FloatProcessValue : ProcessValue
{
    public virtual float? Value { get; set; }
    public override ProcessValueType Type { get { return ProcessValueType.Float; } }
}

enum ProcessValueType : int
{
    Int = 1,
    Float = 2
}

// FluentNHibernate Mappings

class ProcessValueMap  : ClassMap<ProcessValue>
{
    public ProcessValueMap()
    {
        string processTypeId = "ProcessValueTypeId";

        Id(x => x.Id);

        // just so you can query by type enum also. Querying by clr Type is already implemented
        Map(x => x.Type, processTypeId).CustomType<ProcessValueType>().ReadOnly().Access.None();
        DiscriminateSubClassesOnColumn(processTypeId);
    }
}

class IntProcessValueMap  : SubclassMap<IntProcessValue>
{
    public IntProcessValueMap()
    {
        Map(x => x.Value).Nullable();

        DiscriminatorValue((int)ProcessValueType.Int);
    }
}

class FloatProcessValueMap  : SubclassMap<FloatProcessValue>
{
    public FloatProcessValueMap()
    {
        Map(x => x.Value).Nullable();

        DiscriminatorValue((int)ProcessValueType.Float);
    }
}
like image 160
Firo Avatar answered Nov 15 '22 09:11

Firo