Conventions for ICompositeUserTypes and field overrides

Alex Davidson's Avatar

Alex Davidson

23 Aug, 2010 11:47 AM via web

Hi,

We're having some trouble getting NHibernate to automap properties of composite user types. The problem entities look something like:

public class TestClass
{
    public int Id {get;set;}
    public IUserDateStamp Started {get;set;}
    public IUserDateStamp Stopped {get;set;}
}

public interface IUserDateStamp
{
    public IUser User {get;}
    public int UserId {get;}
    public DateTime DateStamp {get;}
}

This has to interop with legacy code and, in places, a legacy database, hence the exposed UserId. IUser is not managed by NHibernate at present. The backing table for TestClass looks like:

CREATE TABLE [dbo].[test_table] (
    [id] [int] NOT NULL PRIMARY KEY,

    [startedDate] [datetime] NOT NULL,
    [startedBy] [int] NOT NULL,

    [stoppedDate] [datetime] NULL,
    [stoppedBy] [int] NULL,

    [timestamp] timestamp
)

IUserDateStamps should map to pairs of fields in their containing entity's backing table.

The concrete implementation of IUserDateStamp is nontrivial, so we can't use a Component to map it. We have a UserDateStampUserType class implementing ICompositeUserType to handle conversion of a pair of fields to a complete UserDateStamp instance and back again, which exposes 'UserId' and 'DateStamp' as PropertyNames.

Firstly, is there any way to get Fluent NHibernate to automap properties of type IUserDateStamp to use UserDateStampUserType? Left to its own devices it creates many-to-one mappings for these properties and then complains that there is no known IUserDateStamp entity. IPropertyConventions never get called for these mappings, so the examples for IUserType and UserTypeConvention do not seem to apply.
Specifying explicit CustomType overrides causes NHibernate to map them as properties backed by two columns, but then they both reference 'UserId' and 'DateStamp' fields. This is fixed by:

mapping.Map(m => m.Started).CustomType<UserDateStampUserType>().Columns.Clear().Columns.Add("startedBy", "startedDate");

But using overrides for this is rather clumsy, as we have many properties like these throughout the object model.

So secondly, is there a way to define conventions for overriding these field mappings?

Thanks,

Alex

  1. 2 Posted by Alex Davidson on 24 Aug, 2010 09:03 AM

    Alex Davidson's Avatar

    Fixed automapping this type with an IUserTypeConvention after all. No idea why it didn't work the first time.

    Column names were a little trickier, since Fluent NHibernate provides no write access to those through IPropertyInstance (at least, not for composite types). I wrote the following base class for mapping composite types:

    public abstract class CompositeUserTypeConvention<TCompositeUserType> : IUserTypeConvention where TCompositeUserType : ICompositeUserType, new()
    {
        private TCompositeUserType userType;
    
        protected CompositeUserTypeConvention()
        {
            userType = Activator.CreateInstance<TCompositeUserType>();
        }
    
        public virtual void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
        {
            criteria.Expect(p => p.Type == userType.ReturnedClass);
        }
    
        public virtual void Apply(IPropertyInstance instance)
        {
            instance.Column(instance.Name);
            instance.CustomType<UserDateStampUserType>();
    
            var mapping =
                (PropertyMapping)instance.GetType().InvokeMember("mapping", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance, null, instance, new object[0]);
    
            var existingColumn = mapping.Columns.First();
            mapping.ClearColumns();
    
            foreach (var columnName in GetColumnNames(instance, userType))
            {
                var column = existingColumn.Clone();
                column.Name = columnName;
                mapping.AddColumn(column);
            }
        }
    
        protected abstract IEnumerable<string> GetColumnNames(IPropertyInstance property, TCompositeUserType userType);
    }

    Horrible, fragile reflection, but works nicely :)

  2. 3 Posted by Prashanth on 02 Dec, 2010 07:29 PM

    Prashanth's Avatar

    Hi, my problem is similar to the above so can you please send me the code for the UserDateStampUserType class implementing ICompositeUserType.

    -- Thanks

  3. 4 Posted by Steve Powell on 01 Feb, 2011 11:50 AM

    Steve Powell's Avatar

    Thanks Alex. That worked a treat!

Reply to this discussion

Preview Comments are parsed with Markdown. Help with syntax

Attached Files

    You can attach files up to 10MB

    Ten divided by two is what?