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 : IJoinedSubclassConv开发者_开发知识库ention
{
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
warappamapping.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'")
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
精彩评论