Activities of "enisn"

You can follow this documentation: https://abp.io/docs/latest/framework/infrastructure/event-bus/local#transaction-exception-behavior

It refers;

Event handlers are always executed in the same unit of work scope, that means in the same database transaction with the code that published the event. If an event handler throws an exception,

It doesn't support multiple databases or connection strings. Runs all your operations in the same unit of work. That means there is no single database transaction that depends on each other. When you face a problem they'll rollback separately according to your scenario. You may visit: https://abp.io/docs/latest/framework/architecture/domain-driven-design/unit-of-work#unit-of-work

If you use different repositories and DbContexts within the same Unit of Work (UOW), it will still roll back both while using LocalEventBus or directly coding in the same method with 2 different repositories or DbContext instances. This is a feature of Unit of Work and not a native database transaction

so you say even if it is a different database, unitofwork is going to rollback the transaction?

Unit Of Work is database agnostic, it doesn't know which database are you using. If one of your repositories throws an exception while committing it'll try rollback all.

  1. When an exception occurs during the unit of work:
    • The RollbackAsync() method is called
    • It attempts to rollback ALL registered database APIs and transaction APIs
    • Each rollback operation is wrapped in a try-catch to ensure all DBs get rollback attempts
  2. For multiple DbContexts:
    • Each DbContext is registered as a separate database API
    • They can be connected to different databases
    • When one fails, the UnitOfWork will attempt to rollback all of them So to answer your question: Yes, both DbContexts will be rolled back even if they're connected to different databases. This is by design to maintain data consistency across your entire business transaction.

Here's a sequence of what happens:

1. DbContext1 (Database1) - Succeeds
2. DbContext2 (Database2) - Throws Exception
3. UnitOfWork catches exception
4. UnitOfWork calls RollbackAllAsync()
5. Both DbContext1 and DbContext2 get rollback attempts

This behavior ensures atomicity across multiple databases - either all operations succeed, or none of them do. This is particularly important for distributed transactions where you need to maintain consistency across different data stores.

Hi

There no such feature neither in ABP Framework and Entity Framework Core.

Normally, transaction between different databases is not possible but some specific cases. As far as I know, it can be possible in SqlServer somehow but making it stable for all scenarios is another tough topic. You can go with this implementation but you have to implement it by yourself. https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/distributed-transactions


We suggest using EventBus for this kind of scenarios. You can publish an event to update the other database and go on.

If you use LocalEventBus, it will be a local transaction, and whenever an exception occurs in the consumer side, the transaction from the publisher side will be rolled back.

If you use DistributedEventBus, it will be a distributed transaction, and whenever an exception occurs in the consumer side, it'll be retried until it becomes successful. You should handle this in your consumer side and make it eventually consistent.

Note:

Make sure you're using Outbox / Inbox for Transactional Events feature for the distributed event bus.

Hi, we fixed this problem and it'll be published as a patch version of LeptonX v4.0.

I got it better right now. Micro-Service template is renewed in a near history. It seems it's a design decision. Still I deliver your feedbacks to the team and we'll consider these for some changes.

Here some suggestions for your points for now:

Point 1

... AbpEventInvox/AbpEventOutbox should include part of the microservice name

You can it happen by configuring in your each Services's DbContext:

 protected override void OnModelCreating(ModelBuilder builder)
{
    // ...

    var microserviceName = "IdentityService";

    builder.Entity<IncomingEventRecord>(b =>
    {
        b.ToTable($"{microserviceName}_AbpEventInbox");
    });

    builder.Entity<OutgoingEventRecord>(b =>
    {
        b.ToTable($"{microserviceName}_AbpEventOutbox");
    });
}

Point 2

By referring to Connection String - configuring-the-database-structures documentation, you can create groups with multip connection string names like this:

Configure<AbpDbConnectionOptions>(options =>
{
    options.Databases.Configure("MyDatabaseGroup", db =>
    {
        db.MappedConnections.Add("Administration");
        db.MappedConnections.Add("AuditLoggingService");
        db.MappedConnections.Add("AbpBlobStoring");
        db.MappedConnections.Add("SaasService");
        // ...
    });
});

And then, you can configure it for using in the SAAS module, since saas does not show all the connecting string by default. You have to configure them to make them selectable for each tenant in saas module:

Configure<AbpDbConnectionOptions>(options => 
{
    options.Databases.Configure("MyDatabaseGroup", database => 
    {
        database.IsUsedByTenants = true;
    });
});

But you want use separate databases for each service. So you make make tem one by one available to choose in Saas Module:

Configure<AbpDbConnectionOptions>(options => 
{
    options.Databases.Configure("Administration", database => 
    {
        database.IsUsedByTenants = true;
    });

    options.Databases.Configure("Identity", database => 
    {
        database.IsUsedByTenants = true;
    });

    options.Databases.Configure("AuditLogging", database => 
    {
        database.IsUsedByTenants = true;
    });
    
    // ...});

Or if you make it automatically instead choosing always in the UI, you can eeplace the Connection String Resolver completely and just append tenant name and requested connectionstring name to the database name in the connectionstring by following this:

https://abp.io/docs/latest/framework/fundamentals/connection-strings#replace-the-connection-string-resolver

I found the root of the problem.

It happens because of the fallback to the default connection string. 👇 https://github.com/abpframework/abp/blob/74d516829be7f05cfae7d4a67f18591b41e5446a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/MultiTenantConnectionStringResolver.cs#L76-L79

But in the micro-service solution, each service uses their own named connection string, not default one. But MultiTenantConnectionStringResolver fallbacks to the default connection string and it happens on each service.

Here the workaround that I found and make it work for now.

  1. Create a new MicroServiceConnectionStringResolver.cs and disable fallbacking to Tenant's default connectionstring
using System;
using Microsoft.Extensions.Options;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;

namespace AbpSolution1.AuthServer;

[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IConnectionStringResolver), typeof(DefaultConnectionStringResolver))]
public class MicroServiceConnectionStringResolver : MultiTenantConnectionStringResolver
{
    private readonly ICurrentTenant _currentTenant;
    private readonly IServiceProvider _serviceProvider;
    public MicroServiceConnectionStringResolver(IOptionsMonitor<AbpDbConnectionOptions> options, ICurrentTenant currentTenant, IServiceProvider serviceProvider) : base(options, currentTenant, serviceProvider)
    {
        _currentTenant = currentTenant;
        _serviceProvider = serviceProvider;
    }

    public override async Task<string> ResolveAsync(string? connectionStringName = null)
    {if (_currentTenant.Id == null)
        {
            //No current tenant, fallback to default logic
            return await base.ResolveAsync(connectionStringName);
        }

        var tenant = await FindTenantConfigurationAsync(_currentTenant.Id.Value);

        if (tenant == null || tenant.ConnectionStrings.IsNullOrEmpty())
        {
            //Tenant has not defined any connection string, fallback to default logic
            return await base.ResolveAsync(connectionStringName);
        }

        var tenantDefaultConnectionString = tenant.ConnectionStrings?.Default;

        //Requesting default connection string...
        if (connectionStringName == null ||
            connectionStringName == ConnectionStrings.DefaultConnectionStringName)
        {
            //Return tenant's default or global default
            return !tenantDefaultConnectionString.IsNullOrWhiteSpace()
                ? tenantDefaultConnectionString!
                : Options.ConnectionStrings.Default!;
        }

        //Requesting specific connection string...
        var connString = tenant.ConnectionStrings?.GetOrDefault(connectionStringName);
        if (!connString.IsNullOrWhiteSpace())
        {
            //Found for the tenant
            return connString!;
        }

        //Fallback to the mapped database for the specific connection string
        var database = Options.Databases.GetMappedDatabaseOrNull(connectionStringName);
        if (database != null && database.IsUsedByTenants)
        {
            connString = tenant.ConnectionStrings?.GetOrDefault(database.DatabaseName);
            if (!connString.IsNullOrWhiteSpace())
            {
                //Found for the tenant
                return connString!;
            }
        }

        // Disable fallback to tenant's default connection string as A WORKAROUBD
        ////Fallback to tenant's default connection string if available
        // if (!tenantDefaultConnectionString.IsNullOrWhiteSpace())
        // {
        //     return tenantDefaultConnectionString!;
        // }

        return await base.ResolveAsync(connectionStringName);
    }
}

You can create a new module and share this class across all the services to replace IConnectionStringResolver implementation in all the services.


In my case, I went without inbox-outbox pattern, but still face this problem when changed tenant's connectionstring:

I only replaced this file for AuthServer and it worked. But you'll need to override this service for each service because of inbox/outbox tables

Ok,

I could reproduce the problem. I'll check and find problem & solution soon

Hi,

I'm trying to reproduce this problem but before I go, I need some information

Did you add any entity framework core migration manually in this scenario?

Seems like outdated issue, create new one if the problem still exists. (Your credit has been refunded)

Alternatively

If you use Language management module, you'll need to update value in the UI

  • Navigate to Language Management > Language Texts in the menu.
  • Choose AbpUiMultiTenancy in the Resource Name dropdown and update translation of given texts.

Hi,

Localizations comes from here: https://github.com/abpframework/abp/blob/664af59cd7778b40b635540d6213d7b6b0985dea/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/en.json#L4-L11

You can add this keys into localization json file and override AbpUiMultiTenancyResource by following this documentation: https://abp.io/docs/latest/framework/fundamentals/localization#extending-existing-resource


Configure<AbpLocalizationOptions>(options =>
{
    // ...

    options.Resources
        .Get<AbpUiMultiTenancyResource>()
        .AddVirtualJson("/Localization/YourProjectResource");

    // ...
});

Note that: Your .Domain.Shared module probably won't have reference for AbpUiMultiTenancyResource class. So, you'll need to do that in the host application such as .Web or HttpApi.Host or .AuthServer according to your configuration.

Showing 261 to 270 of 780 entries
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.1.0-preview. Updated on November 04, 2025, 06:41