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

SaveChangesAsync Infinite Loop when Insert with DbUpdateException #168

Closed
ubeyou opened this issue Sep 28, 2018 · 4 comments
Closed

SaveChangesAsync Infinite Loop when Insert with DbUpdateException #168

ubeyou opened this issue Sep 28, 2018 · 4 comments
Assignees

Comments

@ubeyou
Copy link

ubeyou commented Sep 28, 2018

Describe the bug
SaveChangesAsync Infinite Loop when DbUpdateException.
Only happens in AuditDbContext, DbContext can't reproduce the bug.

To Reproduce
Adding an entry with the same primary key & save changes.

Expected behavior
It should return DbUpdateException just like how It works on DbContext

Libraries (specify the Audit.NET extensions being used including version):

  • Audit.NET: 13.1.2
  • Audit.EntityFramework: 13.1.2

Additional context

  1. I've tried to debug line by line, apparently the code itself loop back to the SaveChangesAsync.
    When first received an exception, it went into SaveScopeAsync

    await SaveScopeAsync(context, scope, efEvent);

  2. AuditScope.SaveAsync ()

    UpdateAuditEvent(@event, context);

  3. AuditScope.SaveEventAsync ()

    await SaveEventAsync();

  4. await this._dataProvider.InsertEventAsync(this._event);

    _eventId = await _dataProvider.InsertEventAsync(_event);

  5. await (auditDbContext as IAuditBypass).SaveChangesBypassAuditAsync()

    await (auditDbContext as IAuditBypass).SaveChangesBypassAuditAsync();

  6. int num = await base.SaveChangesAsync();

    return await base.SaveChangesAsync();

step 6 will loop back to the SaveChangesAsync again & formed an infinite loop.

@thepirat000
Copy link
Owner

thepirat000 commented Sep 29, 2018

I was not able to reproduce exactly, but I've found a problem and I think that's the cause of the issue.

I'm assuming you use the EntityFrameworkDataProvider to save the audits on the same DbContext who has the audited entities. If this is not correct, please share your configuration and contexts.

Say you are saving a change on your context. If the change fails (i.e. DbUpdateException) then the framework still tries to save the audit before throwing the exception, but since you use the exact same context for saving audits, the audit saving fails because the context detects the previous change and try to re-apply it. Nevertheless I don't see why you are getting an infinite loop.

Fortunately you could workaround it by indicating the framework you want to create a new context instance for each audit operation, with the .UseDbContext<T>() extension on the configuration api.

For example :

Audit.Core.Configuration.Setup()
    .UseEntityFramework(_ => _
        .UseDbContext<YourContext>()    // <---- ADD THIS LINE 
        .AuditTypeExplicitMapper(m => m
            .Map<Blog, BlogAudit>()
            .Map<Post, PostAudit>()
            .AuditEntityAction<IAuditEntity>((ev, entry, entity) =>
            {
                entity.AuditAction = entry.Action;
                entity.Exception = ev.GetEntityFrameworkEvent().ErrorMessage;
            })));

I'm wondering if that should be the default behavior...

Note: I've added a unit test for this here

@ubeyou
Copy link
Author

ubeyou commented Sep 29, 2018

Thanks. It works after applied your workaround above.

Based on my observations, after applied the solution,
The second time it loop SaveChangesAsync, EntityFrameworkEvent will become null & it will stop right there. The infinite loop stopped due to new context.

var efEvent = CreateAuditEvent(context);

@penihel
Copy link

penihel commented Jun 15, 2019

I had the same problem.

But, when i used UseDbContext i had another problem. Because i use transactions with System.Transactions on dotnet core 2.2. Two instances of my DbContext throws this exception:

"This platform does not support distributed transactions."

So, i solved like this:

           
            Configuration.AddCustomAction(ActionType.OnEventSaving, scope =>
            {

                if (scope.Event.Environment.Exception != null)
                {
                    scope.Discard();
                }

            });
`

@Den4ikzxcv
Copy link

Den4ikzxcv commented Feb 21, 2024

@penihel for me your way doesn't work, but worked this approach:

 Audit.Core.Configuration.Setup()
     .UseEntityFramework(_ => _
     .AuditTypeMapper(t => typeof(AuditLog))
     .AuditEntityAction<AuditLog>((ev, entry, entity) =>
     {
         // To fix audit infinity cycle in case of DbUpdateException
         if (ev.Environment.Exception != null)
         {
             return false;
         }
         ...
    }).IgnoreMatchedProperties(true));

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

No branches or pull requests

4 participants