5

Assume I have the following two classes:

public class User : Entity
{
    public virtual IList<Item> Items { get; set; }
}

public class Item : Entity
{
    public virtual User Owner { get; set; }
}

I created two mapping classes:

public class UserMap : ClassMap<User>
{
    public UserMap()
    {
        Id(x => x.Id);
        HasMany(x => x.Items);
    }
}

public class ItemMap : ClassMap<Item>
{
    public ItemMap()
    {
        Id(x => x.Id);
        References(x => x.Owner);
    }
}

This will result in a table Item that has a column UserId and a column OwnerId. When I use KeyColumn("OwnerId") on the HasMany mapping, it works with only the OwnerId column, but I would like to avoid that. Is there a way to tell NHibernate, to use the column created by the mapping in ItemMap?

Why I want to avoid specifying the column explicitly:
The column name OwnerId is automatically being generated based on the name of the property and some rules. If I change either the rules or the property name, I need to remember to change that KeyColumn, too. So, basically, it is not refactoring save.

Daniel Hilgarth
  • 171,043
  • 40
  • 335
  • 443
  • Can you change the property name to User? public virtual User User { get; set; }. You'd have to change your references mapping too of course – Cole W Nov 05 '11 at 19:49
  • @ColeW: Well, changing the property to User would obviously fix that, but than the name of the property would be not as good as it is now. – Daniel Hilgarth Nov 05 '11 at 20:25
  • I agree but I don't think there is clean way of doing what you're asking otherwise. – Cole W Nov 05 '11 at 22:19

1 Answers1

2

Updated: fixed bug

if you apply the rules in the maps then

public static void KeyColumnFromReference<TChild>(
    this OneToManyPart<TChild> collectionmap, ClassMap<TChild> map, Expression<Func<TChild, object>> referenceprop)
{
    string propertyname = GetPropertyName(referenceprop);
    var column = ((IMappingProvider)map).GetClassMapping()
        .References.First(m => m.Name == propertyname)
        .Columns.First().Name;

    collectionmap.KeyColumn(column);
}

public static void KeyColumnFromReference<T, TChild>(
    this OneToManyPart<TChild> collectionmap, ClassMap<TChild> map)
{
    var column = ((IMappingProvider)map).GetClassMapping()
        .References.First(m => m.Type == typeof(TChild))
        .Columns.First().Name;

    collectionmap.KeyColumn(column);
}

public UserMap()
{
    HasMany(x => x.Items)
        .KeyColumnFromReference<User, Item>(new ItemMap());

    // or

    HasMany(x => x.Items)
        .KeyColumnFromReference(new ItemMap(), u => u.Owner);
}

if you apply the rules as conventions then you need to implement IHasManyConvention and apply the same rules on the EntityType and propertyname (which you have to get through reflection from the ChildType)

Update:

class ForeignKeyConvention : IHasManyConvention
{
    public void Apply(IOneToManyCollectionInstance instance)
    {
        // to force the compiler to take the Name property and not the Name method
        string propertyName = ((ICollectionInspector)instance).Name;

        // should be equal to the convention for the reference key column
        instance.Key.Column(propertyName + instance.EntityType.Name + "id");
    }
}
Firo
  • 30,626
  • 4
  • 55
  • 94
  • Thanks, that looks good. However, I wonder whether you are sure this code is working. It looks like endless recursion to me: In the ctor of `UserMap`, you create a new instance of `UserMap`... Wouldn't it be better to simply pass `this`? – Daniel Hilgarth Nov 14 '11 at 10:53
  • Thanks for the code - I just tested it. Unfortunately, there is a problem left: It doesn't take the conventions for formatting the foreign keys into account... Can you please update your code accordingly? Sample: My convention is to append "Id" to the property name. Your code appends "_id". – Daniel Hilgarth Nov 17 '11 at 19:30
  • if you set it through convention then see my last sentence. best option is to implement another Convention which updates accordingly. the code is only for the case where you specify the Column in the ClassMap – Firo Nov 19 '11 at 16:54
  • Can you please update your answer with an implementation of `IHasManyConvention`? I don't see how I can get the correct column name this way. – Daniel Hilgarth Nov 27 '11 at 09:14