Hi, i've done all that, but how do customize the UI?
Posted this article on the topic: https://medium.com/@kaffeeebene/abp-in-depth-storing-key-tenant-data-in-the-host-database-while-keeping-the-rest-in-tenant-ca2aca0e4cd2
Thanks!
Creating a knowledge article on this topic and will post the link here in a bit
Hi maliming,
I've managed to resolve the issues and get the desired implementation. Give me a few hours to add the code snippets here for others to reference.
Thank you
Thank you for the insights. As I do not want to deviate from the framework design, I will implement the manual assignment of TenantIdCustom
upon entity creation (We forsee only a limited number of such entities)
In addition, I've implemented a DataFilter within MyApplcaitionDbContext.cs
protected bool IsMultiTenantInHostDb => DataFilter?.IsEnabled<IMultiTenantInHostDb>() ?? false;
protected override bool ShouldFilterEntity<TEntity>(IMutableEntityType entityType)
{
if (typeof(IMultiTenantInHostDb).IsAssignableFrom(typeof(TEntity)))
{
return true; // Apply filter for entities implementing IMultiTenantInHostDb
}
return base.ShouldFilterEntity<TEntity>(entityType);
}
protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>(ModelBuilder modelBuilder)
{
var expression = base.CreateFilterExpression<TEntity>(modelBuilder);
if (typeof(IMultiTenantInHostDb).IsAssignableFrom(typeof(TEntity)))
{
// Get the current tenant ID
var currentTenantId = CurrentTenant.Id;
Console.WriteLine("currentTenantId:");
Console.WriteLine(currentTenantId);
// Create the filter expression
Expression<Func<TEntity, bool>> tenantFilter = e =>
!IsMultiTenantInHostDb ||
(EF.Property<Guid?>(e, "TenantIdCustom") == currentTenantId || EF.Property<Guid?>(e, "TenantIdCustom") == null);
expression = expression == null ? tenantFilter : QueryFilterExpressionHelper.CombineExpressions(expression, tenantFilter);
}
return expression;
}
as per your reference for ef core filter: https://abp.io/docs/latest/framework/infrastructure/data-filtering?_redirected=B8ABF606AA1BDF5C629883DF1061649A#entityframework-core
However the currentTenantId
coming as null, even when logged in as a tenant. Is there some issue with my usage of CurrentTenant.Id?
Something like this? (I not sure if ICurrentTenant
is available for dependency injection on a Entity level though)
Injecting into DomainService
should work right? But this is not desirable, as I would need to override the CreateAsync method for every DomainService for the entity. I would prefer if the Entity itself does the initialization, similar to classes implementing IMultiTenant
Hi Maliming,
Refering to documentation snippet below. As I would like to auto-populate my TenantIdCustom
property (**not ** inherited from IMultiTenant), is there a way to implement the auto-population logic similar to how ABP framework auto populates TenantId?
Will post a code implementation shortly
Hi Maliming,
Noted on that. As I still need some filtering by TenantId
, A possible workaround for me is to add TenantId
without implementing IMultiTenant
.
Is there any way to automatically populate this TenantId
field with the CurrentTenant
?
Thank you
Hi Maliming,
Just as context, our business requirement is that for some custom entities (e.g. AppBooks), that our data must reside only in the Host Database (even if tenant is using its own DB Connection String). But the data must still be accessible from the Tenant.
Is there a better way to achieve this outcome in the ABP framework?
Thank you
Thank you. The workaround I will then try is to implement a new DbContext class and apply the [IgnoreMultiTenancy]
attribute
I've Created a class called HostOnlyDbContext.cs
[ConnectionStringName("Default")]
[IgnoreMultiTenancy]
public class HostOnlyDbContext : AbpSolution4DbContextBase<HostOnlyDbContext>
{
public DbSet<Experience> Experiences { get; set; } = null!;
public HostOnlyDbContext(DbContextOptions<HostOnlyDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
builder.SetMultiTenancySide(MultiTenancySides.Both);
builder.Entity<Experience>(b =>
{
b.ToTable(AbpSolution4Consts.DbTablePrefix + "Experiences", AbpSolution4Consts.DbSchema);
b.ConfigureByConvention();
b.Property(x => x.TenantId).HasColumnName(nameof(Experience.TenantId));
b.Property(x => x.Name).HasColumnName(nameof(Experience.Name));
});
}
And also registered the DbContext in AbpSolution4EntityFrameworkCoreModule.cs
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAbpDbContext<AbpSolution4DbContext>(options =>
{
/* Remove "includeAllEntities: true" to create
* default repositories only for aggregate roots */
options.AddDefaultRepositories(includeAllEntities: true);
options.AddRepository<Conversation, Conversations.EfCoreConversationRepository>();
});
context.Services.AddAbpDbContext<HostOnlyDbContext>(options =>
{
options.AddDefaultRepositories(includeAllEntities: true);
options.AddRepository<Experience, Experiences.EfCoreExperienceRepository>();
});
Would this work? I am having trouble understanding how the original AbpSolution4DbContext
is being injected and whether it is possible to reference AbpSolution4DbContext
for the Conversation
entity but use HostOnlyDbContext
for the Experience
entity.
also, is there a way to migrate BOTH the AbpSolution4DbContext
and HostOnlyDbContext
during DB Migration? Currently it is only running migrations for AbpSolution4DbContext