diff --git a/src/FluentNHibernate.Specs/FluentInterface/ClassMapSpecs/ClassMapSpecs.Component.cs b/src/FluentNHibernate.Specs/FluentInterface/ClassMapSpecs/ClassMapSpecs.Component.cs index 2cf5d6e26..316277d1d 100644 --- a/src/FluentNHibernate.Specs/FluentInterface/ClassMapSpecs/ClassMapSpecs.Component.cs +++ b/src/FluentNHibernate.Specs/FluentInterface/ClassMapSpecs/ClassMapSpecs.Component.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Generic; using FluentNHibernate.Mapping; using FluentNHibernate.Mapping.Providers; using FluentNHibernate.MappingModel.ClassBased; @@ -86,6 +87,36 @@ public class when_class_map_is_told_to_map_a_dynamic_component_using_reveal : Pr protected static ClassMapping mapping; } + public class when_class_map_is_told_to_map_a_generic_dynamic_component : ProviderSpec + { + Because of = () => + mapping = map_as_class(m => m.DynamicComponent(x => x.GenericDynamicComponent, c => { })); + + Behaves_like a_component_in_a_classlike; + + protected static ClassMapping mapping; + } + + public class when_class_map_is_told_to_map_a_generic_dynamic_component_from_a_field : ProviderSpec + { + Because of = () => + mapping = map_as_class(m => m.DynamicComponent(x => x.GenericDynamicComponent, c => { })); + + Behaves_like a_component_in_a_classlike; + + protected static ClassMapping mapping; + } + + public class when_class_map_is_told_to_map_a_generic_dynamic_component_using_reveal : ProviderSpec + { + Because of = () => + mapping = map_as_class(m => m.DynamicComponent(Reveal.Member>("GenericDynamicComponent"), c => { })); + + Behaves_like a_component_in_a_classlike; + + protected static ClassMapping mapping; + } + public class when_class_map_is_told_to_map_a_reference_component : ProviderSpec { Because of = () => diff --git a/src/FluentNHibernate.Specs/FluentInterface/Fixtures/EntityWithComponent.cs b/src/FluentNHibernate.Specs/FluentInterface/Fixtures/EntityWithComponent.cs index a7e2473f1..9075abc1e 100644 --- a/src/FluentNHibernate.Specs/FluentInterface/Fixtures/EntityWithComponent.cs +++ b/src/FluentNHibernate.Specs/FluentInterface/Fixtures/EntityWithComponent.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Generic; namespace FluentNHibernate.Specs.FluentInterface.Fixtures { @@ -7,12 +8,14 @@ class EntityWithComponent { public ComponentTarget Component { get; set; } public IDictionary DynamicComponent { get; set; } + public IDictionary GenericDynamicComponent { get; set; } } class EntityWithFieldComponent { public ComponentTarget Component; public IDictionary DynamicComponent; + public IDictionary GenericDynamicComponent; } class ComponentTarget diff --git a/src/FluentNHibernate.Specs/FluentInterface/SubclassMapSpecs/SubclassMapSpecs.Component.cs b/src/FluentNHibernate.Specs/FluentInterface/SubclassMapSpecs/SubclassMapSpecs.Component.cs index e1420f7e3..174fe2aaa 100644 --- a/src/FluentNHibernate.Specs/FluentInterface/SubclassMapSpecs/SubclassMapSpecs.Component.cs +++ b/src/FluentNHibernate.Specs/FluentInterface/SubclassMapSpecs/SubclassMapSpecs.Component.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Collections.Generic; using FluentNHibernate.MappingModel.ClassBased; using FluentNHibernate.Specs.FluentInterface.Fixtures; using Machine.Specifications; @@ -65,6 +66,36 @@ public class when_subclass_map_is_told_to_map_a_dynamic_component_using_reveal : protected static SubclassMapping mapping; } + public class when_subclass_map_is_told_to_map_a_generic_dynamic_component : ProviderSpec + { + Because of = () => + mapping = map_as_subclass(m => m.DynamicComponent(x => x.GenericDynamicComponent, c => { })); + + Behaves_like a_component_in_a_classlike; + + protected static SubclassMapping mapping; + } + + public class when_subclass_map_is_told_to_map_a_generic_dynamic_component_from_a_field : ProviderSpec + { + Because of = () => + mapping = map_as_subclass(m => m.DynamicComponent(x => x.GenericDynamicComponent, c => { })); + + Behaves_like a_component_in_a_classlike; + + protected static SubclassMapping mapping; + } + + public class when_subclass_map_is_told_to_map_a_generic_dynamic_component_using_reveal : ProviderSpec + { + Because of = () => + mapping = map_as_subclass(m => m.DynamicComponent(Reveal.Member>("GenericDynamicComponent"), c => { })); + + Behaves_like a_component_in_a_classlike; + + protected static SubclassMapping mapping; + } + public class when_subclass_map_is_told_to_map_a_reference_component : ProviderSpec { Because of = () => diff --git a/src/FluentNHibernate.Testing/ConventionsTests/ApplyingToModel/GenericDynamicComponentConventionTests.cs b/src/FluentNHibernate.Testing/ConventionsTests/ApplyingToModel/GenericDynamicComponentConventionTests.cs new file mode 100644 index 000000000..691e56565 --- /dev/null +++ b/src/FluentNHibernate.Testing/ConventionsTests/ApplyingToModel/GenericDynamicComponentConventionTests.cs @@ -0,0 +1,94 @@ +using System; +using System.Linq; +using FluentNHibernate.Conventions.Helpers.Builders; +using FluentNHibernate.Conventions.Instances; +using FluentNHibernate.Mapping; +using FluentNHibernate.MappingModel.ClassBased; +using FluentNHibernate.Testing.DomainModel.Mapping; +using NUnit.Framework; + +namespace FluentNHibernate.Testing.ConventionsTests.ApplyingToModel +{ + [TestFixture] + public class GenericDynamicComponentConventionTests + { + private PersistenceModel model; + + [SetUp] + public void CreatePersistenceModel() + { + model = new PersistenceModel(); + } + + [Test] + public void ShouldSetAccessProperty() + { + Convention(x => x.Access.Property()); + + VerifyModel(x => x.Access.ShouldEqual("property")); + } + + [Test] + public void ShouldSetInsertProperty() + { + Convention(x => x.Insert()); + + VerifyModel(x => x.Insert.ShouldBeTrue()); + } + + [Test] + public void ShouldSetUpdateProperty() + { + Convention(x => x.Update()); + + VerifyModel(x => x.Update.ShouldBeTrue()); + } + + [Test] + public void ShouldSetUniqueProperty() + { + Convention(x => x.Unique()); + + VerifyModel(x => x.Unique.ShouldBeTrue()); + } + + [Test] + public void ShouldSetOptimisticLockProperty() + { + Convention(x => x.OptimisticLock()); + + VerifyModel(x => x.OptimisticLock.ShouldBeTrue()); + } + + #region Helpers + + private void Convention(Action convention) + { + model.Conventions.Add(new DynamicComponentConventionBuilder().Always(convention)); + } + + private void VerifyModel(Action modelVerification) + { + var classMap = new ClassMap(); + classMap.Id(x => x.Id); + var map = classMap.DynamicComponent(x => x.GenericExtensionData, m => + { + m.Map(x => (string)x["Name"]); + m.Map(x => (int)x["Age"]); + m.Map(x => (string)x["Profession"]); + }); + + model.Add(classMap); + + var generatedModels = model.BuildMappings(); + var modelInstance = (ComponentMapping)generatedModels + .First(x => x.Classes.FirstOrDefault(c => c.Type == typeof(PropertyTarget)) != null) + .Classes.First() + .Components.Where(x => x is ComponentMapping).First(); + + modelVerification(modelInstance); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/FluentNHibernate.Testing/ConventionsTests/OverridingFluentInterface/GenericDynamicComponentConventionTests.cs b/src/FluentNHibernate.Testing/ConventionsTests/OverridingFluentInterface/GenericDynamicComponentConventionTests.cs new file mode 100644 index 000000000..65d8203fd --- /dev/null +++ b/src/FluentNHibernate.Testing/ConventionsTests/OverridingFluentInterface/GenericDynamicComponentConventionTests.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using FluentNHibernate.Conventions.Helpers.Builders; +using FluentNHibernate.Conventions.Instances; +using FluentNHibernate.Mapping; +using FluentNHibernate.MappingModel.ClassBased; +using FluentNHibernate.Testing.DomainModel.Mapping; +using NUnit.Framework; + +namespace FluentNHibernate.Testing.ConventionsTests.OverridingFluentInterface +{ + [TestFixture] + public class GenericDynamicComponentConventionTests + { + private PersistenceModel model; + private IMappingProvider mapping; + private Type mappingType; + + [SetUp] + public void CreatePersistenceModel() + { + model = new PersistenceModel(); + } + + [Test] + public void AccessShouldntBeOverwritten() + { + Mapping(x => x.Access.Field()); + + Convention(x => x.Access.Property()); + + VerifyModel(x => x.Access.ShouldEqual("field")); + } + + [Test] + public void InsertShouldntBeOverwritten() + { + Mapping(x => x.Insert()); + + Convention(x => x.Not.Insert()); + + VerifyModel(x => x.Insert.ShouldBeTrue()); + } + + [Test] + public void UpdateShouldntBeOverwritten() + { + Mapping(x => x.Update()); + + Convention(x => x.Not.Update()); + + VerifyModel(x => x.Update.ShouldBeTrue()); + } + + [Test] + public void UniqueShouldntBeOverwritten() + { + Mapping(x => x.Unique()); + + Convention(x => x.Not.Unique()); + + VerifyModel(x => x.Unique.ShouldBeTrue()); + } + + [Test] + public void OptimisticLockShouldntBeOverwritten() + { + Mapping(x => x.OptimisticLock()); + + Convention(x => x.Not.OptimisticLock()); + + VerifyModel(x => x.OptimisticLock.ShouldBeTrue()); + } + + #region Helpers + + private void Convention(Action convention) + { + model.Conventions.Add(new DynamicComponentConventionBuilder().Always(convention)); + } + + private void Mapping(Action>> mappingDefinition) + { + var classMap = new ClassMap(); + classMap.Id(x => x.Id); + var map = classMap.DynamicComponent(x => x.GenericExtensionData, m => + { + m.Map(x => (string)x["Name"]); + m.Map(x => (int)x["Age"]); + m.Map(x => (string)x["Profession"]); + }); + + mappingDefinition(map); + + mapping = classMap; + mappingType = typeof(PropertyTarget); + } + + private void VerifyModel(Action modelVerification) + { + model.Add(mapping); + + var generatedModels = model.BuildMappings(); + var modelInstance = (ComponentMapping)generatedModels + .First(x => x.Classes.FirstOrDefault(c => c.Type == mappingType) != null) + .Classes.First() + .Components.First(); + + modelVerification(modelInstance); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/FluentNHibernate.Testing/ConventionsTests/RunnableConventionsTests.cs b/src/FluentNHibernate.Testing/ConventionsTests/RunnableConventionsTests.cs index 3e0afee72..66d7d4da0 100644 --- a/src/FluentNHibernate.Testing/ConventionsTests/RunnableConventionsTests.cs +++ b/src/FluentNHibernate.Testing/ConventionsTests/RunnableConventionsTests.cs @@ -150,6 +150,22 @@ public void ShouldApplyIDynamicComponentConvention() .Access.ShouldEqual("field"); } + [Test] + public void ShouldApplyGenericIDynamicComponentConvention() + { + TestConvention(new DynamicComponentConvention(), () => + { + var map = new ClassMap(); + + map.Id(x => x.Id); + map.DynamicComponent(x => x.GenericDynamicComponent, c => { }); + + return map; + }) + .Components.First() + .Access.ShouldEqual("field"); + } + [Test] public void ShouldApplyIHasManyConvention() { @@ -738,6 +754,7 @@ private class Target public string Property { get; set; } public OtherObject Component { get; set; } public IDictionary DynamicComponent { get; set; } + public IDictionary GenericDynamicComponent { get; set; } public OtherObject Other { get; set; } public int Id { get; set; } public IDictionary DictionaryBag { get; set; } diff --git a/src/FluentNHibernate.Testing/DomainModel/Access/Mappings/ParentModelMapping.cs b/src/FluentNHibernate.Testing/DomainModel/Access/Mappings/ParentModelMapping.cs index 461bb70f3..a90f89a97 100644 --- a/src/FluentNHibernate.Testing/DomainModel/Access/Mappings/ParentModelMapping.cs +++ b/src/FluentNHibernate.Testing/DomainModel/Access/Mappings/ParentModelMapping.cs @@ -33,6 +33,7 @@ public ParentModelMapping() .IdentityType(typeof(int)); DynamicComponent(x => x.Dynamic, x => { }); + DynamicComponent(x => x.GenericDynamic, x => { }); HasMany(x => x.MapOne).AsMap("type"); HasMany(x => x.SetOne).AsSet(); diff --git a/src/FluentNHibernate.Testing/DomainModel/Access/ParentModel.cs b/src/FluentNHibernate.Testing/DomainModel/Access/ParentModel.cs index f64cb1d38..d374df2a3 100644 --- a/src/FluentNHibernate.Testing/DomainModel/Access/ParentModel.cs +++ b/src/FluentNHibernate.Testing/DomainModel/Access/ParentModel.cs @@ -18,7 +18,7 @@ class ParentModel public virtual OneToOneModel One { get; private set; } public virtual object Any { get; private set; } public virtual IDictionary Dynamic { get; private set; } - + public virtual IDictionary GenericDynamic { get; private set; } public virtual IDictionary MapOne { get; private set; } public virtual ISet SetOne { get; private set; } public virtual IList ListOne { get; private set; } diff --git a/src/FluentNHibernate.Testing/DomainModel/Mapping/ClassMapXmlCreationTester.cs b/src/FluentNHibernate.Testing/DomainModel/Mapping/ClassMapXmlCreationTester.cs index 382413d4a..8dd7d81a8 100644 --- a/src/FluentNHibernate.Testing/DomainModel/Mapping/ClassMapXmlCreationTester.cs +++ b/src/FluentNHibernate.Testing/DomainModel/Mapping/ClassMapXmlCreationTester.cs @@ -552,7 +552,7 @@ public class MappedObject public IList Children { get; set; } public IDictionary Dictionary { get; set; } - + public IDictionary GenericDictionary { get; set; } public long Id { get; set; } public int Version { get; set; } diff --git a/src/FluentNHibernate.Testing/DomainModel/Mapping/DynamicComponentTester.cs b/src/FluentNHibernate.Testing/DomainModel/Mapping/DynamicComponentTester.cs index 4ea4f8dcd..9485acd3a 100644 --- a/src/FluentNHibernate.Testing/DomainModel/Mapping/DynamicComponentTester.cs +++ b/src/FluentNHibernate.Testing/DomainModel/Mapping/DynamicComponentTester.cs @@ -24,6 +24,45 @@ public void CanGenerateDynamicComponentsWithSingleProperties() .Element("//class/dynamic-component/property[@name='Profession']").Exists(); } + [Test] + public void CanGenerateDynamicComponentsWithPropertyFromLocalVariable() + { + var propertyName = "Profession"; + new MappingTester() + .ForMapping(c => + c.DynamicComponent(x => x.ExtensionData, m => + { + m.Map(x => (string)x[propertyName]); + })) + .Element("//class/dynamic-component/property[@name='Profession']").Exists(); + } + + [Test] + public void CanGenerateDynamicComponentsWithPropertyFromClass() + { + var property = new { Name = "Profession" }; + new MappingTester() + .ForMapping(c => + c.DynamicComponent(x => x.ExtensionData, m => + { + m.Map(x => (string)x[property.Name]); + })) + .Element("//class/dynamic-component/property[@name='Profession']").Exists(); + } + + [Test] + public void CanGenerateDynamicComponentsWithPropertyFromClass2() + { + var property = new { Info = new { Name = "Profession" } }; + new MappingTester() + .ForMapping(c => + c.DynamicComponent(x => x.ExtensionData, m => + { + m.Map(x => (string)x[property.Info.Name]); + })) + .Element("//class/dynamic-component/property[@name='Profession']").Exists(); + } + [Test] public void DynamicComponentIsGeneratedWithOnlyOnePropertyReference() { @@ -120,6 +159,16 @@ public void CanMapDynamicComponent() .Element("class/dynamic-component/dynamic-component").Exists(); } + [Test] + public void CanMapGenericDynamicComponent() + { + new MappingTester() + .ForMapping(m => + m.DynamicComponent(x => x.ExtensionData, c => + c.DynamicComponent(x => (IDictionary)x["Component"], sc => { }))) + .Element("class/dynamic-component/dynamic-component").Exists(); + } + [Test] public void DynamicComponentHasName() { diff --git a/src/FluentNHibernate.Testing/DomainModel/Mapping/GenericDynamicComponentTester.cs b/src/FluentNHibernate.Testing/DomainModel/Mapping/GenericDynamicComponentTester.cs new file mode 100644 index 000000000..b923b26e4 --- /dev/null +++ b/src/FluentNHibernate.Testing/DomainModel/Mapping/GenericDynamicComponentTester.cs @@ -0,0 +1,215 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using NUnit.Framework; + +namespace FluentNHibernate.Testing.DomainModel.Mapping +{ + [TestFixture] + public class GenericDynamicComponentTester + { + [Test] + public void CanGenerateDynamicComponentsWithSingleProperties() + { + new MappingTester() + .ForMapping(c => + c.DynamicComponent(x => x.GenericExtensionData, m => + { + m.Map(x => (string)x["Name"]); + m.Map(x => (int)x["Age"]); + m.Map(x => (string)x["Profession"]); + })) + .Element("//class/dynamic-component/property[@name='Name']").Exists() + .Element("//class/dynamic-component/property[@name='Age']").Exists() + .Element("//class/dynamic-component/property[@name='Profession']").Exists(); + } + + [Test] + public void CanGenerateDynamicComponentsWithPropertyFromLocalVariable() + { + var propertyName = "Profession"; + new MappingTester() + .ForMapping(c => + c.DynamicComponent(x => x.GenericExtensionData, m => + { + m.Map(x => (string) x[propertyName]); + })) + .Element("//class/dynamic-component/property[@name='Profession']").Exists(); + } + + [Test] + public void CanGenerateDynamicComponentsWithPropertyFromClass() + { + var property = new {Name = "Profession" }; + new MappingTester() + .ForMapping(c => + c.DynamicComponent(x => x.GenericExtensionData, m => + { + m.Map(x => (string)x[property.Name]); + })) + .Element("//class/dynamic-component/property[@name='Profession']").Exists(); + } + + [Test] + public void CanGenerateDynamicComponentsWithPropertyFromClass2() + { + var property = new {Info = new {Name = "Profession"}}; + new MappingTester() + .ForMapping(c => + c.DynamicComponent(x => x.GenericExtensionData, m => + { + m.Map(x => (string)x[property.Info.Name]); + })) + .Element("//class/dynamic-component/property[@name='Profession']").Exists(); + } + + [Test] + public void DynamicComponentIsGeneratedWithOnlyOnePropertyReference() + { + //Regression test for issue 223 + new MappingTester() + .ForMapping(c => + c.DynamicComponent(x => x.GenericExtensionData, m => + { + m.Map(x => (string)x["Name"]); + })) + .Element("//class/dynamic-component").HasThisManyChildNodes(1); + } + + [Test] + public void CanGenerateDynamicComponentsWithInt32Property() + { + new MappingTester() + .ForMapping(c => + c.DynamicComponent(x => x.GenericExtensionData, m => + { + m.Map(x => (int)x["Age"]); + })).Element("//class/dynamic-component/property").HasAttribute("type", typeof(Int32).AssemblyQualifiedName); + + } + + [Test] + public void CanGenerateDynamicComponentsWithStringProperty() + { + new MappingTester() + .ForMapping(c => + c.DynamicComponent(x => x.GenericExtensionData, m => + { + m.Map(x => (string)x["Name"]); + })).Element("//class/dynamic-component/property").HasAttribute("type", typeof(string).AssemblyQualifiedName); + } + + [Test] + public void CanMapReferences() + { + new MappingTester() + .ForMapping(m => + m.DynamicComponent(x => x.GenericExtensionData, c => + c.References(x => (PropertyReferenceTarget)x["Parent"]))) + .Element("class/dynamic-component/many-to-one").Exists(); + } + + [Test] + public void CanMapHasOne() + { + new MappingTester() + .ForMapping(m => + m.DynamicComponent(x => x.GenericExtensionData, c => + c.HasOne(x => (PropertyReferenceTarget)x["Parent"]))) + .Element("class/dynamic-component/one-to-one").Exists(); + } + + [Test] + public void CanMapHasMany() + { + new MappingTester() + .ForMapping(m => + m.DynamicComponent(x => x.GenericExtensionData, c => + c.HasMany(x => (IList)x["Children"]))) + .Element("class/dynamic-component/bag").Exists(); + } + + [Test] + public void CanMapHasManyToMany() + { + new MappingTester() + .ForMapping(m => + m.DynamicComponent(x => x.GenericExtensionData, c => + c.HasManyToMany(x => (IList)x["Children"]))) + .Element("class/dynamic-component/bag").Exists(); + } + + [Test] + public void CanMapComponent() + { + new MappingTester() + .ForMapping(m => + m.DynamicComponent(x => x.GenericExtensionData, c => + c.Component(x => (PropertyReferenceTarget)x["Component"], sc => { }))) + .Element("class/dynamic-component/component").Exists(); + } + + [Test] + public void CanMapDynamicComponent() + { + new MappingTester() + .ForMapping(m => + m.DynamicComponent(x => x.GenericExtensionData, c => + c.DynamicComponent(x => (IDictionary)x["Component"], sc => { }))) + .Element("class/dynamic-component/dynamic-component").Exists(); + } + + [Test] + public void CanMapGenericDynamicComponent() + { + new MappingTester() + .ForMapping(m => + m.DynamicComponent(x => x.GenericExtensionData, c => + c.DynamicComponent(x => (IDictionary)x["Component"], sc => { }))) + .Element("class/dynamic-component/dynamic-component").Exists(); + } + + [Test] + public void DynamicComponentHasName() + { + new MappingTester() + .ForMapping(m => + m.DynamicComponent(x => x.GenericExtensionData, c => { })) + .Element("class/dynamic-component").HasAttribute("name", "GenericExtensionData"); + } + + [Test] + public void CanSetParentRef() + { + new MappingTester() + .ForMapping(m => + m.DynamicComponent(x => x.GenericExtensionData, c => + c.ParentReference(x => x["Parent"]))) + .Element("class/dynamic-component/parent").HasAttribute("name", "Parent"); + } + + [Test] + public void ComponentCanSetInsert() + { + new MappingTester() + .ForMapping(m => + m.DynamicComponent(x => x.GenericExtensionData, c => + { + c.Insert(); + })) + .Element("class/dynamic-component").HasAttribute("insert", "true"); + } + + [Test] + public void ComponentCanSetUpdate() + { + new MappingTester() + .ForMapping(m => + m.DynamicComponent(x => x.GenericExtensionData, c => + { + c.Update(); + })) + .Element("class/dynamic-component").HasAttribute("update", "true"); + } + } +} \ No newline at end of file diff --git a/src/FluentNHibernate.Testing/DomainModel/Mapping/JoinedSubClassTester.cs b/src/FluentNHibernate.Testing/DomainModel/Mapping/JoinedSubClassTester.cs index 11e625e83..9567dc72b 100644 --- a/src/FluentNHibernate.Testing/DomainModel/Mapping/JoinedSubClassTester.cs +++ b/src/FluentNHibernate.Testing/DomainModel/Mapping/JoinedSubClassTester.cs @@ -173,6 +173,15 @@ public void MapsDynamicComponent() .Element("//joined-subclass/dynamic-component").Exists(); } + [Test] + public void MapsGenericDynamicComponent() + { + new MappingTester() + .ForMapping(map => + map.JoinedSubClass("id", sc => sc.DynamicComponent(x => x.GenericDictionary, c => { }))) + .Element("//joined-subclass/dynamic-component").Exists(); + } + [Test] public void MapsHasMany() { diff --git a/src/FluentNHibernate.Testing/DomainModel/Mapping/PropertyPartTester.cs b/src/FluentNHibernate.Testing/DomainModel/Mapping/PropertyPartTester.cs index e518bb340..c13021d7f 100644 --- a/src/FluentNHibernate.Testing/DomainModel/Mapping/PropertyPartTester.cs +++ b/src/FluentNHibernate.Testing/DomainModel/Mapping/PropertyPartTester.cs @@ -599,6 +599,7 @@ public class PropertyTarget public byte[] Data { get; set; } public decimal DecimalProperty { get; set; } public IDictionary ExtensionData { get; set; } + public IDictionary GenericExtensionData { get; set; } } public class FieldTarget @@ -612,6 +613,7 @@ public class FieldTarget public byte[] Data; public decimal DecimalProperty; public IDictionary ExtensionData; + public IDictionary GenericExtensionData; } public class PrivatePropertyTarget @@ -625,6 +627,7 @@ public class PrivatePropertyTarget public byte[] Data { get; set; } public decimal DecimalProperty { get; set; } public IDictionary ExtensionData { get; set; } + public IDictionary GenericExtensionData { get; set; } } public class PropertyReferenceTarget diff --git a/src/FluentNHibernate.Testing/DomainModel/Mapping/SubClassTester.cs b/src/FluentNHibernate.Testing/DomainModel/Mapping/SubClassTester.cs index 1fe17505a..8a1c810e1 100644 --- a/src/FluentNHibernate.Testing/DomainModel/Mapping/SubClassTester.cs +++ b/src/FluentNHibernate.Testing/DomainModel/Mapping/SubClassTester.cs @@ -220,6 +220,19 @@ public void MapsDynamicComponent() .Element("//subclass/dynamic-component").Exists(); } + [Test] + public void MapsGenericDynamicComponent() + { + new MappingTester() + .ForMapping(m => + { + m.Id(x => x.Id); + m.DiscriminateSubClassesOnColumn("Type") + .SubClass(sc => sc.DynamicComponent(x => x.GenericDictionary, c => { })); + }) + .Element("//subclass/dynamic-component").Exists(); + } + [Test] public void MapsHasMany() { diff --git a/src/FluentNHibernate.Testing/FluentInterfaceTests/JoinSubPartModelGenerationTests.cs b/src/FluentNHibernate.Testing/FluentInterfaceTests/JoinSubPartModelGenerationTests.cs index f69e909ad..4c4964ad2 100644 --- a/src/FluentNHibernate.Testing/FluentInterfaceTests/JoinSubPartModelGenerationTests.cs +++ b/src/FluentNHibernate.Testing/FluentInterfaceTests/JoinSubPartModelGenerationTests.cs @@ -24,6 +24,14 @@ public void DynamicComponentShouldAddToModelComponentsCollection() .ModelShouldMatch(x => x.Components.Count().ShouldEqual(1)); } + [Test] + public void GenericDynamicComponentShouldAddToModelComponentsCollection() + { + Join("table") + .Mapping(m => m.DynamicComponent(x => x.GenericExtensionData, c => { })) + .ModelShouldMatch(x => x.Components.Count().ShouldEqual(1)); + } + [Test] public void MapShouldAddToModelPropertiesCollection() { diff --git a/src/FluentNHibernate.Testing/FluentInterfaceTests/JoinedSubclassSubPartModelGenerationTests.cs b/src/FluentNHibernate.Testing/FluentInterfaceTests/JoinedSubclassSubPartModelGenerationTests.cs index 5ca6fe734..b9ecb55c1 100644 --- a/src/FluentNHibernate.Testing/FluentInterfaceTests/JoinedSubclassSubPartModelGenerationTests.cs +++ b/src/FluentNHibernate.Testing/FluentInterfaceTests/JoinedSubclassSubPartModelGenerationTests.cs @@ -24,6 +24,14 @@ public void DynamicComponentShouldAddToModelComponentsCollection() .ModelShouldMatch(x => x.Components.Count().ShouldEqual(1)); } + [Test] + public void GenericDynamicComponentShouldAddToModelComponentsCollection() + { + JoinedSubclass() + .Mapping(m => m.DynamicComponent(x => x.GenericExtensionData, c => { })) + .ModelShouldMatch(x => x.Components.Count().ShouldEqual(1)); + } + [Test] public void MapShouldAddToModelPropertiesCollection() { diff --git a/src/FluentNHibernate.Testing/FluentInterfaceTests/SubclassMapForJoinedSubclassSubPartModelGenerationTests.cs b/src/FluentNHibernate.Testing/FluentInterfaceTests/SubclassMapForJoinedSubclassSubPartModelGenerationTests.cs index d9799e263..92dd7d7ab 100644 --- a/src/FluentNHibernate.Testing/FluentInterfaceTests/SubclassMapForJoinedSubclassSubPartModelGenerationTests.cs +++ b/src/FluentNHibernate.Testing/FluentInterfaceTests/SubclassMapForJoinedSubclassSubPartModelGenerationTests.cs @@ -25,6 +25,14 @@ public void DynamicComponentShouldAddToModelComponentsCollection() .ModelShouldMatch(x => x.Components.Count().ShouldEqual(1)); } + [Test] + public void GenericDynamicComponentShouldAddToModelComponentsCollection() + { + SubclassMapForJoinedSubclass() + .Mapping(m => m.DynamicComponent(x => x.GenericExtensionData, c => { })) + .ModelShouldMatch(x => x.Components.Count().ShouldEqual(1)); + } + [Test] public void MapShouldAddToModelPropertiesCollection() { diff --git a/src/FluentNHibernate.Testing/FluentInterfaceTests/SubclassMapForSubclassSubPartModelGenerationTests.cs b/src/FluentNHibernate.Testing/FluentInterfaceTests/SubclassMapForSubclassSubPartModelGenerationTests.cs index a7f50592e..7137fe884 100644 --- a/src/FluentNHibernate.Testing/FluentInterfaceTests/SubclassMapForSubclassSubPartModelGenerationTests.cs +++ b/src/FluentNHibernate.Testing/FluentInterfaceTests/SubclassMapForSubclassSubPartModelGenerationTests.cs @@ -64,6 +64,14 @@ public void DynamicComponentShouldAddToModelComponentsCollection() .ModelShouldMatch(x => x.Components.Count().ShouldEqual(1)); } + [Test] + public void GenericDynamicComponentShouldAddToModelComponentsCollection() + { + SubclassMapForSubclass() + .Mapping(m => m.DynamicComponent(x => x.GenericExtensionData, c => { })) + .ModelShouldMatch(x => x.Components.Count().ShouldEqual(1)); + } + [Test] public void MapShouldAddToModelPropertiesCollection() { diff --git a/src/FluentNHibernate.Testing/FluentInterfaceTests/SubclassSubPartModelGenerationTests.cs b/src/FluentNHibernate.Testing/FluentInterfaceTests/SubclassSubPartModelGenerationTests.cs index 86c311839..e7a544f1a 100644 --- a/src/FluentNHibernate.Testing/FluentInterfaceTests/SubclassSubPartModelGenerationTests.cs +++ b/src/FluentNHibernate.Testing/FluentInterfaceTests/SubclassSubPartModelGenerationTests.cs @@ -25,6 +25,14 @@ public void DynamicComponentShouldAddToModelComponentsCollection() .ModelShouldMatch(x => x.Components.Count().ShouldEqual(1)); } + [Test] + public void GenericDynamicComponentShouldAddToModelComponentsCollection() + { + Subclass() + .Mapping(m => m.DynamicComponent(x => x.GenericExtensionData, c => { })) + .ModelShouldMatch(x => x.Components.Count().ShouldEqual(1)); + } + [Test] public void MapShouldAddToModelPropertiesCollection() { diff --git a/src/FluentNHibernate/Mapping/ClasslikeMapBase.cs b/src/FluentNHibernate/Mapping/ClasslikeMapBase.cs index 1e9eb4280..c1dcf9c7b 100644 --- a/src/FluentNHibernate/Mapping/ClasslikeMapBase.cs +++ b/src/FluentNHibernate/Mapping/ClasslikeMapBase.cs @@ -228,6 +228,36 @@ DynamicComponentPart DynamicComponent(Member member, Action + /// Create a dynamic component mapping. This is a dictionary that represents + /// a limited number of columns in the database. + /// + /// Property containing component + /// Component setup action + /// + /// DynamicComponent(x => x.Data, comp => + /// { + /// comp.Map(x => (int)x["age"]); + /// }); + /// + public DynamicComponentPart> DynamicComponent(Expression>> memberExpression, Action>> dynamicComponentAction) + { + return DynamicComponent(memberExpression.ToMember(), dynamicComponentAction); + } + + DynamicComponentPart> DynamicComponent(Member member, Action>> dynamicComponentAction) + { + OnMemberMapped(member); + + var part = new DynamicComponentPart>(typeof(T), member); + + dynamicComponentAction(part); + + providers.Components.Add(part); + + return part; + } + /// /// Creates a component reference. This is a place-holder for a component that is defined externally with a /// ; the mapping defined in said will be merged diff --git a/src/FluentNHibernate/MappingModel/Output/Sorting/XmlClasslikeNodeSorter.cs b/src/FluentNHibernate/MappingModel/Output/Sorting/XmlClasslikeNodeSorter.cs index a6578f0b5..53c611625 100644 --- a/src/FluentNHibernate/MappingModel/Output/Sorting/XmlClasslikeNodeSorter.cs +++ b/src/FluentNHibernate/MappingModel/Output/Sorting/XmlClasslikeNodeSorter.cs @@ -59,7 +59,7 @@ protected override IDictionary GetSorting() protected override void SortChildren(XmlNode node) { - if (node.Name == "subclass" || node.Name == "joined-subclass" || node.Name == "union-subclass" || node.Name == "component") + if (node.Name == "subclass" || node.Name == "joined-subclass" || node.Name == "union-subclass" || node.Name == "component" || node.Name == "dynamic-component") Sort(node); else if (node.Name == "id") new XmlIdNodeSorter().Sort(node); diff --git a/src/FluentNHibernate/Utils/Reflection/ReflectionHelper.cs b/src/FluentNHibernate/Utils/Reflection/ReflectionHelper.cs index a7d9d3512..b0c48b033 100644 --- a/src/FluentNHibernate/Utils/Reflection/ReflectionHelper.cs +++ b/src/FluentNHibernate/Utils/Reflection/ReflectionHelper.cs @@ -83,10 +83,29 @@ private static PropertyInfo GetDynamicComponentProperty(Expression expression) desiredConversionType = unaryExpression.Type; nextOperand = unaryExpression.Operand; } - - var constExpression = methodCallExpression.Arguments[0] as ConstantExpression; - - return new DummyPropertyInfo((string)constExpression.Value, desiredConversionType); + var expr = DynamicComponentPropertyVisitor.Visit(methodCallExpression.Arguments[0]); + var value = (expr as ConstantExpression)?.Value; + + return new DummyPropertyInfo((string)value, desiredConversionType); + } + private static readonly Visitor DynamicComponentPropertyVisitor = new Visitor(); + private class Visitor:ExpressionVisitor + { + protected override Expression VisitMember(MemberExpression memberExpression) + { + var expression = Visit(memberExpression.Expression); + object container = expression is ConstantExpression constantExpression ? constantExpression.Value : null; + var member = memberExpression.Member; + switch (member) + { + case FieldInfo fieldInfo: + return Expression.Constant(fieldInfo.GetValue(container)); + case PropertyInfo propertyInfo: + return Expression.Constant(propertyInfo.GetValue(container, null)); + default: + return base.VisitMember(memberExpression); + } + } } private static MemberExpression GetMemberExpression(Expression expression)