From 67d4a5064cb87ae610fff71da8308db4c8300cb1 Mon Sep 17 00:00:00 2001 From: Stuart Childs Date: Thu, 14 May 2009 17:31:16 -0500 Subject: [PATCH] Experimental HasMap functionality on Classlike. --- .../HasMapCollectionNameConvention.cs | 17 ++ .../Defaults/HasMapIndexNameConvention.cs | 17 ++ .../Defaults/HasMapTableNameConvention.cs | 17 ++ .../Discovery/HasMapDiscoveryConvention.cs | 35 ++++ .../Conventions/ForeignKeyConvention.cs | 13 +- .../Conventions/IMapConvention.cs | 8 + src/FluentNHibernate/FluentNHibernate.csproj | 9 + .../Mapping/ClasslikeMapBase.cs | 35 ++++ src/FluentNHibernate/Mapping/IClasslike.cs | 2 + src/FluentNHibernate/Mapping/IIndexMapping.cs | 16 ++ .../Mapping/IMapCollectionPart.cs | 21 ++ .../Mapping/IndexMappingBase.cs | 59 ++++++ .../Mapping/MapCollectionPart.cs | 190 ++++++++++++++++++ 13 files changed, 438 insertions(+), 1 deletion(-) create mode 100644 src/FluentNHibernate/Conventions/Defaults/HasMapCollectionNameConvention.cs create mode 100644 src/FluentNHibernate/Conventions/Defaults/HasMapIndexNameConvention.cs create mode 100644 src/FluentNHibernate/Conventions/Defaults/HasMapTableNameConvention.cs create mode 100644 src/FluentNHibernate/Conventions/Discovery/HasMapDiscoveryConvention.cs create mode 100644 src/FluentNHibernate/Conventions/IMapConvention.cs create mode 100644 src/FluentNHibernate/Mapping/IIndexMapping.cs create mode 100644 src/FluentNHibernate/Mapping/IMapCollectionPart.cs create mode 100644 src/FluentNHibernate/Mapping/IndexMappingBase.cs create mode 100644 src/FluentNHibernate/Mapping/MapCollectionPart.cs diff --git a/src/FluentNHibernate/Conventions/Defaults/HasMapCollectionNameConvention.cs b/src/FluentNHibernate/Conventions/Defaults/HasMapCollectionNameConvention.cs new file mode 100644 index 000000000..7184071cb --- /dev/null +++ b/src/FluentNHibernate/Conventions/Defaults/HasMapCollectionNameConvention.cs @@ -0,0 +1,17 @@ +using FluentNHibernate.Mapping; + +namespace FluentNHibernate.Conventions.Defaults +{ + public class HasMapCollectionNameConvention : IMapConvention + { + public bool Accept(IMapCollectionPart target) + { + return string.IsNullOrEmpty(target.CollectionColumnName); + } + + public void Apply(IMapCollectionPart target) + { + target.WithCollectionColumn(target.ValueType.Name + "_id"); + } + } +} diff --git a/src/FluentNHibernate/Conventions/Defaults/HasMapIndexNameConvention.cs b/src/FluentNHibernate/Conventions/Defaults/HasMapIndexNameConvention.cs new file mode 100644 index 000000000..0479c331b --- /dev/null +++ b/src/FluentNHibernate/Conventions/Defaults/HasMapIndexNameConvention.cs @@ -0,0 +1,17 @@ +using FluentNHibernate.Mapping; + +namespace FluentNHibernate.Conventions.Defaults +{ + public class HasMapIndexNameConvention : IMapConvention + { + public bool Accept(IMapCollectionPart target) + { + return string.IsNullOrEmpty(target.IndexColumnName); + } + + public void Apply(IMapCollectionPart target) + { + target.WithIndexColumn(target.IndexType.Name + "_id"); + } + } +} diff --git a/src/FluentNHibernate/Conventions/Defaults/HasMapTableNameConvention.cs b/src/FluentNHibernate/Conventions/Defaults/HasMapTableNameConvention.cs new file mode 100644 index 000000000..db3d70ba5 --- /dev/null +++ b/src/FluentNHibernate/Conventions/Defaults/HasMapTableNameConvention.cs @@ -0,0 +1,17 @@ +using FluentNHibernate.Mapping; + +namespace FluentNHibernate.Conventions.Defaults +{ + class HasMapTableNameConvention : IMapConvention + { + public bool Accept(IMapCollectionPart target) + { + return string.IsNullOrEmpty(target.TableName); + } + + public void Apply(IMapCollectionPart target) + { + target.WithTableName(target.EntityType.Name + "To" + target.ValueType.Name); + } + } +} diff --git a/src/FluentNHibernate/Conventions/Discovery/HasMapDiscoveryConvention.cs b/src/FluentNHibernate/Conventions/Discovery/HasMapDiscoveryConvention.cs new file mode 100644 index 000000000..c337f50a3 --- /dev/null +++ b/src/FluentNHibernate/Conventions/Discovery/HasMapDiscoveryConvention.cs @@ -0,0 +1,35 @@ +using FluentNHibernate.Mapping; + +namespace FluentNHibernate.Conventions.Discovery +{ + /// + /// Discovers any implementations and applies them to + /// an instance. + /// + public class HasMapDiscoveryConvention : IMappingPartConvention + { + private readonly IConventionFinder conventionFinder; + + public HasMapDiscoveryConvention(IConventionFinder conventionFinder) + { + this.conventionFinder = conventionFinder; + } + + public bool Accept(IMappingPart target) + { + return (target is IMapCollectionPart); + } + + public void Apply(IMappingPart target) + { + var conventions = conventionFinder.Find(); + var join = (IMapCollectionPart)target; + + foreach (var convention in conventions) + { + if (convention.Accept(join)) + convention.Apply(join); + } + } + } +} \ No newline at end of file diff --git a/src/FluentNHibernate/Conventions/ForeignKeyConvention.cs b/src/FluentNHibernate/Conventions/ForeignKeyConvention.cs index 198dbf71a..850d869ac 100644 --- a/src/FluentNHibernate/Conventions/ForeignKeyConvention.cs +++ b/src/FluentNHibernate/Conventions/ForeignKeyConvention.cs @@ -4,7 +4,7 @@ namespace FluentNHibernate.Conventions { - public abstract class ForeignKeyConvention : IReferenceConvention, IHasManyConvention, IHasManyToManyConvention + public abstract class ForeignKeyConvention : IReferenceConvention, IHasManyConvention, IHasManyToManyConvention, IMapConvention { private bool acceptParent = true; private bool acceptChild = true; @@ -48,5 +48,16 @@ public void Apply(IManyToManyPart target) if (acceptChild && target.ChildType != null) target.WithChildKeyColumn(GetKeyName(null, target.ChildType)); } + + public bool Accept(IMapCollectionPart target) + { + return target.KeyColumnNames.List().Count == 0; + } + + public void Apply(IMapCollectionPart target) + { + target.KeyColumnNames.Clear(); + target.KeyColumnNames.Add(GetKeyName(null, target.EntityType)); + } } } \ No newline at end of file diff --git a/src/FluentNHibernate/Conventions/IMapConvention.cs b/src/FluentNHibernate/Conventions/IMapConvention.cs new file mode 100644 index 000000000..3de93cf26 --- /dev/null +++ b/src/FluentNHibernate/Conventions/IMapConvention.cs @@ -0,0 +1,8 @@ +using FluentNHibernate.Mapping; + +namespace FluentNHibernate.Conventions +{ + public interface IMapConvention : IConvention + { + } +} diff --git a/src/FluentNHibernate/FluentNHibernate.csproj b/src/FluentNHibernate/FluentNHibernate.csproj index 6d87cbdda..9e3bd11e7 100644 --- a/src/FluentNHibernate/FluentNHibernate.csproj +++ b/src/FluentNHibernate/FluentNHibernate.csproj @@ -110,6 +110,9 @@ + + + @@ -120,6 +123,7 @@ + @@ -176,6 +180,7 @@ + @@ -269,12 +274,16 @@ + + + + diff --git a/src/FluentNHibernate/Mapping/ClasslikeMapBase.cs b/src/FluentNHibernate/Mapping/ClasslikeMapBase.cs index 9c4a15556..d2b7ccb10 100644 --- a/src/FluentNHibernate/Mapping/ClasslikeMapBase.cs +++ b/src/FluentNHibernate/Mapping/ClasslikeMapBase.cs @@ -268,6 +268,36 @@ public ManyToManyPart HasManyToMany(Expression> return MapHasManyToMany(expression); } + public MapCollectionPart HasMap(Expression>> expression) + { + return MapHasMap>(expression); + } + + private MapCollectionPart MapHasMap(Expression> expression) + { + return ReflectionHelper.IsMethodExpression(expression) + ? HasMap(ReflectionHelper.GetMethod(expression)) + : HasMap(ReflectionHelper.GetProperty(expression)); + } + + public MapCollectionPart HasMap(MethodInfo method) + { + var part = new MapCollectionPart(EntityType, method); + + AddPart(part); + + return part; + } + + public MapCollectionPart HasMap(PropertyInfo property) + { + var part = new MapCollectionPart(EntityType, property); + + AddPart(part); + + return part; + } + public VersionPart Version(Expression> expression) { return Version(ReflectionHelper.GetProperty(expression)); @@ -371,6 +401,11 @@ IProperty IClasslike.Map(Expression> expression) return HasManyToMany(ReflectionHelper.GetProperty(expression)); } + IMapCollectionPart IClasslike.HasMap(Expression>> expression) + { + return HasMap(ReflectionHelper.GetProperty(expression)); + } + #endregion } } diff --git a/src/FluentNHibernate/Mapping/IClasslike.cs b/src/FluentNHibernate/Mapping/IClasslike.cs index e78254b66..a3cc85689 100644 --- a/src/FluentNHibernate/Mapping/IClasslike.cs +++ b/src/FluentNHibernate/Mapping/IClasslike.cs @@ -58,6 +58,8 @@ public interface IClasslike /// many-to-many part IManyToManyPart HasManyToMany(Expression>> expression); + IMapCollectionPart HasMap(Expression>> expression); + IVersion Version(Expression> expression); } } \ No newline at end of file diff --git a/src/FluentNHibernate/Mapping/IIndexMapping.cs b/src/FluentNHibernate/Mapping/IIndexMapping.cs new file mode 100644 index 000000000..004fe3686 --- /dev/null +++ b/src/FluentNHibernate/Mapping/IIndexMapping.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml; + +namespace FluentNHibernate.Mapping +{ + public interface IIndexMapping + { + IIndexMapping WithColumn(string columnName); + IIndexMapping WithType(); + IIndexMapping WithType(Type type); + void WriteIndexElement(XmlElement element); + } +} diff --git a/src/FluentNHibernate/Mapping/IMapCollectionPart.cs b/src/FluentNHibernate/Mapping/IMapCollectionPart.cs new file mode 100644 index 000000000..a87c6e066 --- /dev/null +++ b/src/FluentNHibernate/Mapping/IMapCollectionPart.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace FluentNHibernate.Mapping +{ + public interface IMapCollectionPart : IMappingPart + { + Type EntityType { get; } + Type IndexType { get; } + Type ValueType { get; } + string TableName { get; } + string IndexColumnName { get; } + string CollectionColumnName { get; } + IMapCollectionPart WithTableName(string name); + IMapCollectionPart WithIndexColumn(string name); + IMapCollectionPart WithCollectionColumn(string name); + IColumnNameCollection KeyColumnNames { get; } + } +} diff --git a/src/FluentNHibernate/Mapping/IndexMappingBase.cs b/src/FluentNHibernate/Mapping/IndexMappingBase.cs new file mode 100644 index 000000000..25badf39b --- /dev/null +++ b/src/FluentNHibernate/Mapping/IndexMappingBase.cs @@ -0,0 +1,59 @@ +using System; +using System.Xml; +using FluentNHibernate.Utils; + +namespace FluentNHibernate.Mapping +{ + public class IndexMappingBase : IIndexMapping + { + protected Cache properties = new Cache(); + protected string indexElementName; + protected string indexType; + + public IndexMappingBase() + : this("type", "index") + { + + } + + public IndexMappingBase(string indexType, string indexElementName) + { + this.indexType = indexType; + this.indexElementName = indexElementName; + } + + #region IIndexMapping Members + + public IIndexMapping WithColumn(string columnName) + { + properties.Store("column", columnName); + return this; + } + + public IIndexMapping WithType() + { + return WithType(typeof(TIndex)); + } + + public IIndexMapping WithType(Type type) + { + properties.Store(indexType, (indexType == "type") ? type.Name : type.AssemblyQualifiedName); + return this; + } + + public void WriteIndexElement(XmlElement collectionElement) + { + var indexElement = collectionElement.AddElement(indexElementName); + indexElement.WithProperties(properties); + } + + public static IIndexMapping GetIndexMappingTypeFor() + { + return (typeof(TIndex).IsPrimitive || typeof(TIndex) == typeof(string)) + ? new IndexMappingBase() + : new IndexMappingBase("class", "index-many-to-many"); + } + + #endregion + } +} diff --git a/src/FluentNHibernate/Mapping/MapCollectionPart.cs b/src/FluentNHibernate/Mapping/MapCollectionPart.cs new file mode 100644 index 000000000..6009123f5 --- /dev/null +++ b/src/FluentNHibernate/Mapping/MapCollectionPart.cs @@ -0,0 +1,190 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Text; +using System.Reflection; +using System.Xml; +using FluentNHibernate.Utils; + +namespace FluentNHibernate.Mapping +{ + public class MapCollectionPart : IMapCollectionPart + { + private Type entityType; + private MemberInfo member; + private IIndexMapping indexMapping; + private Cache properties = new Cache(); + private ColumnNameCollection> columnNames; + private Cache keyProperties = new Cache(); + private string tableName; + private string indexColumnName; + private string collectionColumnName; + + public MapCollectionPart(Type entityType, MemberInfo member) + { + this.entityType = entityType; + this.member = member; + properties.Store("name", member.Name); + columnNames = new ColumnNameCollection>(this); + } + + public Type EntityType + { + get { return entityType; } + } + + public Type IndexType + { + get { return typeof(TIndex); } + } + public Type ValueType + { + get { return typeof(TValue); } + } + + public MemberInfo Member + { + get { return member; } + } + + public string TableName + { + get { return tableName; } + } + + public string IndexColumnName + { + get { return indexColumnName; } + } + + public string CollectionColumnName + { + get { return collectionColumnName; } + } + + public ColumnNameCollection> KeyColumNames + { + get { return columnNames; } + } + + public MapCollectionPart WithTableName(string name) + { + tableName = name; + return this; + } + + public MapCollectionPart WithIndexColumn(string name) + { + indexColumnName = name; + return this; + } + + public MapCollectionPart WithCollectionColumn(string name) + { + collectionColumnName = name; + return this; + } + + IColumnNameCollection IMapCollectionPart.KeyColumnNames + { + get { return KeyColumNames; } + } + + IMapCollectionPart IMapCollectionPart.WithTableName(string name) + { + return WithTableName(name); + } + + IMapCollectionPart IMapCollectionPart.WithIndexColumn(string name) + { + return WithIndexColumn(name); + } + + IMapCollectionPart IMapCollectionPart.WithCollectionColumn(string name) + { + return WithCollectionColumn(name); + } + + private void WriteKeyElement(IMappingVisitor visitor, XmlElement collectionElement) + { + var columns = columnNames.List(); + + if (columns.Count == 1) + keyProperties.Store("column", columns[0]); + + var key = collectionElement.AddElement("key") + .WithProperties(keyProperties); + + if (columns.Count <= 1) return; + + foreach (var columnName in columns) + { + key.AddElement("column") + .WithAtt("name", columnName); + } + } + + #region IMappingPart Members + + public void Write(XmlElement classElement, IMappingVisitor visitor) + { + XmlElement mapElement = classElement.AddElement("map").WithProperties(properties); + + if (!string.IsNullOrEmpty(TableName)) + mapElement.WithAtt("table", TableName); + + WriteKeyElement(visitor, mapElement); + + indexMapping = IndexMappingBase.GetIndexMappingTypeFor(); + if (!string.IsNullOrEmpty(IndexColumnName)) + indexMapping.WithColumn(IndexColumnName); + indexMapping.WithType(typeof(TIndex)); + indexMapping.WriteIndexElement(mapElement); + + var collection = "many-to-many"; + var collectionType = "class"; + var collectionTypeValue = typeof(TValue).AssemblyQualifiedName; + if (typeof(TValue).IsPrimitive || typeof(TValue) == typeof(string)) + { + collection = "element"; + collectionType = "type"; + collectionTypeValue = typeof(TValue).Name; + } + + var collectionElement = mapElement.AddElement(collection); + if (!string.IsNullOrEmpty(CollectionColumnName)) + collectionElement.WithAtt("column", CollectionColumnName); + + collectionElement.WithAtt(collectionType, collectionTypeValue); + } + + public PartPosition PositionOnDocument + { + get { return PartPosition.Anywhere; } + } + + public int LevelWithinPosition + { + get { return 1; } + } + + #endregion + + #region IHasAttributes Members + + public void SetAttribute(string name, string value) + { + properties.Store(name, value); + } + + public void SetAttributes(Attributes atts) + { + foreach (var key in atts.Keys) + { + SetAttribute(key, atts[key]); + } + } + + #endregion + } +}