EntityFrameworkCore.Triggered icon indicating copy to clipboard operation
EntityFrameworkCore.Triggered copied to clipboard

Cannot trigger all AfterSaveCompleted() if 2 triggers all implement IAfterSaveCompletedTrigger

Open floydfeng2020 opened this issue 2 years ago • 5 comments

ATrigger as below.

public ATrigger : IAfterSaveTrigger<A>, IAfterSaveCompletedTrigger {
    private List<string> _aNames = new List<string>();
    public Task AfterSave(ITriggerContext<A> context, CancellationToken cancellationToken) { 
        if (context.ChangeType == ChangeType.Added) { 
            this._aNames.Add(context.Name);
            return Task.CompletedTask;
        }
    }

        public Task AfterSaveCompleted(CancellationToken cancellationToken) {
            Console.WriteLine($"There are {_aNames.Count()} As to {_aNames.Distinct().Count()}" distinct A names");
            return Task.CompletedTask;
        }
}

BTrigger as below.

public BTrigger : IAfterSaveTrigger<B>, IAfterSaveCompletedTrigger {
        private List<string> _bNames = new List<string>();
        public Task AfterSave(ITriggerContext<B> context, CancellationToken cancellationToken) { 
            if (context.ChangeType == ChangeType.Added) { 
                this._bNames.Add(context.Name);
                return Task.CompletedTask;
            }
        }

        public Task AfterSaveCompleted(CancellationToken cancellationToken) {
            Console.WriteLine($"There are {_bNames.Count()} Bs to {_bNames.Distinct().Count()}" distinct B names");
            return Task.CompletedTask;
        }
}

AfterSave in both ATrigger and BTrigger could be triggered by SaveChanges(), but only ATrigger's AfterSaveCompleted triggered in the end.

floydfeng2020 avatar Jul 20 '22 12:07 floydfeng2020

AfterSaveCompleted would be raised for all triggers for as long as a Save completed successfully. Some questions:

  1. What version of this library are you using?
  2. How are you registering your triggers?
  3. Can you share a reproducible?

koenbeuk avatar Jul 20 '22 20:07 koenbeuk

  1. Version is 2.3.2.
  2. Registering triggers using services.AddAssemblyTriggers(typeof(ATrigger).Assembly, typeof(BTrigger).Assembly);

floydfeng2020 avatar Jul 21 '22 06:07 floydfeng2020

Can you help me complete the following repro?

I'm getting this output:

A only
AfterSave for entity A
AfterSaveCompleted for entity A
AfterSaveCompleted for entity B

B only
AfterSave for entity B
AfterSaveCompleted for entity A
AfterSaveCompleted for entity B

A and B
AfterSave for entity A
AfterSave for entity B
AfterSaveCompleted for entity A
AfterSaveCompleted for entity B

Given this code:

using EntityFrameworkCore.Triggered;
using EntityFrameworkCore.Triggered.Lifecycles;
using Microsoft.EntityFrameworkCore;

using var dbContext = new MyDbContext();

Console.WriteLine("A only");
dbContext.A.Add(new A(1));
dbContext.SaveChanges();

Console.WriteLine();
Console.WriteLine("B only");
dbContext.B.Add(new B(1));
dbContext.SaveChanges();

Console.WriteLine();
Console.WriteLine("A and B");
dbContext.A.Add(new A(2));
dbContext.B.Add(new B(2));
dbContext.SaveChanges();

Console.ReadLine();


record A(int Id);
record B(int Id);

class MyDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseInMemoryDatabase("test");
        optionsBuilder.UseTriggers(triggerOptions => triggerOptions.AddAssemblyTriggers());
    }

    public DbSet<A> A => Set<A>();
    public DbSet<B> B => Set<B>();

    public static Task WriteLineAsync(string message)
    {
        Console.WriteLine(message);
        return Task.CompletedTask;
    }
}

record Trigger1 : IAfterSaveTrigger<A>, IAfterSaveCompletedTrigger
{
    public Task AfterSave(ITriggerContext<A> context, CancellationToken cancellationToken)
        => MyDbContext.WriteLineAsync($"AfterSave for entity A");

    public Task AfterSaveCompleted(CancellationToken cancellationToken) 
        => MyDbContext.WriteLineAsync($"AfterSaveCompleted for entity A");
}

record Trigger2 : IAfterSaveTrigger<B>, IAfterSaveCompletedTrigger
{
    public Task AfterSave(ITriggerContext<B> context, CancellationToken cancellationToken)
        => MyDbContext.WriteLineAsync($"AfterSave for entity B");

    public Task AfterSaveCompleted(CancellationToken cancellationToken)
        => MyDbContext.WriteLineAsync($"AfterSaveCompleted for entity B");
}

koenbeuk avatar Jul 21 '22 17:07 koenbeuk

I have changed the way to register triggers and it works now. optionsBuilder.UseTriggers(triggerOptions => triggerOptions.AddAssemblyTriggers());

floydfeng2020 avatar Jul 25 '22 09:07 floydfeng2020

There may be an issue here with registering triggers directly with the ServiceProvider. Reopened until this issue is confirmed/fixed

koenbeuk avatar Jul 25 '22 12:07 koenbeuk

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Sep 23 '22 22:09 stale[bot]

I think this should be re-opened. I previously had (using ASP.NET Core 6):

public void ConfigureServices(IServiceCollection services) { services.AddAssemblyTriggers(); }

and fixed the same issue by moving to above syntax. I guess that should be fixed no?

Sorry, but don't have time to setup a full repro repo.

cjblomqvist avatar Oct 19 '22 13:10 cjblomqvist

My PR fixes the original issue. We've also had big issues with using above syntax in Azure linux apps (app service) with CPU immediately raising to 100% as soon as we start up/run our ASP.NET Core backend (hence why we decided to put in the effort to fix the issue described here). We can unfortunately not reproduce this locally (neither on Win nor on Linux). We haven't spent a lot of time to dig into it though.

cjblomqvist avatar Oct 21 '22 11:10 cjblomqvist

Thanks for the fix! Note that registering triggers with the DI container instead of the DbContext is something that I was considering deprecating. I'm interested in the CPU immediately raising to 100% issue though. Can you open up a new issue for that?

koenbeuk avatar Oct 21 '22 12:10 koenbeuk

Will do! Thanks for quick action - very much appreciated! 😊👍

cjblomqvist avatar Oct 21 '22 16:10 cjblomqvist

Btw. Can you release a new version? @koenbeuk

cjblomqvist avatar Oct 21 '22 16:10 cjblomqvist

@cjblomqvist Done!

koenbeuk avatar Oct 24 '22 11:10 koenbeuk

Thanks! I've not forgotten the CPU-stuff, but a little bit complex since it's only happening in Azure (AFAICT anyway) - so it'll take me a little bit more time before I have an issue created. It's not forgotten anyway!

cjblomqvist avatar Oct 25 '22 09:10 cjblomqvist