Skip to content

Commit cf930a5

Browse files
bahusoidhazzik
authored andcommitted
Fix exception on session refresh for removed collection item
Fixes nhibernate#1738
1 parent ba46d8f commit cf930a5

File tree

5 files changed

+206
-4
lines changed

5 files changed

+206
-4
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
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;
12+
using NHibernate.Cfg.MappingSchema;
13+
using NHibernate.Mapping.ByCode;
14+
using NUnit.Framework;
15+
16+
namespace NHibernate.Test.NHSpecificTest.GH1738
17+
{
18+
using System.Threading.Tasks;
19+
[TestFixture]
20+
public class RefreshLocallyRemovedCollectionItemFixtureAsync : TestCaseMappingByCode
21+
{
22+
private Guid _id;
23+
24+
protected override HbmMapping GetMappings()
25+
{
26+
var mapper = new ModelMapper();
27+
mapper.Class<Entity>(rc =>
28+
{
29+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
30+
rc.Property(x => x.Name);
31+
rc.Bag(
32+
x => x.Children,
33+
m =>
34+
{
35+
m.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans);
36+
m.Inverse(true);
37+
m.Key(km => km.Column("Parent"));
38+
},
39+
relation => relation.OneToMany());
40+
});
41+
42+
mapper.Class<Child>(rc =>
43+
{
44+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
45+
rc.Property(x => x.Name);
46+
rc.ManyToOne(x => x.Parent);
47+
});
48+
49+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
50+
}
51+
52+
protected override void OnSetUp()
53+
{
54+
using (var session = OpenSession())
55+
using (var transaction = session.BeginTransaction())
56+
{
57+
var e1 = new Entity { Name = "Bob"};
58+
e1.Children.Add(new Child() {Name = "Child", Parent = e1});
59+
session.Save(e1);
60+
transaction.Commit();
61+
_id = e1.Id;
62+
}
63+
}
64+
65+
protected override void OnTearDown()
66+
{
67+
using (var session = OpenSession())
68+
using (var transaction = session.BeginTransaction())
69+
{
70+
session.Delete("from System.Object");
71+
transaction.Commit();
72+
}
73+
}
74+
75+
[Test]
76+
public async Task RefreshLocallyRemovedCollectionItemAsync()
77+
{
78+
Entity entity;
79+
using (var session = OpenSession())
80+
{
81+
entity = await (session.GetAsync<Entity>(_id));
82+
entity.Children.RemoveAt(0);
83+
}
84+
85+
using (var session = OpenSession())
86+
{
87+
await (session.UpdateAsync(entity));
88+
await (session.RefreshAsync(entity));
89+
foreach (var child in entity.Children)
90+
{
91+
await (session.RefreshAsync(child));
92+
}
93+
94+
Assert.That(session.GetSessionImplementation().PersistenceContext.IsReadOnly(entity), Is.False);
95+
}
96+
}
97+
}
98+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace NHibernate.Test.NHSpecificTest.GH1738
5+
{
6+
class Entity
7+
{
8+
public virtual Guid Id { get; set; }
9+
public virtual string Name { get; set; }
10+
public virtual IList<Child> Children { get; set; } = new List<Child>();
11+
}
12+
13+
class Child
14+
{
15+
public virtual Guid Id { get; set; }
16+
public virtual string Name { get; set; }
17+
public virtual Entity Parent { get; set; }
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
using System;
2+
using NHibernate.Cfg.MappingSchema;
3+
using NHibernate.Mapping.ByCode;
4+
using NUnit.Framework;
5+
6+
namespace NHibernate.Test.NHSpecificTest.GH1738
7+
{
8+
[TestFixture]
9+
public class RefreshLocallyRemovedCollectionItemFixture : TestCaseMappingByCode
10+
{
11+
private Guid _id;
12+
13+
protected override HbmMapping GetMappings()
14+
{
15+
var mapper = new ModelMapper();
16+
mapper.Class<Entity>(rc =>
17+
{
18+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
19+
rc.Property(x => x.Name);
20+
rc.Bag(
21+
x => x.Children,
22+
m =>
23+
{
24+
m.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans);
25+
m.Inverse(true);
26+
m.Key(km => km.Column("Parent"));
27+
},
28+
relation => relation.OneToMany());
29+
});
30+
31+
mapper.Class<Child>(rc =>
32+
{
33+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
34+
rc.Property(x => x.Name);
35+
rc.ManyToOne(x => x.Parent);
36+
});
37+
38+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
39+
}
40+
41+
protected override void OnSetUp()
42+
{
43+
using (var session = OpenSession())
44+
using (var transaction = session.BeginTransaction())
45+
{
46+
var e1 = new Entity { Name = "Bob"};
47+
e1.Children.Add(new Child() {Name = "Child", Parent = e1});
48+
session.Save(e1);
49+
transaction.Commit();
50+
_id = e1.Id;
51+
}
52+
}
53+
54+
protected override void OnTearDown()
55+
{
56+
using (var session = OpenSession())
57+
using (var transaction = session.BeginTransaction())
58+
{
59+
session.Delete("from System.Object");
60+
transaction.Commit();
61+
}
62+
}
63+
64+
[Test]
65+
public void RefreshLocallyRemovedCollectionItem()
66+
{
67+
Entity entity;
68+
using (var session = OpenSession())
69+
{
70+
entity = session.Get<Entity>(_id);
71+
entity.Children.RemoveAt(0);
72+
}
73+
74+
using (var session = OpenSession())
75+
{
76+
session.Update(entity);
77+
session.Refresh(entity);
78+
foreach (var child in entity.Children)
79+
{
80+
session.Refresh(child);
81+
}
82+
83+
Assert.That(session.GetSessionImplementation().PersistenceContext.IsReadOnly(entity), Is.False);
84+
}
85+
}
86+
}
87+
}

src/NHibernate/Async/Event/Default/DefaultRefreshEventListener.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,7 @@ public virtual async Task OnRefreshAsync(RefreshEvent @event, IDictionary refres
130130
if (!persister.IsMutable)
131131
source.SetReadOnly(result, true);
132132
else
133-
source.SetReadOnly(result, (e == null ? source.DefaultReadOnly : e.IsReadOnly));
134-
133+
source.SetReadOnly(result, e == null ? source.DefaultReadOnly : !e.IsModifiableEntity());
135134
source.FetchProfile = previousFetchProfile;
136135

137136
// NH Different behavior : we are ignoring transient entities without throw any kind of exception

src/NHibernate/Event/Default/DefaultRefreshEventListener.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,7 @@ public virtual void OnRefresh(RefreshEvent @event, IDictionary refreshedAlready)
112112
if (!persister.IsMutable)
113113
source.SetReadOnly(result, true);
114114
else
115-
source.SetReadOnly(result, (e == null ? source.DefaultReadOnly : e.IsReadOnly));
116-
115+
source.SetReadOnly(result, e == null ? source.DefaultReadOnly : !e.IsModifiableEntity());
117116
source.FetchProfile = previousFetchProfile;
118117

119118
// NH Different behavior : we are ignoring transient entities without throw any kind of exception

0 commit comments

Comments
 (0)