Let's say you have a UsersService, a TenantsService, and a TenantUserRolesService.
Each of these services has a "create" method to create their entity and has the same DbContext dependency injected, except for UsersService which has the Identity UserManager class injected.
Your goal is to provide an API endpoint that lets a new tenant register which creates a User, a Tenant, and a TenantUserRole entity. TenantUserRole is created last and is an associative entity with foreign keys to the newly created User, the newly created Tenant, and the "Owner" Role.
What's the best way to create an atomic transaction between these 3 services, so that if a single create fails, everything is rolled back and the DB is not in a broken state where there are tenants with no users or users with no tenants, etc.
I have looked at: https://docs.microsoft.com/en-us/ef/core/saving/transactions, but I can't figure out if there is a way to use those methods between different service classes.
The only way I can think of is to take an imperative approach and manually check the result of each create, and if there is an error then use the same entity service to delete the recently created entity.
I'm hoping there is someone who is a bit more knowledgeable on the subject that can point me in the right direction.
If your transaction extend across service boundaries that is so called Transactional integration antipattern.
One way to coupe with that issue is to use external orchestration service (for example Camunda). It's responsibility would be to handle business flow and do some compensation actions on failure. It wouldn't be totally "transactional" - ACID properties would be achieved by compensations.
You can create that service by yourself. It's responsibility would be to manage that specific workflow.