Skip to content

Commit c39adf5

Browse files
authored
Merge pull request #100 from koenbeuk/trigger-instance-improvements
Trigger instance improvements
2 parents 17d3640 + 12f19bc commit c39adf5

File tree

19 files changed

+351
-40
lines changed

19 files changed

+351
-40
lines changed

src/EntityFrameworkCore.Triggered/Internal/ApplicationTriggerServiceProviderAccessor.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ public IServiceProvider GetTriggerServiceProvider()
5757
}
5858
else if (_scopedServiceProviderTransform != null)
5959
{
60-
6160
var dbContextOptions = _internalServiceProvider.GetRequiredService<IDbContextOptions>();
6261
var coreOptionsExtension = dbContextOptions.FindExtension<CoreOptionsExtension>();
6362
var serviceProvider = coreOptionsExtension.ApplicationServiceProvider ?? _internalServiceProvider;
@@ -66,8 +65,8 @@ public IServiceProvider GetTriggerServiceProvider()
6665
}
6766
else
6867
{
69-
_serviceScope = _internalServiceProvider.CreateScope();
70-
_applicationScopedServiceProvider = _serviceScope.ServiceProvider;
68+
var dbContext = _internalServiceProvider.GetRequiredService<ICurrentDbContext>().Context;
69+
_applicationScopedServiceProvider = new HybridServiceProvider(_internalServiceProvider, dbContext);
7170
}
7271

7372
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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+
8+
namespace EntityFrameworkCore.Triggered.Internal
9+
{
10+
public sealed class HybridServiceProvider : IServiceProvider
11+
{
12+
readonly DbContext _dbContext;
13+
readonly IServiceProvider _serviceProvider;
14+
15+
public HybridServiceProvider(IServiceProvider serviceProvider, DbContext dbContext)
16+
{
17+
_serviceProvider = serviceProvider;
18+
_dbContext = dbContext;
19+
}
20+
21+
public object? GetService(Type serviceType)
22+
{
23+
var result = _serviceProvider.GetService(serviceType);
24+
if (result is not null)
25+
{
26+
return result;
27+
}
28+
29+
if (typeof(DbContext).IsAssignableFrom(serviceType))
30+
{
31+
return _dbContext;
32+
}
33+
34+
return default;
35+
}
36+
}
37+
}

src/EntityFrameworkCore.Triggered/Internal/TriggerDiscoveryService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public IEnumerable<TriggerDescriptor> DiscoverTriggers(Type openTriggerType, Typ
4444
{
4545
if (triggerDescriptors == null)
4646
{
47-
triggerDescriptors = new List<TriggerDescriptor>(triggers.Count());
47+
triggerDescriptors = new List<TriggerDescriptor>();
4848
}
4949

5050
if (trigger != null)

src/EntityFrameworkCore.Triggered/Internal/TriggerFactory.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,9 @@ public IEnumerable<object> Resolve(IServiceProvider serviceProvider, Type trigge
3333
var triggerServiceFactories = _internalServiceProvider.GetServices(typeof(ITriggerInstanceFactory<>).MakeGenericType(triggerType)).Cast<ITriggerInstanceFactory>();
3434
if (triggerServiceFactories.Any())
3535
{
36-
var dbContext = _internalServiceProvider.GetService<Microsoft.EntityFrameworkCore.Infrastructure.ICurrentDbContext>()?.Context;
37-
3836
foreach (var triggerServiceFactory in triggerServiceFactories)
3937
{
40-
yield return triggerServiceFactory.Create(dbContext, serviceProvider);
38+
yield return triggerServiceFactory.Create(serviceProvider);
4139
}
4240
}
4341
}

src/EntityFrameworkCore.Triggered/Internal/TriggerInstanceFactory.cs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace EntityFrameworkCore.Triggered.Internal
1111

1212
public interface ITriggerInstanceFactory
1313
{
14-
object Create(DbContext? dbContext, IServiceProvider serviceProvider);
14+
object Create(IServiceProvider serviceProvider);
1515
}
1616

1717
public interface ITriggerInstanceFactory<out TTriggerType> : ITriggerInstanceFactory
@@ -21,23 +21,24 @@ public interface ITriggerInstanceFactory<out TTriggerType> : ITriggerInstanceFac
2121

2222
public sealed class TriggerInstanceFactory<TTriggerType> : ITriggerInstanceFactory<TTriggerType>
2323
{
24-
readonly object? _serviceInstance;
24+
object? _instance;
2525

26-
public TriggerInstanceFactory(object? serviceInstance)
26+
public TriggerInstanceFactory(object? instance)
2727
{
28-
_serviceInstance = serviceInstance;
28+
_instance = instance;
2929
}
3030

31-
public object Create(DbContext? dbContext, IServiceProvider serviceProvider)
31+
public object Create(IServiceProvider serviceProvider)
3232
{
33-
if (_serviceInstance is not null)
33+
if (_instance is not null)
3434
{
35-
return _serviceInstance;
35+
return _instance;
3636
}
3737

38-
var arguments = dbContext is not null ? new object[] { dbContext } : Array.Empty<object>();
38+
// todo: create object factory and cache
39+
_instance = ActivatorUtilities.CreateInstance(serviceProvider, typeof(TTriggerType));
3940

40-
return ActivatorUtilities.CreateInstance(serviceProvider, typeof(TTriggerType), arguments);
41+
return _instance;
4142
}
4243
}
4344
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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+
8+
namespace EntityFrameworkCore.Triggered.IntegrationTests.LifetimeTests
9+
{
10+
public class ApplicationDbContext
11+
#if EFCORETRIGGERED1
12+
: TriggeredDbContext
13+
#else
14+
: DbContext
15+
#endif
16+
{
17+
public ApplicationDbContext(DbContextOptions options) : base(options)
18+
{
19+
}
20+
21+
public DbSet<User> Users { get; set; }
22+
}
23+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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.Extensions.DependencyInjection;
9+
using ScenarioTests;
10+
using Xunit;
11+
12+
namespace EntityFrameworkCore.Triggered.IntegrationTests.LifetimeTests
13+
{
14+
public partial class TriggerLifetimeTestScenario
15+
{
16+
public int SingletonTriggerInstances { get; set; }
17+
public int ScopedTriggerInstances { get; set; }
18+
public int TransientTriggerInstances { get; set; }
19+
20+
[Scenario(NamingPolicy = ScenarioTestMethodNamingPolicy.Test)]
21+
public void Scenario(ScenarioContext scenario)
22+
{
23+
const int iterations = 5;
24+
const int usersPerIteration = 5;
25+
26+
using var serviceProvider = new ServiceCollection()
27+
.AddDbContext<ApplicationDbContext>(options => {
28+
options.UseInMemoryDatabase(scenario.TargetName);
29+
options.UseTriggers(triggerOptions => {
30+
triggerOptions.AddTrigger<Triggers.Users.SingletonTrigger>(ServiceLifetime.Singleton);
31+
triggerOptions.AddTrigger<Triggers.Users.ScopedTrigger>(ServiceLifetime.Scoped);
32+
triggerOptions.AddTrigger<Triggers.Users.TransientTrigger>();
33+
});
34+
})
35+
.AddSingleton(this)
36+
.BuildServiceProvider();
37+
38+
for (var iteration = 0; iteration < iterations; iteration++)
39+
{
40+
using var serviceScope = serviceProvider.CreateScope();
41+
42+
var dbContext = serviceScope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
43+
44+
for (var i = 0; i < usersPerIteration; i++)
45+
{
46+
dbContext.Users.Add(new User { });
47+
}
48+
49+
dbContext.SaveChanges();
50+
}
51+
52+
scenario.Fact("1: There is only 1 singleton trigger instance", () => {
53+
Assert.Equal(1, SingletonTriggerInstances);
54+
});
55+
56+
scenario.Fact("2: There are 5 scoped trigger instances", () => {
57+
Assert.Equal(5, ScopedTriggerInstances);
58+
});
59+
60+
scenario.Fact("3: There are 25 transient trigger instances", () => {
61+
Assert.Equal(25, TransientTriggerInstances);
62+
});
63+
}
64+
}
65+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System.Threading;
2+
using System.Threading.Tasks;
3+
using EntityFrameworkCore.Triggered.Extensions;
4+
5+
namespace EntityFrameworkCore.Triggered.IntegrationTests.LifetimeTests.Triggers.Users
6+
{
7+
class ScopedTrigger : IBeforeSaveTrigger<object>
8+
{
9+
public ScopedTrigger(TriggerLifetimeTestScenario triggerLifetimeTestScenario)
10+
{
11+
triggerLifetimeTestScenario.ScopedTriggerInstances++;
12+
}
13+
14+
public Task BeforeSave(ITriggerContext<object> context, CancellationToken cancellationToken) => Task.CompletedTask;
15+
}
16+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using EntityFrameworkCore.Triggered.Extensions;
8+
9+
namespace EntityFrameworkCore.Triggered.IntegrationTests.LifetimeTests.Triggers.Users
10+
{
11+
12+
class SingletonTrigger : IBeforeSaveTrigger<object>
13+
{
14+
public SingletonTrigger(TriggerLifetimeTestScenario triggerLifetimeTestScenario)
15+
{
16+
triggerLifetimeTestScenario.SingletonTriggerInstances++;
17+
}
18+
19+
public Task BeforeSave(ITriggerContext<object> context, CancellationToken cancellationToken) => Task.CompletedTask;
20+
}
21+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System.Threading;
2+
using System.Threading.Tasks;
3+
using EntityFrameworkCore.Triggered.Extensions;
4+
5+
namespace EntityFrameworkCore.Triggered.IntegrationTests.LifetimeTests.Triggers.Users
6+
{
7+
class TransientTrigger : IBeforeSaveTrigger<object>
8+
{
9+
public TransientTrigger(TriggerLifetimeTestScenario triggerLifetimeTestScenario)
10+
{
11+
triggerLifetimeTestScenario.TransientTriggerInstances++;
12+
}
13+
14+
public Task BeforeSave(ITriggerContext<object> context, CancellationToken cancellationToken) => Task.CompletedTask;
15+
}
16+
}

0 commit comments

Comments
 (0)