What you want to solve is synchronizing two models: You have the in-memory model and the database model. Just propagating each change into the database, as it happens, is too expensive. Also, you want to be able to handle errors (i.e. roll back your model to a consistent state). To be able to do this, you must have a way to say "now, it's consistent".
The current solution is to start a transaction in the database when the model is known to be consistent (usually before you start making changes) and then do all the changes in your in-memory mode, map them somehow to the database, update the database and then commit the transaction.
While this sounds simple, OO programming actively gets in the way. We hide model operations deep in the call structure, we try really hard for users of a piece of code not to know what the code actually does. In a perfect world, your development tools should unroll all the code an operation needs into a single method/function, wrap that in a transaction and be done with it.
This doesn't work. Instead, we decided to introduce a global variable: the session. Which is bad, and we're ashamed of it, so we try to hide this fact but session is global - per operation. Now you need a way to attach the session to the operation. You can say "all code which gets executed in the current thread is one operation". If you do, the natural solution is to make the session global per thread.
Or you have some token. Then you will attach the session to the token and pass that around.
But the fundamental problem is and always was: How to attach a session to a single operation on the model. The hard parts are to know when an operation starts, when it ends and how to handle errors.
For web applications, using a request is the natural way to define an operation: Everything that happens during a request is considered a single step. The app doesn't really keep the model in memory; everything is forgotten at the end of the request and loaded again from the database when the next request comes in. Slow but manageable.
Desktop application are a completely different kind of beast. They usually keep the whole model in memory all the time. We don't persist changes unless the user asks for it (when she "saves" her work) because that would be too slow and, since there is nothing like a request, there is no simple, automatic way to define an "operation".
The idea to attach an operation to an event is good. Only, in desktop apps, you can have multiple threads which communicate with events and now, you need a way to mark all events as "they belong to the operation started with event X received long ago". Events are usually small, immutable pieces of data, so you can't attach your session to them. But you need some kind of token to mark all events which belong together. Which is why most desktop apps either work with a server (which again works like a web app) and without a big in-memory model or they don't use a database but save their model in a custom format (think Office).