diff --git a/src/FluentNHibernate.Testing/DomainModel/Mapping/ComponentPartTester.cs b/src/FluentNHibernate.Testing/DomainModel/Mapping/ComponentPartTester.cs index 14567a1d2..34130ac6e 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() + .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() + .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..9caebece9 100644 --- a/src/FluentNHibernate.Testing/DomainModel/Mapping/MappingTester.cs +++ b/src/FluentNHibernate.Testing/DomainModel/Mapping/MappingTester.cs @@ -189,7 +189,7 @@ public virtual MappingTester 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(elementAtPosition, Is.SameAs(currentElement), $"Expected '{currentElement.Name}' but was '{elementAtPosition.Name}'"); } return this; 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() + .SubClassMapping(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() + .SubClassMapping(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 ApplyFilter(string name) /// /// Tuplizer entity-mode /// Tuplizer type - 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 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 LazyLoad() nextBool = true; return this; } + + /// + /// 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. + /// + /// Tuplizer entity-mode + /// Tuplizer type + 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..5ece64ee6 100644 --- a/src/FluentNHibernate/Mapping/ComponentPartBase.cs +++ b/src/FluentNHibernate/Mapping/ComponentPartBase.cs @@ -167,6 +167,8 @@ 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 0e279b554..94dff86f7 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); } + + /// + /// 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. + /// + /// Tuplizer entity-mode + /// Tuplizer type + public TuplizerPart Tuplizer(TuplizerMode mode, Type tuplizerType) => CreateTuplizerPart(mode, tuplizerType); SubclassMapping IIndeterminateSubclassMappingProvider.GetSubclassMapping(SubclassType type) { @@ -330,6 +339,9 @@ SubclassMapping IIndeterminateSubclassMappingProvider.GetSubclassMapping(Subclas case MappingProviderStore.ProviderType.StoredProcedure: mapping.AddStoredProcedure(((IStoredProcedureMappingProvider)mappingProviderObj).GetStoredProcedureMapping()); break; + case MappingProviderStore.ProviderType.Tupilizer: + mapping.Set(y => y.Tuplizer, Layer.Defaults, (TuplizerMapping)mappingProviderObj); + break; case MappingProviderStore.ProviderType.Subclass: case MappingProviderStore.ProviderType.Filter: case MappingProviderStore.ProviderType.Join: @@ -338,7 +350,6 @@ SubclassMapping IIndeterminateSubclassMappingProvider.GetSubclassMapping(Subclas case MappingProviderStore.ProviderType.NaturalId: case MappingProviderStore.ProviderType.Version: case MappingProviderStore.ProviderType.Discriminator: - case MappingProviderStore.ProviderType.Tupilizer: default: throw new Exception("Internal Error"); } 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(); public bool Lazy => attributes.GetOrDefault(); + + public TuplizerMapping Tuplizer => attributes.GetOrDefault(); 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(); public int BatchSize => attributes.GetOrDefault(); + + public TuplizerMapping Tuplizer => attributes.GetOrDefault(); 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(); + 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(); + var filterXml = writer.Write(mapping); + + document.ImportAndAppendChild(filterXml); + } }