Question

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

Was it helpful?

Solution 2

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

OTHER TIPS

mapping.HasMany(x => x.ManagedUsers) and mapping.HasMany(x => x.ManagedUsergroups) are responsible for the extra CommonThingId-columns.

That should do:

mapping.HasMany<Principal>(x => x.ManagedUsers)
mapping.HasMany<Principal>(x => x.ManagedUsergroups)

and i couldnt resist. I think polymorphism would be better here

public virtual CommonThing CommonThing
{
    get { return _commonThing; }
    set
    {
        if (_commonThing == value)
            return;

        _commonThing = value;

        if (_commonThing != null)
            AddThisToCommonThing(_commonThing);
    }
}

protected abstract void AddThisToCommonThing(CommonThing common);

Edit: @comment: right i havent seen this

you could do a TPH (table-per-hirarchy) mapping for Users: in fluentnhibernate automapping configuration override DiscriminateSubclasses() { return true; }

class SCConvention : ISubclassConvention
{
    Apply(...)
    {
        instance.DiscriminatorValue(instance.Type.Name);
    }
}

mapping.HasMany<Principal>(x => x.ManagedUsers).Where("discriminatorcolumn = 'User'")
mapping.HasMany<Principal>(x => x.ManagedUsergroups).Where("discriminatorcolumn = 'Usergroup'")
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top