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.
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 );
}
)
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();
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