Comments (13)
I didn't find any official documentation stating so, but I think BulkUpdate cannot be intercepted in any way.
https://www.milanjovanovic.tech/blog/how-to-use-the-new-bulk-update-feature-in-ef-core-7
from audit.net.
When using SaveChanges override method without inheritance, you have to make sure you are overriding the following two methods in your DbContext:
int SaveChanges(bool acceptAllChangesOnSuccess)
Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken))
Just so you know, no other SaveChanges override is needed, since the other overloads will call one of these two.
It looks like you are not overriding the first one, so maybe the client that is not triggering the audit is calling the int SaveChanges(bool acceptAllChangesOnSuccess)
Another option is to use a SaveChanges interceptor instead of overriding the SaveChanges:
from audit.net.
Hello!,
I Changed the Context class and override the two methods that you mentioned.
I also tried using the SaveChanges interceptors.
Both didn't work and the changes are not audited still.
When I put a debug point on the overridden method,
_auditContext is populated with correct values, but still, no audit events are triggered.
On application startup,
If I put a debug point on the Audit core configs,
that is reached as well
I have other projects that use the same Persistance package for database operations,
Audit events are being triggerres successfully from those projects though.
I have the same Audit configurations in all of these projects.
Its not working on this particular project only
from audit.net.
Are you calling Audit.Core.Configuration.Setup()
from all of your runnable projects? for example, if you want to audit from an ASP.NET app and a console app, you need to set up the data provider on both.
Can you share a minimal solution that reproduces the issue?
from audit.net.
It seems I found out why Audit events were not being triggered
I am using the following method to Bulk-Update am Entity list
public async Task BulkUpdatePatients(string connectionString, List<Patient> patients)
{
var _db = new customContext(connectionString);
_db .BulkUpdate(patients);
foreach (var patient in patients)
{
var patientSources = patient.PatientSources;
_db .BulkUpdate(patientSources.ToList());
}
await _db .SaveChangesAsync();
}
When saving in the database this way, Audit events are not getting triggered.
But if I add the following in the method body,
`patient.SOME_Column = "String_value";
_db.Patients.Update(patient);
`
The audit event is triggered.
public async Task BulkUpdatePatients(string connectionString, List<Patient> patients)
{
var _db = new customContext(connectionString);
_db .BulkUpdate(patients);
foreach (var patient in patients)
{
patient.SOME_Column = "String_value";
_db.Patients.Update(patient);
var patientSources = patient.PatientSources;
_db .BulkUpdate(patientSources.ToList());
}
await _db .SaveChangesAsync();
}
How can I make it so that doing Bulk-Updates also trigger the Audit Events.
from audit.net.
The Bulk updates and deletes using ExecuteUpdate()
and ExecuteDelete()
are executed directly to the database, without making use of the EF's change tracker, so the SaveChanges interception will not get those events.
Check this: https://learn.microsoft.com/en-us/ef/core/saving/execute-insert-update-delete#change-tracking
The only way would be to add the AuditCommandInterceptor, which will generate logs like:
{
"CommandEvent": {
"Method": 0,
"CommandType": 1,
"CommandText": "DELETE TOP(@__p_0) FROM [v]\r\nFROM [Values] AS [v]",
"Result": 1,
"Database": "Test_1",
"ConnectionId": "3001fe63-b822-4422-b59e-7b941a05b2ad",
"DbConnectionId": "33e15718-7c07-45d8-953c-acea98a0a0a8",
"ContextId": "b72a194f-7b66-4fe2-8a4d-5e178d0edb7d:0",
"IsAsync": false,
"Success": true
},
"EventType": "EF",
...
from audit.net.
I am not getting what changed in the Logs
The generated JSON looks like this:
{"commandEvent":{"method":2, "commandType":1, "commandText":
"SELECT [p].[Id], [p].[Address_Address1],. . .
[p1].[Patient_Id], [p1].[Type]\r\nFROM [Patients] AS [p]\r\nLEFT
JOIN
(\r\n
SELECT [p0].[Id]
,[p0].[DataSourceId]
,[p0].[EmployerId]
,[p0].[LastSourceOfModification]
,[p0].[LocationId]
,[p0].[PatientId]
,. . .
,[d].[autoCreateReferralAppts]\r\n
FROM [PatientDataSources] AS [p0]\r\n
LEFT JOIN [DataSources] AS [d]
ON [p0].[DataSourceId] = [d].[Id]\r\n
WHERE [d].[AnalyticsIntegrated] = CAST(1 AS bit)\r\n
) AS [t]
ON [p].[Id] = [t].[PatientId]\r\nLEFT
JOIN [PhoneNumbers] AS [p1]
ON [p].[Id] = [p1].[Patient_Id]\r\nWHERE [p].[EnterpriseId] IN (. . .)
\r\nORDER BY [p].[Id], [t].[Id], [t].[Id0]", "
database":"888", "connectionId":"b1a843bf-b4ad-442b-9007-0basdasdads", "dbConnectionId":"asdasdasd-aefa-4103-8ac7-86e3bf6f7727", "contextId":"asdadasda-1cac-4d3e-9ae6-e121ad6d4275:0", "isAsync":true},
"eventType":"ExecuteReader", "environment":{"userName":"Shubham.Pradhan", "machineName":"CGNPLAP586", "domainName":"corp", "callingMethodName":"System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start()", "assemblyName":"System.Private.CoreLib, Version = 6.0.0.0, Culture = neutral, PublicKeyToken = sdasdadasd", "culture":"en-US", "customFields":{}}, "customFields":{"user":"API"}, "startDate":"2023-06-22T05:14:29.3827405Z", "duration":0}
The command text has a "SELECT" statement after performing BulkUpdate.
Ive placed the AuditCommandInterceptor config inside DbContext configuration:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.AddInterceptors(new AuditCommandInterceptor());
}
from audit.net.
Hello!
I am facing another issue here
i am getting the old value and new value exactly the same even when there is an update.
My Updating method looks like this:
public async Task BulkUpdatePatientsAndPatientDataSource(string connectionString, List<Patient> patients)
{
Context _db = new MyContext(connectionString);
_db.Patients.UpdateRange(patients);
foreach (Patient patient in patients)
{
ICollection<PatientDataSource> patientDataSources = patient.PatientDataSources;
_db.PatientDataSources.UpdateRange(patientDataSources);
}
await _db.SaveChangesAsync();
}
I called this method like this :
await _patientDemographicsRepository.BulkUpdatePatientsAndPatientDataSource(clientCredential.ConnectionString, patients);
here i passed the list of updated patients
which i got by creating a new instance of the MyContext like this:
MyContext _db = new MyContext(connectionString);
and then modifying a fey patient entities.
Why am I getting the old value and new value exactly the same even when there is an update.
Is there a way I get the correct change records?
The Audit Json looks like this
{
"entityFrameworkEvent": {
"database": "DB001",
"connectionId": "aaaabbbb",
"contextId": "aaaabbbb",
"entries": [
{
"table": "Patients",
"name": "Patient",
"primaryKey": {
"id": "ididididididididididididididid"
},
"action": "Update",
"changes": [
{
"columnName": "Address_Address1Decrypted",
"originalValue": "KAthmandu",
"newValue": "KAthmandu"
},
{
"columnName": "Name_FirstNameDecrypted",
"originalValue": "SuPr",
"newValue": "SuPr"
},
{
"columnName": "Name_LastNameDecrypted",
"originalValue": "Awesome",
"newValue": "Awesome"
},
{
"columnName": "Name_MiddleName",
"originalValue": "",
"newValue": ""
},
{
"columnName": "Name_MiddleNameDecrypted",
"originalValue": "",
"newValue": ""
}
],
"columnValues": {
"id": "ididididididididididididididid",
"address_Address1Decrypted": "KAthmandu",
"name_FirstNameDecrypted": "SuPr",
"name_LastNameDecrypted": "Awesome",
"name_MiddleName": null,
"name_MiddleNameDecrypted": null
},
"valid": true,
"customFields": {}
}
],
"result": 24,
"success": true,
"customFields": {}
},
"eventType": "EF",
"environment": {
"userName": "Shubham.Pradhan",
"machineName": "LAPTOP",
"domainName": "corp",
"callingMethodName": "System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start()",
"assemblyName": "System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=aabbasd",
"culture": "en-US",
"customFields": {}
},
"customFields": {
"user": null,
"environment": "DEV"
},
"startDate": "2023-07-06T14:09:22.1093073Z",
"endDate": "2023-07-06T14:09:22.4538774Z",
"duration": 345
}
from audit.net.
Doing the update with Update / UpdateRange will not retrieve the old values from the database, that's the way EF Change Tracker works.
From EF documentation here "Note that whenever real original property values are not available (e.g. entity was not yet persisted to the database) this will default to the current property values of this entity."
Please check this: #53 (comment)
from audit.net.
I'm thinking that the Audit.EF library could provide an optional mechanism to load the original values before any modification, to cover the use of Update / UpdateRange (and also Remove and manually attached entities). This mechanism could use the GetDatabaseValues() function.
Will take a deeper look
from audit.net.
Starting from version 21.0.2, the Audit.EntityFramework and Audit.EntityFramework.Core libraries introduce a new feature.
The setting called ReloadDatabaseValues
can be globally or individually configured for a DbContext instance. It determines whether the original values of audited entities should be fetched from the database prior to saving the audit event.
Consider the following examples of update and delete operations:
using (var context = new MyAuditedContext())
{
//context.ReloadDatabaseValues = true;
context.Cars.Update(new Car() { Id = 123, Name = "New name" });
await context.SaveChangesAsync();
}
using (var context = new MyAuditedContext())
{
//context.ReloadDatabaseValues = true;
context.Entry(new Car() { Id = 123 }).State = EntityState.Deleted;
await context.SaveChangesAsync();
}
When doing modifications like these, the EF Change Tracker will lack knowledge of the original values.
Enabling the ReloadDatabaseValues
setting triggers an extra database query to retrieve the original values prior to the update operation. As a result, the audit event will contain the original values.
from audit.net.
Hello!
This solution seems to be working when I inherit the AuditDbContext class
[AuditDbContext(Mode = AuditOptionMode.OptIn, IncludeEntityObjects = false, AuditEventType = "EF")]
public class MyContext : AuditDbContext
{
and set the ReloadDatabaseValues when creating the db context
public async Task BulkUpdatePatientsAndPatientDataSource(string connectionString, List<Patient> patients)
{
Context _db = new MyContext(connectionString);
_dbCW.ReloadDatabaseValues = true;
_db.Patients.UpdateRange(patients);
foreach (Patient patient in patients)
{
ICollection<PatientDataSource> patientDataSources = patient.PatientDataSources;
_db.PatientDataSources.UpdateRange(patientDataSources);
}
await _db.SaveChangesAsync();
}
But this is not working when I try and configure it Globally for the DbContext
[AuditDbContext(Mode = AuditOptionMode.OptIn, IncludeEntityObjects = false, AuditEventType = "EF")]
public class MyContext : DbContext
{
public MyContext(string connectionString) : base(GetOptions(connectionString))
{
_auditContext = new DefaultAuditContext(this);
_auditContext.ReloadDatabaseValues = true;
_helper.SetConfig(_auditContext);
}
public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken))
{
return await _helper.SaveChangesAsync(_auditContext, () => base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken));
}
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
return _helper.SaveChanges(_auditContext, () => base.SaveChanges(acceptAllChangesOnSuccess));
}
}
Is this the correct approach to configure it Globally for the DbContext?
from audit.net.
You could do that, but you should call _auditContext.ReloadDatabaseValues = true
AFTER calling _helper.SetConfig()
, otherwise it will be overridden by the default configuration:
public MyContext(string connectionString) : base(GetOptions(connectionString))
{
_auditContext = new DefaultAuditContext(this);
_helper.SetConfig(_auditContext);
_auditContext.ReloadDatabaseValues = true;
}
But the recommended way to set the configuration globally is by using the fluent API, for example:
Audit.EntityFramework.Configuration.Setup()
.ForAnyContext(cfg => cfg.ReloadDatabaseValues());
Or:
Audit.EntityFramework.Configuration.Setup()
.ForContext<MyContext>(cfg => cfg.ReloadDatabaseValues());
Also, I've just realized I didn't add a property to the AuditDbContextAttribute
, but will do it for the next release.
So, you will be able to set the configuration like this:
[AuditDbContext(ReloadDatabaseValues = true, Mode = AuditOptionMode.OptIn, IncludeEntityObjects = false, AuditEventType = "EF")]
public class MyContext : DbContext
{
...
}
from audit.net.
Related Issues (20)
- Related object causes multiple audit entries HOT 3
- Question for WebApi: Audit only on statuscodes HOT 2
- How to use WebApi / Serilog with .Net 4.7.2 HOT 9
- Extra Double Quote in PostgreSqlDataProvider Generated SQL HOT 2
- Sometimes when auditing to CosmosDB a CosmosException occurs HOT 5
- Since the new version of mongo-csharp-driver The Jobject for AuditTarget cannot be serialized/deserialized HOT 2
- AuditCommandInterceptor IncludeReaderResults = true results in "DataReader Open" error HOT 9
- Missing Method Exception: Microsoft.EntityFrameworkCore.Metadata.IEntityType.GetProperties() HOT 3
- Best way to "Include" related entity properties HOT 3
- Audit.EntityFramework.Core add support for EF8 Complex Types HOT 1
- Feature Request: New MongoDB interaction extension to audit a MongoDB .NET Driver instance HOT 1
- How to audit entities that have been changed\deleted because of on cascade behavior? HOT 13
- How to ingest data to AzureTableStorage month wise in the AuditTrail implementation using Audit.NET? HOT 2
- Not available to use dbContext in .UseDbContext<MyDbContext> in Audit.Core.Configuration.Setup HOT 6
- Feature Request - Enable configuration of Azure Blob Storage Tags HOT 3
- CosmoDB & EFCore: Storing document per entry instead of per transation. HOT 4
- Logs to ElasticSearch missing full information from version 23.0.0 HOT 3
- Possibility to set and save the AuditEvent from an external source HOT 2
- Store data to common audit table HOT 5
- How to connect AzureTableStorage using UserAssigned ManagedIdentity in the AuditTrail implementation using Audit.NET? HOT 3
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from audit.net.