Skip to content

Commit 3f8afe3

Browse files
authored
Fix lazy set when adding a transient element with overridden Equals method (#2481)
1 parent 1df24c6 commit 3f8afe3

File tree

9 files changed

+160
-4
lines changed

9 files changed

+160
-4
lines changed

src/NHibernate.Test/Async/Extralazy/ExtraLazyFixture.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1326,6 +1326,52 @@ public async Task SetAddAsync(bool initialize)
13261326
}
13271327
}
13281328

1329+
[Test]
1330+
public async Task SetAddWithOverrideEqualsAsync()
1331+
{
1332+
User gavin;
1333+
User robert;
1334+
User tom;
1335+
1336+
using (var s = OpenSession())
1337+
using (var t = s.BeginTransaction())
1338+
{
1339+
gavin = new User("gavin", "secret");
1340+
robert = new User("robert", "secret");
1341+
tom = new User("tom", "secret");
1342+
await (s.PersistAsync(gavin));
1343+
await (s.PersistAsync(robert));
1344+
await (s.PersistAsync(tom));
1345+
1346+
gavin.Followers.Add(new UserFollower(gavin, robert));
1347+
gavin.Followers.Add(new UserFollower(gavin, tom));
1348+
robert.Followers.Add(new UserFollower(robert, tom));
1349+
1350+
Assert.That(gavin.Followers.Count, Is.EqualTo(2), "Gavin's documents count after adding 2");
1351+
Assert.That(robert.Followers.Count, Is.EqualTo(1), "Robert's followers count after adding one");
1352+
1353+
await (t.CommitAsync());
1354+
}
1355+
1356+
using (var s = OpenSession())
1357+
using (var t = s.BeginTransaction())
1358+
{
1359+
gavin = await (s.GetAsync<User>("gavin"));
1360+
robert = await (s.GetAsync<User>("robert"));
1361+
tom = await (s.GetAsync<User>("tom"));
1362+
1363+
// Re-add
1364+
Assert.That(gavin.Followers.Add(new UserFollower(gavin, robert)), Is.False, "Re-adding element");
1365+
Assert.That(NHibernateUtil.IsInitialized(gavin.Followers), Is.True, "Documents initialization status after re-adding");
1366+
Assert.That(gavin.Followers, Has.Count.EqualTo(2), "Gavin's followers count after re-adding");
1367+
1368+
// Add new
1369+
Assert.That(robert.Followers.Add(new UserFollower(robert, gavin)), Is.True, "Adding element");
1370+
Assert.That(NHibernateUtil.IsInitialized(gavin.Followers), Is.True, "Documents initialization status after adding");
1371+
Assert.That(gavin.Followers, Has.Count.EqualTo(2), "Robert's followers count after re-adding");
1372+
}
1373+
}
1374+
13291375
[TestCase(false, false)]
13301376
[TestCase(false, true)]
13311377
[TestCase(true, false)]

src/NHibernate.Test/Extralazy/ExtraLazyFixture.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1315,6 +1315,52 @@ public void SetAdd(bool initialize)
13151315
}
13161316
}
13171317

1318+
[Test]
1319+
public void SetAddWithOverrideEquals()
1320+
{
1321+
User gavin;
1322+
User robert;
1323+
User tom;
1324+
1325+
using (var s = OpenSession())
1326+
using (var t = s.BeginTransaction())
1327+
{
1328+
gavin = new User("gavin", "secret");
1329+
robert = new User("robert", "secret");
1330+
tom = new User("tom", "secret");
1331+
s.Persist(gavin);
1332+
s.Persist(robert);
1333+
s.Persist(tom);
1334+
1335+
gavin.Followers.Add(new UserFollower(gavin, robert));
1336+
gavin.Followers.Add(new UserFollower(gavin, tom));
1337+
robert.Followers.Add(new UserFollower(robert, tom));
1338+
1339+
Assert.That(gavin.Followers.Count, Is.EqualTo(2), "Gavin's documents count after adding 2");
1340+
Assert.That(robert.Followers.Count, Is.EqualTo(1), "Robert's followers count after adding one");
1341+
1342+
t.Commit();
1343+
}
1344+
1345+
using (var s = OpenSession())
1346+
using (var t = s.BeginTransaction())
1347+
{
1348+
gavin = s.Get<User>("gavin");
1349+
robert = s.Get<User>("robert");
1350+
tom = s.Get<User>("tom");
1351+
1352+
// Re-add
1353+
Assert.That(gavin.Followers.Add(new UserFollower(gavin, robert)), Is.False, "Re-adding element");
1354+
Assert.That(NHibernateUtil.IsInitialized(gavin.Followers), Is.True, "Documents initialization status after re-adding");
1355+
Assert.That(gavin.Followers, Has.Count.EqualTo(2), "Gavin's followers count after re-adding");
1356+
1357+
// Add new
1358+
Assert.That(robert.Followers.Add(new UserFollower(robert, gavin)), Is.True, "Adding element");
1359+
Assert.That(NHibernateUtil.IsInitialized(gavin.Followers), Is.True, "Documents initialization status after adding");
1360+
Assert.That(gavin.Followers, Has.Count.EqualTo(2), "Robert's followers count after re-adding");
1361+
}
1362+
}
1363+
13181364
[TestCase(false, false)]
13191365
[TestCase(false, true)]
13201366
[TestCase(true, false)]

src/NHibernate.Test/Extralazy/User.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ public virtual ISet<Photo> Photos
5050

5151
public virtual ISet<UserPermission> Permissions { get; set; } = new HashSet<UserPermission>();
5252

53+
public virtual ISet<UserFollower> Followers { get; set; } = new HashSet<UserFollower>();
54+
5355
public virtual IList<Company> Companies { get; set; } = new List<Company>();
5456

5557
public virtual IList<CreditCard> CreditCards { get; set; } = new List<CreditCard>();
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using System;
2+
3+
namespace NHibernate.Test.Extralazy
4+
{
5+
public class UserFollower : IEquatable<UserFollower>
6+
{
7+
public UserFollower(User user, User follower)
8+
{
9+
User = user;
10+
Follower = follower;
11+
}
12+
13+
protected UserFollower()
14+
{
15+
}
16+
17+
public virtual int Id { get; set; }
18+
19+
public virtual User User { get; set; }
20+
21+
public virtual User Follower { get; set; }
22+
23+
public override bool Equals(object obj)
24+
{
25+
if (obj == null) return false;
26+
if (ReferenceEquals(this, obj)) return true;
27+
if (obj.GetType() != GetType()) return false;
28+
return Equals((UserFollower) obj);
29+
}
30+
31+
public virtual bool Equals(UserFollower other)
32+
{
33+
if (other == null) return false;
34+
if (ReferenceEquals(this, other)) return true;
35+
return Equals(User.Name, other.User.Name) && Equals(Follower.Name, other.Follower.Name);
36+
}
37+
38+
public override int GetHashCode()
39+
{
40+
unchecked
41+
{
42+
return (User.Name.GetHashCode() * 397) ^ Follower.Name.GetHashCode();
43+
}
44+
}
45+
}
46+
}

src/NHibernate.Test/Extralazy/UserGroup.hbm.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030
<key column="owner"/>
3131
<one-to-many class="Document"/>
3232
</set>
33+
<set name="Followers" inverse="true" lazy="true" cascade="all,delete-orphan">
34+
<key column="userId"/>
35+
<one-to-many class="UserFollower"/>
36+
</set>
3337
<set name="Photos" inverse="true" lazy="true" where="Title like 'PRV%'" cascade="all,delete-orphan">
3438
<key column="owner"/>
3539
<one-to-many class="Photo"/>
@@ -77,6 +81,13 @@
7781
<property name="OriginalIndex" />
7882
<many-to-one name="Owner" not-null="true"/>
7983
</class>
84+
<class name="UserFollower" table="user_follower">
85+
<id name="Id">
86+
<generator class="native"/>
87+
</id>
88+
<many-to-one name="User" column="userId" not-null="true"/>
89+
<many-to-one name="Follower" column="followerId" not-null="true"/>
90+
</class>
8091
<class name="CreditCard" table="credit_card">
8192
<id name="Id">
8293
<generator class="native"/>

src/NHibernate/Async/Collection/AbstractPersistentCollection.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,14 @@ public abstract partial class AbstractPersistentCollection : IPersistentCollecti
7474
return null;
7575
}
7676

77-
internal async Task<bool> IsTransientAsync(object element, CancellationToken cancellationToken)
77+
internal async Task<bool> CanSkipElementExistenceCheckAsync(object element, CancellationToken cancellationToken)
7878
{
7979
cancellationToken.ThrowIfCancellationRequested();
8080
var queryableCollection = (IQueryableCollection) Session.Factory.GetCollectionPersister(Role);
8181
return
8282
queryableCollection != null &&
8383
queryableCollection.ElementType.IsEntityType &&
84+
!queryableCollection.ElementPersister.EntityMetamodel.OverridesEquals &&
8485
!element.IsProxy() &&
8586
!Session.PersistenceContext.IsEntryFor(element) &&
8687
await (ForeignKeys.IsTransientFastAsync(queryableCollection.ElementPersister.EntityName, element, Session, cancellationToken)).ConfigureAwait(false) == true;

src/NHibernate/Collection/AbstractPersistentCollection.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -775,12 +775,13 @@ private AbstractQueueOperationTracker TryFlushAndGetQueueOperationTracker(string
775775
return queueOperationTracker;
776776
}
777777

778-
internal bool IsTransient(object element)
778+
internal bool CanSkipElementExistenceCheck(object element)
779779
{
780780
var queryableCollection = (IQueryableCollection) Session.Factory.GetCollectionPersister(Role);
781781
return
782782
queryableCollection != null &&
783783
queryableCollection.ElementType.IsEntityType &&
784+
!queryableCollection.ElementPersister.EntityMetamodel.OverridesEquals &&
784785
!element.IsProxy() &&
785786
!Session.PersistenceContext.IsEntryFor(element) &&
786787
ForeignKeys.IsTransientFast(queryableCollection.ElementPersister.EntityName, element, Session) == true;

src/NHibernate/Collection/Generic/PersistentGenericSet.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,8 +314,8 @@ public bool Contains(T item)
314314
public bool Add(T o)
315315
{
316316
// Skip checking the element existence in the database if we know that the element
317-
// is transient and the operation queue is enabled
318-
if (WasInitialized || !IsOperationQueueEnabled || !IsTransient(o))
317+
// is transient, the mapped class does not override Equals method and the operation queue is enabled
318+
if (WasInitialized || !IsOperationQueueEnabled || !CanSkipElementExistenceCheck(o))
319319
{
320320
var exists = IsOperationQueueEnabled ? ReadElementExistence(o, out _) : null;
321321
if (!exists.HasValue)

src/NHibernate/Tuple/Entity/EntityMetamodel.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ public EntityMetamodel(PersistentClass persistentClass, ISessionFactoryImplement
9090
type = persistentClass.MappedClass;
9191
rootType = persistentClass.RootClazz.MappedClass;
9292
rootTypeAssemblyQualifiedName = rootType == null ? null : rootType.AssemblyQualifiedName;
93+
OverridesEquals = type != null && ReflectHelper.OverridesEquals(type); // type will be null for dynamic entities
9394

9495
identifierProperty = PropertyFactory.BuildIdentifierProperty(persistentClass,
9596
sessionFactory.GetIdentifierGenerator(rootName));
@@ -549,6 +550,8 @@ public StandardProperty[] Properties
549550
get { return properties; }
550551
}
551552

553+
internal bool OverridesEquals { get; private set; }
554+
552555
public int GetPropertyIndex(string propertyName)
553556
{
554557
int? index = GetPropertyIndexOrNull(propertyName);

0 commit comments

Comments
 (0)