Using EF Core’s Coolest Feature to Audit in .NET

Поделиться
HTML-код
  • Опубликовано: 10 сен 2024
  • The first 200 get 30% off our new Git and GitHub Actions courses on Dometrain with code GIT30: dometrain.com/...
    Subscribe to my weekly newsletter: nickchapsas.com
    Become a Patreon and get special perks: / nickchapsas
    This video is sponsored by AWS. To get $25 free AWS credit, check out this link: aws.amazon.com...
    Hello, everybody. I'm Nick, and in this video, I will show you how you can add database auditing in your .NET applications using Entity Framework Core's interceptors feature.
    Workshops: bit.ly/nickwor...
    Don't forget to comment, like and subscribe :)
    Social Media:
    Follow me on GitHub: github.com/Elf...
    Follow me on Twitter: / nickchapsas
    Connect on LinkedIn: / nick-chapsas
    Keep coding merch: keepcoding.shop
    #csharp #dotnet

Комментарии • 101

  • @oleksii766
    @oleksii766 28 дней назад +34

    Why do we need this KeyedScoped? AuditInterceptor is already scoped as it was created inside scoped DbContext. It can just store this list as part of its internal state.

    • @DominikTHER
      @DominikTHER 26 дней назад +4

      I have the same question

  • @JakobStrasser
    @JakobStrasser 29 дней назад +34

    Would love a follow up about what to use to store the audit.

    • @MintoIssac
      @MintoIssac 27 дней назад

      In my organization we use a cosmos/mongo db with a retention period of 6 month.

  • @num1nex337
    @num1nex337 29 дней назад +40

    I don't know if it was mentioned in this video, but this won't work with the new ExecuteUpdate method from EF Core, you have to use different kind of interceptor and do quite a bit of expressions manipulation.

    • @Dubzer
      @Dubzer 29 дней назад +2

      such things are the reason why I would prefer to audit explicitly

    • @diegoronkkomaki6858
      @diegoronkkomaki6858 29 дней назад +2

      Would temporal tables fix this? They are natively supported by EF.

    • @num1nex337
      @num1nex337 29 дней назад

      @@diegoronkkomaki6858 Temporal tables are part of your database provider auditing logic, so they aren't affected at all by EF Core interceptors. The problem with ExecuteUpdate/Delete methods is that they don't hydrate ChangeTracker, which makes sense since they generate raw sql from just an expression tree. You can solve this by using a QueryExpressionInterceptor, if you'd google for `How to update shadow properties with Entity Framework Core ExecuteUpdate method?` you'll find a stackoverflow post with answer that has a code sample, how one could achieve it.

    • @shahzaibhassan2777
      @shahzaibhassan2777 28 дней назад

      Can you tell how can we do this using some expression manipulation? Cuz, as far as i know if save changes is not called in the first place how can we manipulate what going into database?

    • @shahzaibhassan2777
      @shahzaibhassan2777 28 дней назад

      ​​@@DubzerIn my opinion, this would be risky, as some other dev might miss some important bits. What do you think? It may result in, inconsistencies between entity to entity

  • @andersborum9267
    @andersborum9267 28 дней назад +4

    It's a good video for starters, but don't forget that proper auditing is often quite complex as each business' requirements are typically different. Using a messaging service to store your audits may be a good route, but the argument shouldn't be to preserve database resources; transactional messaging would require use of the outbox pattern (or similar), which takes up resources as well. Aside from that, an interesting take on a common requirement, and I highly recommend EF developers to learn more about the change tracking APIs.

  • @ericvruder
    @ericvruder 29 дней назад +10

    Great video, would love a separate topic on sanitizing/anonymizing data in EF, that would be sweat! Maybe how to create test data based on production data using EF core, in a safe and compliant way?

    • @JollyGiant19
      @JollyGiant19 29 дней назад

      Why not generate a new set of data using Bogus and seed the DB with that?

    • @ericvruder
      @ericvruder 28 дней назад

      @@JollyGiant19 If a customer is experiencing issues in production, I would like to pull his data, protect any pii and/or other sensitive data, and see if the issue is reproducible in the new database
      Edit:
      Also, would like to know how to anonymize data in auditing as well.

    • @rattrick1
      @rattrick1 27 дней назад +1

      ​@@ericvruderI've used the data masking feature in SQL Azure for this before

    • @ericvruder
      @ericvruder 27 дней назад +1

      @@rattrick1 Looks interesting, have a user that doesn't have 'unmasking' permissions and get that to read data into a test database. Thanks for the tip!

  • @frankbanini8884
    @frankbanini8884 26 дней назад +1

    This is on point. I have learned something new today. Thanks, Nick

  • @keyser456
    @keyser456 29 дней назад +3

    That Database window and Sessions (when you connect to a db) in Rider is amazeballs. From my experience so far, its Intellisense blows SSMS and PGAdmin (for Postgres) out of the water. If they could get multiple result sets showing on the same tab/screen (or get a Dump like LINQPad's), it would be legendary.

  • @tymurgubayev4840
    @tymurgubayev4840 28 дней назад

    I had worked with something like this entirely setup using DB triggers (it was an Oracle DB, and the triggers written in its PL/SQL). For affected tables, on any operation changing a row, a new version of the row would be copied to another table (plus some metadata, timestamp etc).
    This creates a timeline of changes for those tables, and you can easily query their state at it was at any point in time (changing table schema becomes a bit tricky though).
    I found this ability to look back in time *across multiple tables* extremely useful.

  • @Tony-dp1rl
    @Tony-dp1rl 28 дней назад +1

    It usually works out to perform better to use Update/Insert Triggers in the database for this sort of tracking when writing your audit to SQL as well. Where it is useful, is when your audit entries are going elsewhere, like log files or a NoSQL database etc. Sending an entirely new transaction to SQL with the same data it already has access to in the trigger is silly.

  • @jernmon
    @jernmon 29 дней назад +4

    7:14 Couldn't you inject the interceptor in the DbContext constructor and pass that in the OnConfiguring method? That way you avoid injecting dependencies to the DbContext that it won't actually use

  • @franklores
    @franklores 29 дней назад +1

    I’ve been waiting for this topic in your channel.

  • @GiovanniOrlandoi7
    @GiovanniOrlandoi7 28 дней назад

    Great video, Nick! Would love to see a follow up to this showing your recommendation on how/where to store the data.

  • @impeRAtoR28161621
    @impeRAtoR28161621 29 дней назад +12

    What about history tables, (temporal) whose support was added in Ef6

  • @PankajNikam
    @PankajNikam 28 дней назад +3

    Temporal tables support is way better than manual things to manage. It's nice that it is also supported in EF Core.

  • @Aweklin
    @Aweklin 29 дней назад

    Absolutely helpful!
    Many thanks @Nick.

  • @marcobaccaro
    @marcobaccaro 19 дней назад

    It's okay based on the options and different ways to do things. However, use your database audit and CDC features to keep your application agnostic of data and not compromise the performance with extra db transactions due to manual audits.

  • @weicco
    @weicco 27 дней назад

    I've actually implemented something like this for myself. I even added blockchain hashing so I can always validate the audit trail per tenant basis. But my implementation does not work as interceptor, which is really cool idea. I'll have to try if I can bake my process inside SavingChanges method.

  • @markhenderson5283
    @markhenderson5283 28 дней назад

    I implemented an interceptor a while back to set some strange db credentials to insert or update certain tables (it was in a PCI environment and even then this was strange and unnecessary). For that to work properly, I had to set and unset them every time EF inserted or updated anything in the database. I had to learn what SavingChanges and SavedChanges did the hard way but once I got it working it worked exceptionally well.

  • @OscarAgreda
    @OscarAgreda 23 дня назад

    Thanks for breaking down EF Core interceptors for auditing! Curious if you've considered using Kafka instead of SNS/SQS for even more scalable event-driven auditing, especially in high-throughput systems.

  • @olucasromero
    @olucasromero 28 дней назад

    Using a compiler feature which I personally say has a library level or source generator use sounds like a workaround hacking. Also how it is used is sooooo much more complicated than should be done for the developer.
    EF Core is a bigger package should make it by themselves

  • @noelfrancisco5778
    @noelfrancisco5778 27 дней назад

    Great topic! Thanks.

  • @timur2887
    @timur2887 28 дней назад +1

    DB audit is better be done on server side instead of client as data may be changed not only by this particular application

  • @user-hg6ju7eu9s
    @user-hg6ju7eu9s 5 дней назад

    Hello,
    I assumed I could find the source code link as you mentioned in the comment, but it’s not there. I also subscribed to your Patreon to access the code, but I couldn't find it there either.

  • @liva236muzika
    @liva236muzika 28 дней назад +1

    Is there a way to use change tracker to figure out what was changed on each entity? E.g. you change customer name, but keep other properties the same - is there a way to figure that fact out using change tracker? Or can I only see the fact customer was updated and that's that?

    • @andersborum9267
      @andersborum9267 28 дней назад +1

      Yeah, all of that information is available from the ChangeTracker and you inspect the metadata of each property to determine it's state (and access any previous value, if applicable).

    • @liva236muzika
      @liva236muzika 28 дней назад

      @@andersborum9267 I looked previously, but I couldn't figure it out. Any links to relevant properties or classes would be much appreciated.

    • @geraldmaale
      @geraldmaale 28 дней назад

      Iterate through `entry.Properties` and then, you can use `property.CurrentValue` for new records and `property.OriginalValue` for old records.

  • @szynkie6710
    @szynkie6710 28 дней назад

    It is possible to add a user and do not have an audit record, because the application may fail in/before savedChangesAsync, am I right?

  • @thanzeeljalaldeen
    @thanzeeljalaldeen 29 дней назад +1

    where is the github link you said its free please?

  • @jameshancock
    @jameshancock 28 дней назад

    Would love to see your thoughts on capturing audit logs in something that is highly performant and can be reported on well.

  • @julienraillard3567
    @julienraillard3567 28 дней назад +1

    I don't get the point for why using Keyedscopedscervice for the audit list, could you please explain it ? :)

    • @julienraillard3567
      @julienraillard3567 28 дней назад

      Oh i found the answer, totally forgot that previous Scoped method doesnt allow types like List where keyedscopedscervice do :D

    • @agoe431
      @agoe431 День назад

      @@julienraillard3567 where is the key "Audit" then used?

  • @grupomexcomer
    @grupomexcomer 28 дней назад

    Let’s say the application is living on premise without the access to any cloud. What would be the best target (storage) to save audits? Could it be a separate DB just for that?

  • @BlTemplar
    @BlTemplar 29 дней назад +8

    ExecuteUpdate/ExecuteDelete/raw sql make this approach questionable. You are locking yourself in using only EF DbContext for modifying data. If you really need audit, just do it explicitly.
    The better use-case for interceptors is adding hints to queries. For example you can use built-in EF TagWith for something like TagWith("FOR UPDATE") and add the interceptor to handle it. The interceptor can look for "-- FOR UPDATE" comment in the sql and modify the sql accordingly.

    • @diadetediotedio6918
      @diadetediotedio6918 29 дней назад

      I think you don't need universal approaches for absolutely everything, so this should be relatively fine if you constrain your codebase.

    • @elpe21
      @elpe21 25 дней назад

      @@diadetediotedio6918 I agree. On the other hand, If I'm not mistaken, you won't audit any migrations you might do with EF.

  • @AlexanderShumakov
    @AlexanderShumakov 29 дней назад

    Good and simple approach. But there should be some advanced way to handle audit logs in transactions or get DB generated values.

  • @LaRevelacion
    @LaRevelacion 29 дней назад +1

    Can these interceptors work per each set? I mean, for what I'm seeing I assume this interceptor will work globally, but I'm wondering if I can configure interceptors to have customized behavior for each set that I have, it maybe just for few that I care to intercept

    • @Scott_Stone
      @Scott_Stone 15 дней назад

      You saw him doing the logic based on type. From there you can build anything.

  • @3dnoob
    @3dnoob 29 дней назад +2

    Is there a better way to do handle it and consume it from the PG level? 😊 because I’m using ExecuteDelete/Update a lot.

    • @AlexanderShumakov
      @AlexanderShumakov 29 дней назад

      Probably you can try to build audit messages from WAL (write ahead logs). Still there is no common library and it would be custom code.

    • @3dnoob
      @3dnoob 27 дней назад

      @@AlexanderShumakov I believe WAL is disabled by default on CosmosDb for PG (Citus). I might try some other approaches.

    • @AlexanderShumakov
      @AlexanderShumakov 27 дней назад +1

      @@3dnoob That's true. Usually you don't need it and you should know why do you need Wal. That was just one of the hints beside of DB triggers 😁

  • @jcorrify
    @jcorrify 25 дней назад

    I work in healthcare and my organization needs to get a certification for our product. One key aspect is the auditing of read access for some sensible information. Would it be possible to use a different kind of interceptor to do the job?

  • @istovall2624
    @istovall2624 27 дней назад

    how would this work with the bulk update? idk but i would imagine that the change tracker doesnt do much when that's executed kind of like executing raw sql. but it would be cool. i need to test that!

  • @AlFasGD
    @AlFasGD 29 дней назад

    24:10 this PK looks more like a Primary Key than a Partition Key, since it's the table's PK

  • @lordicemaniac
    @lordicemaniac 28 дней назад

    if dbcontext is scoped, wouldn't be enough to make interceptor scoped too and just add field with the list of changes?
    Edit: oh, now i see that you registered interceptor with AddInterceptor(new AuditInterceptor...), would it be possible to register it with factory from ID?

  • @lordicemaniac
    @lordicemaniac 28 дней назад

    this was very interesting

  • @igorilyichyov6414
    @igorilyichyov6414 29 дней назад

    Great video!

  • @stoino1848
    @stoino1848 29 дней назад

    Great video. Would like to see a follow up on the error handling tho. I would say if you have audit logging in your APP it is mandatory. What happens if you cannot publish the audit log? Rollback? Retry till success (not a guarante)? Etc

    • @elpe21
      @elpe21 25 дней назад

      all depends :) If you have on-site app that can lose access to the Internet and you publish logs to some cloud-service then I would probably save them in a file and have a service that picks them up and send them for processing. Stopping the whole system because broadband is down would be silly.

  • @grupomexcomer
    @grupomexcomer 28 дней назад

    Where would be the right place to save those audits? S3 files?

  • @ThekillingGoku
    @ThekillingGoku 28 дней назад

    A Microsoft MVP sponsored by AWS alright. You'd have expected this to go the Azure route otherwise.
    Still, nothing wrong with showing a bit of the competition.
    As for the dumping ground for this, I guess considering the amount of data to be expected logging this granular you'd likely choose data lake for those logs rather than the RDBMS.
    Maybe a data warehouse if you want the queryability. Though if you're using Azure anyway, I think Log Analytics would fit the bill just fine for this sort of logging (though since that's got limited data retention, you'd likely need archiving still).

  • @tbgelectr0
    @tbgelectr0 23 дня назад

    But should not AuditEntries.Clear() also delete other users Audits stored savestates?

    • @acousticbrothers1491
      @acousticbrothers1491 13 дней назад +1

      it is clearing per user request scope which means each request scope will have its own AuditEntries list.

  • @alexlo5655
    @alexlo5655 28 дней назад

    Hi Nick,
    You mentioned that I could access the code for this video for free, but I can't seem to find the link.
    Is it necessary to be a Patreon member to access the code, or perhaps the link was accidentally omitted from the description?
    Thanks for clarifying!

    • @nickchapsas
      @nickchapsas  28 дней назад +1

      Hello. In my infinite wisdom I forgot to set up the code link. I will be doing that in the next couple of days. Sorry for that

  • @nothingisreal6345
    @nothingisreal6345 29 дней назад +1

    Ok it is sponsored by AWS. But sending a message for every save to the cloud and the receive it and write it back to the DB is not the cheapest solution. Why not simply have a background worker that you trigger and that does the saving in an async way (certainly some audit changes could get lost if this crashes).

    • @BienenChef
      @BienenChef 29 дней назад +3

      I think this was mostly done for demonstration purposes (to justify the sponsored video). I doubt Nick would use such a setup for logging a high volume of entity changes!

    • @NotACat20
      @NotACat20 29 дней назад +1

      Actually this not best solution. Best is create CRUD SP and do all work in SP. In such case you not need EF completely. This might sounds "oldy", but this really a way to go for big databases, when dba and performance and logic is really needed. Same things even better can be done via db triggers: don't understimate database changes audit which caused by maintenance/supporting divisions. The difference what data+audit is integrated in DB, and is atomic.
      Every solution have own applications, but power of linq is making dynamic queries. Outside of this power - it strictly worse than good old ways.

    • @nickchapsas
      @nickchapsas  29 дней назад +1

      Because with a background worker you can lose audit messages

    • @thelostrider1
      @thelostrider1 28 дней назад

      ​@nickchapsas Genuine question, how can you not lose messages there? You said the audit is not saved sync with the other data (which I guess it means, it's not saved in the same transaction to the database)?

    • @nothingisreal6345
      @nothingisreal6345 25 дней назад

      @@NotACat20 in sql Server this is build feature called change data capture. But that might not log all details IMHO. The best approach is too first research if the rdbms supports the required auditing out of the box and what the performance implications might be. But this video was about demoing the ef core event handling

  • @RootAndroidAndEthicalHacker
    @RootAndroidAndEthicalHacker 28 дней назад

    no link?

  • @GigAHerZ64
    @GigAHerZ64 28 дней назад +3

    Just use temporal table with author instead. You get all the history / snapshot features for free!

    • @robertmrobo8954
      @robertmrobo8954 22 часа назад +1

      Exactly what I went with. Very very less work.
      Or should I say 'no work' at all. Just added migrations.

  • @z0nx
    @z0nx 29 дней назад

    outbox pattern yes?

  • @IsaacOjeda
    @IsaacOjeda 27 дней назад

    Began with a simple task and ended up tackling SQS Queues.

    • @nickchapsas
      @nickchapsas  27 дней назад +3

      Auditing isn’t a simple task buddy

  • @Arcadenut1
    @Arcadenut1 28 дней назад

    And that's it!... X 20.. lol

  • @grupomexcomer
    @grupomexcomer 28 дней назад

    What IDE do you use?

  • @deliyevurdum
    @deliyevurdum 28 дней назад

    coolest feature vs 26 minutes long neverending video :)

  • @alissoncandatem1896
    @alissoncandatem1896 29 дней назад

    in our company we use nhibernate, so we are stuck with envers wich is not bad but the docs for it are so old and not updated at all :(

  • @troncek
    @troncek 28 дней назад +1

    We usually solve this by adding triggers on the DB level. Application shouldn't be aware that there is any auditing going on other than to return audit entries when needed.

    • @nickchapsas
      @nickchapsas  28 дней назад +1

      What if the database doesn’t support triggers? What if you’re working with multiple providers. The database shouldn’t be aware of auditing either

    • @robertmckee9272
      @robertmckee9272 28 дней назад

      Applications need to be aware of triggers when using EF Core on Sql Server because certain T-SQL statements don't work the same when triggers are present (unfortunately).

  • @MatinDevs
    @MatinDevs 28 дней назад

    Roses are red, violets are blue, C# is easy, you can learn too

  • @bitmanagent67
    @bitmanagent67 27 дней назад

    Typical .NET!!! Convoluted AF!!!