|
| 1 | +using System; |
| 2 | +using System.Reflection; |
| 3 | +using BenchmarkDotNet.Attributes; |
| 4 | +using Microsoft.EntityFrameworkCore; |
| 5 | +using Microsoft.Extensions.DependencyInjection; |
| 6 | + |
| 7 | +namespace EntityFrameworkCore.Triggered.Benchmarks |
| 8 | +{ |
| 9 | + /// <summary> |
| 10 | + /// Measures the setup cost (assembly scan + trigger registration) without building the |
| 11 | + /// ServiceProvider, isolating the overhead of AddAssemblyTriggers vs explicit AddTrigger<T>. |
| 12 | + /// |
| 13 | + /// Note: TriggerTypeHelper caches results per type after the first iteration. |
| 14 | + /// BenchmarkDotNet measures steady-state (warm cache). |
| 15 | + /// </summary> |
| 16 | + [MemoryDiagnoser] |
| 17 | + public class AssemblyTriggersSetupBenchmarks |
| 18 | + { |
| 19 | + private static readonly Assembly BenchmarkAssembly = typeof(AssemblyTriggersSetupBenchmarks).Assembly; |
| 20 | + |
| 21 | + /// <summary> |
| 22 | + /// Baseline: 2 triggers registered explicitly via AddTrigger<T>() inside UseTriggers(). |
| 23 | + /// </summary> |
| 24 | + [Benchmark(Baseline = true)] |
| 25 | + public IServiceCollection Explicit_2Triggers_ViaOptions() |
| 26 | + { |
| 27 | + return new ServiceCollection() |
| 28 | + .AddDbContext<TriggeredApplicationContext>(options => |
| 29 | + options.UseInMemoryDatabase("Setup_Explicit_Options").UseTriggers(o => |
| 30 | + { |
| 31 | + o.AddTrigger<Triggers.SetStudentRegistrationDateTrigger>(); |
| 32 | + o.AddTrigger<Triggers.SignStudentUpForMandatoryCourses>(); |
| 33 | + })); |
| 34 | + } |
| 35 | + |
| 36 | + /// <summary> |
| 37 | + /// Assembly scan via TriggersContextOptionsBuilder.AddAssemblyTriggers(). |
| 38 | + /// Discovers 2 Student triggers + 5 StudentCourse triggers = 7 types total. |
| 39 | + /// </summary> |
| 40 | + [Benchmark] |
| 41 | + public IServiceCollection AssemblyTriggers_ViaOptions() |
| 42 | + { |
| 43 | + return new ServiceCollection() |
| 44 | + .AddDbContext<TriggeredApplicationContext>(options => |
| 45 | + options.UseInMemoryDatabase("Setup_Assembly_Options").UseTriggers(o => |
| 46 | + o.AddAssemblyTriggers(BenchmarkAssembly))); |
| 47 | + } |
| 48 | + |
| 49 | + /// <summary> |
| 50 | + /// Baseline: 2 triggers registered explicitly via IServiceCollection.AddTrigger<T>(). |
| 51 | + /// </summary> |
| 52 | + [Benchmark] |
| 53 | + public IServiceCollection Explicit_2Triggers_ViaServiceCollection() |
| 54 | + { |
| 55 | + return new ServiceCollection() |
| 56 | + .AddTriggeredDbContext<TriggeredApplicationContext>(options => |
| 57 | + options.UseInMemoryDatabase("Setup_Explicit_SC")) |
| 58 | + .AddTrigger<Triggers.SetStudentRegistrationDateTrigger>() |
| 59 | + .AddTrigger<Triggers.SignStudentUpForMandatoryCourses>(); |
| 60 | + } |
| 61 | + |
| 62 | + /// <summary> |
| 63 | + /// Assembly scan via IServiceCollection.AddAssemblyTriggers(). |
| 64 | + /// Registers all 7 triggers (2 Student + 5 StudentCourse) directly in the application DI container. |
| 65 | + /// </summary> |
| 66 | + [Benchmark] |
| 67 | + public IServiceCollection AssemblyTriggers_ViaServiceCollection() |
| 68 | + { |
| 69 | + return new ServiceCollection() |
| 70 | + .AddTriggeredDbContext<TriggeredApplicationContext>(options => |
| 71 | + options.UseInMemoryDatabase("Setup_Assembly_SC")) |
| 72 | + .AddAssemblyTriggers(BenchmarkAssembly); |
| 73 | + } |
| 74 | + } |
| 75 | + |
| 76 | + /// <summary> |
| 77 | + /// Measures the per-SaveChanges runtime cost when triggers were registered via |
| 78 | + /// AddAssemblyTriggers (7 triggers in the assembly = 2 for Student + 5 for StudentCourse) |
| 79 | + /// vs explicit registration targeting only the 2 Student triggers. |
| 80 | + /// |
| 81 | + /// Answers: "Does registering extra non-applicable triggers (discovered by assembly scan) |
| 82 | + /// slow down the hot path?" |
| 83 | + /// |
| 84 | + /// A new DbContext is created on every iteration to include instantiation cost. |
| 85 | + /// </summary> |
| 86 | + [MemoryDiagnoser] |
| 87 | + public class AssemblyTriggersRuntimeBenchmarks |
| 88 | + { |
| 89 | + private static readonly Assembly BenchmarkAssembly = typeof(AssemblyTriggersRuntimeBenchmarks).Assembly; |
| 90 | + |
| 91 | + private IServiceProvider _explicit2OptionsProvider; |
| 92 | + private IServiceProvider _assemblyScan7OptionsProvider; |
| 93 | + private IServiceProvider _explicit2ServiceCollectionProvider; |
| 94 | + private IServiceProvider _assemblyScan7ServiceCollectionProvider; |
| 95 | + |
| 96 | + [GlobalSetup] |
| 97 | + public void GlobalSetup() |
| 98 | + { |
| 99 | + _explicit2OptionsProvider = new ServiceCollection() |
| 100 | + .AddTriggeredDbContext<TriggeredApplicationContext>(options => |
| 101 | + options.UseInMemoryDatabase("RT_Explicit_Options").UseTriggers(o => |
| 102 | + { |
| 103 | + o.AddTrigger<Triggers.SetStudentRegistrationDateTrigger>(); |
| 104 | + o.AddTrigger<Triggers.SignStudentUpForMandatoryCourses>(); |
| 105 | + })) |
| 106 | + .BuildServiceProvider(); |
| 107 | + |
| 108 | + _assemblyScan7OptionsProvider = new ServiceCollection() |
| 109 | + .AddTriggeredDbContext<TriggeredApplicationContext>(options => |
| 110 | + options.UseInMemoryDatabase("RT_Assembly_Options").UseTriggers(o => |
| 111 | + o.AddAssemblyTriggers(BenchmarkAssembly))) |
| 112 | + .BuildServiceProvider(); |
| 113 | + |
| 114 | + _explicit2ServiceCollectionProvider = new ServiceCollection() |
| 115 | + .AddTriggeredDbContext<TriggeredApplicationContext>(options => |
| 116 | + options.UseInMemoryDatabase("RT_Explicit_SC")) |
| 117 | + .AddTrigger<Triggers.SetStudentRegistrationDateTrigger>() |
| 118 | + .AddTrigger<Triggers.SignStudentUpForMandatoryCourses>() |
| 119 | + .BuildServiceProvider(); |
| 120 | + |
| 121 | + _assemblyScan7ServiceCollectionProvider = new ServiceCollection() |
| 122 | + .AddTriggeredDbContext<TriggeredApplicationContext>(options => |
| 123 | + options.UseInMemoryDatabase("RT_Assembly_SC")) |
| 124 | + .AddAssemblyTriggers(BenchmarkAssembly) |
| 125 | + .BuildServiceProvider(); |
| 126 | + |
| 127 | + SeedCourse(_explicit2OptionsProvider); |
| 128 | + SeedCourse(_assemblyScan7OptionsProvider); |
| 129 | + SeedCourse(_explicit2ServiceCollectionProvider); |
| 130 | + SeedCourse(_assemblyScan7ServiceCollectionProvider); |
| 131 | + } |
| 132 | + |
| 133 | + private static void SeedCourse(IServiceProvider sp) |
| 134 | + { |
| 135 | + using var scope = sp.CreateScope(); |
| 136 | + using var ctx = scope.ServiceProvider.GetRequiredService<TriggeredApplicationContext>(); |
| 137 | + ctx.Database.EnsureCreated(); |
| 138 | + ctx.Courses.Add(new Course { Id = Guid.NewGuid(), DisplayName = "Mandatory", IsMandatory = true }); |
| 139 | + ctx.SaveChanges(); |
| 140 | + } |
| 141 | + |
| 142 | + /// <summary> |
| 143 | + /// Baseline: 2 Student triggers registered explicitly via UseTriggers options. |
| 144 | + /// </summary> |
| 145 | + [Benchmark(Baseline = true)] |
| 146 | + public void Explicit_2Triggers_ViaOptions() |
| 147 | + { |
| 148 | + using var scope = _explicit2OptionsProvider.CreateScope(); |
| 149 | + using var ctx = scope.ServiceProvider.GetRequiredService<TriggeredApplicationContext>(); |
| 150 | + ctx.Students.Add(new Student { Id = Guid.NewGuid(), DisplayName = "Test" }); |
| 151 | + ctx.SaveChanges(); |
| 152 | + } |
| 153 | + |
| 154 | + /// <summary> |
| 155 | + /// 7 triggers registered via assembly scan (2 Student + 5 StudentCourse) through UseTriggers options. |
| 156 | + /// Only the 2 Student triggers are invoked during this SaveChanges. |
| 157 | + /// </summary> |
| 158 | + [Benchmark] |
| 159 | + public void AssemblyTriggers_7Registered_2Active_ViaOptions() |
| 160 | + { |
| 161 | + using var scope = _assemblyScan7OptionsProvider.CreateScope(); |
| 162 | + using var ctx = scope.ServiceProvider.GetRequiredService<TriggeredApplicationContext>(); |
| 163 | + ctx.Students.Add(new Student { Id = Guid.NewGuid(), DisplayName = "Test" }); |
| 164 | + ctx.SaveChanges(); |
| 165 | + } |
| 166 | + |
| 167 | + /// <summary> |
| 168 | + /// Baseline (application DI path): 2 Student triggers registered explicitly via IServiceCollection. |
| 169 | + /// </summary> |
| 170 | + [Benchmark] |
| 171 | + public void Explicit_2Triggers_ViaServiceCollection() |
| 172 | + { |
| 173 | + using var scope = _explicit2ServiceCollectionProvider.CreateScope(); |
| 174 | + using var ctx = scope.ServiceProvider.GetRequiredService<TriggeredApplicationContext>(); |
| 175 | + ctx.Students.Add(new Student { Id = Guid.NewGuid(), DisplayName = "Test" }); |
| 176 | + ctx.SaveChanges(); |
| 177 | + } |
| 178 | + |
| 179 | + /// <summary> |
| 180 | + /// 7 triggers registered via assembly scan (2 Student + 5 StudentCourse) through IServiceCollection. |
| 181 | + /// Only the 2 Student triggers are invoked during this SaveChanges. |
| 182 | + /// </summary> |
| 183 | + [Benchmark] |
| 184 | + public void AssemblyTriggers_7Registered_2Active_ViaServiceCollection() |
| 185 | + { |
| 186 | + using var scope = _assemblyScan7ServiceCollectionProvider.CreateScope(); |
| 187 | + using var ctx = scope.ServiceProvider.GetRequiredService<TriggeredApplicationContext>(); |
| 188 | + ctx.Students.Add(new Student { Id = Guid.NewGuid(), DisplayName = "Test" }); |
| 189 | + ctx.SaveChanges(); |
| 190 | + } |
| 191 | + } |
| 192 | +} |
| 193 | + |
0 commit comments