C# – How to map this model

cdomain-driven-designfluent-nhibernatenhibernatenhibernate-mapping

A TimeSheetActivity class has a collection of Allocations. An Allocation is a value object (immutable) looking something like this:

public class Allocation : ValueObject {
    public virtual StaffMember StaffMember { get; private set; }
    public virtual TimeSheetActivity Activity { get; private set; }
    public virtual DateTime EventDate { get ... }
    public virtual TimeQuantity TimeSpent { get ... }
}

The most convenient way to use this relationship in the domain is with an IDictionary, ie:

public virtual IEnumerable<Allocation> Allocations { 
 get { return _allocations.Values; } }

private readonly IDictionary<DateTime, Allocation> _allocations = new SortedList<DateTime, Allocation>();

I also need to be able to persist and fetch Allocations by StaffMember. If this was a data driven design, my concept is that there would be an Allocations table which would resolve the many-to-many relationship between Activities and StaffMembers:

table Allocations
    ActivityID
    StaffMemberID
    EventDate
    TimeQuantity

I'm having trouble visualizing the mappings. I'm trying to start with the TimeSheetActivity's Allocations, but I get the error below when I try to generate DDL:

NHibernate.MappingException: (XmlDocument)(3,6): XML validation error: The element 'class' in namespace 'urn:nhibernate-mapping-2.2' has invalid child element 'property' in namespace 'urn:nhibernate-mapping-2.2'. List of possible elements expected: 'meta, subselect, cache, synchronize, comment, tuplizer, id, composite-id' in namespace 'urn:nhibernate-mapping-2.2'.

I can't tell if I'm getting the error because:

A). I haven't yet mapped StaffMember to a collection of Allocations.
B). The mapping I have between Activity and Allocations is wrong.
C). All of the above
D). Other _____________________________________

AllocationMapping (in FNH)

public class AllocationMap : IAutoMappingOverride<Allocation>
{
    public void Override(AutoMap<Allocation> mapping)
    {

        // these are both derived from the time period, so ignore
        mapping.IgnoreProperty(x => x.TimeSpent);
        mapping.IgnoreProperty(x => x.EventDate);

        // TimePeriodUserType knows how to persist the time period start and end dates as DateTimes
        mapping.Map(x => x.TimeSpentPeriod)
          .ColumnNames.Add("PeriodStart", "PeriodEnd")
          .SetAttribute("type", typeof(TimePeriodUserType).AssemblyQualifiedName);

        mapping.References(x => x.Activity).WithForeignKey().FetchType.Join().Not.Nullable();
        mapping.References(x => x.StaffMember).WithForeignKey().FetchType.Join().Not.Nullable();
    }
}

Allocation mapping (generated hbm)

....
<property name="TimeSpentPeriod" type="..., ..., ...">
  <column name="PeriodStart" />
  <column name="PeriodEnd" />
</property>
<many-to-one foreign-key="FK_AllocationToStaffMember" fetch="join" not-null="true" name="StaffMember" column="StaffMemberID" />
<many-to-one foreign-key="FK_AllocationToActivity" fetch="join" not-null="true" name="Activity" column="TimeSheetActivityID" />
....

Activity mapping (FNH)

public class TimeSheetActivityMap : IAutoMappingOverride<TimeSheetActivity>
{
    public void Override(AutoMap<TimeSheetActivity> mapping)
    {
        // this can be replaced by some id given by NHib via an inheritance type mapping?? 
        mapping.IgnoreProperty(x => x.IdScheme);

        // this is derived
        mapping.IgnoreProperty(x => x.TotalTime);

        // this is for client consumption only
        mapping.IgnoreProperty(x => x.Allocations);

        mapping.HasMany(x => x.Allocations)
            .AsMap(x => x.EventDate)
            .Cascade.All()
            .Access.AsCamelCaseField(Prefix.Underscore)
            .WithForeignKeyConstraintName("FK_ActivityAllocation");
    }
}

ActivityMapping (generated hbm)

<set name="Allocations" cascade="all" access="field.camelcase-underscore">
<key foreign-key="FK_ActivityAllocation" column="TimeSheetActivityID" />
<one-to-many class="Smack.ConstructionAdmin.Domain.Model.TimeSheet.Allocation, ..." />
</set>

This is a complicated mapping for my level of experience with NHibernate, so I'd appreciate any help, questions, or criticisms on the mapping technique, the modeling concept, or my approach to working through this.

Cheers,
Berryl

Best Answer

I'm not an expert in FHN but I see that your Allocations property is generated as a Set. You should get a Map element like this :

<map name="Allocations" lazy="true" table="Allocations">
<key column="FK_ActivityAllocation"/>
<index column="EVENT_DATE" type="DateTime"/>
<composite-element class="Allocation">
...
</composite>
</map>

You got some advanced examples here : http://ayende.com/Blog/archive/2009/06/03/nhibernate-mapping-ndash-ltmapgt.aspx