From: <ric...@us...> - 2010-05-27 09:19:43
|
Revision: 4982 http://nhibernate.svn.sourceforge.net/nhibernate/?rev=4982&view=rev Author: ricbrown Date: 2010-05-27 09:19:36 +0000 (Thu, 27 May 2010) Log Message: ----------- Fix NH-2199 (Map with element doesn't support nullable types) Modified Paths: -------------- trunk/nhibernate/releasenotes.txt trunk/nhibernate/src/NHibernate/Collection/Generic/PersistentGenericMap.cs trunk/nhibernate/src/NHibernate/Collection/PersistentMap.cs trunk/nhibernate/src/NHibernate.DomainModel/Baz.cs trunk/nhibernate/src/NHibernate.Test/Legacy/FooBarTest.cs trunk/nhibernate/src/NHibernate.Test/NHibernate.Test.csproj Added Paths: ----------- trunk/nhibernate/src/NHibernate.Test/CollectionTest/NullableValueTypeElementMapFixture.cs trunk/nhibernate/src/NHibernate.Test/CollectionTest/NullableValueTypeElementMapFixture.hbm.xml trunk/nhibernate/src/NHibernate.Test/CollectionTest/Parent.cs Modified: trunk/nhibernate/releasenotes.txt =================================================================== --- trunk/nhibernate/releasenotes.txt 2010-05-24 20:02:27 UTC (rev 4981) +++ trunk/nhibernate/releasenotes.txt 2010-05-27 09:19:36 UTC (rev 4982) @@ -1,3 +1,10 @@ +Build 3.0.0.Alpha1 +============================= +** Known BREAKING CHANGES from NH2.1.1.GA to NH3.0.0.Alpha1 + ##### Run time ##### + * (NH-2199) - null values in maps/dictionaries are no longer silenty ignored/deleted + + Build 2.1.1.GA (rev4814) ============================= Modified: trunk/nhibernate/src/NHibernate/Collection/Generic/PersistentGenericMap.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Collection/Generic/PersistentGenericMap.cs 2010-05-24 20:02:27 UTC (rev 4981) +++ trunk/nhibernate/src/NHibernate/Collection/Generic/PersistentGenericMap.cs 2010-05-27 09:19:36 UTC (rev 4982) @@ -54,7 +54,7 @@ IDictionary<TKey, TValue> sn = (IDictionary<TKey, TValue>)GetSnapshot(); foreach (KeyValuePair<TKey, TValue> e in sn) { - if (e.Value != null && !gmap.ContainsKey(e.Key)) + if (!gmap.ContainsKey(e.Key)) { object key = e.Key; deletes.Add(indexIsFormula ? e.Value : key); @@ -67,7 +67,7 @@ { IDictionary sn = (IDictionary)GetSnapshot(); KeyValuePair<TKey, TValue> e = (KeyValuePair<TKey, TValue>)entry; - return e.Value != null && sn[e.Key] == null; + return !sn.Contains(e.Key); } public override bool NeedsUpdating(object entry, int i, IType elemType) @@ -75,7 +75,9 @@ IDictionary sn = (IDictionary)GetSnapshot(); KeyValuePair<TKey, TValue> e = (KeyValuePair<TKey, TValue>)entry; object snValue = sn[e.Key]; - return e.Value != null && snValue != null && elemType.IsDirty(snValue, e.Value, Session); + bool isNew = !sn.Contains(e.Key); + return e.Value != null && snValue != null && elemType.IsDirty(snValue, e.Value, Session) + || (!isNew && ((e.Value == null) != (snValue == null))); } public override object GetIndex(object entry, int i, ICollectionPersister persister) @@ -96,9 +98,14 @@ public override bool EntryExists(object entry, int i) { - return ((KeyValuePair<TKey, TValue>)entry).Value != null; + return gmap.ContainsKey(((KeyValuePair<TKey, TValue>)entry).Key); } + protected override void AddDuringInitialize(object index, object element) + { + gmap[(TKey)index] = (TValue)element; + } + #region IDictionary<TKey,TValue> Members bool IDictionary<TKey, TValue>.ContainsKey(TKey key) Modified: trunk/nhibernate/src/NHibernate/Collection/PersistentMap.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Collection/PersistentMap.cs 2010-05-24 20:02:27 UTC (rev 4981) +++ trunk/nhibernate/src/NHibernate/Collection/PersistentMap.cs 2010-05-27 09:19:36 UTC (rev 4982) @@ -109,13 +109,15 @@ object element = role.ReadElement(rs, owner, descriptor.SuffixedElementAliases, Session); object index = role.ReadIndex(rs, descriptor.SuffixedIndexAliases, Session); - if (element != null) - { - map[index] = element; - } + AddDuringInitialize(index, element); return element; } + protected virtual void AddDuringInitialize(object index, object element) + { + map[index] = element; + } + public override IEnumerable Entries(ICollectionPersister persister) { return map; @@ -158,7 +160,7 @@ foreach (DictionaryEntry e in sn) { object key = e.Key; - if (e.Value != null && map[key] == null) + if (!map.Contains(key)) { deletes.Add(indexIsFormula ? e.Value : key); } @@ -170,7 +172,7 @@ { IDictionary sn = (IDictionary) GetSnapshot(); DictionaryEntry e = (DictionaryEntry) entry; - return e.Value != null && sn[e.Key] == null; + return !sn.Contains(e.Key); } public override bool NeedsUpdating(object entry, int i, IType elemType) @@ -178,7 +180,9 @@ IDictionary sn = (IDictionary) GetSnapshot(); DictionaryEntry e = (DictionaryEntry) entry; object snValue = sn[e.Key]; - return e.Value != null && snValue != null && elemType.IsDirty(snValue, e.Value, Session); + bool isNew = !sn.Contains(e.Key); + return e.Value != null && snValue != null && elemType.IsDirty(snValue, e.Value, Session) + || (!isNew && ((e.Value == null) != (snValue == null))); } public override object GetIndex(object entry, int i, ICollectionPersister persister) @@ -216,7 +220,7 @@ public override bool EntryExists(object entry, int i) { - return ((DictionaryEntry) entry).Value != null; + return map.Contains(((DictionaryEntry) entry).Key); } #region IDictionary Members Modified: trunk/nhibernate/src/NHibernate.DomainModel/Baz.cs =================================================================== --- trunk/nhibernate/src/NHibernate.DomainModel/Baz.cs 2010-05-24 20:02:27 UTC (rev 4981) +++ trunk/nhibernate/src/NHibernate.DomainModel/Baz.cs 2010-05-27 09:19:36 UTC (rev 4982) @@ -391,7 +391,7 @@ StringDateMap = new SortedList(); StringDateMap.Add("now", DateTime.Now); - StringDateMap.Add("never", null); + StringDateMap.Add("never", null); // value is persisted since NH-2199 // according to SQL Server the big bag happened in 1753 ;) StringDateMap.Add("big bang", new DateTime(1753, 01, 01)); //StringDateMap.Add( "millenium", new DateTime( 2000, 01, 01 ) ); Added: trunk/nhibernate/src/NHibernate.Test/CollectionTest/NullableValueTypeElementMapFixture.cs =================================================================== --- trunk/nhibernate/src/NHibernate.Test/CollectionTest/NullableValueTypeElementMapFixture.cs (rev 0) +++ trunk/nhibernate/src/NHibernate.Test/CollectionTest/NullableValueTypeElementMapFixture.cs 2010-05-27 09:19:36 UTC (rev 4982) @@ -0,0 +1,227 @@ +using System; +using System.Collections; +using NUnit.Framework; + +namespace NHibernate.Test.CollectionTest +{ + [TestFixture] + public class NullableValueTypeElementMapFixture : TestCase + { + protected override IList Mappings + { + get { return new[] {"CollectionTest.NullableValueTypeElementMapFixture.hbm.xml"}; } + } + + protected override string MappingsAssembly + { + get { return "NHibernate.Test"; } + } + + protected override void OnTearDown() + { + using (var s = sessions.OpenSession()) + { + s.Delete("from Parent"); + s.Flush(); + } + } + + [Test] + public void ShouldOverwriteElementValueWithNull() + { + Guid parentId; + var date = new DateTime(2010, 09, 08); + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + var parent = new Parent(); + parent.TypedDates[0] = date; + + s.Save(parent); + parentId = parent.Id; + tx.Commit(); + } + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + var parent = s.Load<Parent>(parentId); + + Assert.That(parent.TypedDates.Count, Is.EqualTo(1), + "Should have one child on first reload"); + + Assert.That(parent.TypedDates[0], Is.Not.Null, + "Should have value in map for 0 on first reload"); + + Assert.That(parent.TypedDates[0].Value, Is.EqualTo(date), + "Should have same date as saved in map for 0 on first reload"); + + parent.TypedDates[0] = null; + tx.Commit(); + } + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + var parent = s.Load<Parent>(parentId); + + Assert.That(parent.TypedDates.Count, Is.EqualTo(1), + "Should have one child on reload after nulling"); + + Assert.That(parent.TypedDates[0], Is.Null, + "Should have null value for child on reload after nulling"); + } + } + + [Test] + public void ShouldOverwriteNullElementWithValue() + { + Guid parentId; + var date = new DateTime(2010, 09, 08); + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + var parent = new Parent(); + parent.TypedDates[0] = null; + + s.Save(parent); + parentId = parent.Id; + tx.Commit(); + } + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + var parent = s.Load<Parent>(parentId); + + Assert.That(parent.TypedDates.Count, Is.EqualTo(1), + "Should have 1 child after first reload"); + + Assert.That(parent.TypedDates[0], Is.Null, + "Should have null value after first reload"); + + parent.TypedDates[0] = date; + tx.Commit(); + } + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + var parent = s.Load<Parent>(parentId); + + Assert.That(parent.TypedDates.Count, Is.EqualTo(1), + "Should have 1 child on reload after setting value"); + + Assert.That(parent.TypedDates[0], Is.Not.Null, + "Should have child with value on reload after setting value"); + + Assert.That(parent.TypedDates[0].Value, Is.EqualTo(date)); + } + } + + [Test] + public void ShouldAddAndRemoveNullElements() + { + Guid parentId; + var date = new DateTime(2010, 09, 08); + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + var parent = new Parent(); + parent.TypedDates[0] = null; + parent.TypedDates[1] = date; + + s.Save(parent); + parentId = parent.Id; + tx.Commit(); + } + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + var parent = s.Load<Parent>(parentId); + + Assert.That(parent.TypedDates.Count, Is.EqualTo(2)); + Assert.That(parent.TypedDates[0], Is.Null); + Assert.That(parent.TypedDates[1], Is.EqualTo(date)); + + parent.TypedDates.Remove(0); + parent.TypedDates[2] = null; + tx.Commit(); + } + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + var parent = s.Load<Parent>(parentId); + + Assert.That(parent.TypedDates.Count, Is.EqualTo(2)); + Assert.That(parent.TypedDates[1], Is.EqualTo(date)); + Assert.That(parent.TypedDates[2], Is.Null); + } + } + + [Test] + public void AddRemoveUntypedElements() + { + Guid parentId; + var date = new DateTime(2010, 09, 08); + + int toBeRemoved = 0; + int toBeUpdatedToNull = 1; + int toRemainNull = 2; + int toBeUpdatedFromNull = 3; + int toBeAddedNull = 4; + int toBeAddedNotNull = 5; + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + var parent = new Parent(); + parent.UntypedDates[toBeRemoved] = null; + parent.UntypedDates[toBeUpdatedToNull] = date; + parent.UntypedDates[toRemainNull] = null; + parent.UntypedDates[toBeUpdatedFromNull] = null; + + s.Save(parent); + parentId = parent.Id; + tx.Commit(); + } + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + var parent = s.Load<Parent>(parentId); + + Assert.That(parent.UntypedDates.Count, Is.EqualTo(4)); + Assert.That(parent.UntypedDates[toBeRemoved], Is.Null); + Assert.That(parent.UntypedDates[toBeUpdatedToNull], Is.EqualTo(date)); + Assert.That(parent.UntypedDates[toRemainNull], Is.Null); + Assert.That(parent.UntypedDates[toBeUpdatedFromNull], Is.Null); + + parent.UntypedDates.Remove(toBeRemoved); + parent.UntypedDates[toBeUpdatedToNull] = null; + parent.UntypedDates[toBeUpdatedFromNull] = date; + parent.UntypedDates[toBeAddedNull] = null; + parent.UntypedDates[toBeAddedNotNull] = date; + tx.Commit(); + } + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + var parent = s.Load<Parent>(parentId); + + Assert.That(parent.UntypedDates.Count, Is.EqualTo(5)); + Assert.That(parent.UntypedDates[toBeUpdatedToNull], Is.Null); + Assert.That(parent.UntypedDates[toRemainNull], Is.Null); + Assert.That(parent.UntypedDates[toBeUpdatedFromNull], Is.EqualTo(date)); + Assert.That(parent.UntypedDates[toBeAddedNull], Is.Null); + Assert.That(parent.UntypedDates[toBeAddedNotNull], Is.EqualTo(date)); + } + } + } +} Added: trunk/nhibernate/src/NHibernate.Test/CollectionTest/NullableValueTypeElementMapFixture.hbm.xml =================================================================== --- trunk/nhibernate/src/NHibernate.Test/CollectionTest/NullableValueTypeElementMapFixture.hbm.xml (rev 0) +++ trunk/nhibernate/src/NHibernate.Test/CollectionTest/NullableValueTypeElementMapFixture.hbm.xml 2010-05-27 09:19:36 UTC (rev 4982) @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8" ?> +<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernate.Test" namespace="NHibernate.Test.CollectionTest"> + <class name="Parent"> + <id name="Id"> + <generator class="guid.comb" /> + </id> + <map name="TypedDates"> + <key column="ParentId" /> + <index column="Idx" type="int" /> + <element column="Value" /> + </map> + <map name="UntypedDates"> + <key column="ParentId" /> + <index column="Idx" type="int" /> + <element column="Value" type="System.Nullable`1[[System.DateTime, mscorlib]]" /> + </map> + </class> +</hibernate-mapping> Added: trunk/nhibernate/src/NHibernate.Test/CollectionTest/Parent.cs =================================================================== --- trunk/nhibernate/src/NHibernate.Test/CollectionTest/Parent.cs (rev 0) +++ trunk/nhibernate/src/NHibernate.Test/CollectionTest/Parent.cs 2010-05-27 09:19:36 UTC (rev 4982) @@ -0,0 +1,19 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace NHibernate.Test.CollectionTest +{ + public class Parent + { + public virtual Guid Id { get; set; } + public virtual IDictionary<int, DateTime?> TypedDates { get; set; } + public virtual IDictionary UntypedDates { get; set; } + + public Parent() + { + TypedDates = new Dictionary<int, DateTime?>(); + UntypedDates = new Hashtable(); + } + } +} Modified: trunk/nhibernate/src/NHibernate.Test/Legacy/FooBarTest.cs =================================================================== --- trunk/nhibernate/src/NHibernate.Test/Legacy/FooBarTest.cs 2010-05-24 20:02:27 UTC (rev 4981) +++ trunk/nhibernate/src/NHibernate.Test/Legacy/FooBarTest.cs 2010-05-27 09:19:36 UTC (rev 4982) @@ -825,7 +825,7 @@ list = s.CreateQuery("select index(date) from Baz baz join baz.StringDateMap date").List(); Console.WriteLine(list); - Assert.AreEqual(2, list.Count); + Assert.AreEqual(3, list.Count); s.CreateQuery( "from foo in class Foo where foo.Integer not between 1 and 5 and foo.String not in ('cde', 'abc') and foo.String is not null and foo.Integer<=3") Modified: trunk/nhibernate/src/NHibernate.Test/NHibernate.Test.csproj =================================================================== --- trunk/nhibernate/src/NHibernate.Test/NHibernate.Test.csproj 2010-05-24 20:02:27 UTC (rev 4981) +++ trunk/nhibernate/src/NHibernate.Test/NHibernate.Test.csproj 2010-05-27 09:19:36 UTC (rev 4982) @@ -126,6 +126,8 @@ <Compile Include="Classic\Video.cs" /> <Compile Include="CollectionTest\A.cs" /> <Compile Include="CollectionTest\IdBagFixture.cs" /> + <Compile Include="CollectionTest\NullableValueTypeElementMapFixture.cs" /> + <Compile Include="CollectionTest\Parent.cs" /> <Compile Include="CompositeCollection\BaseClassA.cs" /> <Compile Include="CompositeCollection\BaseClassB.cs" /> <Compile Include="CompositeCollection\ChildClassA.cs" /> @@ -2135,6 +2137,7 @@ <EmbeddedResource Include="Criteria\Lambda\Mappings.hbm.xml" /> <EmbeddedResource Include="CfgTest\Loquacious\EntityToCache.hbm.xml" /> <EmbeddedResource Include="DriverTest\SqlServerCeEntity.hbm.xml" /> + <EmbeddedResource Include="CollectionTest\NullableValueTypeElementMapFixture.hbm.xml" /> <Content Include="DynamicEntity\package.html" /> <EmbeddedResource Include="NHSpecificTest\NH2201\Mappings.hbm.xml" /> <EmbeddedResource Include="NHSpecificTest\NH2192\Mappings.hbm.xml" /> This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |