views:

554

answers:

2

I have a database schema where the convention for a foreign key's name is:

ForeignTable.Name + ForeignTable.PrimaryKeyName

So, for a Child table referencing a Parent table with a primary key column named Key, the foreign key will look like ParentKey.

Is there a way to create this convention in my Fluent NHibernate mapping?

Currently I'm using a ForeignKeyConvention implementation like this:

public class ForeignKeyNamingConvention : ForeignKeyConvention
{
    protected override string GetKeyName(PropertyInfo property, Type type)
    {
        if (property == null)
        {
            // Relationship is many-to-many, one-to-many or join.
            if (type == null)
                throw new ArgumentNullException("type");

            return type.Name + "ID";
        }

        // Relationship is many-to-one.
        return property.Name + "ID";
    }
}

This works exactly as I want for all types which have "ID" as a primary key. What I would like to do is replace the constant "ID" with the name of the primary key of the type being referenced.

If this isn't currently possible with Fluent NHibernate, I'm happy to accept that answer.

+1  A: 

Take a look at conventions and especially at implementing a custom foreign key convention.


UPDATE:

Here's an example. Assuming the following domain:

public class Parent
{
    public virtual int Id { get; set; }
}

public class Child
{
    public virtual string Id { get; set; }
    public virtual Parent Parent { get; set; }
}

which needs to be mapped to this schema:

create table Child(
    Id integer primary key, 
    ParentId integer
)

create table Parent(
    Id integer primary key
)

you could use this convention:

public class CustomForeignKeyConvention : IReferenceConvention
{
    public void Apply(IManyToOneInstance instance)
    {
        instance.Column(instance.Class.Name + "Id");
    }
}

and to create the session factory:

var sf = Fluently
    .Configure()
    .Database(
        SQLiteConfiguration.Standard.UsingFile("data.db3").ShowSql()
    )
    .Mappings(
        m => m.AutoMappings.Add(AutoMap
            .AssemblyOf<Parent>()
            .Where(t => t.Namespace == "Entities")
            .Conventions.Add<CustomForeignKeyConvention>
        )
    )
    .BuildSessionFactory();
Darin Dimitrov
`ForeignKeyConvention` is where I started looking, but the lack of clear documentation makes it hard to understand where to find the relevant information from the parent type.
Programming Hero
Thanks for the update, but it doesn't address my issue: Within `Apply(IManyToOneInstance)` how can you obtain the name of the parent key column? Name + "ID" isn't the behaviour I want. Name + Parent.KeyName is.
Programming Hero
There's no such notion as PrimaryKeyName. It's all about conventions. By convention all primary key columns should be called `Id`. You could change this default behavior by implementing an `IIdConvention` for given types or all types, meaning that you already know the types for which this default behavior has been changed. As a consequence in the `IReferenceConvention` depending on the type you may apply the proper suffix.
Darin Dimitrov
My conventions don't demand a consistent primary key name between tables, but foreign keys are always `ForeignTable.Name` + `ForeignTable.PrimaryKeyName`. If this concept is not something the current conventions model can provide, I'm happy to have an answer say so.
Programming Hero
+1  A: 

If you can get the Mapping<T> for a class, you can get the name of its Id column.

public class MyForeignKeyConvention: ForeignKeyConvention
{
    public static IList<IMappingProvider> Mappings = new List<IMappingProvider>();

    protected override string GetKeyName( System.Reflection.PropertyInfo property, Type type )
    {
        var pk = "Id";

        var model = new PersistenceModel();
        foreach( var map in Mappings ) {
            model.Add( map );
        }

        try {
            var mymodel = (IdMapping) model.BuildMappings()
                .First( x => x.Classes.FirstOrDefault( c => c.Type == type ) != null )
                .Classes.First().Id;

            Func<IdMapping, string> getname = x => x.Columns.First().Name;
            pk = getname( mymodel );
        } catch {
        }

        if (property == null) {
            return type.Name + pk;
        }
        return type.Name + property.Name;
    }
}

We can get the Mapping object with a little bit of plumbing.

The constructors of ClassMap<T> can pass this into our collection of Mappers.

For AutoMapping<T>, we can use Override as follows.

.Mappings( m => m.AutoMappings.Add( AutoMap.AssemblyOf<FOO>()
    .Override<User>( u => {
        u.Id( x => x.Id ).Column( "UID" );
        MyForeignKeyConvention.Mappings.Add( u );
    }
)
Lachlan Roche
@Programming Hero this is all inspired by the unit test IdConventionTests.ColumnShouldntBeOverwritten
Lachlan Roche
It's not as direct a solution as I'd have liked, but there's more than enough information for me to explore into. Thank you.
Programming Hero