Skip to content

Commit 26f8d35

Browse files
authored
Fix empty dynamic components causing a phantom update (#3600)
Fix #3421
1 parent b4f8b01 commit 26f8d35

File tree

5 files changed

+227
-32
lines changed

5 files changed

+227
-32
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
//------------------------------------------------------------------------------
2+
// <auto-generated>
3+
// This code was generated by AsyncGenerator.
4+
//
5+
// Changes to this file may cause incorrect behavior and will be lost if
6+
// the code is regenerated.
7+
// </auto-generated>
8+
//------------------------------------------------------------------------------
9+
10+
11+
using System.Collections.Generic;
12+
using System.Linq;
13+
using NHibernate.Cfg;
14+
using NHibernate.Cfg.MappingSchema;
15+
using NHibernate.Mapping.ByCode;
16+
using NHibernate.SqlCommand;
17+
using NUnit.Framework;
18+
using NHibernate.Linq;
19+
20+
namespace NHibernate.Test.NHSpecificTest.GH3421
21+
{
22+
using System.Threading.Tasks;
23+
[TestFixture]
24+
public class ByCodeFixtureAsync : TestCaseMappingByCode
25+
{
26+
private SqlInterceptor _interceptor;
27+
28+
protected override HbmMapping GetMappings()
29+
{
30+
var mapper = new ModelMapper();
31+
mapper.Class<Entity>(rc =>
32+
{
33+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
34+
rc.Property(x => x.Name);
35+
rc.Component(x => x.Attributes, new {
36+
Sku = (string)null
37+
}, dc => {
38+
dc.Property(x => x.Sku);
39+
});
40+
});
41+
42+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
43+
}
44+
45+
protected override void Configure(Configuration configuration)
46+
{
47+
base.Configure(configuration);
48+
49+
_interceptor = new SqlInterceptor();
50+
51+
configuration.SetInterceptor(_interceptor);
52+
}
53+
54+
protected override void OnSetUp()
55+
{
56+
using (var session = OpenSession())
57+
using (var transaction = session.BeginTransaction())
58+
{
59+
var e1 = new Entity { Name = "Bob" };
60+
session.Save(e1);
61+
62+
var e2 = new Entity { Name = "Sally", Attributes = new Dictionary<string, object>() {
63+
{ "Sku", "AAA" }
64+
} };
65+
session.Save(e2);
66+
67+
transaction.Commit();
68+
}
69+
}
70+
71+
protected override void OnTearDown()
72+
{
73+
using (var session = OpenSession())
74+
using (var transaction = session.BeginTransaction())
75+
{
76+
session.CreateQuery("delete from Entity").ExecuteUpdate();
77+
transaction.Commit();
78+
}
79+
}
80+
81+
[Test]
82+
public async Task TestFlushDoesNotTriggerAnUpdateAsync()
83+
{
84+
using (var session = OpenSession())
85+
using (var transaction = session.BeginTransaction())
86+
{
87+
var foo = await (session.Query<Entity>().ToListAsync());
88+
89+
await (session.FlushAsync());
90+
91+
var updateStatements = _interceptor.SqlStatements.Where(s => s.ToString().ToUpper().Contains("UPDATE")).ToList();
92+
93+
Assert.That(updateStatements, Has.Count.EqualTo(0));
94+
}
95+
}
96+
97+
public class SqlInterceptor : EmptyInterceptor
98+
{
99+
public IList<SqlString> SqlStatements = new List<SqlString>();
100+
101+
public override SqlString OnPrepareStatement(SqlString sql)
102+
{
103+
SqlStatements.Add(sql);
104+
105+
return base.OnPrepareStatement(sql);
106+
}
107+
}
108+
}
109+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace NHibernate.Test.NHSpecificTest.GH3421
5+
{
6+
class Entity
7+
{
8+
public virtual Guid Id { get; set; }
9+
public virtual string Name { get; set; }
10+
11+
private IDictionary<string, object> _attributes;
12+
public virtual IDictionary<string, object> Attributes {
13+
get {
14+
if (_attributes == null)
15+
_attributes = new Dictionary<string, object>();
16+
return _attributes;
17+
}
18+
set => _attributes = value;
19+
}
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using NHibernate.Cfg;
4+
using NHibernate.Cfg.MappingSchema;
5+
using NHibernate.Mapping.ByCode;
6+
using NHibernate.SqlCommand;
7+
using NUnit.Framework;
8+
9+
namespace NHibernate.Test.NHSpecificTest.GH3421
10+
{
11+
[TestFixture]
12+
public class ByCodeFixture : TestCaseMappingByCode
13+
{
14+
private SqlInterceptor _interceptor;
15+
16+
protected override HbmMapping GetMappings()
17+
{
18+
var mapper = new ModelMapper();
19+
mapper.Class<Entity>(rc =>
20+
{
21+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
22+
rc.Property(x => x.Name);
23+
rc.Component(x => x.Attributes, new {
24+
Sku = (string)null
25+
}, dc => {
26+
dc.Property(x => x.Sku);
27+
});
28+
});
29+
30+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
31+
}
32+
33+
protected override void Configure(Configuration configuration)
34+
{
35+
base.Configure(configuration);
36+
37+
_interceptor = new SqlInterceptor();
38+
39+
configuration.SetInterceptor(_interceptor);
40+
}
41+
42+
protected override void OnSetUp()
43+
{
44+
using (var session = OpenSession())
45+
using (var transaction = session.BeginTransaction())
46+
{
47+
var e1 = new Entity { Name = "Bob" };
48+
session.Save(e1);
49+
50+
var e2 = new Entity { Name = "Sally", Attributes = new Dictionary<string, object>() {
51+
{ "Sku", "AAA" }
52+
} };
53+
session.Save(e2);
54+
55+
transaction.Commit();
56+
}
57+
}
58+
59+
protected override void OnTearDown()
60+
{
61+
using (var session = OpenSession())
62+
using (var transaction = session.BeginTransaction())
63+
{
64+
session.CreateQuery("delete from Entity").ExecuteUpdate();
65+
transaction.Commit();
66+
}
67+
}
68+
69+
[Test]
70+
public void TestFlushDoesNotTriggerAnUpdate()
71+
{
72+
using (var session = OpenSession())
73+
using (var transaction = session.BeginTransaction())
74+
{
75+
var foo = session.Query<Entity>().ToList();
76+
77+
session.Flush();
78+
79+
var updateStatements = _interceptor.SqlStatements.Where(s => s.ToString().ToUpper().Contains("UPDATE")).ToList();
80+
81+
Assert.That(updateStatements, Has.Count.EqualTo(0));
82+
}
83+
}
84+
85+
public class SqlInterceptor : EmptyInterceptor
86+
{
87+
public IList<SqlString> SqlStatements = new List<SqlString>();
88+
89+
public override SqlString OnPrepareStatement(SqlString sql)
90+
{
91+
SqlStatements.Add(sql);
92+
93+
return base.OnPrepareStatement(sql);
94+
}
95+
}
96+
}
97+
}

src/NHibernate/Async/Type/ComponentType.cs

-16
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,6 @@ public override async Task<bool> IsDirtyAsync(object x, object y, ISessionImplem
3333
{
3434
return false;
3535
}
36-
/*
37-
* NH Different behavior : we don't use the shortcut because NH-1101
38-
* let the tuplizer choose how cosiderer properties when the component is null.
39-
*/
40-
if (EntityMode != EntityMode.Poco && (x == null || y == null))
41-
{
42-
return true;
43-
}
4436
object[] xvalues = GetPropertyValues(x);
4537
object[] yvalues = GetPropertyValues(y);
4638
for (int i = 0; i < xvalues.Length; i++)
@@ -60,14 +52,6 @@ public override async Task<bool> IsDirtyAsync(object x, object y, bool[] checkab
6052
{
6153
return false;
6254
}
63-
/*
64-
* NH Different behavior : we don't use the shortcut because NH-1101
65-
* let the tuplizer choose how cosiderer properties when the component is null.
66-
*/
67-
if (EntityMode != EntityMode.Poco && (x == null || y == null))
68-
{
69-
return true;
70-
}
7155
object[] xvalues = GetPropertyValues(x);
7256
object[] yvalues = GetPropertyValues(y);
7357
int loc = 0;

src/NHibernate/Type/ComponentType.cs

-16
Original file line numberDiff line numberDiff line change
@@ -156,14 +156,6 @@ public override bool IsDirty(object x, object y, ISessionImplementor session)
156156
{
157157
return false;
158158
}
159-
/*
160-
* NH Different behavior : we don't use the shortcut because NH-1101
161-
* let the tuplizer choose how cosiderer properties when the component is null.
162-
*/
163-
if (EntityMode != EntityMode.Poco && (x == null || y == null))
164-
{
165-
return true;
166-
}
167159
object[] xvalues = GetPropertyValues(x);
168160
object[] yvalues = GetPropertyValues(y);
169161
for (int i = 0; i < xvalues.Length; i++)
@@ -182,14 +174,6 @@ public override bool IsDirty(object x, object y, bool[] checkable, ISessionImple
182174
{
183175
return false;
184176
}
185-
/*
186-
* NH Different behavior : we don't use the shortcut because NH-1101
187-
* let the tuplizer choose how cosiderer properties when the component is null.
188-
*/
189-
if (EntityMode != EntityMode.Poco && (x == null || y == null))
190-
{
191-
return true;
192-
}
193177
object[] xvalues = GetPropertyValues(x);
194178
object[] yvalues = GetPropertyValues(y);
195179
int loc = 0;

0 commit comments

Comments
 (0)