a quick reminder from the case I encountered, there are two methods looks very similar in SaveChangesInterceptor, one is called SavingChangesAsync and one is call SavedChangesAsync, in thevideo we are implementing SavingChangesAsync. sometimes your IDE will aurocomplete to the other one. make sure you are overriding the correct method otherwise it won't work
This is populating some audit fields but you still only have the latest state of the entity. There is no capture here of the values that were changed. Yes, I know when a change was made, and you can also include the user to know who changed it. But you don't know what was changed. This may be fine for some use cases. One option to get a real audit log with changes is to use TemporalTables if you are in SQL server. Or, if you really want a more robust method take a look at the EventSourcing pattern.
Thank you for the tutorial, very useful. There is something that I have noticed: the State is always Unchanged in the after change listener. that feels like a very important oversight given it will never tell you what happened. There is always also the issue that EF Core does not have events for delete. 🤦♂
@@MilanJovanovicTech in the SavedChangesAsync (or SavedChanges) method of the interceptor there is no way to know if the entity was inserted or updated (retrieving the entity from the ChangeTracker and asking for the state, it is marked as Unchanged, even if it was just inserted into the database).
I see, seems interesting. Currently I am overriding the savechangesasync method but it has some issues in my use cases. This seems to resolve those definately will give it try :) Thanks :)
Nice feature indeed ! I am wondering, what are pros and cons of Auditing using Temporal tables in SQL Server vs Auditing using Interceptors ? Thanks Milan
There is 'Entity' property on EntityEntry through which you can access properties directly. Also the described method won't work if we want to keep track of IDs generated by the database on insert.
Hi,@@MilanJovanovicTech I able to use -Entity' Property la tell what is difference using auditableEntity.Property(x => x and auditableEntity.Entity. Thanks
Do you have a video on how you set up your visual studio? I love the way your Intellisense looks, and your color scheme. It looks like default with just a few nice modifications.
@@MilanJovanovicTech Thanks man! I'm currently using your implementation of MediatR and Fluent Validation to redo our template for new projects at work. the old template is cqrs done custom and poorly. excited to use packages that my junior devs can read on and move our custom code to best practices and set a standard. became a patron to get that source code thank you so much man!
@@thematthewyoung I'm glad you found some value out of it. And I appreciate the support :) That's amazing that you're taking steps to improve the project template. I'm sure it will pay off.
DB triggers are an excellent option, although they will slow down the DB in high load scenarios? Doing it in application seems simpler to me. But there's a slight issue. Let's say you also want to log the ID of the User who made the change. You can't achieve that via triggers, but you can do that with interceptors.
Milan, thanks for the awesome videos. What if you wanted to track the table columns affected, their previous values before CRUD and the new values after CRUD?
@@MilanJovanovicTech I've done this in .NET 5 many times using auditing setup in MVC. Was just curious if v6 and v7 had the same capability or different coding structure. The coding habits usually change, sometimes drastically when a new SDK version is released.
Hi Milan, something I'd like to understand is where would that information (CreatedOnUtc, ModifiedOnUtic) be stored. Would it be stored on a separate table in the database?
In that case override save changes and save changes async method in db context class and move the entity and entry check logic there. I prefer the override method cause I used save changes frequently.
@@MilanJovanovicTech Yep exactly , I was wondering In clean architecture 'domain' not have knowledge about currentUser, or embed this logic somewhere else
Hey Milan, great video. I have already implemented this kind of audit using EFCore, but I have found one setback in this. If you have related entities in your model, and for some reason it happens that the related entities are modified, then this way of audit will not work because EFCore change tracker doesn't track related entities (as per my experience). Have you found a workaround on this ?
@Milan Jovanović In fact I think I was a bit unclear on my question. I was trying to keep track of my entities using 'CurrentValue' and 'OriginalValue' properties. While EFCore tracks these properties in an entity, it does not work with related entities. I also tried traversing down related entities in different ways but could not get its 'OriginalValue' property. Anyway here's a picture of how I implemented logs in EF while overriding savechangesasync method. The only problem with this approach is that I cannot track changes in related entities i.imgur.com/oGH9ubs.png
@@shkelqimhaxha3985 If you are using SQL server, take a look at temporal tables. SQL does most of the work for you, and EF core (at least 6) supports it.
I'm with the same problem, did you find a solution to get previous value and current value to related entities using entity framework ?@@shkelqimhaxha3985
Thanks for the useful video! Can you please share some knowledge about how to resolve scoped dependencies within the Interceptor? E.g IHttpContextAccessor? Could not find any working examples :(
Milan, is it possible to capture here the ID of the person who made the modification or creation to save not only the date but also who made the transaction?
You would need to inject a service that provides that ID. Since the interceptor is a Singleton service, this will probably be challenging. So another option would be to move this into DbContext.SaveChangesAsync. Since DbContext is scoped, you can inject a service like IHttpContextAccessor. What do you think?
@@MilanJovanovicTech why not register the interceptor as a scoped service? Is there some underlying reason that we shouldn't? Edit: We cannot inject into an interceptor as it prevents us from scaffolding migrations, so using a scoped service was pointless. I did find out however that the solution's a lot easier than I thought. The DBContext has a GetService method that will expose the service from the DB Context's scope.
Thanks Milan for your awesome video 👍 Is there a relation between Notification "e.g: like what SignalR do" and CQRS? if yes, I hope you explain it in one of your upcoming videos if no, I hope you also explain how to use it
I know this is an old video but please respond.. Does it cost performance. Because now the saveChanges method goes through interceptor which will slow down the performance, right?
Hello @Milan i came here from your linkedin profile.. I want to know what is best way for audit trail logging when you are using .net core. I have huge logging mechanism. But want to know best practices and tools for same.
@@MilanJovanovicTech But if you work on a clean architecture type of project and this interceptor will probably be in the infrastructure (persistence) layer, then how do you do in this case ? Assuming that IHttpContextAccessor should only be injected in application or presentation layer
@@shkelqimhaxha3985 I usually use this approach: in asp.net middleware create some sort of user context and fill it with data you need. Then store it within some service in DI (you will definitely need AsyncLocal or something like this to do that) and inject it where required
Sorry I don't consider this Auditing, it is only tracking the last modified date/time not all of the times it's modified and it's not tracking what has modified and the values of the before and after the modification. Also while you talked about keeping track of who created/modified the record you didn't give an example of that only the date/time
You can extend the example, since the ChangeTracker gives you the CurrentValue and OriginalValue of the entity to set the before/after state in the database. Or we could explore temporal tables.
Why using « entre.Proprety.(e => e.dummyField).CurrentValue » ? I never really think about and always used « entry.Entity.dummyField ». Does it make any difference ?
Want to master Clean Architecture? Go here: bit.ly/3PupkOJ
Want to unlock Modular Monoliths? Go here: bit.ly/3SXlzSt
a quick reminder from the case I encountered, there are two methods looks very similar in SaveChangesInterceptor, one is called SavingChangesAsync and one is call SavedChangesAsync, in thevideo we are implementing SavingChangesAsync. sometimes your IDE will aurocomplete to the other one. make sure you are overriding the correct method otherwise it won't work
Thanks for pointing that out, very important
@@MilanJovanovicTech above video is part of which course or youtube series ? I want complete playlist.
This is populating some audit fields but you still only have the latest state of the entity. There is no capture here of the values that were changed. Yes, I know when a change was made, and you can also include the user to know who changed it. But you don't know what was changed. This may be fine for some use cases.
One option to get a real audit log with changes is to use TemporalTables if you are in SQL server.
Or, if you really want a more robust method take a look at the EventSourcing pattern.
Absolutely Bob, you are spot on with Temporal Tables/Event Sourcing if we need more details about _what_ is changed.
4:30 Was that some sort of shortcut you made there to create the foreach that fast? - Or was it the editing of the video that made it look like magic?
Editing magic 😅
Thank you for the tutorial, very useful.
There is something that I have noticed: the State is always Unchanged in the after change listener. that feels like a very important oversight given it will never tell you what happened.
There is always also the issue that EF Core does not have events for delete. 🤦♂
Not entirely sure what you mean? 🤔
@@MilanJovanovicTech in the SavedChangesAsync (or SavedChanges) method of the interceptor there is no way to know if the entity was inserted or updated (retrieving the entity from the ChangeTracker and asking for the state, it is marked as Unchanged, even if it was just inserted into the database).
I see, seems interesting. Currently I am overriding the savechangesasync method but it has some issues in my use cases. This seems to resolve those definately will give it try :) Thanks :)
Stacking interceptors is a nice approach, to be honest.
Nice feature indeed !
I am wondering, what are pros and cons of Auditing
using Temporal tables in SQL Server vs Auditing using Interceptors ?
Thanks Milan
More memory usage, a bit more difficult to query? 🤔
Less complicated to set up.
There is 'Entity' property on EntityEntry through which you can access properties directly. Also the described method won't work if we want to keep track of IDs generated by the database on insert.
The properties have a private setter, so that would't work with my implementation
Hi,@@MilanJovanovicTech I able to use -Entity' Property la tell what is difference using auditableEntity.Property(x => x and auditableEntity.Entity. Thanks
Thanks, nice video. I think VS2022 has also the possibility to cleanup unused namespaces on file save
Could be, I didn't hear of that before. I tend to use default settings in general.
Very useful video, thanks very much!
I'm glad you liked it. Did you have to build something similar before?
Did you share the full code of this project ? I want to learn clean arc. From it
Yes, on Patreon
Do you have a video on how you set up your visual studio? I love the way your Intellisense looks, and your color scheme. It looks like default with just a few nice modifications.
That's coming from ReSharper mostly. I use the R# Dark theme
@@MilanJovanovicTech Thanks man! I'm currently using your implementation of MediatR and Fluent Validation to redo our template for new projects at work. the old template is cqrs done custom and poorly. excited to use packages that my junior devs can read on and move our custom code to best practices and set a standard. became a patron to get that source code thank you so much man!
@@thematthewyoung I'm glad you found some value out of it. And I appreciate the support :)
That's amazing that you're taking steps to improve the project template. I'm sure it will pay off.
Thank you for the tutorial, very useful.
Glad it was helpful!
Opinions on using this approach vs having DB triggers for Insert/Update/Delete operations?
DB triggers are an excellent option, although they will slow down the DB in high load scenarios? Doing it in application seems simpler to me.
But there's a slight issue. Let's say you also want to log the ID of the User who made the change. You can't achieve that via triggers, but you can do that with interceptors.
Good video, many thanks
Glad you enjoyed it
Can the interceptor have a Principal in orher to obtain curren claims of user?
Yes, you can probably do that with IHttpContextAccessor. Note that the HttpContext will be null for server-only scenarios.
Excellent, thank you
Makes me happy you found it useful 😁
Milan, thanks for the awesome videos. What if you wanted to track the table columns affected, their previous values before CRUD and the new values after CRUD?
That's an entirely different thing, you need some sort of history pattern. Check out temporal tables with SQL Server
@@MilanJovanovicTech I've done this in .NET 5 many times using auditing setup in MVC. Was just curious if v6 and v7 had the same capability or different coding structure. The coding habits usually change, sometimes drastically when a new SDK version is released.
Hi Milan, something I'd like to understand is where would that information (CreatedOnUtc, ModifiedOnUtic) be stored. Would it be stored on a separate table in the database?
Separate columns, same table
@@MilanJovanovicTech thank you so much!
What if you need to use the base SaveChanges method in certain cases? How do I disable the interceptor? In case with overriding SaveChanges it's easy.
How do you disable any middleware?
You introduce a switch of some sorts that you can control, and turn on/off as you wish.
In that case override save changes and save changes async method in db context class and move the entity and entry check logic there. I prefer the override method cause I used save changes frequently.
How would you update in this approach if the interface has a user update field?
Find a way to inject the current user ID and save that
@@MilanJovanovicTech Yep exactly , I was wondering In clean architecture 'domain' not have knowledge about currentUser, or embed this logic somewhere else
Hey Milan, great video. I have already implemented this kind of audit using EFCore, but I have found one setback in this. If you have related entities in your model, and for some reason it happens that the related entities are modified, then this way of audit will not work because EFCore change tracker doesn't track related entities (as per my experience). Have you found a workaround on this ?
You can always traverse down the navigation properties using the EntityEntry. But do you want to audit navigation properties if they weren't changed?
@Milan Jovanović In fact I think I was a bit unclear on my question. I was trying to keep track of my entities using 'CurrentValue' and 'OriginalValue' properties. While EFCore tracks these properties in an entity, it does not work with related entities. I also tried traversing down related entities in different ways but could not get its 'OriginalValue' property. Anyway here's a picture of how I implemented logs in EF while overriding savechangesasync method. The only problem with this approach is that I cannot track changes in related entities i.imgur.com/oGH9ubs.png
@@shkelqimhaxha3985 If you are using SQL server, take a look at temporal tables. SQL does most of the work for you, and EF core (at least 6) supports it.
I'm with the same problem, did you find a solution to get previous value and current value to related entities using entity framework ?@@shkelqimhaxha3985
How do you recommend to pass logged in user id to log who added/ changed the record ?
- Grab it from the HttpContext, which means we can only do this in an API request
- Or you can include the UserId as part of the incoming request
@@MilanJovanovicTech Hmm that's what I thought. Thanks for sharing your views, we seem on the same page 🙂
I can do all this in the context file without register any services. Which one is better?
Both are fine
Can you share a sample project with good DDD/CQRS pratices?
Can you share this project onGitHub?
github.com/m-jovanovic/event-reminder
Thanks for the useful video! Can you please share some knowledge about how to resolve scoped dependencies within the Interceptor? E.g IHttpContextAccessor? Could not find any working examples :(
IHttpContextAccessor is a singleton. You can inject it without a problem. The question is will the HttpContext property be null or not :)
simple but useful 👍
Glad you liked it!
Using this implementation only last modified date will be written in db?
Yes, if I understood your question correctly.
Milan, is it possible to capture here the ID of the person who made the modification or creation to save not only the date but also who made the transaction?
You would need to inject a service that provides that ID. Since the interceptor is a Singleton service, this will probably be challenging. So another option would be to move this into DbContext.SaveChangesAsync. Since DbContext is scoped, you can inject a service like IHttpContextAccessor. What do you think?
@@MilanJovanovicTech Interesting, I'm going to try it and I'll tell you later how it went, thanks Milan😃
I am using the below code in UnitOfWork:
public async Task Save(HttpContext httpContext)
{
var userId = httpContext.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var user = await _userManager.FindByIdAsync(userId);
var entries = _context.ChangeTracker.Entries()
.Where(q => q.State == EntityState.Modified || q.State == EntityState.Added);
foreach (var entry in entries)
{
if (entry.State == EntityState.Added)
{
((EntityBase)entry.Entity).CreatedBy = user.UserName;
}
if (entry.State == EntityState.Modified)
{
((EntityBase)entry.Entity).UpdatingDate = DateTime.UtcNow;
((EntityBase)entry.Entity).UpdatedBy = user.UserName;
}
}
await _context.SaveChangesAsync();
}
And The EntityBase:
public abstract class EntityBase
{
public EntityBase()
{
CreationDate = DateTime.UtcNow;
}
[Key]
public int Id { get; set; }
public string CreatedBy { get; set; } = string.Empty;
public string UpdatedBy { get; set; } = string.Empty;
public DateTime CreationDate { get; set; }
public DateTime UpdatingDate { get; set; }
[Timestamp]
public byte[] TimeStamp { get; set; }
}
@@MilanJovanovicTech why not register the interceptor as a scoped service? Is there some underlying reason that we shouldn't?
Edit: We cannot inject into an interceptor as it prevents us from scaffolding migrations, so using a scoped service was pointless.
I did find out however that the solution's a lot easier than I thought.
The DBContext has a GetService method that will expose the service from the DB Context's scope.
Thanks Milan for your awesome video 👍
Is there a relation between Notification "e.g: like what SignalR do" and CQRS?
if yes, I hope you explain it in one of your upcoming videos
if no, I hope you also explain how to use it
No, it's just the name that MediatR uses
Hi Milan,
Thanks for this.
is their any major performance impact if we follow this method for auditing or any other better way ?
No, you won't notice anything as it's all happening in-memory
Would this slow down your performance of the application?
Nope
Nice video. Why choose DateTime instead of DateTimeOffset?
I store UTC time, so it doesn't matter to me
Sorry I couldnt get how you saved this audit in database, if you saved for exemple “old values” and “new values”
Just introduce new tables
Is It possible to recaive link to github for that code?
I share the code with my Patreons only, at the moment.
I know this is an old video but please respond..
Does it cost performance. Because now the saveChanges method goes through interceptor which will slow down the performance, right?
Well, did you try measuring this?
@@MilanJovanovicTech not yet, I just wanted to know directly..
I might measure later.. but I haven't yet
Hello @Milan i came here from your linkedin profile..
I want to know what is best way for audit trail logging when you are using .net core.
I have huge logging mechanism. But want to know best practices and tools for same.
What do you want to audit log?
I thought you are tracking who made what changes with the dates.
Like when user add or delete anything entities get audited along with user info
That would be significantly more complex. We could use temporal tables, or manually create history tables.
nice, thank you
No problem!
How would one pass a username to this interceptor so that a name can be audited
You can inject an IServiceProvider, create a scope, and resolve IHttpContextAccessor.
@@MilanJovanovicTech But if you work on a clean architecture type of project and this interceptor will probably be in the infrastructure (persistence) layer, then how do you do in this case ? Assuming that IHttpContextAccessor should only be injected in application or presentation layer
@@shkelqimhaxha3985 I usually use this approach: in asp.net middleware create some sort of user context and fill it with data you need. Then store it within some service in DI (you will definitely need AsyncLocal or something like this to do that) and inject it where required
@@MilanJovanovicTech will there be a performance cost when creating a scope and resolving services?
Great thanks a lot
Most welcome
I see .net video, I like
🔥🔥🔥
great video! funny note, it looks like you edited out all of your blinks lmao
I don't blink, that's why 🤣
VS theme please?
VS dark theme + ReSharper syntax highlighting
bro is the ms docs
Is that good or bad? Can't tell
Sorry I don't consider this Auditing, it is only tracking the last modified date/time not all of the times it's modified and it's not tracking what has modified and the values of the before and after the modification. Also while you talked about keeping track of who created/modified the record you didn't give an example of that only the date/time
You can extend the example, since the ChangeTracker gives you the CurrentValue and OriginalValue of the entity to set the before/after state in the database. Or we could explore temporal tables.
bro please blink
I ended up rate limiting my blinks. Sucks :(
@@MilanJovanovicTech might want to invest in a better blink server if you’re hitting max blinks
Why using « entre.Proprety.(e => e.dummyField).CurrentValue » ? I never really think about and always used « entry.Entity.dummyField ». Does it make any difference ?
I turn on nullable-reference types feature, so I get a compile error if I don't use '?'