From 3a9f2608a79453fd15a4d7267dbfabdb09844e58 Mon Sep 17 00:00:00 2001 From: Ricardo Peres Date: Wed, 20 Aug 2014 11:39:06 +0100 Subject: [PATCH 1/6] NH-3670 - Add ability to specify generic dictionary for dynamic component - Created additional tests with generic properties for NH-1796, NH-1039, NH-3571 and NH-2664 --- .../DynamicComponentMappingTests.cs | 91 ++++++++++-- .../AbstractPropertyContainerMapperTest.cs | 23 +++ .../ComponentPropertyOnDynamicCompoTests.cs | 53 +++++++ ...DynComponentPropertyOnDynamicCompoTests.cs | 54 +++++++ .../NHSpecificTest/NH1039Generic/Fixture.cs | 57 ++++++++ .../NH1039Generic/Mappings.hbm.xml | 21 +++ .../NHSpecificTest/NH1039Generic/Model.cs | 35 +++++ .../NHSpecificTest/NH1796Generic/Entity.cs | 12 ++ .../NHSpecificTest/NH1796Generic/Fixture.cs | 65 +++++++++ .../NH1796Generic/Mappings.hbm.xml | 16 +++ .../NHSpecificTest/NH2664Generic/Fixture.cs | 134 ++++++++++++++++++ .../NH2664Generic/Mappings.hbm.xml | 17 +++ .../NHSpecificTest/NH2664Generic/Product.cs | 24 ++++ .../NHSpecificTest/NH3571Generic/Fixture.cs | 122 ++++++++++++++++ .../NH3571Generic/Mappings.hbm.xml | 17 +++ .../NHSpecificTest/NH3571Generic/Product.cs | 43 ++++++ src/NHibernate/Linq/Visitors/VisitorUtil.cs | 2 +- .../ByCode/IPlainPropertyContainerMapper.cs | 4 + .../PropertyContainerCustomizer.cs | 15 ++ .../Component/DynamicMapComponentTuplizer.cs | 35 ++++- .../Tuple/DynamicMapInstantiator.cs | 15 +- 21 files changed, 841 insertions(+), 14 deletions(-) create mode 100644 src/NHibernate.Test/NHSpecificTest/NH1039Generic/Fixture.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/NH1039Generic/Mappings.hbm.xml create mode 100644 src/NHibernate.Test/NHSpecificTest/NH1039Generic/Model.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/NH1796Generic/Entity.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/NH1796Generic/Fixture.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/NH1796Generic/Mappings.hbm.xml create mode 100644 src/NHibernate.Test/NHSpecificTest/NH2664Generic/Fixture.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/NH2664Generic/Mappings.hbm.xml create mode 100644 src/NHibernate.Test/NHSpecificTest/NH2664Generic/Product.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/NH3571Generic/Fixture.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/NH3571Generic/Mappings.hbm.xml create mode 100644 src/NHibernate.Test/NHSpecificTest/NH3571Generic/Product.cs diff --git a/src/NHibernate.Test/MappingByCode/ExplicitMappingTests/DynamicComponentMappingTests.cs b/src/NHibernate.Test/MappingByCode/ExplicitMappingTests/DynamicComponentMappingTests.cs index c5db3e91f91..457107d5a4e 100644 --- a/src/NHibernate.Test/MappingByCode/ExplicitMappingTests/DynamicComponentMappingTests.cs +++ b/src/NHibernate.Test/MappingByCode/ExplicitMappingTests/DynamicComponentMappingTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Linq; using NHibernate.Cfg.MappingSchema; using NHibernate.Mapping.ByCode; @@ -21,6 +22,17 @@ public IDictionary Info } } + private class PersonWithGenericInfo + { + public int Id { get; set; } + private IDictionary info; + public IDictionary Info + { + get { return info; } + set { info = value; } + } + } + [Test] public void WhenMapDynCompoThenMapItAndItsProperties() { @@ -38,6 +50,23 @@ public void WhenMapDynCompoThenMapItAndItsProperties() Assert.That(hbmDynamicComponent.Properties.Select(x=> x.Name), Is.EquivalentTo(new [] {"MyInt", "MyDate"})); } + [Test] + public void WhenMapDynCompoThenMapItAndItsPropertiesGeneric() + { + var mapper = new ModelMapper(); + mapper.Class(map => + { + map.Id(x => x.Id, idmap => { }); + map.Component(x => x.Info, new { MyInt = 5, MyDate = DateTime.Now }, z => { }); + }); + + var hbmMapping = mapper.CompileMappingFor(new[] { typeof(PersonWithGenericInfo) }); + var hbmClass = hbmMapping.RootClasses[0]; + var hbmDynamicComponent = hbmClass.Properties.OfType().SingleOrDefault(); + Assert.That(hbmDynamicComponent, Is.Not.Null); + Assert.That(hbmDynamicComponent.Properties.Select(x=> x.Name), Is.EquivalentTo(new [] {"MyInt", "MyDate"})); + } + [Test] public void WhenMapDynCompoPropertiesThenShouldAssignPropertyType() { @@ -54,6 +83,22 @@ public void WhenMapDynCompoPropertiesThenShouldAssignPropertyType() Assert.That(hbmDynamicComponent.Properties.OfType().All(x => !string.IsNullOrEmpty(x.type1)), Is.True); } + [Test] + public void WhenMapDynCompoPropertiesThenShouldAssignPropertyTypeGeneric() + { + var mapper = new ModelMapper(); + mapper.Class(map => + { + map.Id(x => x.Id, idmap => { }); + map.Component(x => x.Info, new { MyInt = 5, MyDate = DateTime.Now }, z => { }); + }); + + var hbmMapping = mapper.CompileMappingFor(new[] { typeof(PersonWithGenericInfo) }); + var hbmClass = hbmMapping.RootClasses[0]; + var hbmDynamicComponent = hbmClass.Properties.OfType().Single(); + Assert.That(hbmDynamicComponent.Properties.OfType().All(x => !string.IsNullOrEmpty(x.type1)), Is.True); + } + [Test] public void WhenMapDynCompoAttributesThenMapAttributes() { @@ -61,14 +106,17 @@ public void WhenMapDynCompoAttributesThenMapAttributes() mapper.Class(map => { map.Id(x => x.Id, idmap => { }); - map.Component(x => x.Info, new { MyInt = 5}, z => - { - z.Access(Accessor.Field); - z.Insert(false); - z.Update(false); - z.Unique(true); - z.OptimisticLock(false); - }); + map.Component( + x => x.Info, + new {MyInt = 5}, + z => + { + z.Access(Accessor.Field); + z.Insert(false); + z.Update(false); + z.Unique(true); + z.OptimisticLock(false); + }); }); var hbmMapping = mapper.CompileMappingFor(new[] { typeof(Person) }); @@ -80,5 +128,32 @@ public void WhenMapDynCompoAttributesThenMapAttributes() Assert.That(hbmDynamicComponent.optimisticlock, Is.False); Assert.That(hbmDynamicComponent.unique, Is.True); } + + [Test] + public void WhenMapDynCompoAttributesThenMapAttributesGeneric() + { + var mapper = new ModelMapper(); + mapper.Class(map => + { + map.Id(x => x.Id, idmap => { }); + map.Component(x => x.Info, new { MyInt = 5 }, z => + { + z.Access(Accessor.Field); + z.Insert(false); + z.Update(false); + z.Unique(true); + z.OptimisticLock(false); + }); + }); + + var hbmMapping = mapper.CompileMappingFor(new[] { typeof(PersonWithGenericInfo) }); + var hbmClass = hbmMapping.RootClasses[0]; + var hbmDynamicComponent = hbmClass.Properties.OfType().SingleOrDefault(); + Assert.That(hbmDynamicComponent.access, Does.Contain("field")); + Assert.That(hbmDynamicComponent.insert, Is.False); + Assert.That(hbmDynamicComponent.update, Is.False); + Assert.That(hbmDynamicComponent.optimisticlock, Is.False); + Assert.That(hbmDynamicComponent.unique, Is.True); + } } } diff --git a/src/NHibernate.Test/MappingByCode/MappersTests/AbstractPropertyContainerMapperTest.cs b/src/NHibernate.Test/MappingByCode/MappersTests/AbstractPropertyContainerMapperTest.cs index 2e2201019a1..90288d083f2 100644 --- a/src/NHibernate.Test/MappingByCode/MappersTests/AbstractPropertyContainerMapperTest.cs +++ b/src/NHibernate.Test/MappingByCode/MappersTests/AbstractPropertyContainerMapperTest.cs @@ -37,6 +37,10 @@ private class MyClassWithDynamic { public IDictionary DynCompo { get; set; } } + private class MyClassWithDynamicGeneric + { + public IDictionary DynCompo { get; set; } + } [Test] public void CantCreateWithoutHbmMapping() @@ -126,6 +130,15 @@ public void AddDynamicComponentProperty() Assert.That(properties.Single(), Is.TypeOf().And.Property("Name").EqualTo("DynCompo")); } + [Test] + public void AddDynamicComponentPropertyGeneric() + { + var properties = new List(); + var map = new StubPropertyContainerMapper(properties); + map.Component(For.Property(x => x.DynCompo), (IDynamicComponentMapper cp) => { }); + Assert.That(properties.Single(), Is.TypeOf().And.Property("Name").EqualTo("DynCompo")); + } + [Test] public void CallDynamicComponentMapper() { @@ -136,6 +149,16 @@ public void CallDynamicComponentMapper() Assert.That(called, Is.True); } + [Test] + public void CallDynamicComponentMapperGeneric() + { + var properties = new List(); + var map = new StubPropertyContainerMapper(properties); + var called = false; + map.Component(For.Property(x => x.DynCompo), (IDynamicComponentMapper cp) => called = true); + Assert.That(called, Is.True); + } + private class HackPropertyContainerMapper : AbstractPropertyContainerMapper { public HackPropertyContainerMapper(System.Type container, HbmMapping mapDoc) : base(container, mapDoc) {} diff --git a/src/NHibernate.Test/MappingByCode/MappersTests/DynamicComponentMapperTests/ComponentPropertyOnDynamicCompoTests.cs b/src/NHibernate.Test/MappingByCode/MappersTests/DynamicComponentMapperTests/ComponentPropertyOnDynamicCompoTests.cs index 3eb10a3163b..29962a22737 100644 --- a/src/NHibernate.Test/MappingByCode/MappersTests/DynamicComponentMapperTests/ComponentPropertyOnDynamicCompoTests.cs +++ b/src/NHibernate.Test/MappingByCode/MappersTests/DynamicComponentMapperTests/ComponentPropertyOnDynamicCompoTests.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Collections.Generic; using System.Linq; using NHibernate.Cfg.MappingSchema; using NHibernate.Mapping.ByCode; @@ -23,6 +24,18 @@ public IDictionary Info } } + private class PersonWithGenericInfo + { + public int Id { get; set; } +#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value + private IDictionary info; +#pragma warning restore CS0649 // Field is never assigned to, and will always have its default value + public IDictionary Info + { + get { return info; } + } + } + private class MyClass { public int Something { get; set; } @@ -40,6 +53,19 @@ public void WhenAddThenHas() Assert.That(component.Properties.Select(x => x.Name), Is.EquivalentTo(new[] { "A" })); } + [Test] + public void WhenAddThenHasGeneric() + { + var mapdoc = new HbmMapping(); + var component = new HbmDynamicComponent(); + var mapper = new DynamicComponentMapper(component, For.Property(p => p.Info), mapdoc); + var propertyInfo = (new { A = (MyClass)null }).GetType().GetProperty("A"); + + mapper.Component(propertyInfo, (IComponentMapper x) => { }); + + Assert.That(component.Properties.Select(x => x.Name), Is.EquivalentTo(new[] { "A" })); + } + [Test] public void WhenCustomizeThenCallCustomizer() { @@ -54,6 +80,20 @@ public void WhenCustomizeThenCallCustomizer() Assert.That(called, Is.True); } + [Test] + public void WhenCustomizeThenCallCustomizerGeneric() + { + var mapdoc = new HbmMapping(); + var component = new HbmDynamicComponent(); + var mapper = new DynamicComponentMapper(component, For.Property(p => p.Info), mapdoc); + var propertyInfo = (new { A = (MyClass)null }).GetType().GetProperty("A"); + + var called = false; + mapper.Component(propertyInfo, (IComponentMapper x) => called = true); + + Assert.That(called, Is.True); + } + [Test] public void WhenCustomizeAccessorThenIgnore() { @@ -66,5 +106,18 @@ public void WhenCustomizeAccessorThenIgnore() Assert.That(component.Properties.OfType().Single().Access, Is.Null.Or.Empty); } + + [Test] + public void WhenCustomizeAccessorThenIgnoreGeneric() + { + var mapdoc = new HbmMapping(); + var component = new HbmDynamicComponent(); + var mapper = new DynamicComponentMapper(component, For.Property(p => p.Info), mapdoc); + var propertyInfo = (new { A = (MyClass)null }).GetType().GetProperty("A"); + + mapper.Component(propertyInfo, (IComponentMapper x) => x.Access(Accessor.Field)); + + Assert.That(component.Properties.OfType().Single().Access, Is.Null.Or.Empty); + } } } diff --git a/src/NHibernate.Test/MappingByCode/MappersTests/DynamicComponentMapperTests/DynComponentPropertyOnDynamicCompoTests.cs b/src/NHibernate.Test/MappingByCode/MappersTests/DynamicComponentMapperTests/DynComponentPropertyOnDynamicCompoTests.cs index cb4f8452b28..8d8fc4fcd50 100644 --- a/src/NHibernate.Test/MappingByCode/MappersTests/DynamicComponentMapperTests/DynComponentPropertyOnDynamicCompoTests.cs +++ b/src/NHibernate.Test/MappingByCode/MappersTests/DynamicComponentMapperTests/DynComponentPropertyOnDynamicCompoTests.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Collections.Generic; using System.Linq; using NHibernate.Cfg.MappingSchema; using NHibernate.Mapping.ByCode; @@ -23,6 +24,19 @@ public IDictionary Info } } + private class PersonWithGenericInfo + { + public int Id { get; set; } +#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value + private IDictionary info; +#pragma warning restore CS0649 // Field is never assigned to, and will always have its default value + public IDictionary Info + { + get { return info; } + } + } + + [Test] public void WhenAddThenHas() { @@ -36,6 +50,19 @@ public void WhenAddThenHas() Assert.That(component.Properties.Select(x => x.Name), Is.EquivalentTo(new[] { "Info" })); } + [Test] + public void WhenAddThenHasGeneric() + { + var mapdoc = new HbmMapping(); + var component = new HbmDynamicComponent(); + var mapper = new DynamicComponentMapper(component, For.Property(p => p.Info), mapdoc); + var propertyInfo = For.Property(p => p.Info);//just as another dyn-compo + + mapper.Component(propertyInfo, (IDynamicComponentMapper x) => { }); + + Assert.That(component.Properties.Select(x => x.Name), Is.EquivalentTo(new[] { "Info" })); + } + [Test] public void WhenCustomizeThenCallCustomizer() { @@ -50,6 +77,20 @@ public void WhenCustomizeThenCallCustomizer() Assert.That(called, Is.True); } + [Test] + public void WhenCustomizeThenCallCustomizerGeneric() + { + var mapdoc = new HbmMapping(); + var component = new HbmDynamicComponent(); + var mapper = new DynamicComponentMapper(component, For.Property(p => p.Info), mapdoc); + var propertyInfo = For.Property(p => p.Info);//just as another dyn-compo + + var called = false; + mapper.Component(propertyInfo, (IDynamicComponentMapper x) => called = true); + + Assert.That(called, Is.True); + } + [Test] public void WhenCustomizeAccessorThenIgnore() { @@ -62,5 +103,18 @@ public void WhenCustomizeAccessorThenIgnore() Assert.That(component.Properties.OfType().Single().Access, Is.Null.Or.Empty); } + + [Test] + public void WhenCustomizeAccessorThenIgnoreGeneric() + { + var mapdoc = new HbmMapping(); + var component = new HbmDynamicComponent(); + var mapper = new DynamicComponentMapper(component, For.Property(p => p.Info), mapdoc); + var propertyInfo = For.Property(p => p.Info);//just as another dyn-compo + + mapper.Component(propertyInfo, (IDynamicComponentMapper x) => x.Access(Accessor.Field)); + + Assert.That(component.Properties.OfType().Single().Access, Is.Null.Or.Empty); + } } } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1039Generic/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1039Generic/Fixture.cs new file mode 100644 index 00000000000..5322334e3ec --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH1039Generic/Fixture.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; + + +namespace NHibernate.Test.NHSpecificTest.NH1039Generic +{ + [TestFixture] + public class Fixture : BugTestCase + { + public override string BugNumber + { + get { return "NH1039Generic"; } + } + + protected override void OnTearDown() + { + base.OnTearDown(); + using (ISession s = OpenSession()) + using (ITransaction tx = s.BeginTransaction()) + { + s.Delete("from Person"); + tx.Commit(); + } + } + + [Test] + public void test() + { + using (ISession s = OpenSession()) + using (ITransaction tx = s.BeginTransaction()) + { + Person person = new Person("1"); + person.Name = "John Doe"; + var set = new HashSet(); + set.Add("555-1234"); + set.Add("555-4321"); + person.Properties.Add("Phones", set); + + s.Save(person); + tx.Commit(); + } + using (ISession s = OpenSession()) + using (ITransaction tx = s.BeginTransaction()) + { + Person person = (Person)s.CreateCriteria(typeof(Person)).UniqueResult(); + + Assert.AreEqual("1", person.ID); + Assert.AreEqual("John Doe", person.Name); + Assert.AreEqual(1, person.Properties.Count); + Assert.That(person.Properties["Phones"], Is.InstanceOf>()); + Assert.IsTrue(((ISet) person.Properties["Phones"]).Contains("555-1234")); + Assert.IsTrue(((ISet) person.Properties["Phones"]).Contains("555-4321")); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1039Generic/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/NH1039Generic/Mappings.hbm.xml new file mode 100644 index 00000000000..5f6c057add1 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH1039Generic/Mappings.hbm.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/NHibernate.Test/NHSpecificTest/NH1039Generic/Model.cs b/src/NHibernate.Test/NHSpecificTest/NH1039Generic/Model.cs new file mode 100644 index 00000000000..44f1d986396 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH1039Generic/Model.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +namespace NHibernate.Test.NHSpecificTest.NH1039Generic +{ + public class Person + { + public Person() { } + public Person(string id) { this._ID = id; } + + private string _ID; + public virtual string ID + { + get { return _ID; } + set { _ID = value; } + } + + private string _Name; + public virtual string Name + { + get { return _Name; } + set { _Name = value; } + } + + private IDictionary _Properties = new Dictionary(); + public virtual IDictionary Properties + { + get { return _Properties; } + set { _Properties = value; } + } + } + +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1796Generic/Entity.cs b/src/NHibernate.Test/NHSpecificTest/NH1796Generic/Entity.cs new file mode 100644 index 00000000000..36b8bf31c54 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH1796Generic/Entity.cs @@ -0,0 +1,12 @@ +using System.Collections; +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.NH1796Generic +{ + public class Entity + { + public virtual int Id { get; set; } + public virtual string Name { get; set; } + public virtual IDictionary DynProps { get; set; } + } +} \ No newline at end of file diff --git a/src/NHibernate.Test/NHSpecificTest/NH1796Generic/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1796Generic/Fixture.cs new file mode 100644 index 00000000000..99d927d9cc3 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH1796Generic/Fixture.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.NH1796Generic +{ + [TestFixture] + public class Fixture: BugTestCase + { + [Test] + public void Merge() + { + var entity = new Entity { Name = "Vinnie Luther" }; + using (ISession s = OpenSession()) + using (ITransaction t = s.BeginTransaction()) + { + s.Save(entity); + t.Commit(); + } + + entity.DynProps = new Dictionary(); + entity.DynProps["StrProp"] = "Modified"; + using (ISession s = OpenSession()) + using (ITransaction t = s.BeginTransaction()) + { + s.Merge(entity); + t.Commit(); + } + + using (ISession s = OpenSession()) + using (ITransaction t = s.BeginTransaction()) + { + s.CreateQuery("delete from Entity").ExecuteUpdate(); + t.Commit(); + } + } + + [Test] + public void SaveOrUpdate() + { + var entity = new Entity { Name = "Vinnie Luther" }; + using (ISession s = OpenSession()) + using (ITransaction t = s.BeginTransaction()) + { + s.SaveOrUpdate(entity); + t.Commit(); + } + + entity.DynProps = new Dictionary(); + entity.DynProps["StrProp"] = "Modified"; + using (ISession s = OpenSession()) + using (ITransaction t = s.BeginTransaction()) + { + s.SaveOrUpdate(entity); + t.Commit(); + } + + using (ISession s = OpenSession()) + using (ITransaction t = s.BeginTransaction()) + { + s.CreateQuery("delete from Entity").ExecuteUpdate(); + t.Commit(); + } + } + } +} \ No newline at end of file diff --git a/src/NHibernate.Test/NHSpecificTest/NH1796Generic/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/NH1796Generic/Mappings.hbm.xml new file mode 100644 index 00000000000..dcdbc19a554 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH1796Generic/Mappings.hbm.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/src/NHibernate.Test/NHSpecificTest/NH2664Generic/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2664Generic/Fixture.cs new file mode 100644 index 00000000000..0a51915da76 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH2664Generic/Fixture.cs @@ -0,0 +1,134 @@ +using System.Collections; +using System.Linq; +using NHibernate.Linq; +using NUnit.Framework; +using System.Linq.Expressions; +using System; + +namespace NHibernate.Test.NHSpecificTest.NH2664Generic +{ + [TestFixture] + public class Fixture : TestCase + { + protected override string MappingsAssembly => "NHibernate.Test"; + + protected override IList Mappings => new[] {"NHSpecificTest.NH2664Generic.Mappings.hbm.xml"}; + + /// + /// push some data into the database + /// Really functions as a save test also + /// + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + session.Save( + new Product + { + ProductId = "1", + Properties = + { + ["Name"] = "First Product", + ["Description"] = "First Description" + } + }); + + session.Save( + new Product + { + ProductId = "2", + Properties = + { + ["Name"] = "Second Product", + ["Description"] = "Second Description" + } + }); + + session.Save( + new Product + { + ProductId = "3", + Properties = + { + ["Name"] = "val", + ["Description"] = "val" + } + }); + + tran.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + session.CreateQuery("delete from Product").ExecuteUpdate(); + tran.Commit(); + } + } + + [Test] + public void Query_DynamicComponent() + { + using (var session = OpenSession()) + { + var product = ( + from p in session.Query() + where (string) p.Properties["Name"] == "First Product" + select p + ).Single(); + + Assert.That(product, Is.Not.Null); + Assert.That(product.Properties["Name"], Is.EqualTo("First Product")); + } + } + + [Test] + public void Multiple_Query_Does_Not_Cache() + { + using (var session = OpenSession()) + { + // Query by name + var product1 = ( + from p in session.Query() + where (string) p.Properties["Name"] == "First Product" + select p + ).Single(); + Assert.That(product1.ProductId, Is.EqualTo("1")); + + // Query by description (this test is to verify that the dictionary + // index isn't cached from the query above. + var product2 = ( + from p in session.Query() + where (string) p.Properties["Description"] == "Second Description" + select p + ).Single(); + Assert.That(product2.ProductId, Is.EqualTo("2")); + } + } + + [Test] + public void Different_Key_In_DynamicComponentDictionary_Returns_Different_Keys() + { + using (var session = OpenSession()) + { + Expression> key1 = () => + from a in session.Query() + where (string) a.Properties["Name"] == "val" + select a; + Expression> key2 = () => + from a in session.Query() + where (string) a.Properties["Description"] == "val" + select a; + + var nhKey1 = new NhLinqExpression(key1.Body, Sfi); + var nhKey2 = new NhLinqExpression(key2.Body, Sfi); + + Assert.That(nhKey2.Key, Is.Not.EqualTo(nhKey1.Key)); + } + } + } +} \ No newline at end of file diff --git a/src/NHibernate.Test/NHSpecificTest/NH2664Generic/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/NH2664Generic/Mappings.hbm.xml new file mode 100644 index 00000000000..e49ef76e13f --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH2664Generic/Mappings.hbm.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/NHibernate.Test/NHSpecificTest/NH2664Generic/Product.cs b/src/NHibernate.Test/NHSpecificTest/NH2664Generic/Product.cs new file mode 100644 index 00000000000..aa972b932ab --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH2664Generic/Product.cs @@ -0,0 +1,24 @@ +using System.Collections; +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.NH2664Generic +{ + public class Product + { + public virtual string ProductId { get; set; } + + private IDictionary _properties; + + public virtual IDictionary Properties + { + get + { + if (_properties == null) + _properties = new Dictionary(); + + return _properties; + } + set { _properties = value; } + } + } +} \ No newline at end of file diff --git a/src/NHibernate.Test/NHSpecificTest/NH3571Generic/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH3571Generic/Fixture.cs new file mode 100644 index 00000000000..1a3a396287d --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3571Generic/Fixture.cs @@ -0,0 +1,122 @@ +using System.Collections; +using System.Linq; +using NHibernate.Linq; +using NUnit.Framework; +using System.Linq.Expressions; +using System; + +namespace NHibernate.Test.NHSpecificTest.NH3571Generic +{ + [TestFixture] + public class Fixture : TestCase + { + protected override string MappingsAssembly => "NHibernate.Test"; + + protected override IList Mappings => new[] {"NHSpecificTest.NH3571Generic.Mappings.hbm.xml"}; + + /// + /// push some data into the database + /// Really functions as a save test also + /// + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + var product = new Product {ProductId = "1"}; + product.Details.Properties["Name"] = "First Product"; + product.Details.Properties["Description"] = "First Description"; + + session.Save(product); + + product = new Product {ProductId = "2"}; + product.Details.Properties["Name"] = "Second Product"; + product.Details.Properties["Description"] = "Second Description"; + + session.Save(product); + + product = new Product {ProductId = "3"}; + product.Details.Properties["Name"] = "val"; + product.Details.Properties["Description"] = "val"; + + session.Save(product); + + tran.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + session.CreateQuery("delete from Product").ExecuteUpdate(); + tran.Commit(); + } + } + + [Test] + public void CanQueryDynamicComponentInComponent() + { + using (var session = OpenSession()) + { + var product = ( + from p in session.Query() + where (string) p.Details.Properties["Name"] == "First Product" + select p + ).Single(); + + Assert.That(product, Is.Not.Null); + Assert.That(product.Details.Properties["Name"], Is.EqualTo("First Product")); + } + } + + [Test] + public void MultipleQueriesShouldNotCache() + { + using (var session = OpenSession()) + { + // Query by name + var product1 = ( + from p in session.Query() + where (string) p.Details.Properties["Name"] == "First Product" + select p + ).Single(); + Assert.That(product1.ProductId, Is.EqualTo("1")); + + // Query by description (this test is to verify that the dictionary + // index isn't cached from the query above. + var product2 = ( + from p in session.Query() + where (string) p.Details.Properties["Description"] == "Second Description" + select p + ).Single(); + Assert.That(product2.ProductId, Is.EqualTo("2")); + } + } + + [Test] + public void DifferentKeyInDynamicComponentDictionaryReturnsDifferentExpressionKeys() + { + using (var session = OpenSession()) + { +// ReSharper disable AccessToDisposedClosure Ok since the expressions aren't actually used after the using block. + Expression> key1 = () => ( + from a in session.Query() + where (string) a.Details.Properties["Name"] == "val" + select a + ); + Expression> key2 = () => ( + from a in session.Query() + where (string) a.Details.Properties["Description"] == "val" + select a); +// ReSharper restore AccessToDisposedClosure + + var nhKey1 = new NhLinqExpression(key1.Body, Sfi); + var nhKey2 = new NhLinqExpression(key2.Body, Sfi); + + Assert.That(nhKey2.Key, Is.Not.EqualTo(nhKey1.Key)); + } + } + } +} \ No newline at end of file diff --git a/src/NHibernate.Test/NHSpecificTest/NH3571Generic/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/NH3571Generic/Mappings.hbm.xml new file mode 100644 index 00000000000..dcd0da1877e --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3571Generic/Mappings.hbm.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/NHibernate.Test/NHSpecificTest/NH3571Generic/Product.cs b/src/NHibernate.Test/NHSpecificTest/NH3571Generic/Product.cs new file mode 100644 index 00000000000..c70990576a6 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3571Generic/Product.cs @@ -0,0 +1,43 @@ +using System.Collections; +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.NH3571Generic +{ + public class Product + { + public Product() + { + Details = new ProductDetails(); + } + public virtual string ProductId { get; set; } + + public virtual ProductDetails Details + { + get; + set; + } + + public virtual IList DetailsList + { + get; + set; + } + } + + public class ProductDetails + { + private IDictionary _properties; + + public virtual IDictionary Properties + { + get + { + if (_properties == null) + _properties = new Dictionary(); + + return _properties; + } + set { _properties = value; } + } + } +} \ No newline at end of file diff --git a/src/NHibernate/Linq/Visitors/VisitorUtil.cs b/src/NHibernate/Linq/Visitors/VisitorUtil.cs index 6175dcf374b..c4fa337d572 100644 --- a/src/NHibernate/Linq/Visitors/VisitorUtil.cs +++ b/src/NHibernate/Linq/Visitors/VisitorUtil.cs @@ -17,7 +17,7 @@ public static bool IsDynamicComponentDictionaryGetter(MethodInfo method, Express // A dynamic component must be an IDictionary with a string key. - if (method.Name != "get_Item" || !typeof(IDictionary).IsAssignableFrom(targetObject.Type)) + if (method.Name != "get_Item" || !typeof(IDictionary).IsAssignableFrom(targetObject.Type) && !typeof(IDictionary).IsAssignableFrom(targetObject.Type)) return false; var key = arguments.First() as ConstantExpression; diff --git a/src/NHibernate/Mapping/ByCode/IPlainPropertyContainerMapper.cs b/src/NHibernate/Mapping/ByCode/IPlainPropertyContainerMapper.cs index 115eae3ef56..b9d57a96824 100644 --- a/src/NHibernate/Mapping/ByCode/IPlainPropertyContainerMapper.cs +++ b/src/NHibernate/Mapping/ByCode/IPlainPropertyContainerMapper.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; @@ -40,6 +41,9 @@ public interface IBasePlainPropertyContainerMapper : IMinimalPlainPr void Component(Expression> property, Action> mapping); void Component(Expression> property); void Component(Expression> property, TComponent dynamicComponentTemplate, Action> mapping); + void Component(Expression>> property, + TComponent dynamicComponentTemplate, + Action> mapping) where TComponent : class; void Component(string notVisiblePropertyOrFieldName, Action> mapping); void Component(string notVisiblePropertyOrFieldName); diff --git a/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/PropertyContainerCustomizer.cs b/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/PropertyContainerCustomizer.cs index 3ec9c7cde69..5a9aef39926 100644 --- a/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/PropertyContainerCustomizer.cs +++ b/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/PropertyContainerCustomizer.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -98,6 +99,13 @@ public void Component(Expression> propert RegisterDynamicComponentMapping(property, mapping); } + public void Component(Expression>> property, + TComponent dynamicComponentTemplate, + Action> mapping) where TComponent : class + { + RegisterDynamicComponentMapping(property, mapping); + } + protected virtual void RegisterDynamicComponentMapping(Expression> property, Action> mapping) { MemberInfo member = TypeExtensions.DecodeMemberAccessExpression(property); @@ -105,6 +113,13 @@ protected virtual void RegisterDynamicComponentMapping(Expression(Expression>> property, Action> mapping) where TComponent : class + { + MemberInfo member = TypeExtensions.DecodeMemberAccessExpression(property); + MemberInfo memberOf = TypeExtensions.DecodeMemberAccessExpressionOf(property); + RegisterDynamicComponentMapping(mapping, member, memberOf); + } + protected void RegisterDynamicComponentMapping(Action> mapping, params MemberInfo[] members) { foreach (var member in members) diff --git a/src/NHibernate/Tuple/Component/DynamicMapComponentTuplizer.cs b/src/NHibernate/Tuple/Component/DynamicMapComponentTuplizer.cs index 681b0128cf3..8b6067094b7 100644 --- a/src/NHibernate/Tuple/Component/DynamicMapComponentTuplizer.cs +++ b/src/NHibernate/Tuple/Component/DynamicMapComponentTuplizer.cs @@ -1,5 +1,9 @@ using System; +using System.Linq; +using System.Collections.Generic; using NHibernate.Properties; +using System.Collections; +using System.Reflection; namespace NHibernate.Tuple.Component { @@ -9,21 +13,48 @@ namespace NHibernate.Tuple.Component [Serializable] public class DynamicMapComponentTuplizer : AbstractComponentTuplizer { + private readonly Mapping.Component component; + public DynamicMapComponentTuplizer(Mapping.Component component) : base(component) { + this.component = component; // Fix for NH-3119 instantiator = BuildInstantiator(component); } public override System.Type MappedClass { - get { return typeof(System.Collections.IDictionary); } + get + { + if ((this.component != null) && (this.component.Owner.MappedClass != null)) + { + var ownerFullName = this.component.Owner.MappedClass.FullName; + var roleName = this.component.RoleName; + var spare = roleName.Substring(ownerFullName.Length + 1); + var parts = spare.Split('.'); + var prop = null as PropertyInfo; + var owner = this.component.Owner.MappedClass; + + for (var i = 0; i < parts.Length; ++i) + { + prop = owner.GetProperty(parts[i], BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); + + owner = prop.PropertyType; + } + + return typeof(IDictionary).IsAssignableFrom(prop.PropertyType) ? typeof(IDictionary) : typeof(IDictionary); + } + else + { + return typeof(IDictionary); + } + } } protected internal override IInstantiator BuildInstantiator(Mapping.Component component) { - return new DynamicMapInstantiator(); + return new DynamicMapInstantiator(component); } protected internal override IGetter BuildGetter(Mapping.Component component, Mapping.Property prop) diff --git a/src/NHibernate/Tuple/DynamicMapInstantiator.cs b/src/NHibernate/Tuple/DynamicMapInstantiator.cs index 0511ea3c7e6..36bb2d52ac2 100644 --- a/src/NHibernate/Tuple/DynamicMapInstantiator.cs +++ b/src/NHibernate/Tuple/DynamicMapInstantiator.cs @@ -12,10 +12,12 @@ public class DynamicMapInstantiator : IInstantiator private readonly string entityName; private readonly HashSet isInstanceEntityNames = new HashSet(); + private readonly Mapping.Component component; - public DynamicMapInstantiator() + public DynamicMapInstantiator(Mapping.Component component) { entityName = null; + this.component = component; } public DynamicMapInstantiator(PersistentClass mappingInfo) @@ -48,7 +50,14 @@ public object Instantiate() protected virtual IDictionary GenerateMap() { - return new Hashtable(); + if ((this.component != null) && (typeof(IDictionary).IsAssignableFrom(this.component.Type.ReturnedClass) == true)) + { + return new Dictionary(); + } + else + { + return new Hashtable(); + } } public bool IsInstance(object obj) @@ -65,7 +74,7 @@ public bool IsInstance(object obj) } else { - return false; + return obj is IDictionary; } } From b248c1090af3d08b6d8817bef2ab8be33cc25500 Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Thu, 7 Jun 2018 00:43:47 +1200 Subject: [PATCH 2/6] Generate Async tests --- .../NHSpecificTest/NH1039Generic/Fixture.cs | 68 ++++++++++ .../NHSpecificTest/NH1796Generic/Fixture.cs | 76 +++++++++++ .../NHSpecificTest/NH2664Generic/Fixture.cs | 124 ++++++++++++++++++ .../NHSpecificTest/NH3571Generic/Fixture.cs | 109 +++++++++++++++ 4 files changed, 377 insertions(+) create mode 100644 src/NHibernate.Test/Async/NHSpecificTest/NH1039Generic/Fixture.cs create mode 100644 src/NHibernate.Test/Async/NHSpecificTest/NH1796Generic/Fixture.cs create mode 100644 src/NHibernate.Test/Async/NHSpecificTest/NH2664Generic/Fixture.cs create mode 100644 src/NHibernate.Test/Async/NHSpecificTest/NH3571Generic/Fixture.cs diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1039Generic/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1039Generic/Fixture.cs new file mode 100644 index 00000000000..dc9c9831243 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1039Generic/Fixture.cs @@ -0,0 +1,68 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System; +using System.Collections.Generic; +using NUnit.Framework; + + +namespace NHibernate.Test.NHSpecificTest.NH1039Generic +{ + using System.Threading.Tasks; + [TestFixture] + public class FixtureAsync : BugTestCase + { + public override string BugNumber + { + get { return "NH1039Generic"; } + } + + protected override void OnTearDown() + { + base.OnTearDown(); + using (ISession s = OpenSession()) + using (ITransaction tx = s.BeginTransaction()) + { + s.Delete("from Person"); + tx.Commit(); + } + } + + [Test] + public async Task testAsync() + { + using (ISession s = OpenSession()) + using (ITransaction tx = s.BeginTransaction()) + { + Person person = new Person("1"); + person.Name = "John Doe"; + var set = new HashSet(); + set.Add("555-1234"); + set.Add("555-4321"); + person.Properties.Add("Phones", set); + + await (s.SaveAsync(person)); + await (tx.CommitAsync()); + } + using (ISession s = OpenSession()) + using (ITransaction tx = s.BeginTransaction()) + { + Person person = (Person)await (s.CreateCriteria(typeof(Person)).UniqueResultAsync()); + + Assert.AreEqual("1", person.ID); + Assert.AreEqual("John Doe", person.Name); + Assert.AreEqual(1, person.Properties.Count); + Assert.That(person.Properties["Phones"], Is.InstanceOf>()); + Assert.IsTrue(((ISet) person.Properties["Phones"]).Contains("555-1234")); + Assert.IsTrue(((ISet) person.Properties["Phones"]).Contains("555-4321")); + } + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1796Generic/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1796Generic/Fixture.cs new file mode 100644 index 00000000000..7d31066907f --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1796Generic/Fixture.cs @@ -0,0 +1,76 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System.Collections.Generic; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.NH1796Generic +{ + using System.Threading.Tasks; + [TestFixture] + public class FixtureAsync: BugTestCase + { + [Test] + public async Task MergeAsync() + { + var entity = new Entity { Name = "Vinnie Luther" }; + using (ISession s = OpenSession()) + using (ITransaction t = s.BeginTransaction()) + { + await (s.SaveAsync(entity)); + await (t.CommitAsync()); + } + + entity.DynProps = new Dictionary(); + entity.DynProps["StrProp"] = "Modified"; + using (ISession s = OpenSession()) + using (ITransaction t = s.BeginTransaction()) + { + await (s.MergeAsync(entity)); + await (t.CommitAsync()); + } + + using (ISession s = OpenSession()) + using (ITransaction t = s.BeginTransaction()) + { + await (s.CreateQuery("delete from Entity").ExecuteUpdateAsync()); + await (t.CommitAsync()); + } + } + + [Test] + public async Task SaveOrUpdateAsync() + { + var entity = new Entity { Name = "Vinnie Luther" }; + using (ISession s = OpenSession()) + using (ITransaction t = s.BeginTransaction()) + { + await (s.SaveOrUpdateAsync(entity)); + await (t.CommitAsync()); + } + + entity.DynProps = new Dictionary(); + entity.DynProps["StrProp"] = "Modified"; + using (ISession s = OpenSession()) + using (ITransaction t = s.BeginTransaction()) + { + await (s.SaveOrUpdateAsync(entity)); + await (t.CommitAsync()); + } + + using (ISession s = OpenSession()) + using (ITransaction t = s.BeginTransaction()) + { + await (s.CreateQuery("delete from Entity").ExecuteUpdateAsync()); + await (t.CommitAsync()); + } + } + } +} \ No newline at end of file diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2664Generic/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2664Generic/Fixture.cs new file mode 100644 index 00000000000..8080952e1f9 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2664Generic/Fixture.cs @@ -0,0 +1,124 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System.Collections; +using System.Linq; +using NHibernate.Linq; +using NUnit.Framework; +using System.Linq.Expressions; +using System; + +namespace NHibernate.Test.NHSpecificTest.NH2664Generic +{ + using System.Threading.Tasks; + [TestFixture] + public class FixtureAsync : TestCase + { + protected override string MappingsAssembly => "NHibernate.Test"; + + protected override IList Mappings => new[] {"NHSpecificTest.NH2664Generic.Mappings.hbm.xml"}; + + /// + /// push some data into the database + /// Really functions as a save test also + /// + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + session.Save( + new Product + { + ProductId = "1", + Properties = + { + ["Name"] = "First Product", + ["Description"] = "First Description" + } + }); + + session.Save( + new Product + { + ProductId = "2", + Properties = + { + ["Name"] = "Second Product", + ["Description"] = "Second Description" + } + }); + + session.Save( + new Product + { + ProductId = "3", + Properties = + { + ["Name"] = "val", + ["Description"] = "val" + } + }); + + tran.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + session.CreateQuery("delete from Product").ExecuteUpdate(); + tran.Commit(); + } + } + + [Test] + public async Task Query_DynamicComponentAsync() + { + using (var session = OpenSession()) + { + var product = await (( + from p in session.Query() + where (string) p.Properties["Name"] == "First Product" + select p + ).SingleAsync()); + + Assert.That(product, Is.Not.Null); + Assert.That(product.Properties["Name"], Is.EqualTo("First Product")); + } + } + + [Test] + public async Task Multiple_Query_Does_Not_CacheAsync() + { + using (var session = OpenSession()) + { + // Query by name + var product1 = await (( + from p in session.Query() + where (string) p.Properties["Name"] == "First Product" + select p + ).SingleAsync()); + Assert.That(product1.ProductId, Is.EqualTo("1")); + + // Query by description (this test is to verify that the dictionary + // index isn't cached from the query above. + var product2 = await (( + from p in session.Query() + where (string) p.Properties["Description"] == "Second Description" + select p + ).SingleAsync()); + Assert.That(product2.ProductId, Is.EqualTo("2")); + } + } + } +} \ No newline at end of file diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3571Generic/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3571Generic/Fixture.cs new file mode 100644 index 00000000000..3af2d88003a --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3571Generic/Fixture.cs @@ -0,0 +1,109 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System.Collections; +using System.Linq; +using NHibernate.Linq; +using NUnit.Framework; +using System.Linq.Expressions; +using System; + +namespace NHibernate.Test.NHSpecificTest.NH3571Generic +{ + using System.Threading.Tasks; + [TestFixture] + public class FixtureAsync : TestCase + { + protected override string MappingsAssembly => "NHibernate.Test"; + + protected override IList Mappings => new[] {"NHSpecificTest.NH3571Generic.Mappings.hbm.xml"}; + + /// + /// push some data into the database + /// Really functions as a save test also + /// + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + var product = new Product {ProductId = "1"}; + product.Details.Properties["Name"] = "First Product"; + product.Details.Properties["Description"] = "First Description"; + + session.Save(product); + + product = new Product {ProductId = "2"}; + product.Details.Properties["Name"] = "Second Product"; + product.Details.Properties["Description"] = "Second Description"; + + session.Save(product); + + product = new Product {ProductId = "3"}; + product.Details.Properties["Name"] = "val"; + product.Details.Properties["Description"] = "val"; + + session.Save(product); + + tran.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var tran = session.BeginTransaction()) + { + session.CreateQuery("delete from Product").ExecuteUpdate(); + tran.Commit(); + } + } + + [Test] + public async Task CanQueryDynamicComponentInComponentAsync() + { + using (var session = OpenSession()) + { + var product = await (( + from p in session.Query() + where (string) p.Details.Properties["Name"] == "First Product" + select p + ).SingleAsync()); + + Assert.That(product, Is.Not.Null); + Assert.That(product.Details.Properties["Name"], Is.EqualTo("First Product")); + } + } + + [Test] + public async Task MultipleQueriesShouldNotCacheAsync() + { + using (var session = OpenSession()) + { + // Query by name + var product1 = await (( + from p in session.Query() + where (string) p.Details.Properties["Name"] == "First Product" + select p + ).SingleAsync()); + Assert.That(product1.ProductId, Is.EqualTo("1")); + + // Query by description (this test is to verify that the dictionary + // index isn't cached from the query above. + var product2 = await (( + from p in session.Query() + where (string) p.Details.Properties["Description"] == "Second Description" + select p + ).SingleAsync()); + Assert.That(product2.ProductId, Is.EqualTo("2")); + } + } + } +} \ No newline at end of file From 04983f81df82eba1329cc57fa3d94c9195973621 Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Wed, 6 Jun 2018 22:37:45 +1200 Subject: [PATCH 3/6] Fix mapping dynamic component inside join --- .../JoinGenericDynamicComponentTests.cs | 195 ++++++++++++++++++ .../ByCode/IPlainPropertyContainerMapper.cs | 4 +- .../Impl/CustomizersImpl/JoinCustomizer.cs | 8 + .../PropertyContainerCustomizer.cs | 6 +- 4 files changed, 206 insertions(+), 7 deletions(-) create mode 100644 src/NHibernate.Test/MappingByCode/ExplicitMappingTests/JoinGenericDynamicComponentTests.cs diff --git a/src/NHibernate.Test/MappingByCode/ExplicitMappingTests/JoinGenericDynamicComponentTests.cs b/src/NHibernate.Test/MappingByCode/ExplicitMappingTests/JoinGenericDynamicComponentTests.cs new file mode 100644 index 00000000000..d21147c6acd --- /dev/null +++ b/src/NHibernate.Test/MappingByCode/ExplicitMappingTests/JoinGenericDynamicComponentTests.cs @@ -0,0 +1,195 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NHibernate.Mapping.ByCode.Conformist; +using NUnit.Framework; + +namespace NHibernate.Test.MappingByCode.ExplicitMappingTests +{ + [TestFixture] + public class JoinGenericDynamicComponentTests + { + public class MyClass + { + public virtual int Code { get; set; } + public virtual string JoinedName { get; set; } + public virtual MyOther Relation { get; set; } + public virtual MyOther JoinedRelation { get; set; } + public virtual IDictionary Attributes { get; set; } + public virtual IDictionary JoinedAttributes { get; set; } + } + + public class MyOther + { + public int Id { get; set; } + } + + public class MyClassMap : ClassMapping + { + public MyClassMap() + { + // basic table related properties + ManyToOne(x => x.Relation); + Component(p => p.Attributes, + new + { + IsVisible = false, + Hash = default(Guid), + Reference = default(MyOther) + }, + m => + { + m.Property(p => p.IsVisible); + m.Property(p => p.Hash); + m.ManyToOne(p => p.Reference); + }); + Property(x => x.Code); + + // joined table stuff + Join("JoinedAttributes", x => + { + x.Property(p => p.JoinedName); + x.Component(p => p.JoinedAttributes, + new + { + OtherReference = default(MyOther), + Description = string.Empty, + Age = 0, + }, + m => + { + m.ManyToOne(p => p.OtherReference); + m.Property(p => p.Description); + m.Property(p => p.Age); + }); + x.ManyToOne(p => p.JoinedRelation); + }); + } + } + + private static HbmClass CompileClassMapping() + { + var mapper = new ModelMapper(); + mapper.AddMapping(typeof(MyClassMap)); + + var hbmMapping = mapper.CompileMappingFor(new[] { typeof(MyClass) }); + var hbmClass = hbmMapping.RootClasses[0]; + + return hbmClass; + } + + [Test] + public void WhenPropertyIsMappedOnRootThenItBelongsToRootTable() + { + // + var hbmClass = CompileClassMapping(); + Assert.That(hbmClass, Is.Not.Null); + + var rootProperties = hbmClass.Properties; + // p.Name == "Code"); + + Assert.That(hbmPropCode, Is.Not.Null); + Assert.That(hbmPropCode, Is.TypeOf()); + } + + [Test] + public void WhenDynamicComponentIsMappedOnRootThenItBelongsToRootTable() + { + // + var hbmClass = CompileClassMapping(); + Assert.That(hbmClass, Is.Not.Null); + + var rootProperties = hbmClass.Properties; + // p.Name == "Attributes") + ; + + Assert.That(hbmPropAttributes, Is.Not.Null); + Assert.That(hbmPropAttributes, Is.TypeOf()); + } + + [Test] + public void WhenRelationIsMappedOnRootThenItBelongsToRootTable() + { + // + var hbmClass = CompileClassMapping(); + Assert.That(hbmClass, Is.Not.Null); + + var rootProperties = hbmClass.Properties; + // p.Name == "Relation"); + + Assert.That(hbmPropRelation, Is.Not.Null); + Assert.That(hbmPropRelation, Is.TypeOf()); + } + + [Test] + public void WhenJoinedPropertyIsMappedOnJoinThenItBelongsToJoinTable() + { + // + var hbmClass = CompileClassMapping(); + Assert.That(hbmClass, Is.Not.Null); + + // + var hbmJoined = hbmClass.Joins.FirstOrDefault(); + Assert.That(hbmJoined, Is.Not.Null); + + var rootProperties = hbmJoined.Properties; + // + // p.Name == "JoinedName"); + + Assert.That(hbmPropJoinedName, Is.Not.Null); + Assert.That(hbmPropJoinedName, Is.TypeOf()); + } + + [Test] + public void WhenJoinedRelationIsMappedOnJoinThenItBelongsToJoinTable() + { + // + var hbmClass = CompileClassMapping(); + Assert.That(hbmClass, Is.Not.Null); + + // + var hbmJoined = hbmClass.Joins.FirstOrDefault(); + Assert.That(hbmJoined, Is.Not.Null); + + var rootProperties = hbmJoined.Properties; + // + // p.Name == "JoinedRelation"); + + Assert.That(hbmPropJoinedRelation, Is.Not.Null); + Assert.That(hbmPropJoinedRelation, Is.TypeOf()); + } + + [Test] + public void WhenJoinedDynamicComponentIsMappedOnJoinThenItBelongsToJoinTable() + { + // + var hbmClass = CompileClassMapping(); + Assert.That(hbmClass, Is.Not.Null); + + // + var hbmJoined = hbmClass.Joins.FirstOrDefault(); + Assert.That(hbmJoined, Is.Not.Null); + + var rootProperties = hbmJoined.Properties; + // + // + var hbmPropJoinedAttributes = rootProperties + .FirstOrDefault(p => p.Name == "JoinedAttributes"); + + Assert.That(hbmPropJoinedAttributes, Is.Not.Null); + Assert.That(hbmPropJoinedAttributes, Is.TypeOf()); + } + } +} \ No newline at end of file diff --git a/src/NHibernate/Mapping/ByCode/IPlainPropertyContainerMapper.cs b/src/NHibernate/Mapping/ByCode/IPlainPropertyContainerMapper.cs index b9d57a96824..582f77ab79a 100644 --- a/src/NHibernate/Mapping/ByCode/IPlainPropertyContainerMapper.cs +++ b/src/NHibernate/Mapping/ByCode/IPlainPropertyContainerMapper.cs @@ -41,9 +41,7 @@ public interface IBasePlainPropertyContainerMapper : IMinimalPlainPr void Component(Expression> property, Action> mapping); void Component(Expression> property); void Component(Expression> property, TComponent dynamicComponentTemplate, Action> mapping); - void Component(Expression>> property, - TComponent dynamicComponentTemplate, - Action> mapping) where TComponent : class; + void Component(Expression>> property, TComponent dynamicComponentTemplate, Action> mapping) where TComponent : class; void Component(string notVisiblePropertyOrFieldName, Action> mapping); void Component(string notVisiblePropertyOrFieldName); diff --git a/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/JoinCustomizer.cs b/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/JoinCustomizer.cs index f7dbf87d8a1..8e09887f393 100644 --- a/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/JoinCustomizer.cs +++ b/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/JoinCustomizer.cs @@ -140,6 +140,14 @@ protected override void RegisterDynamicComponentMapping(Expression(Expression>> property + , Action> mapping) + { + MemberInfo member = TypeExtensions.DecodeMemberAccessExpression(property); + ExplicitDeclarationsHolder.AddAsPropertySplit(new SplitDefinition(typeof(TEntity), splitGroupId, member)); + base.RegisterDynamicComponentMapping(property, mapping); + } + protected override void RegisterManyToOneMapping(Expression> property, Action mapping) { MemberInfo member = TypeExtensions.DecodeMemberAccessExpression(property); diff --git a/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/PropertyContainerCustomizer.cs b/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/PropertyContainerCustomizer.cs index 5a9aef39926..b76e28f6eee 100644 --- a/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/PropertyContainerCustomizer.cs +++ b/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/PropertyContainerCustomizer.cs @@ -99,9 +99,7 @@ public void Component(Expression> propert RegisterDynamicComponentMapping(property, mapping); } - public void Component(Expression>> property, - TComponent dynamicComponentTemplate, - Action> mapping) where TComponent : class + public void Component(Expression>> property, TComponent dynamicComponentTemplate, Action> mapping) where TComponent : class { RegisterDynamicComponentMapping(property, mapping); } @@ -117,7 +115,7 @@ protected virtual void RegisterDynamicComponentMapping(Expression(mapping, member, memberOf); + RegisterDynamicComponentMapping(mapping, member, memberOf); } protected void RegisterDynamicComponentMapping(Action> mapping, params MemberInfo[] members) From ebca62ad7dd577a89c1ea09954d1643a7dfa5fa7 Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Wed, 6 Jun 2018 13:51:44 +1200 Subject: [PATCH 4/6] Simplify DynamicComponentInstantiator --- .../Tuple/Component/ComponentMetamodel.cs | 1 + .../Component/DynamicComponentInstantiator.cs | 15 +++++ .../Component/DynamicMapComponentTuplizer.cs | 61 +++---------------- .../Tuple/DynamicMapInstantiator.cs | 17 ++---- 4 files changed, 30 insertions(+), 64 deletions(-) create mode 100644 src/NHibernate/Tuple/Component/DynamicComponentInstantiator.cs diff --git a/src/NHibernate/Tuple/Component/ComponentMetamodel.cs b/src/NHibernate/Tuple/Component/ComponentMetamodel.cs index b42673c4cb9..274b9e90cb9 100644 --- a/src/NHibernate/Tuple/Component/ComponentMetamodel.cs +++ b/src/NHibernate/Tuple/Component/ComponentMetamodel.cs @@ -26,6 +26,7 @@ public ComponentMetamodel(Mapping.Component component) propertyIndexes[property.Name] = i; i++; } + EntityMode = component.HasPocoRepresentation ? EntityMode.Poco : EntityMode.Map; var componentTuplizerFactory = new ComponentTuplizerFactory(); diff --git a/src/NHibernate/Tuple/Component/DynamicComponentInstantiator.cs b/src/NHibernate/Tuple/Component/DynamicComponentInstantiator.cs new file mode 100644 index 00000000000..719a1afd8be --- /dev/null +++ b/src/NHibernate/Tuple/Component/DynamicComponentInstantiator.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; + +namespace NHibernate.Tuple.Component +{ + [Serializable] + internal class DynamicComponentInstantiator : IInstantiator + { + public object Instantiate(object id) => Instantiate(); + + public object Instantiate() => new Dictionary(); + + public bool IsInstance(object obj) => obj is Dictionary; + } +} \ No newline at end of file diff --git a/src/NHibernate/Tuple/Component/DynamicMapComponentTuplizer.cs b/src/NHibernate/Tuple/Component/DynamicMapComponentTuplizer.cs index 8b6067094b7..7332a7f861d 100644 --- a/src/NHibernate/Tuple/Component/DynamicMapComponentTuplizer.cs +++ b/src/NHibernate/Tuple/Component/DynamicMapComponentTuplizer.cs @@ -1,9 +1,7 @@ using System; -using System.Linq; -using System.Collections.Generic; using NHibernate.Properties; using System.Collections; -using System.Reflection; +using System.Collections.Generic; namespace NHibernate.Tuple.Component { @@ -13,64 +11,23 @@ namespace NHibernate.Tuple.Component [Serializable] public class DynamicMapComponentTuplizer : AbstractComponentTuplizer { - private readonly Mapping.Component component; - public DynamicMapComponentTuplizer(Mapping.Component component) : base(component) { - this.component = component; // Fix for NH-3119 instantiator = BuildInstantiator(component); } - public override System.Type MappedClass - { - get - { - if ((this.component != null) && (this.component.Owner.MappedClass != null)) - { - var ownerFullName = this.component.Owner.MappedClass.FullName; - var roleName = this.component.RoleName; - var spare = roleName.Substring(ownerFullName.Length + 1); - var parts = spare.Split('.'); - var prop = null as PropertyInfo; - var owner = this.component.Owner.MappedClass; - - for (var i = 0; i < parts.Length; ++i) - { - prop = owner.GetProperty(parts[i], BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); - - owner = prop.PropertyType; - } - - return typeof(IDictionary).IsAssignableFrom(prop.PropertyType) ? typeof(IDictionary) : typeof(IDictionary); - } - else - { - return typeof(IDictionary); - } - } - } - - protected internal override IInstantiator BuildInstantiator(Mapping.Component component) - { - return new DynamicMapInstantiator(component); - } + public override System.Type MappedClass => + typeof(Dictionary); - protected internal override IGetter BuildGetter(Mapping.Component component, Mapping.Property prop) - { - return BuildPropertyAccessor(prop).GetGetter(null, prop.Name); - } + protected internal override IInstantiator BuildInstantiator(Mapping.Component component) => + new DynamicComponentInstantiator(); - protected internal override ISetter BuildSetter(Mapping.Component component, Mapping.Property prop) - { - return BuildPropertyAccessor(prop).GetSetter(null, prop.Name); - } - - private IPropertyAccessor BuildPropertyAccessor(Mapping.Property property) - { - return PropertyAccessorFactory.DynamicMapPropertyAccessor; - } + protected internal override IGetter BuildGetter(Mapping.Component component, Mapping.Property prop) => + PropertyAccessorFactory.DynamicMapPropertyAccessor.GetGetter(null, prop.Name); + protected internal override ISetter BuildSetter(Mapping.Component component, Mapping.Property prop) => + PropertyAccessorFactory.DynamicMapPropertyAccessor.GetSetter(null, prop.Name); } } diff --git a/src/NHibernate/Tuple/DynamicMapInstantiator.cs b/src/NHibernate/Tuple/DynamicMapInstantiator.cs index 36bb2d52ac2..0a8cce0e4a9 100644 --- a/src/NHibernate/Tuple/DynamicMapInstantiator.cs +++ b/src/NHibernate/Tuple/DynamicMapInstantiator.cs @@ -12,12 +12,12 @@ public class DynamicMapInstantiator : IInstantiator private readonly string entityName; private readonly HashSet isInstanceEntityNames = new HashSet(); - private readonly Mapping.Component component; - public DynamicMapInstantiator(Mapping.Component component) + //Since v5.2 + [Obsolete("This constructor is not used and will be removed in a future version.")] + public DynamicMapInstantiator() { entityName = null; - this.component = component; } public DynamicMapInstantiator(PersistentClass mappingInfo) @@ -50,14 +50,7 @@ public object Instantiate() protected virtual IDictionary GenerateMap() { - if ((this.component != null) && (typeof(IDictionary).IsAssignableFrom(this.component.Type.ReturnedClass) == true)) - { - return new Dictionary(); - } - else - { - return new Hashtable(); - } + return new Hashtable(); } public bool IsInstance(object obj) @@ -74,7 +67,7 @@ public bool IsInstance(object obj) } else { - return obj is IDictionary; + return false; } } From 7f74a96174a741990838151382ae2c5c7d5c54e5 Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Thu, 7 Jun 2018 00:18:40 +1200 Subject: [PATCH 5/6] Update documentation --- doc/reference/modules/basic_mapping.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/reference/modules/basic_mapping.xml b/doc/reference/modules/basic_mapping.xml index c6d349b29fd..923714dec99 100644 --- a/doc/reference/modules/basic_mapping.xml +++ b/doc/reference/modules/basic_mapping.xml @@ -2081,7 +2081,8 @@ The <dynamic-component> element allows an IDictionary - to be mapped as a component, where the property names refer to keys of the dictionary. + or IDictionaryto be mapped as a component, where the property + names refer to keys of the dictionary. From dd1081d8fada01d585bd26566eb389ef59b885c8 Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Thu, 7 Jun 2018 13:44:56 +1200 Subject: [PATCH 6/6] Fix code style --- .../Mapping/ByCode/Impl/CustomizersImpl/JoinCustomizer.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/JoinCustomizer.cs b/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/JoinCustomizer.cs index 8e09887f393..530890e04f0 100644 --- a/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/JoinCustomizer.cs +++ b/src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/JoinCustomizer.cs @@ -132,16 +132,14 @@ protected override void RegisterComponentMapping(Expression(Expression> property - , Action> mapping) + protected override void RegisterDynamicComponentMapping(Expression> property, Action> mapping) { MemberInfo member = TypeExtensions.DecodeMemberAccessExpression(property); ExplicitDeclarationsHolder.AddAsPropertySplit(new SplitDefinition(typeof(TEntity), splitGroupId, member)); base.RegisterDynamicComponentMapping(property, mapping); } - protected override void RegisterDynamicComponentMapping(Expression>> property - , Action> mapping) + protected override void RegisterDynamicComponentMapping(Expression>> property, Action> mapping) { MemberInfo member = TypeExtensions.DecodeMemberAccessExpression(property); ExplicitDeclarationsHolder.AddAsPropertySplit(new SplitDefinition(typeof(TEntity), splitGroupId, member)); @@ -169,4 +167,4 @@ protected override void RegisterIdBagMapping(Expression