-
-
Notifications
You must be signed in to change notification settings - Fork 323
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
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 called in an infinite loop with invalid data #399
Comments
Can you share the affected model classes and its EF configuration, so I can try to reproduce ? |
Here is the class I use where all the database code resides :
SqlDbContextBase derives from DbContext and have no overrides of the save functions you see in here, this was created only for projects where I do need to use some form of audits. The catches were added to make sure they were thrown which they are. This is the AuditLog model :
This helper class is what I use to setup the audit and also to add an audit scope right before performing an operation :
The model I push to the database to be persisted is very basic, contains a GUID primary key with a GUID as a foreign key (which doesn't exist in the other table) and some data (whatever you want it to be that looks something like this :
TranslatableBase is just a mechanism I use to support translation tables (just to give you some context), but this is not what is causing the issue and you don't have to create anything similar. Basically it adds more fields. Like I was saying before, everything works fine if everything is valid, but as soon as I try to push an invalid foreign key and as soon as it hits the helper's SaveChangesAsync, it will raise the exception and call itself again. I did make sure it was not something I did that prevented the base class from reaching DbContext and did check that DbContext was being called which is what the helper is being passed as an argument. If I disable the Audit, the exception is thrown normally without any issues. |
Please check this #168 (comment)
I guess it's the same issue, could you apply the suggested workaround and re-test? |
Also I'm pretty sure the call to So you probably need to remove that public class SqlDbContextAuditNetBase : DbContext, IAuditBypass
{
public int SaveChangesBypassAudit()
{
return base.SaveChanges();
}
public Task<int> SaveChangesBypassAuditAsync()
{
return base.SaveChangesAsync();
}
} |
It seems that removing DetectChanges doesn't change anything. I did try the IAuditBypass and that worked. I'm just trying to evaluate the consequences of going that route if there are any. I didn't get the chance to look at the other issue yet but from the small resume there is there, it does seem very similar. |
So I was able to fix it with using the IAuditBypass and also by adding the right context to use during the setup as you proposed in this issue #168 (comment)
Thank you very much, problem is fixed! |
I guess I was too quick. While an error is thrown now when there is an error and exists gracefully, when I am trying to insert valid data, I get this error by adding .UseDbContext() An error occured while processing request : One or more errors occurred. (Cannot create an abstract class.) And indeed, my base class is abstract as I do not want ANYONE to use it since it is useless anyone without adding the specific code to create models, etc. And then I did tried to use my top level context, the one where I have all the fluent API Definitions and I get this : An error occured while processing request : One or more errors occurred. (No database provider has been configured for this DbContext. A provider can be configured by overriding the 'DbContext.OnConfiguring' method or by using 'AddDbContext' on the application service provider. If 'AddDbContext' is used, then also ensure that your DbContext type accepts a DbContextOptions object in its constructor and passes it to the base constructor for DbContext.) which is strange since the provider is defined. I'm not sure what is going on.
Any clues? I think the problem is the where clause. If that is the case then I'll probably have no choice but the use the Bypass... The solution proposed by the error is not something I can use since I am using a DbContext factory where the provider is defined.. So the error really doesn't make any sense to me. |
When you use the parameterless So if you pass the abstract class as the generic type, it will fail when trying to create an instance of that type. You need to pass the concrete implementation type, and if you need to pass arguments to the constructor you can use the overload .UseDbContext<SqlDbContextConcrete>(new DbContextOptionsBuilder<SqlDbContextConcrete>().UseSqlServer("connection string..").Options) Or you can pass a lambda function like a factory that returns the audit context with the overload .UseDbContext(ev =>
{
return contextAccessor.HttpContext.RequestServices.GetService<SqlDbContextConcrete>();
}) |
I guess it makes sense if you need to write to the Audit table you will need a context with the provider.. I don't need to do this with the AuditBypass so I probably will go with that instead but I'll see. Thanks for the details. |
This is very strange, by implementing IAuditBypass, the save functions are no longer being called. I thought it did before but I noticed I disabled the audits while doing a test. Is there anything else specific I need to do in the config when I implement IAuditBypass on the context? |
Alright so I got the issues resolved by adding a UsqDbContext and passing the right factory to get the context. Now there is one last bug I noticed. Whenever I try to insert a record that is not valid and returns a, lets say, foreign key violation, that record will still get audited in the database which is not the expected behavior, on top of that the record reports the it is valid which is not expected either. Do you have any recommendations? |
You should be able see the Error Message on the ErrorMessage property on the EF Audit Event. If you don't want to audit failed transactions you have some options:
Or...
.AuditEntityAction<AuditLog>((ev, entry, entity) =>
{
if (!string.IsNullOrEmpty(ev.GetEntityFrameworkEvent().ErrorMessage))
{
return false;
}
entity.AuditId = Guid.NewGuid();
// ...
return true;
}) Another thing I noticed is that it seems you are calling your Note that |
All try that tomorrow to see if the scope discard works. Or it may have to wait until next week. As for calling AddAuditScope before each action, the reason that is done is because we haven't found a better way to get the user information from the JWT data or how to make that work when I add a custom action from the setup. I did try to get the information from the HTTPContext, getting the authorization header and "unwrapping" the JWT but each time the authorization header would be empty. So I am unsure why this happens but that is the main reason. If you have any solution to propose, let me know. Thanks for your help! |
Alright the discard option on errors work. Unfortunately, there is no way for me to solve the scope of who is logging. Logging doesn't happen from the Web API. Requests are sent to the system via a WEB API which is sent to RabbitMQ and picked up by a Request Processor system. Each requests are processed depending on a priority and only then do we know which user sent the request which is why I was calling an AddCustomAction to log the user performing the action. Each of these requests are processed in their own thread. So, it is impossible for me to do it the way you propose since I can process many in parallel. In other words, it is impossible for me to do it as you propose using the HttpContext which is very generic and applies very well to a WebAPI but in our case, requests are not known in advance and cannot pre-insert a configuration that would extract a user ID in advance especially that many can be processed in parallel. Is there any other way with your Framework that you would propose I should identify who is performing an action? Basically, I need to do this before each save and it shouldn't interfere with all other parallel executions, in other words, if operations are performed by others in parallel, each userId shouldn't be overriden by others for requests processed on other threads.. Now I'm I'm not so sure, with what you explained, if adding that through the configuration system will work. It seems to work now because I am a single user. Anyways, looking forward to see what you propose and if there is another way other than going through the configuration to do what I need. |
Let me know what you think but it seems to me and from what you said that given how specific my situation is that there is no way for me to identify the user given the parallel processing nature of the app, that I would need another approach. Looked at the documentation but it is not clear exactly how to proceed. |
Can you share the code where you call your |
Share that code would be a bit complicated.. We have a DAL which is used to communicate with the Database layer. The database layer contains the context where the saving happens. In the DAL, that is where the custom action to keep track of the user is called and right after a call to the database is initiated. In that sense, everything is in the same "scope". I can provide you the code where the a new entity is created :
The call to SqlAuditNetHelper.LogUserPerformingTheAction(userId); contains the following code :
And I changed the setup of audit.net slightly to prevent logging on errors following your previous input :
This works but I am guessing, after what you said, it will probably fail if I have different users performing actions leading to reporting users who didn't really perform the operation. Unless I misunderstood what you meant. The CreateEntityAsync each from their own threads, meaning they all have their own context and not all threads are calling the same CreateEntityAsync of course. |
Yes, the multiple calls to AddCustomAction will not work properly on multiple threads and will be leaking memory. The best solution really depends on your design. Does your |
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
Hi,
I have found a bug where, if I am passing a model that has a foreign key dependency and that foreign key is invalid (doesn't exist), I would expect a DbUpdateException to be thrown but what happens instead is that SaveChangedAsync is called in a loop over and over again which causes the database volume to increase until the application stops.
The implementation I went for is the one with the DbContextHelper which is very close to what is detailed here : https://github.com/thepirat000/Audit.NET/blob/master/src/Audit.EntityFramework/README.md#entity-framework-data-provider
If I try to save data that is valid, everything happens without any issues, I will get an Audit record and the data will be written to the database, otherwise, upon calling SaveChangesAsync, the helper seems to call SaveChangesAsync over and over.
I disabled Audit.NET to see if it was an issue with my implementation and send invalid data again and I do get DbUpdateException thrown and I'm able to log the error and exits without an issue.
The text was updated successfully, but these errors were encountered: