How To Track Entity Changes With EF Core | Audit Logging

  Рет қаралды 28,391

Milan Jovanović

Milan Jovanović

Күн бұрын

Пікірлер: 117
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
Want to master Clean Architecture? Go here: bit.ly/3PupkOJ Want to unlock Modular Monoliths? Go here: bit.ly/3SXlzSt
@williamliao4399
@williamliao4399 Жыл бұрын
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
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Thanks for pointing that out, very important
@mohammadumar443
@mohammadumar443 2 ай бұрын
@@MilanJovanovicTech above video is part of which course or youtube series ? I want complete playlist.
@vprix2000
@vprix2000 11 ай бұрын
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
@MilanJovanovicTech 11 ай бұрын
Not entirely sure what you mean? 🤔
@vprix2000
@vprix2000 11 ай бұрын
@@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).
@bilalmehrban
@bilalmehrban 2 жыл бұрын
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 :)
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
Stacking interceptors is a nice approach, to be honest.
@pilotboba
@pilotboba 2 жыл бұрын
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.
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
Absolutely Bob, you are spot on with Temporal Tables/Event Sourcing if we need more details about _what_ is changed.
@CRBarchager
@CRBarchager 2 жыл бұрын
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?
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
Editing magic 😅
@KenzoArts
@KenzoArts 11 ай бұрын
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
@MilanJovanovicTech
@MilanJovanovicTech 11 ай бұрын
More memory usage, a bit more difficult to query? 🤔 Less complicated to set up.
@androidsavior
@androidsavior Жыл бұрын
Did you share the full code of this project ? I want to learn clean arc. From it
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Yes, on Patreon
@jolambrichts
@jolambrichts 2 жыл бұрын
Thanks, nice video. I think VS2022 has also the possibility to cleanup unused namespaces on file save
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
Could be, I didn't hear of that before. I tend to use default settings in general.
@thematthewyoung
@thematthewyoung 2 жыл бұрын
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
@MilanJovanovicTech 2 жыл бұрын
That's coming from ReSharper mostly. I use the R# Dark theme
@thematthewyoung
@thematthewyoung 2 жыл бұрын
@@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!
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
@@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.
@kodindoyannick5328
@kodindoyannick5328 8 ай бұрын
Thank you for the tutorial, very useful.
@MilanJovanovicTech
@MilanJovanovicTech 8 ай бұрын
Glad it was helpful!
@elpe21
@elpe21 2 жыл бұрын
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.
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
The properties have a private setter, so that would't work with my implementation
@321zipzapzoom
@321zipzapzoom 9 ай бұрын
Hi,@@MilanJovanovicTech I able to use -Entity' Property la tell what is difference using auditableEntity.Property(x => x and auditableEntity.Entity. Thanks
@emwagner
@emwagner Жыл бұрын
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
@MilanJovanovicTech Жыл бұрын
That's an entirely different thing, you need some sort of history pattern. Check out temporal tables with SQL Server
@emwagner
@emwagner Жыл бұрын
@@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.
@mladenstankovic2428
@mladenstankovic2428 2 жыл бұрын
Opinions on using this approach vs having DB triggers for Insert/Update/Delete operations?
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
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.
@johncerpa3782
@johncerpa3782 2 жыл бұрын
Excellent, thank you
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
Makes me happy you found it useful 😁
@sergiom.954
@sergiom.954 2 жыл бұрын
Very useful video, thanks very much!
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
I'm glad you liked it. Did you have to build something similar before?
@shkelqimhaxha3985
@shkelqimhaxha3985 2 жыл бұрын
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 ?
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
You can always traverse down the navigation properties using the EntityEntry. But do you want to audit navigation properties if they weren't changed?
@shkelqimhaxha3985
@shkelqimhaxha3985 2 жыл бұрын
​​ @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
@pilotboba
@pilotboba 2 жыл бұрын
@@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.
@ruandias6257
@ruandias6257 11 ай бұрын
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
@techfamily401
@techfamily401 Жыл бұрын
Good video, many thanks
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Glad you enjoyed it
@rktmimob
@rktmimob 2 жыл бұрын
Hi Milan, Thanks for this. is their any major performance impact if we follow this method for auditing or any other better way ?
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
No, you won't notice anything as it's all happening in-memory
@jmvd_uy8639
@jmvd_uy8639 Жыл бұрын
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?
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Separate columns, same table
@jmvd_uy8639
@jmvd_uy8639 Жыл бұрын
@@MilanJovanovicTech thank you so much!
@Tamer_Ali
@Tamer_Ali 2 жыл бұрын
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
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
No, it's just the name that MediatR uses
@gerarduab9960
@gerarduab9960 Жыл бұрын
Can the interceptor have a Principal in orher to obtain curren claims of user?
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Yes, you can probably do that with IHttpContextAccessor. Note that the HttpContext will be null for server-only scenarios.
@rahulanrajasekaram4366
@rahulanrajasekaram4366 7 ай бұрын
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 :(
@MilanJovanovicTech
@MilanJovanovicTech 7 ай бұрын
IHttpContextAccessor is a singleton. You can inject it without a problem. The question is will the HttpContext property be null or not :)
@rubiglam
@rubiglam 8 ай бұрын
Nice video. Why choose DateTime instead of DateTimeOffset?
@MilanJovanovicTech
@MilanJovanovicTech 8 ай бұрын
I store UTC time, so it doesn't matter to me
@birukayalew3862
@birukayalew3862 2 жыл бұрын
simple but useful 👍
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
Glad you liked it!
@sajadmalik9097
@sajadmalik9097 Ай бұрын
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?
@MilanJovanovicTech
@MilanJovanovicTech Ай бұрын
Well, did you try measuring this?
@sajadmalik9097
@sajadmalik9097 Ай бұрын
@@MilanJovanovicTech not yet, I just wanted to know directly.. I might measure later.. but I haven't yet
@baolee4622
@baolee4622 2 жыл бұрын
I can do all this in the context file without register any services. Which one is better?
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
Both are fine
@hemantpanchal8087
@hemantpanchal8087 9 ай бұрын
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
@MilanJovanovicTech 9 ай бұрын
What do you want to audit log?
@02244
@02244 2 жыл бұрын
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.
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
How do you disable any middleware? You introduce a switch of some sorts that you can control, and turn on/off as you wish.
@dhmilmile1
@dhmilmile1 2 жыл бұрын
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.
@microtech2448
@microtech2448 2 ай бұрын
How do you recommend to pass logged in user id to log who added/ changed the record ?
@MilanJovanovicTech
@MilanJovanovicTech 2 ай бұрын
- 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
@microtech2448
@microtech2448 2 ай бұрын
@@MilanJovanovicTech Hmm that's what I thought. Thanks for sharing your views, we seem on the same page 🙂
@novaploca2080
@novaploca2080 2 жыл бұрын
Using this implementation only last modified date will be written in db?
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
Yes, if I understood your question correctly.
@hamzehhanandeh3647
@hamzehhanandeh3647 7 ай бұрын
nice, thank you
@MilanJovanovicTech
@MilanJovanovicTech 7 ай бұрын
No problem!
@anonim9783
@anonim9783 Жыл бұрын
How would you update in this approach if the interface has a user update field?
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Find a way to inject the current user ID and save that
@anonim9783
@anonim9783 Жыл бұрын
@@MilanJovanovicTech Yep exactly , I was wondering In clean architecture 'domain' not have knowledge about currentUser, or embed this logic somewhere else
@danielgoldberg7473
@danielgoldberg7473 Жыл бұрын
great video! funny note, it looks like you edited out all of your blinks lmao
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
I don't blink, that's why 🤣
@farhad_mirshekar
@farhad_mirshekar Жыл бұрын
Great thanks a lot
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Most welcome
@unskeptable
@unskeptable 2 жыл бұрын
I see .net video, I like
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
🔥🔥🔥
@kondziossj3
@kondziossj3 2 жыл бұрын
Is It possible to recaive link to github for that code?
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
I share the code with my Patreons only, at the moment.
@sunilanthony17
@sunilanthony17 2 жыл бұрын
Would this slow down your performance of the application?
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
Nope
@kuba89089
@kuba89089 2 жыл бұрын
Can you share a sample project with good DDD/CQRS pratices?
@gokmeneskin
@gokmeneskin 2 жыл бұрын
Can you share this project onGitHub?
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
github.com/m-jovanovic/event-reminder
@fernandocalmet
@fernandocalmet 2 жыл бұрын
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?
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
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?
@fernandocalmet
@fernandocalmet 2 жыл бұрын
@@MilanJovanovicTech Interesting, I'm going to try it and I'll tell you later how it went, thanks Milan😃
@abdulnaserramadan37
@abdulnaserramadan37 2 жыл бұрын
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; } }
@purplepiranha
@purplepiranha 2 ай бұрын
@@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.
@JoaoRoberto-mm4qj
@JoaoRoberto-mm4qj Жыл бұрын
Sorry I couldnt get how you saved this audit in database, if you saved for exemple “old values” and “new values”
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Just introduce new tables
@aah134-K
@aah134-K 2 жыл бұрын
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
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
That would be significantly more complex. We could use temporal tables, or manually create history tables.
@davearkley7014
@davearkley7014 2 жыл бұрын
How would one pass a username to this interceptor so that a name can be audited
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
You can inject an IServiceProvider, create a scope, and resolve IHttpContextAccessor.
@shkelqimhaxha3985
@shkelqimhaxha3985 2 жыл бұрын
@@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
@qwertyqwerty7072
@qwertyqwerty7072 2 жыл бұрын
@@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
@rahulanrajasekaram4366
@rahulanrajasekaram4366 7 ай бұрын
@@MilanJovanovicTech will there be a performance cost when creating a scope and resolving services?
@coolfyb
@coolfyb Жыл бұрын
VS theme please?
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
VS dark theme + ReSharper syntax highlighting
@nickadams2361
@nickadams2361 10 ай бұрын
bro is the ms docs
@MilanJovanovicTech
@MilanJovanovicTech 10 ай бұрын
Is that good or bad? Can't tell
@ChrisWard74
@ChrisWard74 4 ай бұрын
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
@MilanJovanovicTech
@MilanJovanovicTech 4 ай бұрын
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.
@sebastianwhiffen
@sebastianwhiffen Жыл бұрын
bro please blink
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
I ended up rate limiting my blinks. Sucks :(
@sebastianwhiffen
@sebastianwhiffen Жыл бұрын
@@MilanJovanovicTech might want to invest in a better blink server if you’re hitting max blinks
@porcinetdu6944
@porcinetdu6944 Жыл бұрын
Why using « entre.Proprety.(e => e.dummyField).CurrentValue » ? I never really think about and always used « entry.Entity.dummyField ». Does it make any difference ?
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
I turn on nullable-reference types feature, so I get a compile error if I don't use '?'
Entity Framework 7 - Inheritance
13:19
Coding Tutorials
Рет қаралды 3,8 М.
40 Years Of Software Engineering Experience In 19 Minutes
19:10
Continuous Delivery
Рет қаралды 92 М.
龟兔赛跑:好可爱的小乌龟#short #angel #clown
01:00
Super Beauty team
Рет қаралды 88 МЛН
Я сделала самое маленькое в мире мороженое!
00:43
А что бы ты сделал? @LimbLossBoss
00:17
История одного вокалиста
Рет қаралды 11 МЛН
Tracking Data Changes in C# .NET
20:42
Hassan Habib
Рет қаралды 50 М.
Using EF Core’s Coolest Feature to Audit in .NET
26:06
Nick Chapsas
Рет қаралды 37 М.
One LINQ Extension Method You NEED To Know For Cleaner EF Core Queries
11:08
A Crash Course in Audit Logs
11:00
DevSecCon
Рет қаралды 17 М.
Message Passing - The Visitor Design Pattern
12:59
Software Design
Рет қаралды 506
Stop Using FirstOrDefault in .NET! | Code Cop #021
12:54
Nick Chapsas
Рет қаралды 88 М.
How does EF Core keeps track of changes?
9:51
Hubert Mijalski
Рет қаралды 4,8 М.
Entity Framework Core 6 - Easy Audits with Temporal Tables
26:42
Programming with Felipe Gavilan
Рет қаралды 7 М.
Testing in .NET is About to Change
12:54
Nick Chapsas
Рет қаралды 77 М.
The Best Way To Add Audit Tables to Your Database
8:48
Database Star
Рет қаралды 7 М.
龟兔赛跑:好可爱的小乌龟#short #angel #clown
01:00
Super Beauty team
Рет қаралды 88 МЛН