Skip to content

Commit dbdd795

Browse files
authored
Merge pull request #31 from koenbeuk/hotifx/change-cleanup
Fixed issue with unsetting changes
2 parents 60edbe0 + 8e2284e commit dbdd795

File tree

12 files changed

+155
-10
lines changed

12 files changed

+155
-10
lines changed

src/EntityFrameworkCore.Triggered.Abstractions/ITriggerSession.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,8 @@ namespace EntityFrameworkCore.Triggered
1010
public interface ITriggerSession
1111
{
1212
/// <summary>
13-
/// Captures any pending changes in the DbContext
13+
/// Discoveres any new pending changes in the DbContext
1414
/// </summary>
15-
/// <remarks>
16-
/// Some triggers, like AfterSaveTriggers need help in knowing what has changed since by the time they are raised, the DbContext should have already committed all pending changes
17-
/// Normally this is done by calling RaiseBeforeSaveTriggers prior to the DbContext committing on its changes however in case that is not possible, DiscoverChanges can be used to make a snapshot
18-
/// </remarks>
1915
void DiscoverChanges();
2016
/// <summary>
2117
/// Makes a snapshot of all changes in the DbContext and invokes BeforeSaveTriggers recursively based on the recursive settings until all changes have been processed
@@ -27,6 +23,10 @@ public interface ITriggerSession
2723
/// <param name="skipDetectedChanges">Allows BeforeSaveTriggers not to include previously detected changes. Only new changes will be detected and fired upon. This is useful in case of multiple calls to RaiseBeforeSaveTriggers</param>
2824
Task RaiseBeforeSaveTriggers(bool skipDetectedChanges, CancellationToken cancellationToken = default);
2925
/// <summary>
26+
/// Captures and locks all discovered changes
27+
/// </summary>
28+
void CaptureDiscoveredChanges();
29+
/// <summary>
3030
/// Invokes AfterSaveTriggers non-recursively. Calling this method expects that either RaiseBeforeSaveTriggers() or DiscoverChanges() has called
3131
/// </summary>
3232
/// <returns></returns>

src/EntityFrameworkCore.Triggered/Internal/AfterSaveTriggerDescriptor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
namespace EntityFrameworkCore.Triggered.Internal
1010
{
11-
public class AfterSaveTriggerDescriptor : ITriggerTypeDescriptor
11+
public sealed class AfterSaveTriggerDescriptor : ITriggerTypeDescriptor
1212
{
1313
readonly Func<object, object, CancellationToken, Task> _invocationDelegate;
1414
readonly Type _triggerType;

src/EntityFrameworkCore.Triggered/Internal/BeforeSaveTriggerDescriptor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
namespace EntityFrameworkCore.Triggered.Internal
1010
{
11-
public class BeforeSaveTriggerDescriptor : ITriggerTypeDescriptor
11+
public sealed class BeforeSaveTriggerDescriptor : ITriggerTypeDescriptor
1212
{
1313
readonly Func<object, object, CancellationToken, Task> _invocationDelegate;
1414
readonly Type _triggerType;

src/EntityFrameworkCore.Triggered/Internal/TriggerContextDescriptor.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ public TriggerContextDescriptor(EntityEntry entityEntry, ChangeType changeType)
2323

2424
}
2525

26-
2726
public ChangeType ChangeType => _changeType;
2827
public object Entity => _entityEntry!.Entity;
2928
public Type EntityType => _entityEntry!.Entity.GetType();

src/EntityFrameworkCore.Triggered/Internal/TriggerContextTracker.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using System;
22
using System.Collections.Concurrent;
33
using System.Collections.Generic;
4+
using System.Diagnostics;
45
using System.Linq;
6+
using System.Security.Cryptography.X509Certificates;
57
using System.Text;
68
using System.Threading.Tasks;
79
using EntityFrameworkCore.Triggered.Internal.RecursionStrategy;
@@ -94,6 +96,38 @@ public IEnumerable<TriggerContextDescriptor> DiscoverChanges()
9496
}
9597
}
9698

99+
public void CaptureChanges()
100+
{
101+
if (_discoveredChanges != null && _discoveredChanges.Count > 0)
102+
{
103+
List<TriggerContextDescriptor>? ignoreCandidates = null;
104+
105+
foreach (var discoveredChange in _discoveredChanges)
106+
{
107+
var currentEntityEntry = _changeTracker.Context.Entry(discoveredChange.Entity);
108+
var changeType = ResolveChangeType(currentEntityEntry);
109+
110+
if (changeType != discoveredChange.ChangeType)
111+
{
112+
if (ignoreCandidates == null)
113+
{
114+
ignoreCandidates = new List<TriggerContextDescriptor>();
115+
}
116+
117+
ignoreCandidates.Add(discoveredChange);
118+
}
119+
}
120+
121+
if (ignoreCandidates != null)
122+
{
123+
foreach (var ignoreCandidate in ignoreCandidates)
124+
{
125+
_discoveredChanges.Remove(ignoreCandidate);
126+
}
127+
}
128+
}
129+
}
130+
97131
public IEnumerable<TriggerContextDescriptor>? DiscoveredChanges => _discoveredChanges;
98132
}
99133
}

src/EntityFrameworkCore.Triggered/TriggerSession.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ public Task RaiseBeforeSaveTriggers(bool skipDetectedChanges, CancellationToken
120120
return RaiseTriggers(typeof(IBeforeSaveTrigger<>), strategy, entityType => new BeforeSaveTriggerDescriptor(entityType), cancellationToken);
121121
}
122122

123+
public void CaptureDiscoveredChanges()
124+
{
125+
_tracker.CaptureChanges();
126+
}
127+
123128
public Task RaiseAfterSaveTriggers(CancellationToken cancellationToken = default)
124129
{
125130
if (_afterSaveTriggerContextDiscoveryStrategy == null)

src/EntityFrameworkCore.Triggered/TriggeredDbContext.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ public override int SaveChanges(bool acceptAllChangesOnSuccess)
5555
try
5656
{
5757
_triggerSession.RaiseBeforeSaveTriggers(default).GetAwaiter().GetResult();
58+
_triggerSession.CaptureDiscoveredChanges();
5859
var result = base.SaveChanges(acceptAllChangesOnSuccess);
5960
_triggerSession.RaiseAfterSaveTriggers(default).GetAwaiter().GetResult();
6061

@@ -80,6 +81,7 @@ public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess,
8081
{
8182

8283
await _triggerSession.RaiseBeforeSaveTriggers(cancellationToken).ConfigureAwait(false);
84+
_triggerSession.CaptureDiscoveredChanges();
8385
var result = base.SaveChanges(acceptAllChangesOnSuccess);
8486
await _triggerSession.RaiseAfterSaveTriggers(cancellationToken).ConfigureAwait(false);
8587

test/EntityFrameworkCore.Triggered.Tests/Internal/TriggerContextTrackerTests.cs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,5 +98,63 @@ public void DiscoveredChanges_MultipleCallsToDiscoverChanges_ReturnsAllChanges()
9898

9999
Assert.Equal(2, discoveredChanges.Count());
100100
}
101+
102+
[Fact]
103+
public void CaptureDiscoveredChangesAfterAdd_UnchangedEntry_RemovesDiscoveredChange()
104+
{
105+
using var dbContext = new TestDbContext();
106+
var subject = new TriggerContextTracker(dbContext.ChangeTracker, new EntityAndTypeRecursionStrategy());
107+
108+
var testModel = new TestModel();
109+
dbContext.Entry(testModel).State = EntityState.Added;
110+
111+
var disoveredChanges = subject.DiscoverChanges();
112+
Assert.Single(disoveredChanges);
113+
Assert.Single(subject.DiscoveredChanges);
114+
115+
dbContext.Entry(testModel).State = EntityState.Unchanged;
116+
disoveredChanges = subject.DiscoverChanges();
117+
subject.CaptureChanges();
118+
Assert.Empty(subject.DiscoveredChanges);
119+
}
120+
121+
[Fact]
122+
public void CaptureDiscoveredChangesAfterAdd_DetachedEntry_RemovesDiscoveredChange()
123+
{
124+
using var dbContext = new TestDbContext();
125+
var subject = new TriggerContextTracker(dbContext.ChangeTracker, new EntityAndTypeRecursionStrategy());
126+
127+
var testModel = new TestModel();
128+
dbContext.Entry(testModel).State = EntityState.Added;
129+
130+
var disoveredChanges = subject.DiscoverChanges();
131+
Assert.Single(disoveredChanges);
132+
Assert.Single(subject.DiscoveredChanges);
133+
134+
dbContext.Entry(testModel).State = EntityState.Detached;
135+
subject.CaptureChanges();
136+
Assert.Empty(subject.DiscoveredChanges);
137+
}
138+
139+
[Fact]
140+
public void CaptureDiscoveredChanges_DeletedEntry_UpdatesDiscoveredChange()
141+
{
142+
using var dbContext = new TestDbContext();
143+
var subject = new TriggerContextTracker(dbContext.ChangeTracker, new EntityAndTypeRecursionStrategy());
144+
145+
var testModel = new TestModel();
146+
dbContext.Entry(testModel).State = EntityState.Added;
147+
148+
var disoveredChanges = subject.DiscoverChanges();
149+
Assert.Single(disoveredChanges);
150+
Assert.Single(subject.DiscoveredChanges);
151+
152+
dbContext.Entry(testModel).State = EntityState.Deleted;
153+
disoveredChanges = subject.DiscoverChanges();
154+
Assert.Equal(2, subject.DiscoveredChanges.Count());
155+
156+
subject.CaptureChanges();
157+
Assert.Single(subject.DiscoveredChanges);
158+
}
101159
}
102160
}

test/EntityFrameworkCore.Triggered.Tests/Stubs/TriggerServiceStub.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ public class TriggerServiceStub : ITriggerService
1212
{
1313
public int CreateSessionCalls;
1414
public IServiceProvider ServiceProvider;
15+
public TriggerSessionStub LastSession;
1516

1617
public ITriggerSession CreateSession(DbContext context, IServiceProvider serviceProvider)
1718
{
1819
CreateSessionCalls += 1;
1920
ServiceProvider = serviceProvider;
20-
return new TriggerSessionStub();
21+
LastSession = new TriggerSessionStub();
22+
return LastSession;
2123
}
2224
}
2325
}

test/EntityFrameworkCore.Triggered.Tests/Stubs/TriggerSessionStub.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,17 @@ public class TriggerSessionStub : ITriggerSession
1313
{
1414
public int RaiseAfterSaveTriggersCalls;
1515
public int RaiseBeforeSaveTriggersCalls;
16+
public int CaptureDiscoveredChangesCalls;
17+
public int DiscoverChangesCalls;
1618

17-
public void DiscoverChanges()
19+
public void CaptureDiscoveredChanges()
1820
{
21+
CaptureDiscoveredChangesCalls += 1;
22+
}
1923

24+
public void DiscoverChanges()
25+
{
26+
DiscoverChangesCalls += 1;
2027
}
2128

2229
public void Dispose()

0 commit comments

Comments
 (0)