Want to master Clean Architecture? Go here: bit.ly/3PupkOJ Want to unlock Modular Monoliths? Go here: bit.ly/3SXlzSt
@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 Жыл бұрын
Thanks for pointing that out, very important
@mohammadumar4432 ай бұрын
@@MilanJovanovicTech above video is part of which course or youtube series ? I want complete playlist.
@vprix200011 ай бұрын
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. 🤦♂
@MilanJovanovicTech11 ай бұрын
Not entirely sure what you mean? 🤔
@vprix200011 ай бұрын
@@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).
@bilalmehrban2 жыл бұрын
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 :)
@MilanJovanovicTech2 жыл бұрын
Stacking interceptors is a nice approach, to be honest.
@pilotboba2 жыл бұрын
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.
@MilanJovanovicTech2 жыл бұрын
Absolutely Bob, you are spot on with Temporal Tables/Event Sourcing if we need more details about _what_ is changed.
@CRBarchager2 жыл бұрын
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?
@MilanJovanovicTech2 жыл бұрын
Editing magic 😅
@KenzoArts11 ай бұрын
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
@MilanJovanovicTech11 ай бұрын
More memory usage, a bit more difficult to query? 🤔 Less complicated to set up.
@androidsavior Жыл бұрын
Did you share the full code of this project ? I want to learn clean arc. From it
@MilanJovanovicTech Жыл бұрын
Yes, on Patreon
@jolambrichts2 жыл бұрын
Thanks, nice video. I think VS2022 has also the possibility to cleanup unused namespaces on file save
@MilanJovanovicTech2 жыл бұрын
Could be, I didn't hear of that before. I tend to use default settings in general.
@thematthewyoung2 жыл бұрын
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.
@MilanJovanovicTech2 жыл бұрын
That's coming from ReSharper mostly. I use the R# Dark theme
@thematthewyoung2 жыл бұрын
@@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!
@MilanJovanovicTech2 жыл бұрын
@@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.
@kodindoyannick53288 ай бұрын
Thank you for the tutorial, very useful.
@MilanJovanovicTech8 ай бұрын
Glad it was helpful!
@elpe212 жыл бұрын
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.
@MilanJovanovicTech2 жыл бұрын
The properties have a private setter, so that would't work with my implementation
@321zipzapzoom9 ай бұрын
Hi,@@MilanJovanovicTech I able to use -Entity' Property la tell what is difference using auditableEntity.Property(x => x and auditableEntity.Entity. Thanks
@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 Жыл бұрын
That's an entirely different thing, you need some sort of history pattern. Check out temporal tables with SQL Server
@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.
@mladenstankovic24282 жыл бұрын
Opinions on using this approach vs having DB triggers for Insert/Update/Delete operations?
@MilanJovanovicTech2 жыл бұрын
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.
@johncerpa37822 жыл бұрын
Excellent, thank you
@MilanJovanovicTech2 жыл бұрын
Makes me happy you found it useful 😁
@sergiom.9542 жыл бұрын
Very useful video, thanks very much!
@MilanJovanovicTech2 жыл бұрын
I'm glad you liked it. Did you have to build something similar before?
@shkelqimhaxha39852 жыл бұрын
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 ?
@MilanJovanovicTech2 жыл бұрын
You can always traverse down the navigation properties using the EntityEntry. But do you want to audit navigation properties if they weren't changed?
@shkelqimhaxha39852 жыл бұрын
@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
@pilotboba2 жыл бұрын
@@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.
@ruandias625711 ай бұрын
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 Жыл бұрын
Good video, many thanks
@MilanJovanovicTech Жыл бұрын
Glad you enjoyed it
@rktmimob2 жыл бұрын
Hi Milan, Thanks for this. is their any major performance impact if we follow this method for auditing or any other better way ?
@MilanJovanovicTech2 жыл бұрын
No, you won't notice anything as it's all happening in-memory
@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 Жыл бұрын
Separate columns, same table
@jmvd_uy8639 Жыл бұрын
@@MilanJovanovicTech thank you so much!
@Tamer_Ali2 жыл бұрын
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
@MilanJovanovicTech2 жыл бұрын
No, it's just the name that MediatR uses
@gerarduab9960 Жыл бұрын
Can the interceptor have a Principal in orher to obtain curren claims of user?
@MilanJovanovicTech Жыл бұрын
Yes, you can probably do that with IHttpContextAccessor. Note that the HttpContext will be null for server-only scenarios.
@rahulanrajasekaram43667 ай бұрын
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 :(
@MilanJovanovicTech7 ай бұрын
IHttpContextAccessor is a singleton. You can inject it without a problem. The question is will the HttpContext property be null or not :)
@rubiglam8 ай бұрын
Nice video. Why choose DateTime instead of DateTimeOffset?
@MilanJovanovicTech8 ай бұрын
I store UTC time, so it doesn't matter to me
@birukayalew38622 жыл бұрын
simple but useful 👍
@MilanJovanovicTech2 жыл бұрын
Glad you liked it!
@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Ай бұрын
Well, did you try measuring this?
@sajadmalik9097Ай бұрын
@@MilanJovanovicTech not yet, I just wanted to know directly.. I might measure later.. but I haven't yet
@baolee46222 жыл бұрын
I can do all this in the context file without register any services. Which one is better?
@MilanJovanovicTech2 жыл бұрын
Both are fine
@hemantpanchal80879 ай бұрын
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.
@MilanJovanovicTech9 ай бұрын
What do you want to audit log?
@022442 жыл бұрын
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.
@MilanJovanovicTech2 жыл бұрын
How do you disable any middleware? You introduce a switch of some sorts that you can control, and turn on/off as you wish.
@dhmilmile12 жыл бұрын
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.
@microtech24482 ай бұрын
How do you recommend to pass logged in user id to log who added/ changed the record ?
@MilanJovanovicTech2 ай бұрын
- 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
@microtech24482 ай бұрын
@@MilanJovanovicTech Hmm that's what I thought. Thanks for sharing your views, we seem on the same page 🙂
@novaploca20802 жыл бұрын
Using this implementation only last modified date will be written in db?
@MilanJovanovicTech2 жыл бұрын
Yes, if I understood your question correctly.
@hamzehhanandeh36477 ай бұрын
nice, thank you
@MilanJovanovicTech7 ай бұрын
No problem!
@anonim9783 Жыл бұрын
How would you update in this approach if the interface has a user update field?
@MilanJovanovicTech Жыл бұрын
Find a way to inject the current user ID and save that
@anonim9783 Жыл бұрын
@@MilanJovanovicTech Yep exactly , I was wondering In clean architecture 'domain' not have knowledge about currentUser, or embed this logic somewhere else
@danielgoldberg7473 Жыл бұрын
great video! funny note, it looks like you edited out all of your blinks lmao
@MilanJovanovicTech Жыл бұрын
I don't blink, that's why 🤣
@farhad_mirshekar Жыл бұрын
Great thanks a lot
@MilanJovanovicTech Жыл бұрын
Most welcome
@unskeptable2 жыл бұрын
I see .net video, I like
@MilanJovanovicTech2 жыл бұрын
🔥🔥🔥
@kondziossj32 жыл бұрын
Is It possible to recaive link to github for that code?
@MilanJovanovicTech2 жыл бұрын
I share the code with my Patreons only, at the moment.
@sunilanthony172 жыл бұрын
Would this slow down your performance of the application?
@MilanJovanovicTech2 жыл бұрын
Nope
@kuba890892 жыл бұрын
Can you share a sample project with good DDD/CQRS pratices?
@gokmeneskin2 жыл бұрын
Can you share this project onGitHub?
@MilanJovanovicTech2 жыл бұрын
github.com/m-jovanovic/event-reminder
@fernandocalmet2 жыл бұрын
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?
@MilanJovanovicTech2 жыл бұрын
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?
@fernandocalmet2 жыл бұрын
@@MilanJovanovicTech Interesting, I'm going to try it and I'll tell you later how it went, thanks Milan😃
@abdulnaserramadan372 жыл бұрын
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; } }
@purplepiranha2 ай бұрын
@@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 Жыл бұрын
Sorry I couldnt get how you saved this audit in database, if you saved for exemple “old values” and “new values”
@MilanJovanovicTech Жыл бұрын
Just introduce new tables
@aah134-K2 жыл бұрын
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
@MilanJovanovicTech2 жыл бұрын
That would be significantly more complex. We could use temporal tables, or manually create history tables.
@davearkley70142 жыл бұрын
How would one pass a username to this interceptor so that a name can be audited
@MilanJovanovicTech2 жыл бұрын
You can inject an IServiceProvider, create a scope, and resolve IHttpContextAccessor.
@shkelqimhaxha39852 жыл бұрын
@@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
@qwertyqwerty70722 жыл бұрын
@@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
@rahulanrajasekaram43667 ай бұрын
@@MilanJovanovicTech will there be a performance cost when creating a scope and resolving services?
@coolfyb Жыл бұрын
VS theme please?
@MilanJovanovicTech Жыл бұрын
VS dark theme + ReSharper syntax highlighting
@nickadams236110 ай бұрын
bro is the ms docs
@MilanJovanovicTech10 ай бұрын
Is that good or bad? Can't tell
@ChrisWard744 ай бұрын
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
@MilanJovanovicTech4 ай бұрын
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 Жыл бұрын
bro please blink
@MilanJovanovicTech Жыл бұрын
I ended up rate limiting my blinks. Sucks :(
@sebastianwhiffen Жыл бұрын
@@MilanJovanovicTech might want to invest in a better blink server if you’re hitting max blinks
@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 Жыл бұрын
I turn on nullable-reference types feature, so I get a compile error if I don't use '?'