Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support EF interceptors #1591

Closed
rezathecoder opened this issue Oct 15, 2024 · 6 comments
Closed

Support EF interceptors #1591

rezathecoder opened this issue Oct 15, 2024 · 6 comments
Labels

Comments

@rezathecoder
Copy link

Hi
Is it possible to support EF interceptors when using BulkSaveChanges ?
@borisdj

@Xor-el
Copy link

Xor-el commented Oct 15, 2024

Hello @rezathecoder if you are using the BulkSaveChanges() which supports Change Tracking then Yes it's possible albeit with a twist. your Interceptor has to inherit from DbCommandInterceptor. Inheriting from SaveChangesInterceptor would not cut it when using BulkSaveChanges().

@rezathecoder
Copy link
Author

rezathecoder commented Oct 15, 2024

Hello @rezathecoder if you are using the BulkSaveChanges() which supports Change Tracking then Yes it's possible albeit with a twist. your Interceptor has to inherit from DbCommandInterceptor. Inheriting from SaveChangesInterceptor would not cut it when using BulkSaveChanges().

Can you please guide me so i can imlplement that?
I need to do some work before and after saving on entries

@Xor-el
Copy link

Xor-el commented Oct 15, 2024

Hello @rezathecoder if you are using the BulkSaveChanges() which supports Change Tracking then Yes it's possible albeit with a twist. your Interceptor has to inherit from DbCommandInterceptor. Inheriting from SaveChangesInterceptor would not cut it when using BulkSaveChanges().

Can you please guide me so i can imlplement that? I need to do some work before and after saving on entries

below is an example of doing work before saving the entries. for after saving the entries, you have to override the NonQueryExecuted and NonQueryExecutedAsync methods.

    public sealed class NonQueryAuditableEntityInterceptor : DbCommandInterceptor
    {
        private readonly ILogger<NonQueryAuditableEntityInterceptor> _logger;
        private readonly TimeProvider _timeProvider;

        public NonQueryAuditableEntityInterceptor(ILogger<NonQueryAuditableEntityInterceptor> logger, TimeProvider timeProvider)
        {
            _logger = logger;
            _timeProvider = timeProvider;
        }

        public override InterceptionResult<int> NonQueryExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<int> result)
        {
            if (eventData.Context is not null)
            {
                UpdateAuditableEntities(eventData.Context);
            }

            return base.NonQueryExecuting(command, eventData, result);
        }

        public override ValueTask<InterceptionResult<int>> NonQueryExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = default)
        {
            if (eventData.Context is not null)
            {
                UpdateAuditableEntities(eventData.Context);
            }

            return base.NonQueryExecutingAsync(command, eventData, result, cancellationToken);
        }

        private void UpdateAuditableEntities(DbContext context)
        {
            var utcNow = _timeProvider.GetUtcNow().UtcDateTime;

            var changeTracker = context.ChangeTracker;
            changeTracker.DetectChanges();
            var entityEntries = changeTracker.Entries<IAuditableEntity>().Where(x => x.State == EntityState.Added || x.State == EntityState.Modified).ToList();

            foreach (var entityEntry in entityEntries)
            {
                switch (entityEntry.State)
                {
                    case EntityState.Added:
                        SetCurrentPropertyValue(entityEntry, nameof(IAuditableEntity.CreatedOnUtc), utcNow);
                        break;
                    case EntityState.Modified:
                        SetCurrentPropertyValue(entityEntry, nameof(IAuditableEntity.ModifiedOnUtc), utcNow);
                        break;
                }
            }

            return;

            static void SetCurrentPropertyValue(EntityEntry entry, string propertyName, DateTime utcNow) => entry.Property(propertyName).CurrentValue = utcNow;
        }
    }


    public interface IAuditableEntity
    {
        DateTime CreatedOnUtc { get; }
        DateTime? ModifiedOnUtc { get; }
    }

*** Remember to add the Interceptor in your DbContextOptionsBuilder instance

@ArtemMaslow
Copy link

Hi
@Xor-el Is it possible add additional data to save to another entity?

@Xor-el
Copy link

Xor-el commented Oct 23, 2024

@ArtemMaslow I have no idea.

@borisdj
Copy link
Owner

borisdj commented Oct 24, 2024

@rezathecoder one way to add some custom logic would be to override BulkMethods you use as explained here:
Create extension method #56-comment
Another option would be to use config CustomSqlPostProcess (check info in ReadMe)

Regarding Interceptors here is some useful info:
https://learn.microsoft.com/en-us/ef/core/logging-events-diagnostics/interceptors
https://medium.com/the-tech-collective/part-3-using-interceptors-with-entity-framework-core-0475f49c8947

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants