views:

144

answers:

1

Using Fluent NHibernate, I cannot seem to devise the necessary automapping conventions for the following (seemingly simple and common) use-case:

public class MyClass
{
    private int _specialIdentityField
    private string _firstname;
    public Id { get { return _specialIdentityField; }; }
    public virtual string Firstname
    {
        get
        {
            return _firstname;
        }
        set
        {
            _firstname = value;
        }
    }
}

public class OtherClass
{
    private int _specialIdentityField
    private string _lastname;
    public Id { get { return _specialIdentityField; }; }
    public virtual string Lastname
    {
        get
        {
            return _lastname;
        }
        set
        {
            _lastname = value;
        }
    }
}

The desired mappings are like so:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="field.camelcase-underscore" auto-import="true" default-cascade="none" default-lazy="true">
    <class xmlns="urn:nhibernate-mapping-2.2" mutable="true" name="MyClass" table="`MyClass`">
        <id name="_specialIdentityField" type="System.Int32" access=field>
          <column name="Id" />
          <generator class="identity" />
        </id>
        <property name="Firstname" type="System.String">
          <column name="Firstname" />
        </property>
    </class>
    <class xmlns="urn:nhibernate-mapping-2.2" mutable="true" name="OtherClass" table="`OtherClass`">
        <id name="_specialIdentityField" type="System.Int32" access=field>
          <column name="Id" />
          <generator class="identity" />
        </id>
        <property name="Lastname" type="System.String">
          <column name="Lastname" />
        </property>
    </class>
</hibernate-mapping>

Basically the rules are:

  • everything is field-camelcase-underscore as an access type EXCEPT identity
  • identity is a fixed-name field in every class (name=_someSpecialIdentityField)
  • identity access is always field-only and bears no relation to the name of the RO property that surrounds it

The part of this that is completely tripping me up is the convention-mapping of the identity-related elements (clearly the convention-mapping of the properties is completely standard fare). The issue I am having is how to tell FNH conventions that my identity field is a fixed-name. All of the convention overrides that I can find seem to assume there will always be some relationship between the property that represents identity and the name of its underlying backing field (e.g. I can set a 'custom prefix' for the backing field, but cannot seem to see how I can just say "this is always the name of the backing field").

Its obvious to me how to accomplish this with explicit mapping (and for that matter, with XML mapping files) but not obvious at all to me how to accomplish this with convention-based (Automapping) mapping in FNH.

This can't be an atypical use-case so I must just be overlooking something terribly obvious. Thoughts from any FNH gurus appreciated!

A: 

EDIT: Take a look at the IAutomappingConfiguration interface. Create you own implementation or override the DefaultAutomappingConfiguration class.

public virtual bool IsId(Member member)
    {
        return member.Name.Equals("id", StringComparison.InvariantCultureIgnoreCase);
    }

then you add it to the initialization:

Fluently.Configure( Configuration )
                .Mappings( cfg =>
                { cfg.AutoMappings.Add( IAutomappingConfigurationInstance )}

===============================================

Hi Steve, I think I know what you are trying to do. I use Proteus and FNH automapping. To do the trick with the id I created a wrapper around Proteus which do two things:

1) Maps the ID

2) Hides the Setter for the id

public abstract class EntityObject<TEntity> : IdentityPersistenceBase<TEntity, Guid>, IEntity
    where TEntity : class, IEntity
{
    public virtual Guid Id
    {
        get { return _persistenceId; }
    }

    Guid IIdentifiedEntity<Guid>.Id
    {
        get { return _persistenceId; }
        set { _persistenceId = value; }
    }

    public virtual int Version
    {
        get { return _persistenceVersion; }
        set { _persistenceVersion = value; }
    }
}

And to avoid the property IsTransient to be persisted + other stuff you can create MappingAlternation:

public class EntityAlteration : IAutoMappingAlteration
{

    public void Alter( AutoPersistenceModel model )
    {
        model.OverrideAll( map =>
        {

            Type recordType = map.GetType().GetGenericArguments().Single();
            if( recordType.BaseType.Name == "EntityObject`1" )
            {
                Type changeType = typeof( Change<> ).MakeGenericType( recordType );
                var change = ( IChange )Activator.CreateInstance( changeType );
                change.Go( map );
            }
        } );
    }

}

interface IChange
{
    void Go( object mapObject );
}

class Change<TRecord> : IChange where TRecord : EntityObject<TRecord>
{
    void IChange.Go( object mapObject )
    {
        var map = ( AutoMapping<TRecord> )mapObject;
        map.Id( x => x.Id ).GeneratedBy.Guid().Access.Property();
        map.IgnoreProperty( x => x.IsTransient );
    }
}

PS: I am really missing the times when you were active in the online space. It was exciting Summer and Authumn 2 years ago...

mynkow
wow -- perfect, if not a bit overly-complicated! About my departure from the online space, unfortunately the local community sort of took over a lot of my focus/attention but you will soon see my return to active online life; stay tuned!
sbohlen
I edited the answer with probably better solution.
mynkow