Skip to content

Commit 7e440bf

Browse files
druidroadfredericDelaporte
authored andcommitted
Fix IsModified so that null equates empty components
* And fix ComponentType.IsModified(…) method throws exception when used with a component as "old" parameter. * Fix #1486
1 parent 418d35a commit 7e440bf

File tree

6 files changed

+267
-8
lines changed

6 files changed

+267
-8
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using System;
2+
3+
4+
namespace NHibernate.Test.NHSpecificTest.GH1486
5+
{
6+
public class Person
7+
{
8+
public virtual int Id { get; set; }
9+
10+
public virtual string Name { get; set; }
11+
12+
public virtual Address Address { get; set; }
13+
14+
public virtual int Version { get; set; }
15+
16+
public Person()
17+
{
18+
}
19+
20+
public Person(int id, string name, Address address)
21+
{
22+
Id = id;
23+
Name = name;
24+
Address = address;
25+
}
26+
}
27+
28+
public class Address : IEquatable<Address>
29+
{
30+
public virtual string PostalCode { get; set; }
31+
32+
public virtual string State { get; set; }
33+
34+
public virtual string Street { get; set; }
35+
36+
public Address()
37+
{ }
38+
39+
public Address(string postalCode, string state, string street)
40+
{
41+
PostalCode = postalCode;
42+
State = state;
43+
Street = street;
44+
}
45+
46+
public bool Equals(Address other)
47+
{
48+
if (ReferenceEquals(null, other)) return false;
49+
if (ReferenceEquals(this, other)) return true;
50+
return string.Equals(Street, other.Street) && string.Equals(State, other.State) && string.Equals(PostalCode, other.PostalCode);
51+
}
52+
53+
public override bool Equals(object obj)
54+
{
55+
if (ReferenceEquals(null, obj)) return false;
56+
if (ReferenceEquals(this, obj)) return true;
57+
if (obj.GetType() != GetType()) return false;
58+
return Equals((Address) obj);
59+
}
60+
61+
public override int GetHashCode()
62+
{
63+
unchecked
64+
{
65+
var hashCode = (PostalCode != null ? PostalCode.GetHashCode() : 0);
66+
hashCode = (hashCode * 397) ^ (State != null ? State.GetHashCode() : 0);
67+
hashCode = (hashCode * 397) ^ (Street != null ? Street.GetHashCode() : 0);
68+
return hashCode;
69+
}
70+
}
71+
72+
public static bool operator ==(Address left, Address right)
73+
{
74+
return Equals(left, right);
75+
}
76+
77+
public static bool operator !=(Address left, Address right)
78+
{
79+
return !Equals(left, right);
80+
}
81+
}
82+
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
using System.Linq;
2+
using NUnit.Framework;
3+
using NHibernate.Cfg;
4+
using NHibernate.Type;
5+
6+
namespace NHibernate.Test.NHSpecificTest.GH1486
7+
{
8+
[TestFixture]
9+
public class Fixture : BugTestCase
10+
{
11+
private readonly OnFlushDirtyInterceptor _interceptor = new OnFlushDirtyInterceptor();
12+
13+
protected override void Configure(Configuration configuration)
14+
{
15+
base.Configure(configuration);
16+
configuration.SetInterceptor(_interceptor);
17+
}
18+
19+
protected override void OnSetUp()
20+
{
21+
using (var session = OpenSession())
22+
{
23+
using (var transaction = session.BeginTransaction())
24+
{
25+
var john = new Person(1, "John", new Address());
26+
session.Save(john);
27+
28+
var mary = new Person(2, "Mary", null);
29+
session.Save(mary);
30+
31+
var bob = new Person(3, "Bob", new Address("1", "A", "B"));
32+
session.Save(bob);
33+
34+
session.Flush();
35+
transaction.Commit();
36+
}
37+
}
38+
}
39+
40+
protected override void OnTearDown()
41+
{
42+
using (var session = OpenSession())
43+
using (var transaction = session.BeginTransaction())
44+
{
45+
session.Delete("from System.Object");
46+
session.Flush();
47+
transaction.Commit();
48+
}
49+
}
50+
51+
/// <summary>
52+
/// The test case was imported from Hibernate HHH-11237 and adjusted for NHibernate.
53+
/// </summary>
54+
[Test]
55+
public void TestSelectBeforeUpdate()
56+
{
57+
using (var session = OpenSession())
58+
{
59+
using (var transaction = session.BeginTransaction())
60+
{
61+
var john = session.Get<Person>(1);
62+
_interceptor.Reset();
63+
john.Address = null;
64+
session.Flush();
65+
Assert.That(_interceptor.CallCount, Is.EqualTo(0), "unexpected flush dirty count for John");
66+
67+
_interceptor.Reset();
68+
var mary = session.Get<Person>(2);
69+
mary.Address = new Address();
70+
session.Flush();
71+
Assert.That(_interceptor.CallCount, Is.EqualTo(0), "unexpected flush dirty count for Mary");
72+
transaction.Commit();
73+
}
74+
}
75+
76+
Person johnObj;
77+
Person maryObj;
78+
using (var session = OpenSession())
79+
{
80+
using (var transaction = session.BeginTransaction())
81+
{
82+
johnObj = session.Get<Person>(1);
83+
}
84+
}
85+
86+
using (var session = OpenSession())
87+
{
88+
using (var transaction = session.BeginTransaction())
89+
{
90+
maryObj = session.Get<Person>(2);
91+
}
92+
}
93+
94+
using (var session = OpenSession())
95+
{
96+
using (var transaction = session.BeginTransaction())
97+
{
98+
_interceptor.Reset();
99+
johnObj.Address = null;
100+
session.Update(johnObj);
101+
session.Flush();
102+
Assert.That(_interceptor.CallCount, Is.EqualTo(0), "unexpected flush dirty count for John update");
103+
104+
_interceptor.Reset();
105+
maryObj.Address = new Address();
106+
session.Update(maryObj);
107+
session.Flush();
108+
Assert.That(_interceptor.CallCount, Is.EqualTo(0), "unexpected flush dirty count for Mary update");
109+
transaction.Commit();
110+
}
111+
}
112+
}
113+
114+
[Test]
115+
public void TestDirectCallToIsModified()
116+
{
117+
using (var session = OpenSession())
118+
using (var transaction = session.BeginTransaction())
119+
{
120+
var person = session.Load<Person>(3);
121+
Assert.That(person, Is.Not.Null, "Bob is not found.");
122+
Assert.That(person.Address, Is.Not.Null, "Bob's address is missing.");
123+
var sessionImplementor = session.GetSessionImplementation();
124+
125+
var metaData = session.SessionFactory.GetClassMetadata(typeof(Person));
126+
foreach (var propertyType in metaData.PropertyTypes)
127+
{
128+
if (!(propertyType is ComponentType componentType) || componentType.ReturnedClass.Name != "Address")
129+
continue;
130+
131+
var checkable = new [] { true, true, true };
132+
Assert.That(
133+
() => componentType.IsModified(new object[] { "", "", "" }, person.Address, checkable, sessionImplementor),
134+
Throws.Nothing,
135+
"Checking component against an array snapshot failed");
136+
var isModified = componentType.IsModified(person.Address, person.Address, checkable, sessionImplementor);
137+
Assert.That(isModified, Is.False, "Checking same component failed");
138+
isModified = componentType.IsModified(new Address("1", "A", "B"), person.Address, checkable, sessionImplementor);
139+
Assert.That(isModified, Is.False, "Checking equal component failed");
140+
}
141+
transaction.Rollback();
142+
}
143+
}
144+
}
145+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernate.Test" namespace="NHibernate.Test.NHSpecificTest.GH1486">
3+
4+
<class name="Person" select-before-update="true">
5+
<id name="Id" type="integer" />
6+
<property name="Name" type="string" length="50"/>
7+
<property name="Version" type="integer" />
8+
<component name="Address">
9+
<property name="PostalCode" type="string" length="20" />
10+
<property name="Street" type="string" length="100" />
11+
<property name="State" type="string" length="50" />
12+
</component>
13+
</class>
14+
15+
</hibernate-mapping>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using NHibernate.Type;
2+
3+
namespace NHibernate.Test.NHSpecificTest.GH1486
4+
{
5+
public class OnFlushDirtyInterceptor : EmptyInterceptor
6+
{
7+
public int CallCount = 0;
8+
9+
public override bool OnFlushDirty(object entity, object id, object[] currentState, object[] previousState, string[] propertyNames, IType[] types)
10+
{
11+
CallCount++;
12+
return false;
13+
}
14+
15+
public void Reset()
16+
{
17+
CallCount = 0;
18+
}
19+
}
20+
}

src/NHibernate/Tuple/Component/AbstractComponentTuplizer.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@ public virtual void SetPropertyValues(object component, object[] values)
101101

102102
public virtual object GetPropertyValue(object component, int i)
103103
{
104-
return getters[i].Get(component);
104+
// NH Different behavior : for NH-1101
105+
return component == null ? null : getters[i].Get(component);
105106
}
106107

107108
/// <summary> This method does not populate the component parent</summary>

src/NHibernate/Type/ComponentType.cs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -525,16 +525,12 @@ public override object SemiResolve(object value, ISessionImplementor session, ob
525525

526526
public override bool IsModified(object old, object current, bool[] checkable, ISessionImplementor session)
527527
{
528-
if (current == null)
528+
if (old == current)
529529
{
530-
return old != null;
531-
}
532-
if (old == null)
533-
{
534-
return current != null;
530+
return false;
535531
}
536532
object[] currentValues = GetPropertyValues(current, session);
537-
object[] oldValues = (Object[]) old;
533+
var oldValues = old is object[] objects ? objects : GetPropertyValues(old, session);
538534
int loc = 0;
539535
for (int i = 0; i < currentValues.Length; i++)
540536
{

0 commit comments

Comments
 (0)