Skip to content

Fix IsModified so that a null equates empty components when using select-before-update #1487

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 16, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions src/NHibernate.Test/NHSpecificTest/GH1486/Entities.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using System;


namespace NHibernate.Test.NHSpecificTest.GH1486
{
public class Person
{
public virtual int Id { get; set; }

public virtual string Name { get; set; }

public virtual Address Address { get; set; }

public virtual int Version { get; set; }

public Person()
{
}

public Person(int id, string name, Address address)
{
Id = id;
Name = name;
Address = address;
}
}

public class Address : IEquatable<Address>
{
public virtual string PostalCode { get; set; }

public virtual string State { get; set; }

public virtual string Street { get; set; }

public Address()
{ }

public Address(string postalCode, string state, string street)
{
PostalCode = postalCode;
State = state;
Street = street;
}

public bool Equals(Address other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return string.Equals(Street, other.Street) && string.Equals(State, other.State) && string.Equals(PostalCode, other.PostalCode);
}

public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != GetType()) return false;
return Equals((Address) obj);
}

public override int GetHashCode()
{
unchecked
{
var hashCode = (PostalCode != null ? PostalCode.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (State != null ? State.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (Street != null ? Street.GetHashCode() : 0);
return hashCode;
}
}

public static bool operator ==(Address left, Address right)
{
return Equals(left, right);
}

public static bool operator !=(Address left, Address right)
{
return !Equals(left, right);
}
}
}
145 changes: 145 additions & 0 deletions src/NHibernate.Test/NHSpecificTest/GH1486/Fixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
using System.Linq;
using NUnit.Framework;
using NHibernate.Cfg;
using NHibernate.Type;

namespace NHibernate.Test.NHSpecificTest.GH1486
{
[TestFixture]
public class Fixture : BugTestCase
{
private readonly OnFlushDirtyInterceptor _interceptor = new OnFlushDirtyInterceptor();

protected override void Configure(Configuration configuration)
{
base.Configure(configuration);
configuration.SetInterceptor(_interceptor);
}

protected override void OnSetUp()
{
using (var session = OpenSession())
{
using (var transaction = session.BeginTransaction())
{
var john = new Person(1, "John", new Address());
session.Save(john);

var mary = new Person(2, "Mary", null);
session.Save(mary);

var bob = new Person(3, "Bob", new Address("1", "A", "B"));
session.Save(bob);

session.Flush();
transaction.Commit();
}
}
}

protected override void OnTearDown()
{
using (var session = OpenSession())
using (var transaction = session.BeginTransaction())
{
session.Delete("from System.Object");
session.Flush();
transaction.Commit();
}
}

/// <summary>
/// The test case was imported from Hibernate HHH-11237 and adjusted for NHibernate.
/// </summary>
[Test]
public void TestSelectBeforeUpdate()
{
using (var session = OpenSession())
{
using (var transaction = session.BeginTransaction())
{
var john = session.Get<Person>(1);
_interceptor.Reset();
john.Address = null;
session.Flush();
Assert.That(_interceptor.CallCount, Is.EqualTo(0), "unexpected flush dirty count for John");

_interceptor.Reset();
var mary = session.Get<Person>(2);
mary.Address = new Address();
session.Flush();
Assert.That(_interceptor.CallCount, Is.EqualTo(0), "unexpected flush dirty count for Mary");
transaction.Commit();
}
}

Person johnObj;
Person maryObj;
using (var session = OpenSession())
{
using (var transaction = session.BeginTransaction())
{
johnObj = session.Get<Person>(1);
}
}

using (var session = OpenSession())
{
using (var transaction = session.BeginTransaction())
{
maryObj = session.Get<Person>(2);
}
}

using (var session = OpenSession())
{
using (var transaction = session.BeginTransaction())
{
_interceptor.Reset();
johnObj.Address = null;
session.Update(johnObj);
session.Flush();
Assert.That(_interceptor.CallCount, Is.EqualTo(0), "unexpected flush dirty count for John update");

_interceptor.Reset();
maryObj.Address = new Address();
session.Update(maryObj);
session.Flush();
Assert.That(_interceptor.CallCount, Is.EqualTo(0), "unexpected flush dirty count for Mary update");
transaction.Commit();
}
}
}

[Test]
public void TestDirectCallToIsModified()
{
using (var session = OpenSession())
using (var transaction = session.BeginTransaction())
{
var person = session.Load<Person>(3);
Assert.That(person, Is.Not.Null, "Bob is not found.");
Assert.That(person.Address, Is.Not.Null, "Bob's address is missing.");
var sessionImplementor = session.GetSessionImplementation();

var metaData = session.SessionFactory.GetClassMetadata(typeof(Person));
foreach (var propertyType in metaData.PropertyTypes)
{
if (!(propertyType is ComponentType componentType) || componentType.ReturnedClass.Name != "Address")
continue;

var checkable = new [] { true, true, true };
Assert.That(
() => componentType.IsModified(new object[] { "", "", "" }, person.Address, checkable, sessionImplementor),
Throws.Nothing,
"Checking component against an array snapshot failed");
var isModified = componentType.IsModified(person.Address, person.Address, checkable, sessionImplementor);
Assert.That(isModified, Is.False, "Checking same component failed");
isModified = componentType.IsModified(new Address("1", "A", "B"), person.Address, checkable, sessionImplementor);
Assert.That(isModified, Is.False, "Checking equal component failed");
}
transaction.Rollback();
}
}
}
}
15 changes: 15 additions & 0 deletions src/NHibernate.Test/NHSpecificTest/GH1486/Mappings.hbm.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernate.Test" namespace="NHibernate.Test.NHSpecificTest.GH1486">

<class name="Person" select-before-update="true">
<id name="Id" type="integer" />
<property name="Name" type="string" length="50"/>
<property name="Version" type="integer" />
<component name="Address">
<property name="PostalCode" type="string" length="20" />
<property name="Street" type="string" length="100" />
<property name="State" type="string" length="50" />
</component>
</class>

</hibernate-mapping>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using NHibernate.Type;

namespace NHibernate.Test.NHSpecificTest.GH1486
{
public class OnFlushDirtyInterceptor : EmptyInterceptor
{
public int CallCount = 0;

public override bool OnFlushDirty(object entity, object id, object[] currentState, object[] previousState, string[] propertyNames, IType[] types)
{
CallCount++;
return false;
}

public void Reset()
{
CallCount = 0;
}
}
}
3 changes: 2 additions & 1 deletion src/NHibernate/Tuple/Component/AbstractComponentTuplizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ public virtual void SetPropertyValues(object component, object[] values)

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

/// <summary> This method does not populate the component parent</summary>
Expand Down
10 changes: 3 additions & 7 deletions src/NHibernate/Type/ComponentType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -525,16 +525,12 @@ public override object SemiResolve(object value, ISessionImplementor session, ob

public override bool IsModified(object old, object current, bool[] checkable, ISessionImplementor session)
{
if (current == null)
if (old == current)
{
return old != null;
}
if (old == null)
{
return current != null;
return false;
}
object[] currentValues = GetPropertyValues(current, session);
object[] oldValues = (Object[]) old;
var oldValues = old is object[] objects ? objects : GetPropertyValues(old, session);
int loc = 0;
for (int i = 0; i < currentValues.Length; i++)
{
Expand Down