Skip to content

Commit f91b6c5

Browse files
committed
Registry-level changes prevention
1 parent 644f3ab commit f91b6c5

File tree

7 files changed

+104
-39
lines changed

7 files changed

+104
-39
lines changed

Orm/Xtensive.Orm/Orm/Internals/EntityChangeRegistry.cs

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ public sealed class EntityChangeRegistry : SessionBound
2121
private readonly HashSet<EntityState> removed = new();
2222
private int count;
2323

24+
private bool changesDisabled;
25+
2426
/// <summary>
2527
/// Gets the number of registered entities.
2628
/// </summary>
@@ -34,7 +36,7 @@ internal void Register(EntityState item)
3436
{
3537
// Remove-create sequences fix for Issue 690
3638
if (item.PersistenceState == PersistenceState.New && removed.Contains(item)) {
37-
EnsureChangesAreNotPersisting();
39+
EnsureRegistrationsAllowed();
3840
_ = removed.Remove(item);
3941
count--;
4042
if (item.DifferentialTuple.Difference == null) {
@@ -44,19 +46,19 @@ internal void Register(EntityState item)
4446
item.SetPersistenceState(PersistenceState.Modified);
4547
}
4648
else if (item.PersistenceState == PersistenceState.Removed && @new.Contains(item)) {
47-
EnsureChangesAreNotPersisting();
49+
EnsureRegistrationsAllowed();
4850
_ = @new.Remove(item);
4951
count--;
5052
return;
5153
}
5254
else if (item.PersistenceState == PersistenceState.Removed && modified.Contains(item)) {
53-
EnsureChangesAreNotPersisting();
55+
EnsureRegistrationsAllowed();
5456
_ = modified.Remove(item);
5557
count--;
5658
}
5759

5860
var container = GetContainer(item.PersistenceState);
59-
EnsureChangesAreNotPersisting();
61+
EnsureRegistrationsAllowed();
6062
if (container.Add(item)) {
6163
count++;
6264
}
@@ -84,18 +86,32 @@ public void Clear()
8486
removed.Clear();
8587
}
8688

89+
internal Core.Disposable PreventChanges()
90+
{
91+
changesDisabled = true;
92+
return new Core.Disposable((a) => changesDisabled = false);
93+
}
94+
8795
/// <exception cref="ArgumentOutOfRangeException"><paramref name="state"/> is out of range.</exception>
8896
private HashSet<EntityState> GetContainer(PersistenceState state)
8997
{
9098
switch (state) {
91-
case PersistenceState.New:
92-
return @new;
93-
case PersistenceState.Modified:
94-
return modified;
95-
case PersistenceState.Removed:
96-
return removed;
97-
default:
98-
throw new ArgumentOutOfRangeException("state");
99+
case PersistenceState.New:
100+
return @new;
101+
case PersistenceState.Modified:
102+
return modified;
103+
case PersistenceState.Removed:
104+
return removed;
105+
default:
106+
throw new ArgumentOutOfRangeException(nameof(state));
107+
}
108+
}
109+
110+
private void EnsureRegistrationsAllowed()
111+
{
112+
if (changesDisabled) {
113+
throw new InvalidOperationException(
114+
string.Format(Strings.ExSessionXIsActivelyPersistingChangesNoPersistentChangesAllowed, Session.Guid));
99115
}
100116
}
101117

Orm/Xtensive.Orm/Orm/Internals/EntitySetChangeRegistry.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// Created by: Alexey Kulakov
55
// Created: 2014.03.27
66

7+
using System;
78
using System.Collections.Generic;
89

910
namespace Xtensive.Orm.Internals
@@ -15,6 +16,8 @@ public sealed class EntitySetChangeRegistry : SessionBound
1516
{
1617
private readonly HashSet<EntitySetState> modifiedEntitySets = new();
1718

19+
private bool changesDisabled;
20+
1821
/// <summary>
1922
/// Count of registered <see cref="EntitySetState"/>.
2023
/// </summary>
@@ -26,7 +29,7 @@ public sealed class EntitySetChangeRegistry : SessionBound
2629
/// <param name="entitySetState"><see cref="EntitySetState"/> to bound.</param>
2730
public void Register(EntitySetState entitySetState)
2831
{
29-
EnsureChangesAreNotPersisting();
32+
EnsureRegistrationsAllowed();
3033
_ = modifiedEntitySets.Add(entitySetState);
3134
}
3235

@@ -41,11 +44,26 @@ public void Register(EntitySetState entitySetState)
4144
/// </summary>
4245
public void Clear() => modifiedEntitySets.Clear();
4346

47+
internal Core.Disposable PreventChanges()
48+
{
49+
changesDisabled = true;
50+
return new Core.Disposable((a) => changesDisabled = false);
51+
}
52+
53+
private void EnsureRegistrationsAllowed()
54+
{
55+
if (changesDisabled) {
56+
throw new InvalidOperationException(
57+
string.Format(Strings.ExSessionXIsActivelyPersistingChangesNoPersistentChangesAllowed, Session.Guid));
58+
}
59+
}
60+
4461
/// <summary>
4562
/// Initializes a new instance of this class.
4663
/// </summary>
4764
/// <param name="session"><see cref="Session"/>, to which current instance
4865
/// is bound.</param>
66+
///
4967
public EntitySetChangeRegistry(Session session)
5068
: base(session)
5169
{

Orm/Xtensive.Orm/Orm/Internals/NonPairedReferenceChangesRegistry.cs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ public Identifier(EntityState entityState, AssociationInfo association)
5454
private readonly IDictionary<Identifier, HashSet<EntityState>> addedReferences = new Dictionary<Identifier, HashSet<EntityState>>();
5555
private readonly object accessGuard = new();
5656

57+
private bool changesDisabled;
58+
5759
public int RemovedReferencesCount => removedReferences.Values.Sum(el => el.Count);
5860

5961
public int AddedReferencesCount => addedReferences.Values.Sum(el => el.Count);
@@ -98,6 +100,12 @@ public void Clear()
98100
}
99101
}
100102

103+
internal Core.Disposable PreventChanges()
104+
{
105+
changesDisabled = true;
106+
return new Core.Disposable((a) => changesDisabled = false);
107+
}
108+
101109
private void RegisterChange(EntityState referencedState, EntityState referencingState, EntityState noLongerReferencedState, AssociationInfo association)
102110
{
103111
ArgumentValidator.EnsureArgumentNotNull(association, "association");
@@ -129,7 +137,7 @@ private void RegisterChange(EntityState referencedState, EntityState referencing
129137
private void RegisterRemoveInternal(Identifier oldKey, EntityState referencingState)
130138
{
131139
if (addedReferences.TryGetValue(oldKey, out var addedRefs)) {
132-
EnsureChangesAreNotPersisting();
140+
EnsureRegistrationsAllowed();
133141
if (addedRefs.Remove(referencingState)) {
134142
if (addedRefs.Count == 0) {
135143
_ = addedReferences.Remove(oldKey);
@@ -143,14 +151,14 @@ private void RegisterRemoveInternal(Identifier oldKey, EntityState referencingSt
143151
}
144152
return;
145153
}
146-
EnsureChangesAreNotPersisting();
154+
EnsureRegistrationsAllowed();
147155
removedReferences.Add(oldKey, new HashSet<EntityState>{referencingState});
148156
}
149157

150158
private void RegisterAddInternal(Identifier newKey, EntityState referencingState)
151159
{
152160
if (removedReferences.TryGetValue(newKey, out var removedRefs)) {
153-
EnsureChangesAreNotPersisting();
161+
EnsureRegistrationsAllowed();
154162
if (removedRefs.Remove(referencingState)) {
155163
if (removedRefs.Count == 0) {
156164
_ = removedReferences.Remove(newKey);
@@ -165,7 +173,7 @@ private void RegisterAddInternal(Identifier newKey, EntityState referencingState
165173
}
166174
return;
167175
}
168-
EnsureChangesAreNotPersisting();
176+
EnsureRegistrationsAllowed();
169177
addedReferences.Add(newKey, new HashSet<EntityState>{referencingState});
170178
}
171179

@@ -276,6 +284,14 @@ private EntityState GetStructureFieldValue(FieldInfo fieldOfStructure, Structure
276284
private string BuildNameOfEntityField(FieldInfo fieldOfOwner, FieldInfo referenceFieldOfStructure) =>
277285
$"{fieldOfOwner.Name}.{referenceFieldOfStructure.Name}";
278286

287+
private void EnsureRegistrationsAllowed()
288+
{
289+
if (changesDisabled) {
290+
throw new InvalidOperationException(
291+
string.Format(Strings.ExSessionXIsActivelyPersistingChangesNoPersistentChangesAllowed, Session.Guid));
292+
}
293+
}
294+
279295
internal NonPairedReferenceChangesRegistry(Session session)
280296
: base(session)
281297
{

Orm/Xtensive.Orm/Orm/Internals/ReferenceFieldsChangesRegistry.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// Created by: Alexey Kulakov
55
// Created: 2014.04.07
66

7+
using System;
78
using System.Collections.Generic;
89
using Xtensive.Core;
910
using Xtensive.Orm.Model;
@@ -17,6 +18,8 @@ internal sealed class ReferenceFieldsChangesRegistry : SessionBound
1718
{
1819
private readonly HashSet<ReferenceFieldChangeInfo> changes = new();
1920

21+
private bool changesDisabled;
22+
2023
/// <summary>
2124
/// Registrates information about field which value was set.
2225
/// </summary>
@@ -63,13 +66,27 @@ public void Clear()
6366
{
6467
changes.Clear();
6568
}
66-
69+
70+
internal Core.Disposable PreventChanges()
71+
{
72+
changesDisabled = true;
73+
return new Core.Disposable((a) => changesDisabled = false);
74+
}
75+
6776
private void Register(ReferenceFieldChangeInfo fieldChangeInfo)
6877
{
69-
EnsureChangesAreNotPersisting();
78+
EnsureRegistrationsAllowed();
7079
_ = changes.Add(fieldChangeInfo);
7180
}
7281

82+
private void EnsureRegistrationsAllowed()
83+
{
84+
if (changesDisabled) {
85+
throw new System.InvalidOperationException(
86+
string.Format(Strings.ExSessionXIsActivelyPersistingChangesNoPersistentChangesAllowed, Session.Guid));
87+
}
88+
}
89+
7390
public ReferenceFieldsChangesRegistry(Session session)
7491
: base(session)
7592
{

Orm/Xtensive.Orm/Orm/Session.Persist.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,12 +134,15 @@ private async ValueTask Persist(PersistReason reason, bool isAsync, Cancellation
134134

135135
var ts = await InnerOpenTransaction(
136136
TransactionOpenMode.Default, IsolationLevel.Unspecified, false, isAsync, token);
137+
138+
IDisposable changesGuard = null;
137139
try {
138140
IsPersisting = true;
139141
persistingIsFailed = false;
140142
SystemEvents.NotifyPersisting();
141143
Events.NotifyPersisting();
142-
ChangesInProcessing = true;
144+
145+
changesGuard = PreventRegistryChanges();
143146
using (OpenSystemLogicOnlyRegion()) {
144147
DemandTransaction();
145148
if (IsDebugEventLoggingEnabled) {
@@ -173,11 +176,16 @@ private async ValueTask Persist(PersistReason reason, bool isAsync, Cancellation
173176
}
174177
catch (Exception) {
175178
persistingIsFailed = true;
179+
changesGuard.Dispose();
180+
changesGuard = null;
181+
176182
RollbackChangesOfEntitySets();
177183
RestoreEntityChangesAfterPersistFailed();
178184
throw;
179185
}
180186
finally {
187+
changesGuard.DisposeSafely();
188+
changesGuard = null;
181189
if (persistIsSuccessful || !Configuration.Supports(SessionOptions.NonTransactionalEntityStates)) {
182190
DropDifferenceBackup();
183191
foreach (var item in itemsToPersist.GetItems(PersistenceState.New)) {
@@ -210,12 +218,12 @@ private async ValueTask Persist(PersistReason reason, bool isAsync, Cancellation
210218
}
211219
}
212220

213-
ChangesInProcessing = false;
214221
SystemEvents.NotifyPersisted();
215222
Events.NotifyPersisted();
216223
}
217224
finally {
218225
IsPersisting = false;
226+
changesGuard.DisposeSafely();
219227
if (isAsync) {
220228
await ts.DisposeAsync().ConfigureAwait(false);
221229
}
@@ -363,5 +371,13 @@ private void ProcessChangesOfEntitySets(Action<EntitySetState> action)
363371
foreach (var entitySet in itemsToProcess)
364372
action.Invoke(entitySet);
365373
}
374+
375+
private IDisposable PreventRegistryChanges()
376+
{
377+
return EntityChangeRegistry.PreventChanges()
378+
& EntitySetChangeRegistry.PreventChanges()
379+
& NonPairedReferencesRegistry.PreventChanges()
380+
& ReferenceFieldsChangesRegistry.PreventChanges();
381+
}
366382
}
367383
}

Orm/Xtensive.Orm/Orm/Session.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,6 @@ private static readonly Type
113113
/// </summary>
114114
internal bool IsPersisting { get; private set; }
115115

116-
/// <summary>
117-
/// Gets a value indicating that saving changes in that stage when any other persistent
118-
/// object cannot be changed within session.
119-
/// </summary>
120-
internal bool ChangesInProcessing { get; private set; }
121-
122116
/// <summary>
123117
/// Gets a value indicating whether session is disconnected:
124118
/// session supports non-transactional entity states and does not support autosaving of changes.

Orm/Xtensive.Orm/Orm/SessionBound.cs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,6 @@ protected void EnsureTheSameSession(SessionBound other)
3333
throw new ArgumentException(Strings.ExSessionOfAnotherSessionBoundMustBeTheSame, nameof(other));
3434
}
3535

36-
/// <summary>
37-
/// Ensures that <see cref="Session"/> is not on stage when changes are persisting
38-
/// and any changes are forbidden.
39-
/// </summary>
40-
protected void EnsureChangesAreNotPersisting()
41-
{
42-
if (Session.ChangesInProcessing) {
43-
throw new InvalidOperationException(
44-
string.Format(Strings.ExSessionXIsActivelyPersistingChangesNoPersistentChangesAllowed, Session.Guid));
45-
}
46-
}
47-
4836
#region IContextBound<Session> Members
4937

5038
Session IContextBound<Session>.Context {

0 commit comments

Comments
 (0)