borisdj / EFCore.BulkExtensions

Entity Framework EF Core efcore Bulk Batch Extensions with BulkCopy in .Net for Insert Update Delete Read (CRUD), Truncate and SaveChanges operations on SQL Server, PostgreSQL, MySQL, SQLite
https://codis.tech/efcorebulk
Other
3.67k stars 588 forks source link

Support EF interceptors #1591

Closed rezathecoder closed 4 weeks ago

rezathecoder commented 1 month ago

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

Xor-el commented 1 month ago

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 commented 1 month ago

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 commented 1 month ago

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 commented 1 month ago

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

Xor-el commented 1 month ago

@ArtemMaslow I have no idea.

borisdj commented 4 weeks ago

@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