views:

956

answers:

1

Hello:

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

+2  A: 

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

Matthieu
It sure should, and does now that I changed my model; I made Allocation an Entity, which in my framework means it will wind up with an Id that will conveniently serve as a surrogate key for the associative table. Which raises the question(s) about whether Allocation really was an entity in my domain model the whole time, or am I dummying up a collection of value types to make it work (and if so, is there a cleaner way to get a surrogate key or should you use a composite-key or..). Anyway, thanks for the tip and thanks for the Ayende posting I need to read! Cheers
Berryl