Skip to content

Commit 9982401

Browse files
authored
Merge pull request #56 from koenbeuk/feature/explicitly-controlled-trigger-sessions
Added support for explicitly controllable trigger sessions
2 parents e336e18 + 59e99f8 commit 9982401

15 files changed

Lines changed: 339 additions & 14 deletions

File tree

src/EntityFrameworkCore.Triggered.Abstractions/ITriggerSession.cs

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

55
namespace EntityFrameworkCore.Triggered
66
{
7-
public interface ITriggerSession
7+
public interface ITriggerSession : IDisposable
88
{
99
/// <summary>
1010
/// Discoveres any new pending changes in the DbContext
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using Microsoft.EntityFrameworkCore;
7+
using Microsoft.EntityFrameworkCore.Infrastructure;
8+
9+
namespace EntityFrameworkCore.Triggered.Extensions
10+
{
11+
public static class DbContextExtensions
12+
{
13+
public static ITriggerSession CreateTriggerSession(this DbContext dbContext, IServiceProvider? serviceProvider = null)
14+
{
15+
var triggerService = dbContext.GetService<ITriggerService>() ?? throw new InvalidOperationException("Trigger service infrastructure is not configured");
16+
17+
return triggerService.CreateSession(dbContext, serviceProvider);
18+
}
19+
}
20+
}

src/EntityFrameworkCore.Triggered/ITriggerService.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,7 @@ namespace EntityFrameworkCore.Triggered
66
public interface ITriggerService
77
{
88
ITriggerSession CreateSession(DbContext context, IServiceProvider? serviceProvider = null);
9+
10+
ITriggerSession? Current { get; set; }
911
}
1012
}

src/EntityFrameworkCore.Triggered/Infrastructure/Internal/TriggersOptionExtension.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,10 @@ public void ApplyServices(IServiceCollection services)
132132

133133
services.TryAddSingleton<ITriggerTypeRegistryService, TriggerTypeRegistryService>();
134134
services.TryAddScoped<ITriggerDiscoveryService, TriggerDiscoveryService>();
135-
services.TryAddScoped<ITriggerService, TriggerService>();
135+
136+
services.TryAddScoped<TriggerService>();
137+
services.TryAddScoped<IResettableService>(serviceProvider => serviceProvider.GetRequiredService<TriggerService>());
138+
services.TryAddScoped<ITriggerService>(serviceProvider => serviceProvider.GetRequiredService<TriggerService>());
136139

137140
#if EFCORETRIGGERED2
138141
services.TryAddScoped<IInterceptor, TriggerSessionSaveChangesInterceptor>();

src/EntityFrameworkCore.Triggered/Internal/TriggerSessionSaveChangesInterceptor.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.ComponentModel.Design;
34
using System.Diagnostics;
45
using System.Linq;
56
using System.Text;
@@ -38,7 +39,15 @@ private void EnlistTriggerSession(DbContextEventData eventData)
3839
if (_triggerSession == null)
3940
{
4041
var triggerService = eventData.Context.GetService<ITriggerService>() ?? throw new InvalidOperationException("Triggers are not configured");
41-
_triggerSession = triggerService.CreateSession(eventData.Context);
42+
43+
if (triggerService.Current != null)
44+
{
45+
_triggerSession = triggerService.Current;
46+
}
47+
else
48+
{
49+
_triggerSession = triggerService.CreateSession(eventData.Context);
50+
}
4251
}
4352

4453
_parallelSaveChangesCount += 1;
@@ -56,6 +65,7 @@ private void DelistTriggerSession(DbContextEventData eventData)
5665

5766
if (_parallelSaveChangesCount == 0)
5867
{
68+
_triggerSession.Dispose();
5969
_triggerSession = null;
6070
}
6171
}
@@ -91,7 +101,7 @@ public async ValueTask<InterceptionResult<int>> SavingChangesAsync(DbContextEven
91101
{
92102
if (!(eventData.Context is TriggeredDbContext))
93103
{
94-
EnlistTriggerSession(eventData);
104+
EnlistTriggerSession(eventData);
95105

96106
var defaultAutoDetectChangesEnabled = eventData.Context.ChangeTracker.AutoDetectChangesEnabled;
97107

src/EntityFrameworkCore.Triggered/TriggerService.cs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
11
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
24
using EntityFrameworkCore.Triggered.Internal;
35
using EntityFrameworkCore.Triggered.Internal.RecursionStrategy;
46
using Microsoft.EntityFrameworkCore;
7+
using Microsoft.EntityFrameworkCore.Infrastructure;
58
using Microsoft.Extensions.Logging;
69
using Microsoft.Extensions.Options;
710

811
namespace EntityFrameworkCore.Triggered
912
{
10-
public class TriggerService : ITriggerService
13+
public class TriggerService : ITriggerService, IResettableService
1114
{
1215
readonly ITriggerDiscoveryService _triggerDiscoveryService;
1316
readonly IRecursionStrategy _recursionStrategy;
1417
readonly ILoggerFactory _loggerFactory;
1518
readonly TriggerOptions _options;
1619

20+
ITriggerSession? _currentTriggerSession;
21+
1722
public TriggerService(ITriggerDiscoveryService triggerDiscoveryService, IRecursionStrategy recursionStrategy, ILoggerFactory loggerFactory, IOptionsSnapshot<TriggerOptions> triggerOptionsSnapshot)
1823
{
1924
_triggerDiscoveryService = triggerDiscoveryService ?? throw new ArgumentNullException(nameof(triggerDiscoveryService));
@@ -22,6 +27,12 @@ public TriggerService(ITriggerDiscoveryService triggerDiscoveryService, IRecursi
2227
_options = triggerOptionsSnapshot.Value;
2328
}
2429

30+
public ITriggerSession? Current
31+
{
32+
get => _currentTriggerSession;
33+
set => _currentTriggerSession = value;
34+
}
35+
2536
public ITriggerSession CreateSession(DbContext context, IServiceProvider? serviceProvider)
2637
{
2738
if (context is null)
@@ -36,7 +47,19 @@ public ITriggerSession CreateSession(DbContext context, IServiceProvider? servic
3647
_triggerDiscoveryService.SetServiceProvider(serviceProvider);
3748
}
3849

39-
return new TriggerSession(_options, _triggerDiscoveryService, triggerContextTracker, _loggerFactory.CreateLogger<TriggerSession>());
50+
var triggerSession = new TriggerSession(this, _options, _triggerDiscoveryService, triggerContextTracker, _loggerFactory.CreateLogger<TriggerSession>());
51+
52+
_currentTriggerSession = triggerSession;
53+
54+
return triggerSession;
55+
}
56+
57+
public void ResetState() => _currentTriggerSession?.Dispose();
58+
public Task ResetStateAsync(CancellationToken cancellationToken = default)
59+
{
60+
_currentTriggerSession?.Dispose();
61+
62+
return Task.CompletedTask;
4063
}
4164
}
4265
}

src/EntityFrameworkCore.Triggered/TriggerSession.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,17 @@ public class TriggerSession : ITriggerSession
1515
static ITriggerContextDiscoveryStrategy? _afterSaveTriggerContextDiscoveryStrategy;
1616
static ITriggerContextDiscoveryStrategy? _afterSaveFailedTriggerContextDiscoveryStrategy;
1717

18+
readonly ITriggerService _triggerService;
1819
readonly TriggerOptions _options;
1920
readonly ITriggerDiscoveryService _triggerDiscoveryService;
2021
readonly TriggerContextTracker _tracker;
2122
readonly ILogger<TriggerSession> _logger;
2223

2324
bool _raiseBeforeSaveTriggersCalled;
2425

25-
public TriggerSession(TriggerOptions options, ITriggerDiscoveryService triggerDiscoveryService, TriggerContextTracker tracker, ILogger<TriggerSession> logger)
26+
public TriggerSession(ITriggerService triggerService, TriggerOptions options, ITriggerDiscoveryService triggerDiscoveryService, TriggerContextTracker tracker, ILogger<TriggerSession> logger)
2627
{
28+
_triggerService = triggerService ?? throw new ArgumentNullException(nameof(triggerService));
2729
_options = options ?? throw new ArgumentNullException(nameof(options));
2830
_triggerDiscoveryService = triggerDiscoveryService ?? throw new ArgumentNullException(nameof(ITriggerDiscoveryService));
2931
_tracker = tracker ?? throw new ArgumentNullException(nameof(tracker));
@@ -139,5 +141,13 @@ public Task RaiseAfterSaveFailedTriggers(Exception exception, CancellationToken
139141
return RaiseTriggers(typeof(IAfterSaveFailedTrigger<>), exception, _afterSaveFailedTriggerContextDiscoveryStrategy, entityType => new AfterSaveFailedTriggerDescriptor(entityType, exception), cancellationToken);
140142

141143
}
144+
145+
public void Dispose()
146+
{
147+
if (_triggerService.Current == this)
148+
{
149+
_triggerService.Current = null;
150+
}
151+
}
142152
}
143153
}

src/EntityFrameworkCore.Triggered/TriggeredDbContext.cs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,16 @@ bool RaiseAfterSavFailedTriggers(Exception exception)
6565
if (_triggerSession == null)
6666
{
6767
var triggerService = this.GetService<ITriggerService>() ?? throw new InvalidOperationException("Triggers are not configured");
68-
_triggerSession = triggerService.CreateSession(this, _triggerServiceProvider);
69-
createdTriggerSession = true;
68+
69+
if (triggerService.Current != null)
70+
{
71+
_triggerSession = triggerService.Current;
72+
}
73+
else
74+
{
75+
_triggerSession = triggerService.CreateSession(this, _triggerServiceProvider);
76+
createdTriggerSession = true;
77+
}
7078
}
7179

7280
try
@@ -120,8 +128,16 @@ Task RaiseAfterSavFailedTriggers(Exception exception, CancellationToken cancella
120128
if (_triggerSession == null)
121129
{
122130
var triggerService = this.GetService<ITriggerService>() ?? throw new InvalidOperationException("Triggers are not configured");
123-
_triggerSession = triggerService.CreateSession(this);
124-
createdTriggerSession = true;
131+
132+
if (triggerService.Current != null)
133+
{
134+
_triggerSession = triggerService.Current;
135+
}
136+
else
137+
{
138+
_triggerSession = triggerService.CreateSession(this, _triggerServiceProvider);
139+
createdTriggerSession = true;
140+
}
125141
}
126142
try
127143
{

test/EntityFrameworkCore.Triggered.Tests/EFCore5DbContextTests.cs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Threading.Tasks;
33
using EntityFrameworkCore.Triggered.Tests.Stubs;
4+
using EntityFrameworkCore.Triggered.Extensions;
45
using Microsoft.Data.Sqlite;
56
using Microsoft.EntityFrameworkCore;
67
using Microsoft.EntityFrameworkCore.Diagnostics;
@@ -63,7 +64,7 @@ TestDbContext CreateSubject(bool stubService = true)
6364
=> new TestDbContext(stubService);
6465

6566
[Fact]
66-
public void SaveChanges_CreatesChangeHandlerSession()
67+
public void SaveChanges_CreatesTriggerrSession()
6768
{
6869
var subject = CreateSubject();
6970
var triggerServiceStub = (TriggerServiceStub)subject.GetService<ITriggerService>();
@@ -73,7 +74,7 @@ public void SaveChanges_CreatesChangeHandlerSession()
7374
}
7475

7576
[Fact]
76-
public void SaveChangesWithAccept_CreatesChangeHandlerSession()
77+
public void SaveChangesWithAccept_CreatesTriggerSession()
7778
{
7879
var subject = CreateSubject();
7980
var triggerServiceStub = (TriggerServiceStub)subject.GetService<ITriggerService>();
@@ -83,7 +84,7 @@ public void SaveChangesWithAccept_CreatesChangeHandlerSession()
8384
}
8485

8586
[Fact]
86-
public async Task SaveChangesAsync_CreatesChangeHandlerSession()
87+
public async Task SaveChangesAsync_CreatesTriggerSession()
8788
{
8889
var subject = CreateSubject();
8990
var triggerServiceStub = (TriggerServiceStub)subject.GetService<ITriggerService>();
@@ -92,6 +93,24 @@ public async Task SaveChangesAsync_CreatesChangeHandlerSession()
9293
Assert.Equal(1, triggerServiceStub.CreateSessionCalls);
9394
}
9495

96+
[Fact]
97+
public void SaveChanges_ExplicitTriggerSession_ReturnsActiveTriggerSession()
98+
{
99+
var subject = CreateSubject(true);
100+
var triggerServiceStub = (TriggerServiceStub)subject.GetService<ITriggerService>();
101+
102+
using var triggerSession = subject.CreateTriggerSession();
103+
104+
subject.TestModels.Add(new TestModel {
105+
Id = Guid.NewGuid(),
106+
Name = "test1"
107+
});
108+
109+
subject.SaveChanges();
110+
111+
Assert.Equal(1, triggerServiceStub.CreateSessionCalls);
112+
}
113+
95114
[Fact]
96115
public void SaveChanges_RecursiveCall_ReturnsActiveTriggerSession()
97116
{
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using EntityFrameworkCore.Triggered.Extensions;
7+
using Microsoft.EntityFrameworkCore;
8+
using Microsoft.EntityFrameworkCore.Infrastructure;
9+
using Xunit;
10+
11+
namespace EntityFrameworkCore.Triggered.Tests.Extensions
12+
{
13+
public class DbContextExtensionTests
14+
{
15+
class TestModel { public int Id { get; set; } public string Name { get; set; } }
16+
17+
class TestDbContext : DbContext
18+
{
19+
public DbSet<TestModel> TestModels { get; set; }
20+
21+
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.UseInMemoryDatabase("test").UseTriggers();
22+
}
23+
24+
[Fact]
25+
public void CreateTriggerSession_ValidDbContext_CreatesNewSession()
26+
{
27+
var context = new TestDbContext();
28+
var triggerSession = DbContextExtensions.CreateTriggerSession(context);
29+
30+
Assert.NotNull(triggerSession);
31+
Assert.NotNull(context.GetService<ITriggerService>()?.Current);
32+
}
33+
}
34+
}

0 commit comments

Comments
 (0)