Skip to content

Instantly share code, notes, and snippets.

@SamerX
Last active November 20, 2019 08:37
Show Gist options
  • Save SamerX/e6726b9b0c5ecaf0bfd4d6611603afd9 to your computer and use it in GitHub Desktop.
Save SamerX/e6726b9b0c5ecaf0bfd4d6611603afd9 to your computer and use it in GitHub Desktop.
NHibernate CustomHasManyToManyStep
public class CustomHasManyToManyStep : IAutomappingStep
{
private readonly IAutomappingConfiguration cfg;
public CustomHasManyToManyStep(IAutomappingConfiguration cfg)
{
this.cfg = cfg;
}
public bool ShouldMap(Member member)
{
var type = member.PropertyType;
if (type.Namespace != "Iesi.Collections.Generic" &&
type.Namespace != "System.Collections.Generic")
return false;
if (type.HasInterface(typeof(IDictionary)) || type.ClosesInterface(typeof(IDictionary<,>)) || type.Closes(typeof(System.Collections.Generic.IDictionary<,>)))
return false;
//SamerA: Tweak to allow unidirectional ManyToMany mapping along with the Bidirectional
return (IsBidirectionalManyToMany(member) || IsUnidirectionalManyToMany(member));
}
static Member GetInverseCollectionProperty(Member member)
{
var type = member.PropertyType;
var expectedInversePropertyType = type.GetGenericTypeDefinition()
.MakeGenericType(member.DeclaringType);
var argument = type.GetGenericArguments()[0];
return argument.GetProperties()
.Select(x => x.ToMember())
.Where(x => x.PropertyType == expectedInversePropertyType && x != member)
.FirstOrDefault();
}
static bool IsBidirectionalManyToMany(Member member)
{
var type = member.PropertyType;
var expectedInversePropertyType = type.GetGenericTypeDefinition()
.MakeGenericType(member.DeclaringType);
var argument = type.GetGenericArguments()[0];
return argument.GetProperties()
.Select(x => x.ToMember())
.Any(x => x.PropertyType == expectedInversePropertyType && x != member);
}
static bool IsUnidirectionalManyToMany(Member member)
{
var type = member.PropertyType;
var argument = type.GetGenericArguments()[0];
return argument.GetProperties()
.Select(x => x.ToMember()).All(x => x.PropertyType != member.DeclaringType && x != member);
}
public void Map(ClassMappingBase classMap, Member member)
{
var inverseProperty = GetInverseCollectionProperty(member);
var parentSide = inverseProperty == null ? member.DeclaringType : cfg.GetParentSideForManyToMany(member.DeclaringType, inverseProperty.DeclaringType);
var mapping = GetCollection(member);
ConfigureModel(member, mapping, classMap, parentSide);
classMap.AddCollection(mapping);
}
static CollectionMapping GetCollection(Member property)
{
var collectionType = CollectionTypeResolver.Resolve(property);
return CollectionMapping.For(collectionType);
}
void ConfigureModel(Member member, CollectionMapping mapping, ClassMappingBase classMap, Type parentSide)
{
// TODO: Make the child type safer
mapping.SetDefaultValue(x => x.Name, member.Name);
mapping.Relationship = CreateManyToMany(member, member.PropertyType.GetGenericArguments()[0], classMap.Type);
mapping.ContainingEntityType = classMap.Type;
mapping.ChildType = member.PropertyType.GetGenericArguments()[0];
mapping.Member = member;
SetDefaultAccess(member, mapping);
SetKey(member, classMap, mapping);
if (parentSide != member.DeclaringType)
mapping.Inverse = true;
}
void SetDefaultAccess(Member member, CollectionMapping mapping)
{
var resolvedAccess = MemberAccessResolver.Resolve(member);
if (resolvedAccess != Access.Property && resolvedAccess != Access.Unset)
{
// if it's a property or unset then we'll just let NH deal with it, otherwise
// set the access to be whatever we determined it might be
mapping.SetDefaultValue(x => x.Access, resolvedAccess.ToString());
}
if (member.IsProperty && !member.CanWrite)
mapping.SetDefaultValue(x => x.Access, cfg.GetAccessStrategyForReadOnlyProperty(member).ToString());
}
ICollectionRelationshipMapping CreateManyToMany(Member property, Type child, Type parent)
{
var mapping = new ManyToManyMapping
{
Class = new TypeReference(property.PropertyType.GetGenericArguments()[0]),
ContainingEntityType = parent
};
mapping.AddDefaultColumn(new ColumnMapping { Name = child.Name + "_id" });
return mapping;
}
void SetKey(Member property, ClassMappingBase classMap, CollectionMapping mapping)
{
var columnName = property.DeclaringType.Name + "_id";
var key = new KeyMapping();
key.ContainingEntityType = classMap.Type;
key.AddDefaultColumn(new ColumnMapping { Name = columnName });
mapping.SetDefaultValue(x => x.Key, key);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment