EFCore.BulkExtensions icon indicating copy to clipboard operation
EFCore.BulkExtensions copied to clipboard

Support EF interceptors

Open rezathecoder opened this issue 1 year ago • 3 comments

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

rezathecoder avatar Oct 15 '24 09:10 rezathecoder

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().

Xor-el avatar Oct 15 '24 10:10 Xor-el

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

rezathecoder avatar Oct 15 '24 10:10 rezathecoder

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

Xor-el avatar Oct 15 '24 11:10 Xor-el

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

ArtemMaslow avatar Oct 23 '24 12:10 ArtemMaslow

@ArtemMaslow I have no idea.

Xor-el avatar Oct 23 '24 18:10 Xor-el

@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

borisdj avatar Oct 24 '24 11:10 borisdj