Using ICompositeUserType on different tables with different column names

advertisements

I've been trying to get an NHibernate ICompositeUserType mapping to work. But am stuck trying to make the implementation generic enough to use on different tables.

Our legacy database has many tables with latitudes and longitudes in and I want to map them into my domain objects as a Position class. The problem is each table has different names for the latitude and longitude columns.

I've created an implementation of the ICompositeUserType but I seem to have to set the PropertyNames to the names of the columns in a table, which means I can't use the CustomType on different tables (with different column names).

I thought I should set the PropertyNames to the names of the properties in my Position class, and map them to the table columns in my ClassMap, but this seems to throw a NHibernate exception of:

NHibernate.MappingException: property mapping has wrong number of columns

It feels like I'm doinging something wrong in my mapping, but I can't figure it out.

Here is a snippet of code from my ICompositeUserType:

public class PositionUserType : ICompositeUserType
{
  private readonly IType[] _propertyTypes =
     new [] { NHibernateUtil.Double , NHibernateUtil.Double };

  private readonly string[] _propertyNames =
     new[] { "Latitude", "Longitude"  };

  public string[] PropertyNames { get { return _propertyNames; } }

  public IType[] PropertyTypes { get { return _propertyTypes; } }

  // Other methods omitted
}

and my Class Map:

public class LastPositionMap : ClassMap<LastPosition>
{
    public LastPositionMap()
    {
        Map(p => p.Position)
            .Columns.Add("LPLongitude", "LPLongitude")
            .CustomType(typeof(PositionUserType));

        // Other mapping omitted
    }
}

and the Position class

public class Position
{
  public double Latitude { get; private set; }
  public double Longitude { get; private set; }

  public Position(double latitude, double longitude)
  {
    Latitude = latitude;
    Longitude = longitude;
  }
}

I've currently got a work around where I can use a Component fluent map, but this means my Position class must be mutable, and I would prefer it if it wasn't.

Can anyone help? I've had a good look at several articles and books but I still can't seem to get it to work.

Thanks,

Adam


You need to clear the Column collection before adding the column name overrides in your mapping:

public class LastPositionMap : ClassMap<LastPosition>
{
    public LastPositionMap()
    {
        Map(p => p.Position)

            // Clear the columns to get rid of Fluent NH's default column names
            .Columns.Clear()

            .ColumnsAdd("LPLongitude", "LPLongitude")
            .CustomType(typeof(PositionUserType));

        // Other mapping omitted
    }
}

If you don't clear the column collection before adding the custom names, Fluent NH simply appends the new column names to the column collection for the user type mapping, which leads to that you get too many columns for the given user type.

If you generate the actual XML-mappings from your fluent mappings (by using Mappings.ExportTo() in your fluent configuration) you would probably see something like this:

<property <!-- details here -->
  <column name="Latitude" />
  <column name="Longitude" />
  <column name="LPLongitude" />
  <column name="LPLatitude" />
</property>

when it should actually be:

<property <!-- details here -->
  <column name="LPLatitude" />
  <column name="LPLongitude" />
</property>