Skip to content

Commit 7176cf8

Browse files
committed
2 parents 31da08b + dbdd795 commit 7176cf8

File tree

15 files changed

+223
-31
lines changed

15 files changed

+223
-31
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/TriggerContext.cs

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,31 +12,23 @@ public class TriggerContext<TEntity> : ITriggerContext<TEntity>
1212
where TEntity: class
1313
{
1414
readonly ChangeType _type;
15-
readonly EntityEntry _entityEntry;
16-
readonly Lazy<TEntity?> _unmodifiedEntityLazy;
17-
18-
TEntity? CreateUnmodified()
19-
{
20-
if (ChangeType == ChangeType.Added)
21-
{
22-
return null;
23-
}
24-
else
25-
{
26-
return (TEntity)_entityEntry.OriginalValues.ToObject();
27-
}
28-
}
15+
readonly TEntity _entity;
16+
readonly TEntity? _unmodifiedEntity;
2917

3018

3119
public TriggerContext(EntityEntry entityEntry, ChangeType changeType)
3220
{
3321
_type = changeType;
34-
_entityEntry = entityEntry;
35-
_unmodifiedEntityLazy = new Lazy<TEntity?>(CreateUnmodified, false);
22+
_entity = (TEntity)entityEntry.Entity;
23+
24+
if (changeType != ChangeType.Added)
25+
{
26+
_unmodifiedEntity = (TEntity)entityEntry.OriginalValues.ToObject();
27+
}
3628
}
3729

3830
public ChangeType ChangeType => _type;
39-
public TEntity Entity => (TEntity)_entityEntry.Entity;
40-
public TEntity? UnmodifiedEntity => _unmodifiedEntityLazy.Value;
31+
public TEntity Entity => _entity;
32+
public TEntity? UnmodifiedEntity => _unmodifiedEntity;
4133
}
4234
}

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
@@ -58,6 +58,7 @@ public override int SaveChanges(bool acceptAllChangesOnSuccess)
5858
try
5959
{
6060
_triggerSession.RaiseBeforeSaveTriggers(default).GetAwaiter().GetResult();
61+
_triggerSession.CaptureDiscoveredChanges();
6162
var result = base.SaveChanges(acceptAllChangesOnSuccess);
6263
_triggerSession.RaiseAfterSaveTriggers(default).GetAwaiter().GetResult();
6364

@@ -86,6 +87,7 @@ public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess,
8687
{
8788

8889
await _triggerSession.RaiseBeforeSaveTriggers(cancellationToken).ConfigureAwait(false);
90+
_triggerSession.CaptureDiscoveredChanges();
8991
var result = base.SaveChanges(acceptAllChangesOnSuccess);
9092
await _triggerSession.RaiseAfterSaveTriggers(cancellationToken).ConfigureAwait(false);
9193

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

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,40 @@
44
using System.Text;
55
using System.Threading.Tasks;
66
using EntityFrameworkCore.Triggered.Internal;
7+
using Microsoft.EntityFrameworkCore;
78
using Xunit;
89

910
namespace EntityFrameworkCore.Triggered.Tests.Internal
1011
{
1112
public class TriggerContextFactoryTests
1213
{
14+
class TestModel
15+
{
16+
public Guid Id { get; set; }
17+
public string Name { get; set; }
18+
}
19+
20+
class TestDbContext : DbContext
21+
{
22+
public DbSet<TestModel> TestModels { get; set; }
23+
24+
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
25+
{
26+
base.OnConfiguring(optionsBuilder);
27+
28+
optionsBuilder.UseInMemoryDatabase(nameof(TriggerContextFactoryTests));
29+
}
30+
}
31+
1332
[Fact]
1433
public void Activate_ReturnsInstance()
1534
{
16-
var result = TriggerContextFactory<object>.Activate(null, ChangeType.Added);
35+
using var dbContext = new TestDbContext();
36+
var entityEntry = dbContext.Entry(new TestModel { });
37+
38+
var result = TriggerContextFactory<object>.Activate(entityEntry, ChangeType.Added);
1739

1840
Assert.NotNull(result);
1941
}
20-
2142
}
2243
}

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
}

0 commit comments

Comments
 (0)