From e5846708cb805a3ccda7009a8deae6dad46939c6 Mon Sep 17 00:00:00 2001 From: Jeff Hagen Date: Fri, 8 Aug 2025 12:04:37 -0400 Subject: [PATCH 1/8] Add ability to specify idbag (closes #415) --- .../DomainModel/Mapping/ManyToManyTester.cs | 55 ++++++++++++++ .../Automapping/Steps/IdentityStep.cs | 12 +--- .../Mapping/CollectionIdPart.cs | 68 ++++++++++++++++++ .../Mapping/GeneratorBuilder.cs | 13 ++++ src/FluentNHibernate/Mapping/IdentityPart.cs | 10 +-- .../Providers/ICollectionIdMappingProvider.cs | 8 +++ src/FluentNHibernate/Mapping/ToManyBase.cs | 23 ++++++ .../MappingModel/Collections/Collection.cs | 3 +- .../Collections/CollectionIdMapping.cs | 71 +++++++++++++++++++ .../Collections/CollectionMapping.cs | 10 +++ .../Output/XmlCollectionIdWriter.cs | 43 +++++++++++ .../Output/XmlCollectionWriter.cs | 30 +++----- .../MappingModel/Output/XmlIdBagWriter.cs | 39 ++++++++++ .../MappingModel/Output/XmlWriterContainer.cs | 3 + .../Visitors/ConventionVisitor.cs | 5 +- .../Visitors/DefaultMappingModelVisitor.cs | 4 ++ .../Visitors/IMappingModelVisitor.cs | 2 + .../Visitors/NullMappingModelVisitor.cs | 10 +++ 18 files changed, 366 insertions(+), 43 deletions(-) create mode 100644 src/FluentNHibernate/Mapping/CollectionIdPart.cs create mode 100644 src/FluentNHibernate/Mapping/Providers/ICollectionIdMappingProvider.cs create mode 100644 src/FluentNHibernate/MappingModel/Collections/CollectionIdMapping.cs create mode 100644 src/FluentNHibernate/MappingModel/Output/XmlCollectionIdWriter.cs create mode 100644 src/FluentNHibernate/MappingModel/Output/XmlIdBagWriter.cs diff --git a/src/FluentNHibernate.Testing/DomainModel/Mapping/ManyToManyTester.cs b/src/FluentNHibernate.Testing/DomainModel/Mapping/ManyToManyTester.cs index 9c3711f28..e7a27a17a 100644 --- a/src/FluentNHibernate.Testing/DomainModel/Mapping/ManyToManyTester.cs +++ b/src/FluentNHibernate.Testing/DomainModel/Mapping/ManyToManyTester.cs @@ -1,3 +1,4 @@ +using System; using System.Collections; using System.Collections.Generic; using NUnit.Framework; @@ -333,4 +334,58 @@ public void CanSpecifyMultipleChildKeyColumns() .Element("class/bag/many-to-many/column[@name='ID1']").Exists() .Element("class/bag/many-to-many/column[@name='ID2']").Exists(); } + + [Test] + public void CanSpecifyIdBag() + { + new MappingTester() + .ForMapping(m => m.HasManyToMany(x => x.MapOfChildren) + .AsIdBag(x => x.Column("Id").GeneratedBy.Identity())) + .Element("class/idbag/collection-id").Exists() + .HasAttribute("column", "Id") + .HasAttribute("type", typeof(int).AssemblyQualifiedName) + .Element("class/idbag/collection-id/generator").Exists() + .HasAttribute("class", "identity"); + } + + [Test] + public void CanSpecifyIdBagWithLength() + { + var x = new MappingTester() + .ForMapping(m => m.HasManyToMany(x => x.MapOfChildren) + .AsIdBag(x => x.Column("Id").Length(10))) + .Element("class/idbag/collection-id").Exists() + .HasAttribute("column", "Id") + .HasAttribute("type", typeof(string).AssemblyQualifiedName) + .HasAttribute("length", "10") + .Element("class/idbag/collection-id/generator").Exists() + .HasAttribute("class", "assigned"); + } + + [Test] + public void CanSpecifyIdBagWithNonGenericType() + { + var x = new MappingTester() + .ForMapping(m => m.HasManyToMany(x => x.MapOfChildren) + .AsIdBag(typeof(string), x => x.Column("Id").Length(10))) + .Element("class/idbag/collection-id").Exists() + .HasAttribute("column", "Id") + .HasAttribute("type", typeof(string).AssemblyQualifiedName) + .HasAttribute("length", "10") + .Element("class/idbag/collection-id/generator").Exists() + .HasAttribute("class", "assigned"); + } + + [Test] + public void CanSpecifyIdBagWithGenerator() + { + var x = new MappingTester() + .ForMapping(m => m.HasManyToMany(x => x.MapOfChildren) + .AsIdBag(typeof(int), x => x.GeneratedBy.Identity())) + .Element("class/idbag/collection-id").Exists() + .HasAttribute("column", "Id") + .HasAttribute("type", typeof(int).AssemblyQualifiedName) + .Element("class/idbag/collection-id/generator").Exists() + .HasAttribute("class", "identity"); + } } diff --git a/src/FluentNHibernate/Automapping/Steps/IdentityStep.cs b/src/FluentNHibernate/Automapping/Steps/IdentityStep.cs index e91b16d34..3a6cbea97 100644 --- a/src/FluentNHibernate/Automapping/Steps/IdentityStep.cs +++ b/src/FluentNHibernate/Automapping/Steps/IdentityStep.cs @@ -9,8 +9,6 @@ namespace FluentNHibernate.Automapping.Steps; public class IdentityStep(IAutomappingConfiguration cfg) : IAutomappingStep { - readonly List identityCompatibleTypes = new List { typeof(long), typeof(int), typeof(short), typeof(byte) }; - public bool ShouldMap(Member member) { return cfg.IsId(member); @@ -52,15 +50,7 @@ void SetDefaultAccess(Member member, IdMapping mapping) GeneratorMapping GetDefaultGenerator(Member property) { var generatorMapping = new GeneratorMapping(); - var defaultGenerator = new GeneratorBuilder(generatorMapping, property.PropertyType, Layer.Defaults); - - if (property.PropertyType == typeof(Guid)) - defaultGenerator.GuidComb(); - else if (identityCompatibleTypes.Contains(property.PropertyType)) - defaultGenerator.Identity(); - else - defaultGenerator.Assigned(); - + new GeneratorBuilder(generatorMapping, property.PropertyType, Layer.Defaults).SetDefault(); return generatorMapping; } } diff --git a/src/FluentNHibernate/Mapping/CollectionIdPart.cs b/src/FluentNHibernate/Mapping/CollectionIdPart.cs new file mode 100644 index 000000000..528acc8ef --- /dev/null +++ b/src/FluentNHibernate/Mapping/CollectionIdPart.cs @@ -0,0 +1,68 @@ +using System; +using FluentNHibernate.Mapping.Providers; +using FluentNHibernate.MappingModel; +using FluentNHibernate.MappingModel.Collections; +using FluentNHibernate.MappingModel.Identity; + +namespace FluentNHibernate.Mapping; + +public class CollectionIdPart : ICollectionIdMappingProvider +{ + readonly Type entity; + readonly AttributeStore attributes = new AttributeStore(); + + /// + /// Specify the generator + /// + /// + /// .AsIdBag<int>(x => x.Column("Id").GeneratedBy.Identity()) + /// + public IdentityGenerationStrategyBuilder GeneratedBy { get; } + + public CollectionIdPart(Type entityType, Type idColumnType) + { + attributes.Set("Type", Layer.UserSupplied, new TypeReference(idColumnType)); + entity = entityType; + GeneratedBy = new IdentityGenerationStrategyBuilder(this, idColumnType, entityType); + SetDefaultGenerator(idColumnType); + } + + /// + /// Specifies the id column length + /// + /// Column length + public CollectionIdPart Length(int length) + { + attributes.Set("Length", Layer.UserSupplied, length); + return this; + } + + /// + /// Specifies the column name for the collection id. + /// + /// Column name + public CollectionIdPart Column(string idColumnName) + { + attributes.Set("Column", Layer.UserSupplied, idColumnName); + return this; + } + + void SetDefaultGenerator(Type idColumnType) + { + var generatorMapping = new GeneratorMapping(); + new GeneratorBuilder(generatorMapping, idColumnType, Layer.UserSupplied).SetDefault(); + attributes.Set("Generator", Layer.Defaults, generatorMapping); + } + + CollectionIdMapping ICollectionIdMappingProvider.GetCollectionIdMapping() + { + var mapping = new CollectionIdMapping(attributes.Clone()); + + mapping.ContainingEntityType = entity; + mapping.Set(x => x.Column, Layer.Defaults, "Id"); + if (GeneratedBy.IsDirty) + mapping.Set(x => x.Generator, Layer.UserSupplied, GeneratedBy.GetGeneratorMapping()); + + return mapping; + } +} diff --git a/src/FluentNHibernate/Mapping/GeneratorBuilder.cs b/src/FluentNHibernate/Mapping/GeneratorBuilder.cs index c2cd7bf3a..656d0f27a 100644 --- a/src/FluentNHibernate/Mapping/GeneratorBuilder.cs +++ b/src/FluentNHibernate/Mapping/GeneratorBuilder.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using FluentNHibernate.MappingModel.Identity; using NHibernate.Id; @@ -6,6 +7,8 @@ namespace FluentNHibernate.Mapping; class GeneratorBuilder(GeneratorMapping mapping, Type identityType, int layer) { + readonly HashSet identityCompatibleTypes = new HashSet{ typeof(long), typeof(int), typeof(short), typeof(byte) }; + void SetGenerator(string generator) { mapping.Set(x => x.Class, layer, generator); @@ -31,6 +34,16 @@ void EnsureStringIdentityType() if (identityType != typeof(string)) throw new InvalidOperationException("Identity type must be string"); } + internal void SetDefault() + { + if (identityType == typeof(Guid)) + GuidComb(); + else if (identityCompatibleTypes.Contains(identityType)) + Identity(); + else + Assigned(); + } + static bool IsIntegralType(Type t) { // do we think we'll encounter more? diff --git a/src/FluentNHibernate/Mapping/IdentityPart.cs b/src/FluentNHibernate/Mapping/IdentityPart.cs index 0b5ecc342..117accd26 100644 --- a/src/FluentNHibernate/Mapping/IdentityPart.cs +++ b/src/FluentNHibernate/Mapping/IdentityPart.cs @@ -250,15 +250,7 @@ internal void SetName(string newName) void SetDefaultGenerator() { var generatorMapping = new GeneratorMapping(); - var defaultGenerator = new GeneratorBuilder(generatorMapping, identityType, Layer.UserSupplied); - - if (identityType == typeof(Guid)) - defaultGenerator.GuidComb(); - else if (identityType == typeof(int) || identityType == typeof(long)) - defaultGenerator.Identity(); - else - defaultGenerator.Assigned(); - + new GeneratorBuilder(generatorMapping, identityType, Layer.UserSupplied).SetDefault(); attributes.Set("Generator", Layer.Defaults, generatorMapping); } diff --git a/src/FluentNHibernate/Mapping/Providers/ICollectionIdMappingProvider.cs b/src/FluentNHibernate/Mapping/Providers/ICollectionIdMappingProvider.cs new file mode 100644 index 000000000..397cd0ca5 --- /dev/null +++ b/src/FluentNHibernate/Mapping/Providers/ICollectionIdMappingProvider.cs @@ -0,0 +1,8 @@ +using FluentNHibernate.MappingModel.Collections; + +namespace FluentNHibernate.Mapping.Providers; + +public interface ICollectionIdMappingProvider +{ + CollectionIdMapping GetCollectionIdMapping(); +} diff --git a/src/FluentNHibernate/Mapping/ToManyBase.cs b/src/FluentNHibernate/Mapping/ToManyBase.cs index ba827c149..92590beed 100644 --- a/src/FluentNHibernate/Mapping/ToManyBase.cs +++ b/src/FluentNHibernate/Mapping/ToManyBase.cs @@ -22,6 +22,7 @@ public abstract class ToManyBase : ICollectionMappingProvider protected readonly AttributeStore relationshipAttributes = new AttributeStore(); Func collectionBuilder; IndexMapping indexMapping; + CollectionIdMapping collectionIdMapping; protected Member member; readonly List filters = []; @@ -154,6 +155,26 @@ public T AsBag() return (T)this; } + /// + /// Use an idbag collection + /// + public T AsIdBag(Action customId = null) + { + return AsIdBag(typeof(TIdType), customId); + } + + /// + /// Use an idbag collection + /// + public T AsIdBag(Type idColType, Action customId = null) + { + collectionBuilder = attrs => CollectionMapping.IdBag(attrs); + var builder = new CollectionIdPart(typeof(T), idColType); + customId?.Invoke(builder); + collectionIdMapping = ((ICollectionIdMappingProvider)builder).GetCollectionIdMapping(); + return (T)this; + } + /// /// Use a list collection /// @@ -725,6 +746,8 @@ protected virtual CollectionMapping GetCollectionMapping() // HACK: Index only on list and map - shouldn't have to do this! if (mapping.Collection == Collection.Array || mapping.Collection == Collection.List || mapping.Collection == Collection.Map) mapping.Set(x => x.Index, Layer.Defaults, indexMapping); + else if (mapping.Collection == Collection.IdBag) + mapping.Set(x => x.CollectionId, Layer.Defaults, collectionIdMapping); if (elementPart is not null) { diff --git a/src/FluentNHibernate/MappingModel/Collections/Collection.cs b/src/FluentNHibernate/MappingModel/Collections/Collection.cs index 23d9e4a70..daa88474d 100644 --- a/src/FluentNHibernate/MappingModel/Collections/Collection.cs +++ b/src/FluentNHibernate/MappingModel/Collections/Collection.cs @@ -6,5 +6,6 @@ public enum Collection Bag, Map, List, - Set + Set, + IdBag } diff --git a/src/FluentNHibernate/MappingModel/Collections/CollectionIdMapping.cs b/src/FluentNHibernate/MappingModel/Collections/CollectionIdMapping.cs new file mode 100644 index 000000000..8384777b7 --- /dev/null +++ b/src/FluentNHibernate/MappingModel/Collections/CollectionIdMapping.cs @@ -0,0 +1,71 @@ +using System; +using System.Linq.Expressions; +using FluentNHibernate.MappingModel.Identity; +using FluentNHibernate.Utils; +using FluentNHibernate.Visitors; + +namespace FluentNHibernate.MappingModel.Collections; + +[Serializable] +public class CollectionIdMapping(AttributeStore attributes) : MappingBase, IEquatable +{ + readonly AttributeStore attributes = attributes; + + public override void AcceptVisitor(IMappingModelVisitor visitor) + { + visitor.ProcessCollectionId(this); + if (Generator is not null) + visitor.Visit(Generator); + } + + public GeneratorMapping Generator => attributes.GetOrDefault(); + + public string Column => attributes.GetOrDefault(); + + public int Length => attributes.GetOrDefault(); + + public TypeReference Type => attributes.GetOrDefault(); + + public Type ContainingEntityType { get; set; } + + public bool Equals(CollectionIdMapping other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(other.attributes, attributes) && + other.ContainingEntityType == ContainingEntityType; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != typeof(CollectionIdMapping)) return false; + return Equals((CollectionIdMapping)obj); + } + + public override int GetHashCode() + { + unchecked + { + int result = (attributes is not null ? attributes.GetHashCode() : 0); + result = (result * 397) ^ (ContainingEntityType is not null ? ContainingEntityType.GetHashCode() : 0); + return result; + } + } + + public void Set(Expression> expression, int layer, T value) + { + Set(expression.ToMember().Name, layer, value); + } + + protected override void Set(string attribute, int layer, object value) + { + attributes.Set(attribute, layer, value); + } + + public override bool IsSpecified(string attribute) + { + return attributes.IsSpecified(attribute); + } +} diff --git a/src/FluentNHibernate/MappingModel/Collections/CollectionMapping.cs b/src/FluentNHibernate/MappingModel/Collections/CollectionMapping.cs index 47f703d78..803bceb9f 100644 --- a/src/FluentNHibernate/MappingModel/Collections/CollectionMapping.cs +++ b/src/FluentNHibernate/MappingModel/Collections/CollectionMapping.cs @@ -32,6 +32,9 @@ public override void AcceptVisitor(IMappingModelVisitor visitor) { visitor.ProcessCollection(this); + if (CollectionId is not null && Collection == Collection.IdBag) + visitor.Visit(CollectionId); + if (Key is not null) visitor.Visit(Key); @@ -109,6 +112,8 @@ public override void AcceptVisitor(IMappingModelVisitor visitor) public string Sort => attributes.GetOrDefault(); public IIndexMapping Index => attributes.GetOrDefault(); + + public CollectionIdMapping CollectionId => attributes.GetOrDefault(); public bool Equals(CollectionMapping other) { @@ -174,6 +179,11 @@ public static CollectionMapping Bag(AttributeStore underlyingStore) { return For(Collection.Bag, underlyingStore); } + + public static CollectionMapping IdBag(AttributeStore underlyingStore) + { + return For(Collection.IdBag, underlyingStore); + } public static CollectionMapping List() { diff --git a/src/FluentNHibernate/MappingModel/Output/XmlCollectionIdWriter.cs b/src/FluentNHibernate/MappingModel/Output/XmlCollectionIdWriter.cs new file mode 100644 index 000000000..83187cc0b --- /dev/null +++ b/src/FluentNHibernate/MappingModel/Output/XmlCollectionIdWriter.cs @@ -0,0 +1,43 @@ +using System.Xml; +using FluentNHibernate.MappingModel.Collections; +using FluentNHibernate.MappingModel.Identity; +using FluentNHibernate.Utils; +using FluentNHibernate.Visitors; + +namespace FluentNHibernate.MappingModel.Output; + +public class XmlCollectionIdWriter(IXmlWriterServiceLocator serviceLocator) : NullMappingModelVisitor, IXmlWriter +{ + XmlDocument document; + + public XmlDocument Write(CollectionIdMapping mappingModel) + { + document = null; + mappingModel.AcceptVisitor(this); + return document; + } + + public override void ProcessCollectionId(CollectionIdMapping mapping) + { + document = new XmlDocument(); + + var element = document.AddElement("collection-id"); + + if (mapping.IsSpecified("Column")) + element.WithAtt("column", mapping.Column); + + if (mapping.IsSpecified("Type")) + element.WithAtt("type", mapping.Type); + + if (mapping.IsSpecified("Length")) + element.WithAtt("length", mapping.Length); + } + + public override void Visit(GeneratorMapping mapping) + { + var writer = serviceLocator.GetWriter(); + var generatorXml = writer.Write(mapping); + + document.ImportAndAppendChild(generatorXml); + } +} diff --git a/src/FluentNHibernate/MappingModel/Output/XmlCollectionWriter.cs b/src/FluentNHibernate/MappingModel/Output/XmlCollectionWriter.cs index 7f469f1c9..587f2269b 100644 --- a/src/FluentNHibernate/MappingModel/Output/XmlCollectionWriter.cs +++ b/src/FluentNHibernate/MappingModel/Output/XmlCollectionWriter.cs @@ -22,28 +22,16 @@ public XmlDocument Write(CollectionMapping mappingModel) public override void ProcessCollection(CollectionMapping mapping) { - IXmlWriter writer = null; - - switch (mapping.Collection) + IXmlWriter writer = mapping.Collection switch { - case Collection.Array: - writer = new XmlArrayWriter(serviceLocator); - break; - case Collection.Bag: - writer = new XmlBagWriter(serviceLocator); - break; - case Collection.List: - writer = new XmlListWriter(serviceLocator); - break; - case Collection.Map: - writer = new XmlMapWriter(serviceLocator); - break; - case Collection.Set: - writer = new XmlSetWriter(serviceLocator); - break; - default: - throw new InvalidOperationException("Unrecognised collection type " + mapping.Collection); - } + Collection.Array => new XmlArrayWriter(serviceLocator), + Collection.Bag => new XmlBagWriter(serviceLocator), + Collection.List => new XmlListWriter(serviceLocator), + Collection.Map => new XmlMapWriter(serviceLocator), + Collection.Set => new XmlSetWriter(serviceLocator), + Collection.IdBag => new XmlIdBagWriter(serviceLocator), + _ => throw new InvalidOperationException("Unrecognised collection type " + mapping.Collection) + }; document = writer.Write(mapping); } diff --git a/src/FluentNHibernate/MappingModel/Output/XmlIdBagWriter.cs b/src/FluentNHibernate/MappingModel/Output/XmlIdBagWriter.cs new file mode 100644 index 000000000..60a90fcd2 --- /dev/null +++ b/src/FluentNHibernate/MappingModel/Output/XmlIdBagWriter.cs @@ -0,0 +1,39 @@ +using System.Xml; +using FluentNHibernate.MappingModel.Collections; +using FluentNHibernate.MappingModel.Identity; +using FluentNHibernate.Utils; + +namespace FluentNHibernate.MappingModel.Output; + +public class XmlIdBagWriter(IXmlWriterServiceLocator serviceLocator) : BaseXmlCollectionWriter(serviceLocator), IXmlWriter +{ + readonly IXmlWriterServiceLocator _serviceLocator = serviceLocator; + + public XmlDocument Write(CollectionMapping mappingModel) + { + document = null; + mappingModel.AcceptVisitor(this); + return document; + } + + public override void ProcessCollection(CollectionMapping mapping) + { + document = new XmlDocument(); + var element = document.AddElement("idbag"); + WriteBaseCollectionAttributes(element, mapping); + } + + public override void Visit(CollectionIdMapping mapping) + { + var writer = _serviceLocator.GetWriter(); + var xml = writer.Write(mapping); + document.ImportAndAppendChild(xml); + } + + public override void Visit(GeneratorMapping mapping) + { + var writer = _serviceLocator.GetWriter(); + var generatorXml = writer.Write(mapping); + document.ImportAndAppendChild(generatorXml); + } +} diff --git a/src/FluentNHibernate/MappingModel/Output/XmlWriterContainer.cs b/src/FluentNHibernate/MappingModel/Output/XmlWriterContainer.cs index a931b1624..81ac240a3 100644 --- a/src/FluentNHibernate/MappingModel/Output/XmlWriterContainer.cs +++ b/src/FluentNHibernate/MappingModel/Output/XmlWriterContainer.cs @@ -129,6 +129,9 @@ void RegisterIdWriters() RegisterWriter(c => new XmlKeyManyToOneWriter(c.Resolve())); + + RegisterWriter(c => + new XmlCollectionIdWriter(c.Resolve())); } void RegisterComponentWriters() diff --git a/src/FluentNHibernate/Visitors/ConventionVisitor.cs b/src/FluentNHibernate/Visitors/ConventionVisitor.cs index 70be75c4c..5c244cbe9 100644 --- a/src/FluentNHibernate/Visitors/ConventionVisitor.cs +++ b/src/FluentNHibernate/Visitors/ConventionVisitor.cs @@ -95,7 +95,10 @@ public override void ProcessCollection(CollectionMapping mapping) Apply(conventions, new OneToManyCollectionInstance(mapping)); } - collections[mapping.Collection](mapping); + if (collections.TryGetValue(mapping.Collection, out var processor)) + { + processor(mapping); + } } #pragma warning disable 612,618 diff --git a/src/FluentNHibernate/Visitors/DefaultMappingModelVisitor.cs b/src/FluentNHibernate/Visitors/DefaultMappingModelVisitor.cs index 7ca334469..161b3088b 100644 --- a/src/FluentNHibernate/Visitors/DefaultMappingModelVisitor.cs +++ b/src/FluentNHibernate/Visitors/DefaultMappingModelVisitor.cs @@ -184,4 +184,8 @@ public override void Visit(KeyManyToOneMapping mapping) mapping.AcceptVisitor(this); } + public override void Visit(CollectionIdMapping mapping) + { + mapping.AcceptVisitor(this); + } } diff --git a/src/FluentNHibernate/Visitors/IMappingModelVisitor.cs b/src/FluentNHibernate/Visitors/IMappingModelVisitor.cs index 00490d283..5d455ef98 100644 --- a/src/FluentNHibernate/Visitors/IMappingModelVisitor.cs +++ b/src/FluentNHibernate/Visitors/IMappingModelVisitor.cs @@ -44,6 +44,7 @@ public interface IMappingModelVisitor void ProcessStoredProcedure(StoredProcedureMapping mapping); void ProcessTuplizer(TuplizerMapping mapping); void ProcessCollection(MappingModel.Collections.CollectionMapping mapping); + void ProcessCollectionId(CollectionIdMapping mapping); /// /// This bad boy is the entry point to the visitor @@ -84,4 +85,5 @@ public interface IMappingModelVisitor void Visit(FilterDefinitionMapping mapping); void Visit(StoredProcedureMapping mapping); void Visit(TuplizerMapping mapping); + void Visit(CollectionIdMapping mapping); } diff --git a/src/FluentNHibernate/Visitors/NullMappingModelVisitor.cs b/src/FluentNHibernate/Visitors/NullMappingModelVisitor.cs index b10af2113..d70f09286 100644 --- a/src/FluentNHibernate/Visitors/NullMappingModelVisitor.cs +++ b/src/FluentNHibernate/Visitors/NullMappingModelVisitor.cs @@ -188,6 +188,11 @@ public virtual void ProcessCollection(MappingModel.Collections.CollectionMapping } + public virtual void ProcessCollectionId(CollectionIdMapping mapping) + { + + } + public virtual void Visit(IEnumerable mappings) { @@ -356,4 +361,9 @@ public virtual void Visit(TuplizerMapping mapping) { } + + public virtual void Visit(CollectionIdMapping mapping) + { + + } } From a564b11a653057a56f81ca87242b9626a8c01a6a Mon Sep 17 00:00:00 2001 From: Jeff Hagen Date: Fri, 8 Aug 2025 12:19:20 -0400 Subject: [PATCH 2/8] deep source fixes --- src/FluentNHibernate/Mapping/CollectionIdPart.cs | 12 +++++++----- .../MappingModel/Collections/CollectionMapping.cs | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/FluentNHibernate/Mapping/CollectionIdPart.cs b/src/FluentNHibernate/Mapping/CollectionIdPart.cs index 528acc8ef..877cff05a 100644 --- a/src/FluentNHibernate/Mapping/CollectionIdPart.cs +++ b/src/FluentNHibernate/Mapping/CollectionIdPart.cs @@ -9,7 +9,7 @@ namespace FluentNHibernate.Mapping; public class CollectionIdPart : ICollectionIdMappingProvider { readonly Type entity; - readonly AttributeStore attributes = new AttributeStore(); + readonly AttributeStore attributes = new(); /// /// Specify the generator @@ -17,7 +17,7 @@ public class CollectionIdPart : ICollectionIdMappingProvider /// /// .AsIdBag<int>(x => x.Column("Id").GeneratedBy.Identity()) /// - public IdentityGenerationStrategyBuilder GeneratedBy { get; } + public IdentityGenerationStrategyBuilder GeneratedBy { get; }F public CollectionIdPart(Type entityType, Type idColumnType) { @@ -56,9 +56,11 @@ void SetDefaultGenerator(Type idColumnType) CollectionIdMapping ICollectionIdMappingProvider.GetCollectionIdMapping() { - var mapping = new CollectionIdMapping(attributes.Clone()); - - mapping.ContainingEntityType = entity; + var mapping = new CollectionIdMapping(attributes.Clone()) + { + ContainingEntityType = entity + }; + mapping.Set(x => x.Column, Layer.Defaults, "Id"); if (GeneratedBy.IsDirty) mapping.Set(x => x.Generator, Layer.UserSupplied, GeneratedBy.GetGeneratorMapping()); diff --git a/src/FluentNHibernate/MappingModel/Collections/CollectionMapping.cs b/src/FluentNHibernate/MappingModel/Collections/CollectionMapping.cs index 803bceb9f..f8a90bcf9 100644 --- a/src/FluentNHibernate/MappingModel/Collections/CollectionMapping.cs +++ b/src/FluentNHibernate/MappingModel/Collections/CollectionMapping.cs @@ -7,7 +7,7 @@ namespace FluentNHibernate.MappingModel.Collections; [Serializable] -public class CollectionMapping : MappingBase, IRelationship, IEquatable +public sealed class CollectionMapping : MappingBase, IRelationship, IEquatable { readonly AttributeStore attributes; readonly List filters = []; From 7de06fd49187a380f1e660b64c02f19127724895 Mon Sep 17 00:00:00 2001 From: Jeff Hagen Date: Fri, 8 Aug 2025 12:27:47 -0400 Subject: [PATCH 3/8] cleanup --- .../DomainModel/Mapping/ManyToManyTester.cs | 6 +++--- src/FluentNHibernate/Mapping/CollectionIdPart.cs | 2 +- src/FluentNHibernate/Visitors/ValidationVisitor.cs | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/FluentNHibernate.Testing/DomainModel/Mapping/ManyToManyTester.cs b/src/FluentNHibernate.Testing/DomainModel/Mapping/ManyToManyTester.cs index e7a27a17a..3df85e0d5 100644 --- a/src/FluentNHibernate.Testing/DomainModel/Mapping/ManyToManyTester.cs +++ b/src/FluentNHibernate.Testing/DomainModel/Mapping/ManyToManyTester.cs @@ -351,7 +351,7 @@ public void CanSpecifyIdBag() [Test] public void CanSpecifyIdBagWithLength() { - var x = new MappingTester() + new MappingTester() .ForMapping(m => m.HasManyToMany(x => x.MapOfChildren) .AsIdBag(x => x.Column("Id").Length(10))) .Element("class/idbag/collection-id").Exists() @@ -365,7 +365,7 @@ public void CanSpecifyIdBagWithLength() [Test] public void CanSpecifyIdBagWithNonGenericType() { - var x = new MappingTester() + new MappingTester() .ForMapping(m => m.HasManyToMany(x => x.MapOfChildren) .AsIdBag(typeof(string), x => x.Column("Id").Length(10))) .Element("class/idbag/collection-id").Exists() @@ -379,7 +379,7 @@ public void CanSpecifyIdBagWithNonGenericType() [Test] public void CanSpecifyIdBagWithGenerator() { - var x = new MappingTester() + new MappingTester() .ForMapping(m => m.HasManyToMany(x => x.MapOfChildren) .AsIdBag(typeof(int), x => x.GeneratedBy.Identity())) .Element("class/idbag/collection-id").Exists() diff --git a/src/FluentNHibernate/Mapping/CollectionIdPart.cs b/src/FluentNHibernate/Mapping/CollectionIdPart.cs index 877cff05a..655b90f3c 100644 --- a/src/FluentNHibernate/Mapping/CollectionIdPart.cs +++ b/src/FluentNHibernate/Mapping/CollectionIdPart.cs @@ -17,7 +17,7 @@ public class CollectionIdPart : ICollectionIdMappingProvider /// /// .AsIdBag<int>(x => x.Column("Id").GeneratedBy.Identity()) /// - public IdentityGenerationStrategyBuilder GeneratedBy { get; }F + public IdentityGenerationStrategyBuilder GeneratedBy { get; } public CollectionIdPart(Type entityType, Type idColumnType) { diff --git a/src/FluentNHibernate/Visitors/ValidationVisitor.cs b/src/FluentNHibernate/Visitors/ValidationVisitor.cs index a104c2c40..e7c83468d 100644 --- a/src/FluentNHibernate/Visitors/ValidationVisitor.cs +++ b/src/FluentNHibernate/Visitors/ValidationVisitor.cs @@ -33,6 +33,8 @@ public override void ProcessCollection(CollectionMapping mapping) "Remove Inverse from one side of the relationship", mapping.ContainingEntityType); } + + //if (mapping.Collection == Collection.IdBag && mapping.Relationship.) } /// From d90575320d4a88ca34116c11166445d77c4c99f7 Mon Sep 17 00:00:00 2001 From: Jeff Hagen Date: Fri, 8 Aug 2025 12:30:25 -0400 Subject: [PATCH 4/8] oops --- src/FluentNHibernate/Visitors/ValidationVisitor.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/FluentNHibernate/Visitors/ValidationVisitor.cs b/src/FluentNHibernate/Visitors/ValidationVisitor.cs index e7c83468d..a104c2c40 100644 --- a/src/FluentNHibernate/Visitors/ValidationVisitor.cs +++ b/src/FluentNHibernate/Visitors/ValidationVisitor.cs @@ -33,8 +33,6 @@ public override void ProcessCollection(CollectionMapping mapping) "Remove Inverse from one side of the relationship", mapping.ContainingEntityType); } - - //if (mapping.Collection == Collection.IdBag && mapping.Relationship.) } /// From 28acdc8fa69962029d23da739a98ae22e022943f Mon Sep 17 00:00:00 2001 From: Jeff Hagen Date: Fri, 8 Aug 2025 12:38:50 -0400 Subject: [PATCH 5/8] make deepsource happy --- .../MappingModel/Collections/CollectionIdMapping.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FluentNHibernate/MappingModel/Collections/CollectionIdMapping.cs b/src/FluentNHibernate/MappingModel/Collections/CollectionIdMapping.cs index 8384777b7..3494e1304 100644 --- a/src/FluentNHibernate/MappingModel/Collections/CollectionIdMapping.cs +++ b/src/FluentNHibernate/MappingModel/Collections/CollectionIdMapping.cs @@ -7,7 +7,7 @@ namespace FluentNHibernate.MappingModel.Collections; [Serializable] -public class CollectionIdMapping(AttributeStore attributes) : MappingBase, IEquatable +public sealed class CollectionIdMapping(AttributeStore attributes) : MappingBase, IEquatable { readonly AttributeStore attributes = attributes; From 90b7273228ab6608bdb4afb5a5a4388167fcbeaf Mon Sep 17 00:00:00 2001 From: Jeff Hagen Date: Wed, 20 Aug 2025 10:35:16 -0400 Subject: [PATCH 6/8] Refactor servicLocator to protected variable --- .../MappingModel/Output/CollectionAttributeWriter.cs | 1 + src/FluentNHibernate/MappingModel/Output/XmlArrayWriter.cs | 2 -- src/FluentNHibernate/MappingModel/Output/XmlIdBagWriter.cs | 6 ++---- src/FluentNHibernate/MappingModel/Output/XmlListWriter.cs | 2 -- src/FluentNHibernate/MappingModel/Output/XmlMapWriter.cs | 2 -- 5 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/FluentNHibernate/MappingModel/Output/CollectionAttributeWriter.cs b/src/FluentNHibernate/MappingModel/Output/CollectionAttributeWriter.cs index 1fc3cd7aa..ec6694a2e 100644 --- a/src/FluentNHibernate/MappingModel/Output/CollectionAttributeWriter.cs +++ b/src/FluentNHibernate/MappingModel/Output/CollectionAttributeWriter.cs @@ -7,6 +7,7 @@ namespace FluentNHibernate.MappingModel.Output; public abstract class BaseXmlCollectionWriter(IXmlWriterServiceLocator serviceLocator) : NullMappingModelVisitor { + protected readonly IXmlWriterServiceLocator serviceLocator = serviceLocator; protected XmlDocument document; public override void Visit(KeyMapping mapping) diff --git a/src/FluentNHibernate/MappingModel/Output/XmlArrayWriter.cs b/src/FluentNHibernate/MappingModel/Output/XmlArrayWriter.cs index 2647c3f3a..23ddec382 100644 --- a/src/FluentNHibernate/MappingModel/Output/XmlArrayWriter.cs +++ b/src/FluentNHibernate/MappingModel/Output/XmlArrayWriter.cs @@ -7,8 +7,6 @@ namespace FluentNHibernate.MappingModel.Output; public class XmlArrayWriter(IXmlWriterServiceLocator serviceLocator) : BaseXmlCollectionWriter(serviceLocator), IXmlWriter { - readonly IXmlWriterServiceLocator serviceLocator = serviceLocator; - public XmlDocument Write(CollectionMapping mappingModel) { document = null; diff --git a/src/FluentNHibernate/MappingModel/Output/XmlIdBagWriter.cs b/src/FluentNHibernate/MappingModel/Output/XmlIdBagWriter.cs index 60a90fcd2..241a600ea 100644 --- a/src/FluentNHibernate/MappingModel/Output/XmlIdBagWriter.cs +++ b/src/FluentNHibernate/MappingModel/Output/XmlIdBagWriter.cs @@ -7,8 +7,6 @@ namespace FluentNHibernate.MappingModel.Output; public class XmlIdBagWriter(IXmlWriterServiceLocator serviceLocator) : BaseXmlCollectionWriter(serviceLocator), IXmlWriter { - readonly IXmlWriterServiceLocator _serviceLocator = serviceLocator; - public XmlDocument Write(CollectionMapping mappingModel) { document = null; @@ -25,14 +23,14 @@ public override void ProcessCollection(CollectionMapping mapping) public override void Visit(CollectionIdMapping mapping) { - var writer = _serviceLocator.GetWriter(); + var writer = serviceLocator.GetWriter(); var xml = writer.Write(mapping); document.ImportAndAppendChild(xml); } public override void Visit(GeneratorMapping mapping) { - var writer = _serviceLocator.GetWriter(); + var writer = serviceLocator.GetWriter(); var generatorXml = writer.Write(mapping); document.ImportAndAppendChild(generatorXml); } diff --git a/src/FluentNHibernate/MappingModel/Output/XmlListWriter.cs b/src/FluentNHibernate/MappingModel/Output/XmlListWriter.cs index 014867698..04e47d72f 100644 --- a/src/FluentNHibernate/MappingModel/Output/XmlListWriter.cs +++ b/src/FluentNHibernate/MappingModel/Output/XmlListWriter.cs @@ -7,8 +7,6 @@ namespace FluentNHibernate.MappingModel.Output; public class XmlListWriter(IXmlWriterServiceLocator serviceLocator) : BaseXmlCollectionWriter(serviceLocator), IXmlWriter { - readonly IXmlWriterServiceLocator serviceLocator = serviceLocator; - public XmlDocument Write(CollectionMapping mappingModel) { document = null; diff --git a/src/FluentNHibernate/MappingModel/Output/XmlMapWriter.cs b/src/FluentNHibernate/MappingModel/Output/XmlMapWriter.cs index 0c1c71eee..f07a406a1 100644 --- a/src/FluentNHibernate/MappingModel/Output/XmlMapWriter.cs +++ b/src/FluentNHibernate/MappingModel/Output/XmlMapWriter.cs @@ -7,8 +7,6 @@ namespace FluentNHibernate.MappingModel.Output; public class XmlMapWriter(IXmlWriterServiceLocator serviceLocator) : BaseXmlCollectionWriter(serviceLocator), IXmlWriter { - readonly IXmlWriterServiceLocator serviceLocator = serviceLocator; - public XmlDocument Write(CollectionMapping mappingModel) { document = null; From 845804feb1b6842ada2cd77a3faf7a38fc120fb8 Mon Sep 17 00:00:00 2001 From: Jeff Hagen Date: Thu, 21 Aug 2025 09:45:13 -0400 Subject: [PATCH 7/8] PR Fixes --- .../DomainModel/Mapping/ManyToManyTester.cs | 11 +++++++ .../Mapping/ManyToManyPart.cs | 31 +++++++++++++++++++ src/FluentNHibernate/Mapping/ToManyBase.cs | 27 ++-------------- .../Collections/CollectionIdMapping.cs | 2 +- .../Collections/CollectionMapping.cs | 2 +- 5 files changed, 46 insertions(+), 27 deletions(-) diff --git a/src/FluentNHibernate.Testing/DomainModel/Mapping/ManyToManyTester.cs b/src/FluentNHibernate.Testing/DomainModel/Mapping/ManyToManyTester.cs index 3df85e0d5..b185f21d7 100644 --- a/src/FluentNHibernate.Testing/DomainModel/Mapping/ManyToManyTester.cs +++ b/src/FluentNHibernate.Testing/DomainModel/Mapping/ManyToManyTester.cs @@ -388,4 +388,15 @@ public void CanSpecifyIdBagWithGenerator() .Element("class/idbag/collection-id/generator").Exists() .HasAttribute("class", "identity"); } + + [Test] + public void CanSpecifyIdBagWithDefaultIdType() + { + new MappingTester() + .ForMapping(m => m.HasManyToMany(x => x.MapOfChildren) + .AsIdBag(x => x.Column("Id"))) + .Element("class/idbag/collection-id").Exists() + .HasAttribute("column", "Id") + .HasAttribute("type", typeof(int).AssemblyQualifiedName); + } } diff --git a/src/FluentNHibernate/Mapping/ManyToManyPart.cs b/src/FluentNHibernate/Mapping/ManyToManyPart.cs index 1defbe8ce..b7c82e92a 100644 --- a/src/FluentNHibernate/Mapping/ManyToManyPart.cs +++ b/src/FluentNHibernate/Mapping/ManyToManyPart.cs @@ -17,6 +17,7 @@ public class ManyToManyPart : ToManyBase, TChild> readonly Type childType; Type valueType; bool isTernary; + CollectionIdMapping collectionIdMapping; public ManyToManyPart(Type entity, Member property) : this(entity, property, property.PropertyType) @@ -338,11 +339,41 @@ public ManyToManyPart ChildWhere(Expression> where) { return ChildWhere(ExpressionToSql.Convert(@where)); } + + /// + /// Use an idbag collection + /// + public ManyToManyPart AsIdBag(Action customId = null) + { + return AsIdBag(typeof(TIdType), customId); + } + + /// + /// Use an idbag collection with an int id type + /// + public ManyToManyPart AsIdBag(Action customId = null) + { + return AsIdBag(typeof(Int32), customId); + } + + /// + /// Use an idbag collection + /// + public ManyToManyPart AsIdBag(Type idColType, Action customId = null) + { + collectionBuilder = CollectionMapping.IdBag; + var builder = new CollectionIdPart(typeof(ManyToManyPart), idColType); + customId?.Invoke(builder); + collectionIdMapping = ((ICollectionIdMappingProvider)builder).GetCollectionIdMapping(); + return this; + } protected override CollectionMapping GetCollectionMapping() { var collection = base.GetCollectionMapping(); + collection.Set(x => x.CollectionId, Layer.Defaults, collectionIdMapping); + // key columns if (ParentKeyColumns.Count == 0) collection.Key.AddColumn(Layer.Defaults, new ColumnMapping(EntityType.Name + "_id")); diff --git a/src/FluentNHibernate/Mapping/ToManyBase.cs b/src/FluentNHibernate/Mapping/ToManyBase.cs index 92590beed..b20883ef1 100644 --- a/src/FluentNHibernate/Mapping/ToManyBase.cs +++ b/src/FluentNHibernate/Mapping/ToManyBase.cs @@ -16,13 +16,12 @@ public abstract class ToManyBase : ICollectionMappingProvider protected ElementPart elementPart; protected ICompositeElementMappingProvider componentMapping; protected bool nextBool = true; + protected Func collectionBuilder; protected readonly AttributeStore collectionAttributes = new AttributeStore(); protected readonly KeyMapping keyMapping = new KeyMapping(); protected readonly AttributeStore relationshipAttributes = new AttributeStore(); - Func collectionBuilder; IndexMapping indexMapping; - CollectionIdMapping collectionIdMapping; protected Member member; readonly List filters = []; @@ -155,26 +154,6 @@ public T AsBag() return (T)this; } - /// - /// Use an idbag collection - /// - public T AsIdBag(Action customId = null) - { - return AsIdBag(typeof(TIdType), customId); - } - - /// - /// Use an idbag collection - /// - public T AsIdBag(Type idColType, Action customId = null) - { - collectionBuilder = attrs => CollectionMapping.IdBag(attrs); - var builder = new CollectionIdPart(typeof(T), idColType); - customId?.Invoke(builder); - collectionIdMapping = ((ICollectionIdMappingProvider)builder).GetCollectionIdMapping(); - return (T)this; - } - /// /// Use a list collection /// @@ -746,9 +725,7 @@ protected virtual CollectionMapping GetCollectionMapping() // HACK: Index only on list and map - shouldn't have to do this! if (mapping.Collection == Collection.Array || mapping.Collection == Collection.List || mapping.Collection == Collection.Map) mapping.Set(x => x.Index, Layer.Defaults, indexMapping); - else if (mapping.Collection == Collection.IdBag) - mapping.Set(x => x.CollectionId, Layer.Defaults, collectionIdMapping); - + if (elementPart is not null) { mapping.Set(x => x.Element, Layer.Defaults, ((IElementMappingProvider)elementPart).GetElementMapping()); diff --git a/src/FluentNHibernate/MappingModel/Collections/CollectionIdMapping.cs b/src/FluentNHibernate/MappingModel/Collections/CollectionIdMapping.cs index 3494e1304..8384777b7 100644 --- a/src/FluentNHibernate/MappingModel/Collections/CollectionIdMapping.cs +++ b/src/FluentNHibernate/MappingModel/Collections/CollectionIdMapping.cs @@ -7,7 +7,7 @@ namespace FluentNHibernate.MappingModel.Collections; [Serializable] -public sealed class CollectionIdMapping(AttributeStore attributes) : MappingBase, IEquatable +public class CollectionIdMapping(AttributeStore attributes) : MappingBase, IEquatable { readonly AttributeStore attributes = attributes; diff --git a/src/FluentNHibernate/MappingModel/Collections/CollectionMapping.cs b/src/FluentNHibernate/MappingModel/Collections/CollectionMapping.cs index f8a90bcf9..803bceb9f 100644 --- a/src/FluentNHibernate/MappingModel/Collections/CollectionMapping.cs +++ b/src/FluentNHibernate/MappingModel/Collections/CollectionMapping.cs @@ -7,7 +7,7 @@ namespace FluentNHibernate.MappingModel.Collections; [Serializable] -public sealed class CollectionMapping : MappingBase, IRelationship, IEquatable +public class CollectionMapping : MappingBase, IRelationship, IEquatable { readonly AttributeStore attributes; readonly List filters = []; From f4a6c767ff866e74a1e2744a53bb51e009b00735 Mon Sep 17 00:00:00 2001 From: Jeff Hagen Date: Fri, 22 Aug 2025 09:36:52 -0400 Subject: [PATCH 8/8] Make tests better --- .../DomainModel/Mapping/ManyToManyTester.cs | 67 ++++++++++++------- 1 file changed, 43 insertions(+), 24 deletions(-) diff --git a/src/FluentNHibernate.Testing/DomainModel/Mapping/ManyToManyTester.cs b/src/FluentNHibernate.Testing/DomainModel/Mapping/ManyToManyTester.cs index b185f21d7..53021cc03 100644 --- a/src/FluentNHibernate.Testing/DomainModel/Mapping/ManyToManyTester.cs +++ b/src/FluentNHibernate.Testing/DomainModel/Mapping/ManyToManyTester.cs @@ -339,22 +339,21 @@ public void CanSpecifyMultipleChildKeyColumns() public void CanSpecifyIdBag() { new MappingTester() - .ForMapping(m => m.HasManyToMany(x => x.MapOfChildren) - .AsIdBag(x => x.Column("Id").GeneratedBy.Identity())) - .Element("class/idbag/collection-id").Exists() + .ForMapping(m => m.HasManyToMany(x => x.BagOfChildren) + .AsIdBag(x => x.Column("Id"))) + .Element("class/idbag/collection-id").Exists().ShouldBeInParentAtPosition(0) .HasAttribute("column", "Id") .HasAttribute("type", typeof(int).AssemblyQualifiedName) - .Element("class/idbag/collection-id/generator").Exists() - .HasAttribute("class", "identity"); + .Element("class/idbag/many-to-many").Exists(); } [Test] public void CanSpecifyIdBagWithLength() { new MappingTester() - .ForMapping(m => m.HasManyToMany(x => x.MapOfChildren) + .ForMapping(m => m.HasManyToMany(x => x.BagOfChildren) .AsIdBag(x => x.Column("Id").Length(10))) - .Element("class/idbag/collection-id").Exists() + .Element("class/idbag/collection-id").Exists().ShouldBeInParentAtPosition(0) .HasAttribute("column", "Id") .HasAttribute("type", typeof(string).AssemblyQualifiedName) .HasAttribute("length", "10") @@ -366,25 +365,19 @@ public void CanSpecifyIdBagWithLength() public void CanSpecifyIdBagWithNonGenericType() { new MappingTester() - .ForMapping(m => m.HasManyToMany(x => x.MapOfChildren) - .AsIdBag(typeof(string), x => x.Column("Id").Length(10))) - .Element("class/idbag/collection-id").Exists() - .HasAttribute("column", "Id") - .HasAttribute("type", typeof(string).AssemblyQualifiedName) - .HasAttribute("length", "10") - .Element("class/idbag/collection-id/generator").Exists() - .HasAttribute("class", "assigned"); + .ForMapping(m => m.HasManyToMany(x => x.BagOfChildren) + .AsIdBag(typeof(string))) + .Element("class/idbag/collection-id").Exists().ShouldBeInParentAtPosition(0) + .HasAttribute("type", typeof(string).AssemblyQualifiedName); } [Test] public void CanSpecifyIdBagWithGenerator() { new MappingTester() - .ForMapping(m => m.HasManyToMany(x => x.MapOfChildren) + .ForMapping(m => m.HasManyToMany(x => x.BagOfChildren) .AsIdBag(typeof(int), x => x.GeneratedBy.Identity())) - .Element("class/idbag/collection-id").Exists() - .HasAttribute("column", "Id") - .HasAttribute("type", typeof(int).AssemblyQualifiedName) + .Element("class/idbag/collection-id").Exists().ShouldBeInParentAtPosition(0) .Element("class/idbag/collection-id/generator").Exists() .HasAttribute("class", "identity"); } @@ -393,10 +386,36 @@ public void CanSpecifyIdBagWithGenerator() public void CanSpecifyIdBagWithDefaultIdType() { new MappingTester() - .ForMapping(m => m.HasManyToMany(x => x.MapOfChildren) - .AsIdBag(x => x.Column("Id"))) - .Element("class/idbag/collection-id").Exists() - .HasAttribute("column", "Id") - .HasAttribute("type", typeof(int).AssemblyQualifiedName); + .ForMapping(m => m.HasManyToMany(x => x.BagOfChildren) + .AsIdBag()) + .Element("class/idbag/collection-id").Exists().ShouldBeInParentAtPosition(0) + .HasAttribute("type", typeof(int).AssemblyQualifiedName); + } + + [Test] + public void CanSpecifyIdBagWithCompositeElement() + { + new MappingTester() + .ForMapping(m => m.HasManyToMany(x => x.BagOfChildren) + .Component(c => + { + c.Map(x => x.Name); + c.Map(x => x.Active); + }) + .AsIdBag()) + .Element("class/idbag/collection-id").Exists().ShouldBeInParentAtPosition(0) + .Element("class/idbag/composite-element").Exists() + .Element("class/idbag/composite-element/property[@name='Name']").Exists(); + } + + [Test] + public void CanSpecifyIdBagWithElement() + { + var x = new MappingTester() + .ForMapping(m => m.HasManyToMany(x => x.ListOfSimpleChildren) + .Element("Name") + .AsIdBag()); + x.Element("class/idbag/collection-id").Exists().ShouldBeInParentAtPosition(0) + .Element("class/idbag/element").Exists(); } }