Skip to content

Commit

Permalink
Fix empty dynamic components causing a phantom update (#3600)
Browse files Browse the repository at this point in the history
Fix #3421
  • Loading branch information
nfplee authored Sep 1, 2024
1 parent b4f8b01 commit 26f8d35
Show file tree
Hide file tree
Showing 5 changed files with 227 additions and 32 deletions.
109 changes: 109 additions & 0 deletions src/NHibernate.Test/Async/NHSpecificTest/GH3421/Fixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by AsyncGenerator.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------


using System.Collections.Generic;
using System.Linq;
using NHibernate.Cfg;
using NHibernate.Cfg.MappingSchema;
using NHibernate.Mapping.ByCode;
using NHibernate.SqlCommand;
using NUnit.Framework;
using NHibernate.Linq;

namespace NHibernate.Test.NHSpecificTest.GH3421
{
using System.Threading.Tasks;
[TestFixture]
public class ByCodeFixtureAsync : TestCaseMappingByCode
{
private SqlInterceptor _interceptor;

protected override HbmMapping GetMappings()
{
var mapper = new ModelMapper();
mapper.Class<Entity>(rc =>
{
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
rc.Property(x => x.Name);
rc.Component(x => x.Attributes, new {
Sku = (string)null
}, dc => {
dc.Property(x => x.Sku);
});
});

return mapper.CompileMappingForAllExplicitlyAddedEntities();
}

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

_interceptor = new SqlInterceptor();

configuration.SetInterceptor(_interceptor);
}

protected override void OnSetUp()
{
using (var session = OpenSession())
using (var transaction = session.BeginTransaction())
{
var e1 = new Entity { Name = "Bob" };
session.Save(e1);

var e2 = new Entity { Name = "Sally", Attributes = new Dictionary<string, object>() {
{ "Sku", "AAA" }
} };
session.Save(e2);

transaction.Commit();
}
}

protected override void OnTearDown()
{
using (var session = OpenSession())
using (var transaction = session.BeginTransaction())
{
session.CreateQuery("delete from Entity").ExecuteUpdate();
transaction.Commit();
}
}

[Test]
public async Task TestFlushDoesNotTriggerAnUpdateAsync()
{
using (var session = OpenSession())
using (var transaction = session.BeginTransaction())
{
var foo = await (session.Query<Entity>().ToListAsync());

await (session.FlushAsync());

var updateStatements = _interceptor.SqlStatements.Where(s => s.ToString().ToUpper().Contains("UPDATE")).ToList();

Assert.That(updateStatements, Has.Count.EqualTo(0));
}
}

public class SqlInterceptor : EmptyInterceptor
{
public IList<SqlString> SqlStatements = new List<SqlString>();

public override SqlString OnPrepareStatement(SqlString sql)
{
SqlStatements.Add(sql);

return base.OnPrepareStatement(sql);
}
}
}
}
21 changes: 21 additions & 0 deletions src/NHibernate.Test/NHSpecificTest/GH3421/Entity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;

namespace NHibernate.Test.NHSpecificTest.GH3421
{
class Entity
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }

private IDictionary<string, object> _attributes;
public virtual IDictionary<string, object> Attributes {
get {
if (_attributes == null)
_attributes = new Dictionary<string, object>();
return _attributes;
}
set => _attributes = value;
}
}
}
97 changes: 97 additions & 0 deletions src/NHibernate.Test/NHSpecificTest/GH3421/Fixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using System.Collections.Generic;
using System.Linq;
using NHibernate.Cfg;
using NHibernate.Cfg.MappingSchema;
using NHibernate.Mapping.ByCode;
using NHibernate.SqlCommand;
using NUnit.Framework;

namespace NHibernate.Test.NHSpecificTest.GH3421
{
[TestFixture]
public class ByCodeFixture : TestCaseMappingByCode
{
private SqlInterceptor _interceptor;

protected override HbmMapping GetMappings()
{
var mapper = new ModelMapper();
mapper.Class<Entity>(rc =>
{
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
rc.Property(x => x.Name);
rc.Component(x => x.Attributes, new {
Sku = (string)null
}, dc => {
dc.Property(x => x.Sku);
});
});

return mapper.CompileMappingForAllExplicitlyAddedEntities();
}

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

_interceptor = new SqlInterceptor();

configuration.SetInterceptor(_interceptor);
}

protected override void OnSetUp()
{
using (var session = OpenSession())
using (var transaction = session.BeginTransaction())
{
var e1 = new Entity { Name = "Bob" };
session.Save(e1);

var e2 = new Entity { Name = "Sally", Attributes = new Dictionary<string, object>() {
{ "Sku", "AAA" }
} };
session.Save(e2);

transaction.Commit();
}
}

protected override void OnTearDown()
{
using (var session = OpenSession())
using (var transaction = session.BeginTransaction())
{
session.CreateQuery("delete from Entity").ExecuteUpdate();
transaction.Commit();
}
}

[Test]
public void TestFlushDoesNotTriggerAnUpdate()
{
using (var session = OpenSession())
using (var transaction = session.BeginTransaction())
{
var foo = session.Query<Entity>().ToList();

session.Flush();

var updateStatements = _interceptor.SqlStatements.Where(s => s.ToString().ToUpper().Contains("UPDATE")).ToList();

Assert.That(updateStatements, Has.Count.EqualTo(0));
}
}

public class SqlInterceptor : EmptyInterceptor
{
public IList<SqlString> SqlStatements = new List<SqlString>();

public override SqlString OnPrepareStatement(SqlString sql)
{
SqlStatements.Add(sql);

return base.OnPrepareStatement(sql);
}
}
}
}
16 changes: 0 additions & 16 deletions src/NHibernate/Async/Type/ComponentType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,6 @@ public override async Task<bool> IsDirtyAsync(object x, object y, ISessionImplem
{
return false;
}
/*
* NH Different behavior : we don't use the shortcut because NH-1101
* let the tuplizer choose how cosiderer properties when the component is null.
*/
if (EntityMode != EntityMode.Poco && (x == null || y == null))
{
return true;
}
object[] xvalues = GetPropertyValues(x);
object[] yvalues = GetPropertyValues(y);
for (int i = 0; i < xvalues.Length; i++)
Expand All @@ -60,14 +52,6 @@ public override async Task<bool> IsDirtyAsync(object x, object y, bool[] checkab
{
return false;
}
/*
* NH Different behavior : we don't use the shortcut because NH-1101
* let the tuplizer choose how cosiderer properties when the component is null.
*/
if (EntityMode != EntityMode.Poco && (x == null || y == null))
{
return true;
}
object[] xvalues = GetPropertyValues(x);
object[] yvalues = GetPropertyValues(y);
int loc = 0;
Expand Down
16 changes: 0 additions & 16 deletions src/NHibernate/Type/ComponentType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,14 +156,6 @@ public override bool IsDirty(object x, object y, ISessionImplementor session)
{
return false;
}
/*
* NH Different behavior : we don't use the shortcut because NH-1101
* let the tuplizer choose how cosiderer properties when the component is null.
*/
if (EntityMode != EntityMode.Poco && (x == null || y == null))
{
return true;
}
object[] xvalues = GetPropertyValues(x);
object[] yvalues = GetPropertyValues(y);
for (int i = 0; i < xvalues.Length; i++)
Expand All @@ -182,14 +174,6 @@ public override bool IsDirty(object x, object y, bool[] checkable, ISessionImple
{
return false;
}
/*
* NH Different behavior : we don't use the shortcut because NH-1101
* let the tuplizer choose how cosiderer properties when the component is null.
*/
if (EntityMode != EntityMode.Poco && (x == null || y == null))
{
return true;
}
object[] xvalues = GetPropertyValues(x);
object[] yvalues = GetPropertyValues(y);
int loc = 0;
Expand Down

0 comments on commit 26f8d35

Please sign in to comment.