9

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.

Paul Turner
  • 38,949
  • 15
  • 102
  • 166

3 Answers3

7

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();
Mike Cole
  • 14,474
  • 28
  • 114
  • 194
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • `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. – Paul Turner Feb 25 '10 at 19:20
  • 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. – Paul Turner Feb 25 '10 at 20:00
  • 2
    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 Feb 25 '10 at 20:11
  • 1
    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. – Paul Turner Feb 25 '10 at 20:41
3

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
  • 25,678
  • 5
  • 79
  • 77
  • @Programming Hero this is all inspired by the unit test IdConventionTests.ColumnShouldntBeOverwritten – Lachlan Roche Mar 04 '10 at 14:51
  • 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. – Paul Turner Mar 04 '10 at 16:43
  • Had to change this. But works, thank you. Upvote. /*protected override string GetKeyName(System.Reflection.PropertyInfo property, Type type) {*/ protected override string GetKeyName(Member property, Type type) { – granadaCoder Nov 22 '13 at 21:11
  • My (FluentNHibernate) version (that necessitated the change) : 1.3.0.733 – granadaCoder Nov 22 '13 at 21:12
1

For a system wide convention I believe this would serve the purpose best. ( I wasn't sure whether to include the whole text or just a portion here, since I answered it here already)

Here's the solution with links to current Fluent NHibernate & automapping documentation.

The issue (a simple example):

Say you have the simple example (from fluent's wiki) with an Entity and it's Value Objects in a List:

public class Product
{
  public virtual int Id { get; set; }
  //..
  public virtual Shelf { get; set; }
}

public class Shelf
{
  public virtual int Id { get;  set; }
  public virtual IList<Product> Products { get; set; }

  public Shelf()
  {
    Products = new List<Product>();
  }
}

With tables which have e.g.

Shelf 
id int identity

Product 
id int identity 
shelfid int

And a foreign key for shelfid -> Shelf.Id


You would get the error: invalid column name ... shelf_id


Solution:

Add a convention, it can be system wide, or more restricted.

ForeignKey.EndsWith("Id")

Code example:

var cfg = new StoreConfiguration();
var sessionFactory = Fluently.Configure()
  .Database(/* database config */)
  .Mappings(m =>
    m.AutoMappings.Add(
      AutoMap.AssemblyOf<Product>(cfg)
          .Conventions.Setup(c =>
              {
                  c.Add(ForeignKey.EndsWith("Id"));
              }
    )
  .BuildSessionFactory();

Now it will automap the ShelfId column to the Shelf property in Product.


More info

Wiki for Automapping

Table.Is(x => x.EntityType.Name + "Table")
PrimaryKey.Name.Is(x => "ID")
AutoImport.Never()
DefaultAccess.Field()
DefaultCascade.All()
DefaultLazy.Always()
DynamicInsert.AlwaysTrue()
DynamicUpdate.AlwaysTrue()
OptimisticLock.Is(x => x.Dirty())
Cache.Is(x => x.AsReadOnly())
ForeignKey.EndsWith("ID")

See more about Fluent NHibernate automapping conventions

Community
  • 1
  • 1
lko
  • 8,161
  • 9
  • 45
  • 62