Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoid specifying key column in double linked scenario

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.

like image 532
Daniel Hilgarth Avatar asked Nov 05 '22 11:11

Daniel Hilgarth


1 Answers

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");
    }
}
like image 175
Firo Avatar answered Nov 11 '22 15:11

Firo