Search code examples
nhibernatenhibernate-mappingpolymorphismhas-manyjoined-subclass

NHibernate: JoinedSubclass, HasMany


I use FluentNHibernate (Automapping) for mapping, NHibernate 3.2 for data access and SchemaExport to generate my database.

I have a class Principal which is the base class for User and Usergroup. Principal has a property CommonThing of type CommonThing. CommonThing has 2 sets: ManagedUsers and ManagedUsergroups.

Now a column CommonThingId is generated for Principals-table (OK), Users-table (WRONG), Usergroups-table (WRONG).

How can I get FluentNHibernate to only generate the column in Principals-table and not the subclassed tables?

Edit: Classes & Mappings Principal:

public abstract class Principal : Entity
{
    ...
    public virtual CommonThing CommonThing
    {
        get
        {
            return _commonThing;
        }
        set
        {
            if (_commonThing == value)
                return;

            _commonThing = value;

            if (_commonThing == null)
                return;

            if (this is Usergroup)
                _commonThing.AddUsergroup(this as Usergroup);
            else if (this is User)
                _commonThing.AddUser(this as User);
        }
    }
    ...
}

User:

public partial class User : Principal
{
    ...
}

Usergroup:

public partial class Usergroup : Principal
{
    ...
}

CommonThing:

public class CommonThing : Entity
{
    ...
    public virtual IEnumerable<User> ManagedUsers { get { return _managedUsers; } set { _managedUsers = (Iesi.Collections.Generic.ISet<User>)value; } }
    public virtual IEnumerable<Usergroup> ManagedUsergroups { get { return _managedUsergroups; } set { _managedUsergroups = (Iesi.Collections.Generic.ISet<Usergroup>)value; } }
    ...
}

Conventions:

public class ReferenceConvention : IReferenceConvention
{
    public void Apply(IManyToOneInstance instance)
    {
        var keyName = string.Format(CultureInfo.InvariantCulture, "FK_MtO_{0}_in_{1}_{2}",
                                instance.Property.PropertyType.Name,
                                instance.EntityType.Name,
                                instance.Name);
        instance.ForeignKey(keyName);

        instance.LazyLoad();            

        instance.Cascade.SaveUpdate();

        instance.Column(instance.Property.PropertyType.Name + "Id");

        instance.Access.CamelCaseField(CamelCasePrefix.Underscore);
    }
}

public class ForeignKeyConvention : FluentNHibernate.Conventions.ForeignKeyConvention
{
    protected override string GetKeyName(Member property, Type type)
    {
        if (property == null)
            return type.Name + "Id";

        return property.Name + "Id";
    }
}

public class HasManyConvention : IHasManyConvention
{
    public void Apply(IOneToManyCollectionInstance instance)
    {
        var keyName = string.Format(CultureInfo.InvariantCulture, "FK_OtM_{0}_{1}2{2}",
                                instance.Member.ReflectedType.Name,
                                instance.Member.Name,
                                instance.EntityType.Name);

        instance.Key.ForeignKey(keyName);

        if(instance.Key.Columns.Count() != 0)
            instance.Inverse();
        instance.Cascade.SaveUpdate();

        instance.Cache.ReadWrite();
        instance.Cache.IncludeAll();

        instance.Access.CamelCaseField(CamelCasePrefix.Underscore);
    }
}

public class JoinedSubclassConvention : IJoinedSubclassConvention
{
    public void Apply(IJoinedSubclassInstance instance)
    {
        instance.Table("" + Inflector.Net.Inflector.Pluralize(instance.Type.Name));
        instance.Key.Column("Id");

        instance.DynamicInsert();
        instance.DynamicUpdate();

        instance.LazyLoad();            
    }
}

Principal mapping:

public class PrincipalMapping : IAutoMappingOverride<Principal>
{
    public void Override(AutoMapping<Principal> mapping)
    {
        ...
        mapping.References(x => x.CommonThing)
            .LazyLoad()
            .Nullable()
            .Access.CamelCaseField(Prefix.Underscore)
            .Cascade.None();
        ;
        mapping.JoinedSubClass<User>("Id");
        mapping.JoinedSubClass<Usergroup>("Id");
        ...
    }
}

CommonThing mapping:

public class CommonThingMapping : IAutoMappingOverride<CommonThing>
{
    public void Override(AutoMapping<CommonThing> mapping)
    {
        ...
        mapping.HasMany(x => x.ManagedUsers)
            .AsSet()
            .ExtraLazyLoad()
            ;
        mapping.HasMany(x => x.ManagedUsergroups)           
            .ExtraLazyLoad()
            .AsSet()            
            ;
        ...
    }
}

Lg
warappa


Solution

  • So I finally found a way to prevent mapping references which are already mapped:

    public class AutomappingConfiguration : DefaultAutomappingConfiguration
    {
        ...
        public override bool ShouldMap(Member member)
        {
            ...
            var res = base.ShouldMap(member);
            if (res == true &&              
                typeof(IEnumerable).IsAssignableFrom(member.PropertyType) == false) // "References"
            {
                var originalDeclaringType = GetOriginalDeclaringType(member.MemberInfo);
    
                // is Reference declared in a base-type?
                if (!(originalDeclaringType == typeof(Entity) ||
                    originalDeclaringType == typeof(Entity<int>)) &&
                    originalDeclaringType != member.MemberInfo.ReflectedType)
                    return false; // base-type already mapped it...
            }
            return res;
        }
    
        // Helper
        private Type GetOriginalDeclaringType(MemberInfo member)
        {
            List<Type> types = new List<Type>();
    
            Type type = member.ReflectedType;
            while (type != null)
            {
                types.Add(type);
                type = type.BaseType;
            }
    
            types.Reverse();
    
            foreach(var t in types)
            {
                var tmp = t.GetMember(member.Name, BindingFlags.Public |
                            BindingFlags.NonPublic |
                            BindingFlags.Instance |
                            BindingFlags.DeclaredOnly);
                if (tmp.Length != 0)
                {
                    type = t;
                    break;
                }
            }
            return type;
        }
        ...
    }
    

    It can be that there are cases which cause side effects due to this, but in my current very, very complex project it just did what I wanted it to.

    Lg
    warappa