diff --git a/src/FluentNHibernate.Testing/DomainModel/Mapping/ComponentPartTester.cs b/src/FluentNHibernate.Testing/DomainModel/Mapping/ComponentPartTester.cs index 14567a1d2..4354d4f06 100644 --- a/src/FluentNHibernate.Testing/DomainModel/Mapping/ComponentPartTester.cs +++ b/src/FluentNHibernate.Testing/DomainModel/Mapping/ComponentPartTester.cs @@ -1,4 +1,6 @@ -using NUnit.Framework; +using System; +using FluentNHibernate.MappingModel; +using NUnit.Framework; namespace FluentNHibernate.Testing.DomainModel.Mapping; @@ -103,6 +105,34 @@ public void ComponentSetsClass() .ForMapping(m => m.Component(x => x.Component, c => c.Map(x => x.Name))) .Element("class/component").HasAttribute("class", typeof(ComponentTarget).AssemblyQualifiedName); - } - + } + + [Test] + public void ComponentCanSetTuplizer() + { + Type tuplizerType = typeof(NHibernate.Tuple.Entity.PocoEntityTuplizer); + new MappingTester<PropertyTarget>() + .ForMapping(m => + m.Component(x => x.Component, c => c.Tuplizer(TuplizerMode.Poco, tuplizerType))) + .Element("class/component/tuplizer") + .HasAttribute("class", tuplizerType.AssemblyQualifiedName) + .HasAttribute("entity-mode", nameof(TuplizerMode.Poco).ToLower()); + } + + [Test] + public void ComponentCanSetTuplizerInCorrectOrderRegardlessOfDeclaration() + { + Type tuplizerType = typeof(NHibernate.Tuple.Entity.PocoEntityTuplizer); + new MappingTester<PropertyTarget>() + .ForMapping(m => + m.Component(x => x.Component, c => + { + c.Map(x => x.Name); + c.ParentReference(x => x.MyParent); + c.Tuplizer(TuplizerMode.Poco, tuplizerType); + })) + .Element("class/component/tuplizer").Exists().ShouldBeInParentAtPosition(0) + .Element("class/component/parent").Exists().ShouldBeInParentAtPosition(1) + .Element("class/component/property").Exists().ShouldBeInParentAtPosition(2); + } } diff --git a/src/FluentNHibernate.Testing/DomainModel/Mapping/MappingTester.cs b/src/FluentNHibernate.Testing/DomainModel/Mapping/MappingTester.cs index 7c2878f50..f1aaa7fb7 100644 --- a/src/FluentNHibernate.Testing/DomainModel/Mapping/MappingTester.cs +++ b/src/FluentNHibernate.Testing/DomainModel/Mapping/MappingTester.cs @@ -48,7 +48,7 @@ public virtual MappingTester<T> SubClassMapping<TSubClass>(Action<SubclassMap<TS return this; } - + public virtual MappingTester<T> ForMapping(Action<ClassMap<T>> mappingAction) { var classMap = new ClassMap<T>(); @@ -189,7 +189,9 @@ public virtual MappingTester<T> ShouldBeInParentAtPosition(int elementPosition) else { XmlElement elementAtPosition = (XmlElement)currentElement.ParentNode.ChildNodes.Item(elementPosition); - Assert.That(elementAtPosition, Is.EqualTo(currentElement), $"Expected '{currentElement.Name}' but was '{elementAtPosition.Name}'"); + // Assert.That(elementWithoutChildren0, IsEqualTo(elementWithoutChildren1)) always returns true (it defaults + // to the NUnit IEnumerable comparer, which returns equal cause both don't have child nodes). + Assert.That(elementAtPosition?.OuterXml, Is.EqualTo(currentElement.OuterXml), $"Expected '{currentElement.Name}' but was '{elementAtPosition.Name}'"); } return this; diff --git a/src/FluentNHibernate.Testing/DomainModel/Mapping/StoredProcedureTests.cs b/src/FluentNHibernate.Testing/DomainModel/Mapping/StoredProcedureTests.cs index 1f40610eb..7e9997818 100644 --- a/src/FluentNHibernate.Testing/DomainModel/Mapping/StoredProcedureTests.cs +++ b/src/FluentNHibernate.Testing/DomainModel/Mapping/StoredProcedureTests.cs @@ -1,4 +1,6 @@ -using NUnit.Framework; +using System; +using FluentNHibernate.Mapping; +using NUnit.Framework; namespace FluentNHibernate.Testing.DomainModel.Mapping; @@ -56,4 +58,55 @@ public void Can_specify_sql_delete_all() .Element("class/sql-delete-all") .ValueEquals("Delete ABC"); } + + [Test] + public void Can_specify_sql_insert_for_subclass() + { + var check = "Insert ABC"; + CreateMappingTester(x => x.SqlInsert(check)) + .Element("//subclass/sql-insert") + .ValueEquals(check); + } + + [Test] + public void Can_specify_sql_update_for_subclass() + { + var check = "Update ABC"; + CreateMappingTester(x => x.SqlUpdate(check)) + .Element("//subclass/sql-update") + .ValueEquals(check); + } + + [Test] + public void Can_specify_sql_delete_for_subclass() + { + var check = "Delete ABC"; + CreateMappingTester(x => x.SqlDelete(check)) + .Element("//subclass/sql-delete") + .ValueEquals(check); + } + + [Test] + public void Can_specify_sql_delete_all_for_subclass() + { + var check = "Delete ABC"; + CreateMappingTester(x => x.SqlDeleteAll(check)) + .Element("//subclass/sql-delete-all") + .ValueEquals(check); + } + + private MappingTester<MappedObject> CreateMappingTester(Action<SubclassMap<MappedObjectSubclass>> subclassMap) + { + return new MappingTester<MappedObject>() + .SubClassMapping<MappedObjectSubclass>(sc => + { + sc.Map(x => x.SubclassProperty); + subclassMap.Invoke(sc); + }) + .ForMapping(m => + { + m.DiscriminateSubClassesOnColumn("test"); + m.Id(x => x.Id); + }); + } } diff --git a/src/FluentNHibernate.Testing/DomainModel/Mapping/SubClassTester.cs b/src/FluentNHibernate.Testing/DomainModel/Mapping/SubClassTester.cs index d378d2287..604cb12ed 100644 --- a/src/FluentNHibernate.Testing/DomainModel/Mapping/SubClassTester.cs +++ b/src/FluentNHibernate.Testing/DomainModel/Mapping/SubClassTester.cs @@ -1,6 +1,8 @@ +using System; using FluentNHibernate.Conventions; using FluentNHibernate.Conventions.Instances; using FluentNHibernate.Mapping; +using FluentNHibernate.MappingModel; using NUnit.Framework; namespace FluentNHibernate.Testing.DomainModel.Mapping; @@ -590,7 +592,28 @@ public void SubSubclassOneToManyReferenceHasConventionApplied() .HasAttribute("foreign-key", "test_fk"); } - + + [Test] + public void SubclassCanSetTuplizerInCorrectOrder() + { + Type tuplizerType = typeof(NHibernate.Tuple.Entity.PocoEntityTuplizer); + new MappingTester<MappedObject>() + .SubClassMapping<MappedObjectSubclass>(m => + { + m.Map(x => x.SubclassProperty); + m.Tuplizer(TuplizerMode.Poco, tuplizerType); + m.DiscriminatorValue("test"); + }) + .ForMapping(m => + { + m.DiscriminateSubClassesOnColumn("test_column"); + m.Id(x => x.Id); + m.Tuplizer(TuplizerMode.Poco, tuplizerType); + }) + .Element("class/subclass/tuplizer").Exists().ShouldBeInParentAtPosition(0) + .Element("class/tuplizer").Exists().ShouldBeInParentAtPosition(0); + } + public class TestPropertyConvention : IPropertyConvention { public void Apply(IPropertyInstance instance) diff --git a/src/FluentNHibernate.Testing/DomainModel/Mapping/UnionSubclassTester.cs b/src/FluentNHibernate.Testing/DomainModel/Mapping/UnionSubclassTester.cs index e5987293d..efb0e6efb 100644 --- a/src/FluentNHibernate.Testing/DomainModel/Mapping/UnionSubclassTester.cs +++ b/src/FluentNHibernate.Testing/DomainModel/Mapping/UnionSubclassTester.cs @@ -1,3 +1,5 @@ +using System; +using FluentNHibernate.MappingModel; using NUnit.Framework; namespace FluentNHibernate.Testing.DomainModel.Mapping; @@ -44,4 +46,24 @@ public void ShouldAllowEntityNameToBeSetOnUnionSubclasses() }).Element("class/union-subclass") .HasAttribute("entity-name", "name"); } + + [Test] + public void SubclassCanSetTuplizerInCorrectOrder() + { + Type tuplizerType = typeof(NHibernate.Tuple.Entity.PocoEntityTuplizer); + new MappingTester<MappedObject>() + .SubClassMapping<MappedObjectSubclass>(m => + { + m.Map(x => x.SubclassProperty); + m.Tuplizer(TuplizerMode.Poco, tuplizerType); + }) + .ForMapping(m => + { + m.UseUnionSubclassForInheritanceMapping(); + m.Id(x => x.Id); + m.Tuplizer(TuplizerMode.Poco, tuplizerType); + }) + .Element("class/union-subclass/tuplizer").Exists().ShouldBeInParentAtPosition(0) + .Element("class/tuplizer").Exists().ShouldBeInParentAtPosition(0); + } } diff --git a/src/FluentNHibernate/Mapping/ClassMap.cs b/src/FluentNHibernate/Mapping/ClassMap.cs index 0e1991659..7d86203d3 100644 --- a/src/FluentNHibernate/Mapping/ClassMap.cs +++ b/src/FluentNHibernate/Mapping/ClassMap.cs @@ -583,17 +583,8 @@ public ClassMap<T> ApplyFilter(string name) /// </summary> /// <param name="mode">Tuplizer entity-mode</param> /// <param name="tuplizerType">Tuplizer type</param> - public TuplizerPart Tuplizer(TuplizerMode mode, Type tuplizerType) - { - providers.TuplizerMapping = new TuplizerMapping(); - providers.TuplizerMapping.Set(x => x.Mode, Layer.UserSupplied, mode); - providers.TuplizerMapping.Set(x => x.Type, Layer.UserSupplied, new TypeReference(tuplizerType)); - - return new TuplizerPart(providers.TuplizerMapping) - .Type(tuplizerType) - .Mode(mode); - } - + public TuplizerPart Tuplizer(TuplizerMode mode, Type tuplizerType) => CreateTuplizerPart(mode, tuplizerType); + ClassMapping IMappingProvider.GetClassMapping() { var mapping = new ClassMapping(attributes.Clone()); diff --git a/src/FluentNHibernate/Mapping/ClasslikeMapBase.cs b/src/FluentNHibernate/Mapping/ClasslikeMapBase.cs index 4f9b87065..812016fa7 100644 --- a/src/FluentNHibernate/Mapping/ClasslikeMapBase.cs +++ b/src/FluentNHibernate/Mapping/ClasslikeMapBase.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq.Expressions; using FluentNHibernate.Mapping.Providers; +using FluentNHibernate.MappingModel; using FluentNHibernate.Utils; namespace FluentNHibernate.Mapping; @@ -438,6 +439,17 @@ protected StoredProcedurePart StoredProcedure(string element, string innerText) providers.StoredProcedures.Add(part); return part; } + + protected TuplizerPart CreateTuplizerPart(TuplizerMode mode, Type tuplizerType) + { + providers.TuplizerMapping = new TuplizerMapping(); + providers.TuplizerMapping.Set(x => x.Mode, Layer.UserSupplied, mode); + providers.TuplizerMapping.Set(x => x.Type, Layer.UserSupplied, new TypeReference(tuplizerType)); + + return new TuplizerPart(providers.TuplizerMapping) + .Type(tuplizerType) + .Mode(mode); + } internal IEnumerable<IPropertyMappingProvider> Properties => providers.Properties; diff --git a/src/FluentNHibernate/Mapping/ComponentPart.cs b/src/FluentNHibernate/Mapping/ComponentPart.cs index ba95ab242..e46482e01 100644 --- a/src/FluentNHibernate/Mapping/ComponentPart.cs +++ b/src/FluentNHibernate/Mapping/ComponentPart.cs @@ -41,6 +41,15 @@ public ComponentPart<T> LazyLoad() nextBool = true; return this; } + + /// <summary> + /// Configures the tuplizer for this component. The tuplizer defines how to transform + /// a Property-Value to its persistent representation, and viceversa a Column-Value + /// to its in-memory representation, and the EntityMode defines which tuplizer is in use. + /// </summary> + /// <param name="mode">Tuplizer entity-mode</param> + /// <param name="tuplizerType">Tuplizer type</param> + public TuplizerPart Tuplizer(TuplizerMode mode, Type tuplizerType) => CreateTuplizerPart(mode, tuplizerType); IComponentMapping IComponentMappingProvider.GetComponentMapping() { diff --git a/src/FluentNHibernate/Mapping/ComponentPartBase.cs b/src/FluentNHibernate/Mapping/ComponentPartBase.cs index 95efcde5b..30b362e04 100644 --- a/src/FluentNHibernate/Mapping/ComponentPartBase.cs +++ b/src/FluentNHibernate/Mapping/ComponentPartBase.cs @@ -166,7 +166,9 @@ protected ComponentMapping CreateComponentMapping() if (member is not null) mapping.Set(x => x.Name, Layer.Defaults, member.Name); - + + mapping.Set(x => x.Tuplizer, Layer.UserSupplied, providers.TuplizerMapping); + foreach (var property in providers.Properties) mapping.AddProperty(property.GetPropertyMapping()); diff --git a/src/FluentNHibernate/Mapping/SubClassPart.cs b/src/FluentNHibernate/Mapping/SubClassPart.cs index f641fa43e..4bc8ad060 100644 --- a/src/FluentNHibernate/Mapping/SubClassPart.cs +++ b/src/FluentNHibernate/Mapping/SubClassPart.cs @@ -4,7 +4,6 @@ using FluentNHibernate.Mapping.Providers; using FluentNHibernate.MappingModel; using FluentNHibernate.MappingModel.ClassBased; -using FluentNHibernate.Utils; namespace FluentNHibernate.Mapping; diff --git a/src/FluentNHibernate/Mapping/SubclassMap.cs b/src/FluentNHibernate/Mapping/SubclassMap.cs index 058624d1e..33d2b0f0c 100644 --- a/src/FluentNHibernate/Mapping/SubclassMap.cs +++ b/src/FluentNHibernate/Mapping/SubclassMap.cs @@ -277,6 +277,15 @@ public void Extends(Type type) { attributes.Set("Extends", Layer.UserSupplied, type); } + + /// <summary> + /// Configures the tuplizer for this entity. The tuplizer defines how to transform + /// a Property-Value to its persistent representation, and viceversa a Column-Value + /// to its in-memory representation, and the EntityMode defines which tuplizer is in use. + /// </summary> + /// <param name="mode">Tuplizer entity-mode</param> + /// <param name="tuplizerType">Tuplizer type</param> + public TuplizerPart Tuplizer(TuplizerMode mode, Type tuplizerType) => CreateTuplizerPart(mode, tuplizerType); SubclassMapping IIndeterminateSubclassMappingProvider.GetSubclassMapping(SubclassType type) { @@ -327,16 +336,12 @@ SubclassMapping IIndeterminateSubclassMappingProvider.GetSubclassMapping(Subclas case MappingProviderStore.ProviderType.Any: mapping.AddAny(((IAnyMappingProvider)mappingProviderObj).GetAnyMapping()); break; - case MappingProviderStore.ProviderType.Subclass: - case MappingProviderStore.ProviderType.Filter: case MappingProviderStore.ProviderType.StoredProcedure: - case MappingProviderStore.ProviderType.Join: - case MappingProviderStore.ProviderType.Identity: - case MappingProviderStore.ProviderType.CompositeId: - case MappingProviderStore.ProviderType.NaturalId: - case MappingProviderStore.ProviderType.Version: - case MappingProviderStore.ProviderType.Discriminator: + mapping.AddStoredProcedure(((IStoredProcedureMappingProvider)mappingProviderObj).GetStoredProcedureMapping()); + break; case MappingProviderStore.ProviderType.Tupilizer: + mapping.Set(y => y.Tuplizer, Layer.Defaults, (TuplizerMapping)mappingProviderObj); + break; default: throw new Exception("Internal Error"); } diff --git a/src/FluentNHibernate/MappingModel/ClassBased/ClassMappingBase.cs b/src/FluentNHibernate/MappingModel/ClassBased/ClassMappingBase.cs index cab74d169..7ffd749e8 100644 --- a/src/FluentNHibernate/MappingModel/ClassBased/ClassMappingBase.cs +++ b/src/FluentNHibernate/MappingModel/ClassBased/ClassMappingBase.cs @@ -17,7 +17,7 @@ public abstract class ClassMappingBase(AttributeStore attributes) : MappingBase, public override void AcceptVisitor(IMappingModelVisitor visitor) { mappedMembers.AcceptVisitor(visitor); - + foreach (var subclass in Subclasses) visitor.Visit(subclass); } diff --git a/src/FluentNHibernate/MappingModel/ClassBased/ComponentMapping.cs b/src/FluentNHibernate/MappingModel/ClassBased/ComponentMapping.cs index dabdf523f..7048493c9 100644 --- a/src/FluentNHibernate/MappingModel/ClassBased/ComponentMapping.cs +++ b/src/FluentNHibernate/MappingModel/ClassBased/ComponentMapping.cs @@ -25,6 +25,9 @@ public override void AcceptVisitor(IMappingModelVisitor visitor) { visitor.ProcessComponent(this); + if (Tuplizer is not null) + visitor.Visit(Tuplizer); + base.AcceptVisitor(visitor); } @@ -39,6 +42,8 @@ public override void AcceptVisitor(IMappingModelVisitor visitor) public TypeReference Class => attributes.GetOrDefault<TypeReference>(); public bool Lazy => attributes.GetOrDefault<bool>(); + + public TuplizerMapping Tuplizer => attributes.GetOrDefault<TuplizerMapping>(); public bool Equals(ComponentMapping other) { diff --git a/src/FluentNHibernate/MappingModel/ClassBased/SubclassMapping.cs b/src/FluentNHibernate/MappingModel/ClassBased/SubclassMapping.cs index b453691c1..ebeec9d7e 100644 --- a/src/FluentNHibernate/MappingModel/ClassBased/SubclassMapping.cs +++ b/src/FluentNHibernate/MappingModel/ClassBased/SubclassMapping.cs @@ -27,6 +27,9 @@ public override void AcceptVisitor(IMappingModelVisitor visitor) { visitor.ProcessSubclass(this); + if (Tuplizer is not null) + visitor.Visit(Tuplizer); + if (SubclassType == SubclassType.JoinedSubclass && Key is not null) visitor.Visit(Key); @@ -66,6 +69,8 @@ public override void AcceptVisitor(IMappingModelVisitor visitor) public TypeReference Persister => attributes.GetOrDefault<TypeReference>(); public int BatchSize => attributes.GetOrDefault<int>(); + + public TuplizerMapping Tuplizer => attributes.GetOrDefault<TuplizerMapping>(); public void OverrideAttributes(AttributeStore store) { diff --git a/src/FluentNHibernate/MappingModel/Output/BaseXmlComponentWriter.cs b/src/FluentNHibernate/MappingModel/Output/BaseXmlComponentWriter.cs index 9f86bc6f8..d292c3ee3 100644 --- a/src/FluentNHibernate/MappingModel/Output/BaseXmlComponentWriter.cs +++ b/src/FluentNHibernate/MappingModel/Output/BaseXmlComponentWriter.cs @@ -47,4 +47,12 @@ public override void Visit(ParentMapping parentMapping) document.ImportAndAppendChild(parentXml); } + + public override void Visit(TuplizerMapping mapping) + { + var writer = serviceLocator.GetWriter<TuplizerMapping>(); + var filterXml = writer.Write(mapping); + + document.ImportAndAppendChild(filterXml); + } } diff --git a/src/FluentNHibernate/MappingModel/Output/XmlSubclassWriter.cs b/src/FluentNHibernate/MappingModel/Output/XmlSubclassWriter.cs index 538b65eef..f40382894 100644 --- a/src/FluentNHibernate/MappingModel/Output/XmlSubclassWriter.cs +++ b/src/FluentNHibernate/MappingModel/Output/XmlSubclassWriter.cs @@ -104,4 +104,12 @@ public override void Visit(JoinMapping joinMapping) document.ImportAndAppendChild(xml); } + + public override void Visit(TuplizerMapping mapping) + { + var writer = serviceLocator.GetWriter<TuplizerMapping>(); + var filterXml = writer.Write(mapping); + + document.ImportAndAppendChild(filterXml); + } }