Skip to content

Commit 6bd1d63

Browse files
committed
Add failing test for NH-3772 + Proposed fix for API behavior for IUserCollectionType (NH-3772) when Instantiate returns an initialized collection
1 parent 091fa6b commit 6bd1d63

File tree

6 files changed

+188
-3
lines changed

6 files changed

+188
-3
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System.Collections;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using NHibernate.Collection;
5+
using NHibernate.Collection.Generic;
6+
using NHibernate.Engine;
7+
using NHibernate.Persister.Collection;
8+
using NHibernate.UserTypes;
9+
10+
namespace NHibernate.Test.NHSpecificTest.NH3772 {
11+
public class CustomGenericCollection<T> : IUserCollectionType where T : class {
12+
// Whether or not to use the failing test behavior, since it causes issues with TearDown
13+
public static bool TestBehavior = false;
14+
15+
public IPersistentCollection Instantiate(ISessionImplementor session, ICollectionPersister persister) {
16+
if (!TestBehavior) return new PersistentGenericSet<T>(session);
17+
18+
return new PersistentGenericSet<T>(session, new HashSet<T>(EqualityComparer<T>.Default));
19+
}
20+
21+
public IPersistentCollection Wrap(ISessionImplementor session, object collection) {
22+
var realCollection = (ISet<T>) collection;
23+
return new PersistentGenericSet<T>(session, realCollection);
24+
}
25+
26+
public IEnumerable GetElements(object collection) {
27+
var realCollection = (ISet<T>) collection;
28+
return realCollection.ToList();
29+
}
30+
31+
public bool Contains(object collection, object entity) {
32+
var realCollection = (ISet<T>) collection;
33+
return realCollection.Contains((T) entity);
34+
}
35+
36+
public object IndexOf(object collection, object entity) {
37+
return -1; // no indexing supported
38+
}
39+
40+
public object ReplaceElements(object original, object target, ICollectionPersister persister, object owner,
41+
IDictionary copyCache, ISessionImplementor session) {
42+
var originalCollection = (ISet<T>) original;
43+
var targetCollection = (ISet<T>) target;
44+
45+
targetCollection.Clear();
46+
Utils.AddRange(targetCollection, originalCollection);
47+
48+
return targetCollection;
49+
}
50+
51+
public object Instantiate(int anticipatedSize) {
52+
return new HashSet<T>(EqualityComparer<T>.Default);
53+
}
54+
}
55+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
6+
namespace NHibernate.Test.NHSpecificTest.NH3772 {
7+
public class Entity
8+
{
9+
private ICollection<SubEntity> _subEntities;
10+
public virtual int Id { get; set; }
11+
public virtual string Name { get; set; }
12+
13+
public virtual ICollection<SubEntity> SubEntities {
14+
get { return this._subEntities ?? (this._subEntities = new HashSet<SubEntity>()); }
15+
set { this._subEntities = value; }
16+
}
17+
}
18+
19+
public class SubEntity
20+
{
21+
public virtual int Id { get; set; }
22+
public virtual string Name { get; set; }
23+
}
24+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using System.Diagnostics;
2+
using System.Linq;
3+
using NHibernate.Cfg.MappingSchema;
4+
using NHibernate.Linq;
5+
using NHibernate.Mapping.ByCode;
6+
using NUnit.Framework;
7+
8+
namespace NHibernate.Test.NHSpecificTest.NH3772 {
9+
public class Fixture : TestCaseMappingByCode {
10+
protected override HbmMapping GetMappings() {
11+
var mapper = new ModelMapper();
12+
mapper.Class<Entity>(rc => {
13+
rc.Id(x => x.Id, m => m.Generator(Generators.Identity));
14+
rc.Property(x => x.Name);
15+
16+
rc.Set(x => x.SubEntities,
17+
x => {
18+
x.Type<CustomGenericCollection<SubEntity>>();
19+
},
20+
x => {
21+
x.ManyToMany();
22+
});
23+
});
24+
25+
mapper.Class<SubEntity>(rc => {
26+
rc.Id(x => x.Id, m => m.Generator(Generators.Identity));
27+
rc.Property(x => x.Name);
28+
});
29+
30+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
31+
}
32+
33+
protected override void OnSetUp() {
34+
using (var session = this.OpenSession()) {
35+
using (var transaction = session.BeginTransaction()) {
36+
var e1 = new Entity {Name = "Bob"};
37+
session.Save(e1);
38+
39+
var e2 = new Entity {Name = "Sally"};
40+
session.Save(e2);
41+
42+
var s1 = new SubEntity {Name = "Bob"};
43+
session.Save(s1);
44+
45+
var s2 = new SubEntity {Name = "Sally"};
46+
session.Save(s2);
47+
48+
session.Flush();
49+
transaction.Commit();
50+
}
51+
52+
}
53+
CustomGenericCollection<SubEntity>.TestBehavior = true;
54+
}
55+
56+
protected override void OnTearDown() {
57+
CustomGenericCollection<SubEntity>.TestBehavior = false;
58+
59+
using (var session = this.OpenSession()) {
60+
using (var transaction = session.BeginTransaction()) {
61+
session.Delete("from NHibernate.Test.NHSpecificTest.NH3772.Entity");
62+
session.Delete("from NHibernate.Test.NHSpecificTest.NH3772.SubEntity");
63+
64+
session.Flush();
65+
66+
transaction.Commit();
67+
}
68+
}
69+
}
70+
71+
[Test]
72+
public void CustomCollectionType_ThrowsHibernateException_WhenUserCollectionTypeReturnsInitializedPersistentCollection() {
73+
using (var session = this.OpenSession()) {
74+
using (session.BeginTransaction()) {
75+
TestDelegate action = () => (from e in session.Query<Entity>() where e.Name == "Bob" select e).First();
76+
77+
Assert.That(action, Throws.InstanceOf<HibernateException>().And.Message.EqualTo("UserCollectionType.Instantiate should return a non-initialized persistent collection. Implement UserCollectionType.Instantiate(int anticipatedSize) to actually create the collection that needs to be wrapped by the persistent collection."));
78+
}
79+
}
80+
}
81+
}
82+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System.Collections.Generic;
2+
3+
namespace NHibernate.Test.NHSpecificTest.NH3772 {
4+
public static class Utils {
5+
public static void AddRange<T>(ICollection<T> collection, IEnumerable<T> items) {
6+
foreach (var item in items) {
7+
collection.Add(item);
8+
}
9+
}
10+
}
11+
}

src/NHibernate.Test/NHibernate.Test.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,10 @@
859859
<Compile Include="NHSpecificTest\NH3604\FixtureByCode.cs" />
860860
<Compile Include="NHSpecificTest\NH3754\Fixture.cs" />
861861
<Compile Include="NHSpecificTest\NH3754\Model.cs" />
862+
<Compile Include="NHSpecificTest\NH3772\CustomGenericCollection.cs" />
863+
<Compile Include="NHSpecificTest\NH3772\Entity.cs" />
864+
<Compile Include="NHSpecificTest\NH3772\Fixture.cs" />
865+
<Compile Include="NHSpecificTest\NH3772\Utils.cs" />
862866
<Compile Include="NHSpecificTest\NH646\Domain.cs" />
863867
<Compile Include="NHSpecificTest\NH646\Fixture.cs" />
864868
<Compile Include="NHSpecificTest\NH3234\Fixture.cs" />

src/NHibernate/Type/CustomCollectionType.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,17 @@ public IUserCollectionType UserType
4747

4848
public override IPersistentCollection Instantiate(ISessionImplementor session, ICollectionPersister persister, object key)
4949
{
50-
return userType.Instantiate(session, persister);
50+
IPersistentCollection createdCollection = userType.Instantiate(session, persister);
51+
EnsureNotInitialized(createdCollection);
52+
return createdCollection;
53+
}
54+
55+
private static void EnsureNotInitialized(IPersistentCollection createdCollection)
56+
{
57+
if (createdCollection.WasInitialized) {
58+
throw new HibernateException(
59+
"UserCollectionType.Instantiate should return a non-initialized persistent collection. Implement UserCollectionType.Instantiate(int anticipatedSize) to actually create the collection that needs to be wrapped by the persistent collection.");
60+
}
5161
}
5262

5363
public override IPersistentCollection Wrap(ISessionImplementor session, object collection)
@@ -80,8 +90,7 @@ public override object IndexOf(object collection, object entity)
8090
return userType.IndexOf(collection, entity);
8191
}
8292

83-
public override object ReplaceElements(object original, object target, object owner, IDictionary copyCache,
84-
ISessionImplementor session)
93+
public override object ReplaceElements(object original, object target, object owner, IDictionary copyCache, ISessionImplementor session)
8594
{
8695
ICollectionPersister cp = session.Factory.GetCollectionPersister(Role);
8796
return userType.ReplaceElements(original, target, cp, owner, copyCache, session);

0 commit comments

Comments
 (0)