Skip to content

Commit 43729c0

Browse files
committed
2 parents 5283d47 + 315bb61 commit 43729c0

18 files changed

Lines changed: 262 additions & 54 deletions

File tree

.github/workflows/stale.yml

Lines changed: 0 additions & 24 deletions
This file was deleted.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class StudentSignupTrigger : IBeforeSaveTrigger<Student> {
4444

4545
class SendEmailTrigger : IAfterSaveTrigger<Email> {
4646
readonly IEmailService _emailService;
47+
readonly ApplicationDbContext _applicationDbContext;
4748
public StudentTrigger (ApplicationDbContext applicationDbContext, IEmailService emailservice) {
4849
_applicationDbContext = applicationDbContext;
4950
_emailService = emailService;

src/EntityFrameworkCore.Triggered.Abstractions/ITriggerContext.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace EntityFrameworkCore.Triggered
1+
using System.Collections.Generic;
2+
3+
namespace EntityFrameworkCore.Triggered
24
{
35
public interface ITriggerContext<out TEntity>
46
where TEntity : class
@@ -8,5 +10,10 @@ public interface ITriggerContext<out TEntity>
810
TEntity Entity { get; }
911

1012
TEntity? UnmodifiedEntity { get; }
13+
14+
/// <summary>
15+
/// Gets or sets a key/value collection that can be used to share data within the scope of this Entity
16+
/// </summary>
17+
IDictionary<object, object> Items { get; }
1118
}
1219
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace EntityFrameworkCore.Triggered.Internal
6+
{
7+
public sealed class EntityBagStateManager
8+
{
9+
private readonly Dictionary<object, IDictionary<object, object>> _resolvedBags = new();
10+
11+
public IDictionary<object, object> GetForEntity(object entity)
12+
{
13+
if (!_resolvedBags.TryGetValue(entity, out var bag))
14+
{
15+
bag = new Dictionary<object, object>();
16+
_resolvedBags.Add(entity, bag);
17+
}
18+
19+
return bag;
20+
}
21+
}
22+
}

src/EntityFrameworkCore.Triggered/Internal/TriggerContextDescriptor.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace EntityFrameworkCore.Triggered.Internal
66
{
77
public readonly struct TriggerContextDescriptor
88
{
9-
static readonly ConcurrentDictionary<Type, Func<object, PropertyValues?, ChangeType, object>> _cachedTriggerContextFactories = new();
9+
static readonly ConcurrentDictionary<Type, Func<object, PropertyValues?, ChangeType, EntityBagStateManager, object>> _cachedTriggerContextFactories = new();
1010

1111
readonly EntityEntry _entityEntry;
1212
readonly ChangeType _changeType;
@@ -23,7 +23,7 @@ public TriggerContextDescriptor(EntityEntry entityEntry, ChangeType changeType)
2323
public object Entity => _entityEntry!.Entity;
2424
public Type EntityType => _entityEntry!.Entity.GetType();
2525

26-
public object GetTriggerContext()
26+
public object GetTriggerContext(EntityBagStateManager entityBagStateManager)
2727
{
2828
var entityEntry = _entityEntry;
2929
var changeType = _changeType;
@@ -37,11 +37,11 @@ public object GetTriggerContext()
3737
var entityType = entityEntry.Entity.GetType();
3838

3939
var triggerContextFactory = _cachedTriggerContextFactories.GetOrAdd(entityType, entityType =>
40-
(Func<object, PropertyValues?, ChangeType, object>)typeof(TriggerContextFactory<>).MakeGenericType(entityType)
40+
(Func<object, PropertyValues?, ChangeType, EntityBagStateManager, object >)typeof(TriggerContextFactory<>).MakeGenericType(entityType)
4141
.GetMethod(nameof(TriggerContextFactory<object>.Activate))
42-
.CreateDelegate(typeof(Func<object, PropertyValues?, ChangeType, object>)));
42+
.CreateDelegate(typeof(Func<object, PropertyValues?, ChangeType, EntityBagStateManager, object>)));
4343

44-
return triggerContextFactory(entityEntry.Entity, originalValues, changeType);
44+
return triggerContextFactory(entityEntry.Entity, originalValues, changeType, entityBagStateManager);
4545
}
4646
}
4747
}

src/EntityFrameworkCore.Triggered/Internal/TriggerContextFactory.cs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,32 @@ namespace EntityFrameworkCore.Triggered.Internal
77
public static class TriggerContextFactory<TEntityType>
88
where TEntityType : class
99
{
10-
readonly static Func<object, PropertyValues?, ChangeType, TriggerContext<TEntityType>> _factoryMethod = CreateFactoryMethod();
10+
readonly static Func<object, PropertyValues?, ChangeType, EntityBagStateManager, TriggerContext<TEntityType>> _factoryMethod = CreateFactoryMethod();
1111

12-
static Func<object, PropertyValues?, ChangeType, TriggerContext<TEntityType>> CreateFactoryMethod()
12+
static Func<object, PropertyValues?, ChangeType, EntityBagStateManager, TriggerContext<TEntityType>> CreateFactoryMethod()
1313
{
1414
var entityParamExpression = Expression.Parameter(typeof(object), "object");
1515
var originalValuesParamExpression = Expression.Parameter(typeof(PropertyValues), "originalValues");
1616
var changeTypeParamExpression = Expression.Parameter(typeof(ChangeType), "changeType");
17+
var entityBagStateManagerExpression = Expression.Parameter(typeof(EntityBagStateManager), "entityBagStateManager");
1718

18-
return Expression.Lambda<Func<object, PropertyValues?, ChangeType, TriggerContext<TEntityType>>>(
19+
return Expression.Lambda<Func<object, PropertyValues?, ChangeType, EntityBagStateManager, TriggerContext<TEntityType>>>(
1920
Expression.New(
20-
typeof(TriggerContext<>).MakeGenericType(typeof(TEntityType)).GetConstructor(new[] { typeof(object), typeof(PropertyValues), typeof(ChangeType) }),
21+
typeof(TriggerContext<>).MakeGenericType(typeof(TEntityType)).GetConstructor(new[] { typeof(object), typeof(PropertyValues), typeof(ChangeType), typeof(EntityBagStateManager) }),
2122
entityParamExpression,
2223
originalValuesParamExpression,
23-
changeTypeParamExpression
24+
changeTypeParamExpression,
25+
entityBagStateManagerExpression
2426
),
2527
entityParamExpression,
2628
originalValuesParamExpression,
27-
changeTypeParamExpression
29+
changeTypeParamExpression,
30+
entityBagStateManagerExpression
2831
)
2932
.Compile();
3033
}
3134

32-
public static object Activate(object entity, PropertyValues? originalValues, ChangeType changeType)
33-
=> _factoryMethod(entity, originalValues, changeType);
35+
public static object Activate(object entity, PropertyValues? originalValues, ChangeType changeType, EntityBagStateManager entityBagStateManager)
36+
=> _factoryMethod(entity, originalValues, changeType, entityBagStateManager);
3437
}
3538
}

src/EntityFrameworkCore.Triggered/TriggerContext.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,25 @@
1-
using Microsoft.EntityFrameworkCore.ChangeTracking;
1+
using System.Collections.Generic;
2+
using EntityFrameworkCore.Triggered.Internal;
3+
using Microsoft.EntityFrameworkCore.ChangeTracking;
24

35
namespace EntityFrameworkCore.Triggered
46
{
57
public class TriggerContext<TEntity> : ITriggerContext<TEntity>
68
where TEntity : class
79
{
8-
readonly ChangeType _type;
910
readonly TEntity _entity;
11+
readonly ChangeType _type;
1012
readonly PropertyValues? _originalValues;
13+
readonly EntityBagStateManager _entityBagStateManager;
1114

1215
TEntity? _unmodifiedEntity;
1316

14-
15-
public TriggerContext(object entity, PropertyValues? originalValues, ChangeType changeType)
17+
public TriggerContext(object entity, PropertyValues? originalValues, ChangeType changeType, EntityBagStateManager entityBagStateManager)
1618
{
17-
_type = changeType;
1819
_entity = (TEntity)entity;
1920
_originalValues = originalValues;
21+
_type = changeType;
22+
_entityBagStateManager = entityBagStateManager;
2023
}
2124

2225
public ChangeType ChangeType => _type;
@@ -40,5 +43,7 @@ public TEntity? UnmodifiedEntity
4043
}
4144
}
4245
}
46+
47+
public IDictionary<object, object> Items => _entityBagStateManager.GetForEntity(_entity);
4348
}
4449
}

src/EntityFrameworkCore.Triggered/TriggerSession.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ public class TriggerSession : ITriggerSession
2222
readonly TriggerContextTracker _tracker;
2323
readonly ILogger<TriggerSession> _logger;
2424

25+
readonly EntityBagStateManager _entityBagStateManager = new();
26+
2527
bool _raiseBeforeSaveTriggersCalled;
2628

2729
public TriggerSession(ITriggerService triggerService, TriggerSessionConfiguration configuration, ITriggerDiscoveryService triggerDiscoveryService, TriggerContextTracker tracker, ILogger<TriggerSession> logger)
@@ -91,7 +93,7 @@ public async Task RaiseTriggers(Type openTriggerType, Exception? exception, ITri
9193
_logger.LogInformation("Invoking trigger: {trigger} as {triggerType}", triggerDescriptor.Trigger.GetType(), triggerDescriptor.TypeDescriptor.TriggerType);
9294
}
9395

94-
await triggerDescriptor.Invoke(triggerContextDescriptor.GetTriggerContext(), exception, cancellationToken).ConfigureAwait(false);
96+
await triggerDescriptor.Invoke(triggerContextDescriptor.GetTriggerContext(_entityBagStateManager), exception, cancellationToken).ConfigureAwait(false);
9597
}
9698
}
9799

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using Microsoft.EntityFrameworkCore;
2+
3+
namespace EntityFrameworkCore.Triggered.IntegrationTests.EntityBags
4+
{
5+
public class ApplicationDbContext
6+
#if EFCORETRIGGERED1
7+
: TriggeredDbContext
8+
#else
9+
: DbContext
10+
#endif
11+
{
12+
public ApplicationDbContext(DbContextOptions options) : base(options)
13+
{
14+
}
15+
16+
public DbSet<User> Users { get; set; }
17+
}
18+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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.Extensions.DependencyInjection;
8+
using ScenarioTests;
9+
using Xunit;
10+
11+
namespace EntityFrameworkCore.Triggered.IntegrationTests.EntityBags
12+
{
13+
public partial class EntityBagsTestScenario
14+
{
15+
[Scenario(NamingPolicy = ScenarioTestMethodNamingPolicy.Test)]
16+
public void PlayScenario(ScenarioContext scenario)
17+
{
18+
using var dbcontext = new ApplicationDbContext(
19+
new DbContextOptionsBuilder()
20+
.UseInMemoryDatabase(scenario.TargetName)
21+
.UseTriggers(triggerOptions => {
22+
triggerOptions.AddTrigger<Triggers.StampModifiedOnTrigger>();
23+
triggerOptions.AddTrigger<Triggers.SoftDeleteTrigger>();
24+
})
25+
.Options
26+
);
27+
28+
var user = new User();
29+
dbcontext.Users.Add(user);
30+
dbcontext.SaveChanges();
31+
32+
scenario.Fact("ModifiedOn is null", () => Assert.Null(user.ModifiedOn));
33+
scenario.Fact("DeletedOn is null", () => Assert.Null(user.DeletedOn));
34+
35+
dbcontext.Remove(user);
36+
dbcontext.SaveChanges();
37+
38+
scenario.Fact("ModifiedOn is null after removal", () => Assert.Null(user.ModifiedOn));
39+
scenario.Fact("DeletedOn is not null after removal", () => Assert.NotNull(user.DeletedOn));
40+
41+
user.Name = "Jon";
42+
dbcontext.SaveChanges();
43+
44+
scenario.Fact("ModifiedOn is not null after update", () => Assert.NotNull(user.ModifiedOn));
45+
}
46+
}
47+
}

0 commit comments

Comments
 (0)