417.2‐Multi‐Tenancy of ABP framework applications - chempkovsky/CS82ANGULAR GitHub Wiki
- Please read the article first
- We generated UI for
Volo.Abp.TenantManagement.Tenant
-entity - Now we are going to generate code for
Volo.Abp.TenantManagement.TenantConnectionString
-entity
- Modify
TenantConnectionString.cs
-file of theVolo.Abp.TenantManagement.Domain.csproj
-project as follows:
Click to show the code
using JetBrains.Annotations;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Domain.Entities;
namespace Volo.Abp.TenantManagement;
public class TenantConnectionString : Entity
{
#if (!NOTMODELING)
[Description("IsNot_TenantId")]
[Display(Description = "Tenant id", Name = "Id of the Tenant", Prompt = "Enter Tenant Id", ShortName = "Tenant Id")]
#endif
public virtual Guid TenantId { get; protected set; }
#if (!NOTMODELING)
[Display(Description = "Connection String Name", Name = "Name of the Connection String", Prompt = "Enter Connection String Name", ShortName = "Connection String Name")]
[StringLength(64, MinimumLength = 3, ErrorMessage = "Invalid")]
#endif
public virtual string Name { get; protected set; }
#if (!NOTMODELING)
[Display(Description = "Connection String Value", Name = "Value of the Connection String", Prompt = "Enter Connection String Value", ShortName = "Connection String Value")]
[StringLength(1024, MinimumLength = 3, ErrorMessage = "Invalid")]
#endif
public virtual string Value { get; protected set; }
#if (!NOTMODELING)
public Tenant Tnnt { get; set; } = null!;
#endif
protected TenantConnectionString()
{
}
public TenantConnectionString(Guid tenantId, [NotNull] string name, [NotNull] string value)
{
TenantId = tenantId;
Name = Check.NotNullOrWhiteSpace(name, nameof(name), TenantConnectionStringConsts.MaxNameLength);
SetValue(value);
}
public virtual void SetValue([NotNull] string value)
{
Value = Check.NotNullOrWhiteSpace(value, nameof(value), TenantConnectionStringConsts.MaxValueLength);
}
public override object[] GetKeys()
{
return new object[] { TenantId, Name };
}
}
- It is important to note, that at build time (i.e.
<DefineConstants>NOTMODELING</DefineConstants>
)TenantConnectionString
-class stays unchanged. - It is important to note, that
public Tenant Tnnt { get; set; } = null!;
property was added to the class. We make a hint forCS82ANGULAR
about one-to-many relation. - It is important to note, that
[Description("IsNot_TenantId")]
attribute was added to theTenantId
-property of the class- CS82ANGULAR does not analyze the class inheritance, instead it checks the properties with a predefined name like:
-
TenantId
,ConcurrencyStamp
and so on
-
- On the one hand,
TenantConnectionString
-class does not implementIMultiTenant
-interface, on the other it hasTenantId
-property.- using attributes like
[Description("IsNot_TenantId")]
or[Description("IsNot_ConcurrencyStamp")]
(and so on), we make a hint forCS82ANGULAR
thatTenantId
is not for implementingIMultiTenant
-interface
- using attributes like
- CS82ANGULAR does not analyze the class inheritance, instead it checks the properties with a predefined name like:
- Repeat the steps of the 403 Modify DBContext for the first entity (PhoneType).
- With DbContext wizard we define the primary key
Click to show the picture
- With DbContext wizard we define the foreigth key
Click to show the picture
-
OnModelCreating
must be as follows:
Click to show the code
protected override void OnModelCreating(ModelBuilder builder)
{
#if (!NOTMODELING)
builder.Entity<TenantConnectionString>().HasKey(p => new { p.TenantId, p.Name });
builder.Entity<Tenant>().HasKey(p => p.Id);
builder.Entity<PhbkFile>().HasKey(p => new { p.FileId, p.FlNm });
builder.Entity<PhbkPhone>().HasKey(p => p.Id);
builder.Entity<PhbkEmployee>().HasKey(p => p.Id);
builder.Entity<PhbkDivision>().HasKey(p => p.Id);
builder.Entity<PhbkPhoneType>().HasKey(p => p.Id);
builder.Entity<PhbkEnterprise>().HasKey(p => p.Id);
builder.Entity<PhbkPhoneMtm>().HasKey(p => p.Id);
builder.Entity<PhbkEmpToPhn>().HasKey(p => new { p.EmpIdRef, p.PhnIdRef });
#endif
base.OnModelCreating(builder);
/* Include modules to your migration db context */
builder.ConfigurePermissionManagement();
builder.ConfigureSettingManagement();
builder.ConfigureBackgroundJobs();
builder.ConfigureAuditLogging();
builder.ConfigureFeatureManagement();
builder.ConfigureIdentity();
builder.ConfigureOpenIddict();
builder.ConfigureTenantManagement();
builder.ConfigureBlobStoring();
/* Configure your own tables/entities inside here */
//builder.Entity<YourEntity>(b =>
//{
// b.ToTable(firstappConsts.DbTablePrefix + "YourEntities", firstappConsts.DbSchema);
// b.ConfigureByConvention(); //auto configure for the base class props
// //...
//});
builder.ConfigurefirstappDbContext();
#if (!NOTMODELING)
builder.Entity<PhbkDivision>().HasOne(d => d.Enterprise)
.WithMany(m => m.Divisions)
.HasForeignKey(d => d.EntrprsIdRef)
.HasPrincipalKey(p => p.Id)
.IsRequired(true)
.OnDelete(DeleteBehavior.NoAction);
builder.Entity<PhbkEmployee>().HasOne(d => d.Division)
.WithMany(m => m.Employees)
.HasForeignKey(d => d.DivisionIdRef)
.HasPrincipalKey(p => p.Id)
.IsRequired(true)
.OnDelete(DeleteBehavior.NoAction);
builder.Entity<PhbkPhone>().HasOne(d => d.PhoneType)
.WithMany(m => m.Phones)
.HasForeignKey(d => d.PhoneTypeIdRef)
.HasPrincipalKey(p => p.Id)
.IsRequired(true)
.OnDelete(DeleteBehavior.NoAction);
builder.Entity<PhbkPhone>().HasOne(d => d.Employee)
.WithMany(m => m.Phones)
.HasForeignKey(d => d.EmployeeIdRef)
.HasPrincipalKey(p => p.Id)
.IsRequired(true)
.OnDelete(DeleteBehavior.NoAction);
builder.Entity<PhbkPhoneMtm>().HasOne(d => d.PhoneType)
.WithMany(m => m.PhoneMtms)
.HasForeignKey(d => d.PhoneTypeIdRef)
.HasPrincipalKey(p => p.Id)
.IsRequired(true)
.OnDelete(DeleteBehavior.NoAction);
builder.Entity<PhbkEmpToPhn>().HasOne(d => d.PhoneMtm)
.WithMany(m => m.EmpToPhns)
.HasForeignKey(d => d.PhnIdRef)
.HasPrincipalKey(p => p.Id)
.IsRequired(true)
.OnDelete(DeleteBehavior.NoAction);
builder.Entity<PhbkEmpToPhn>().HasOne(d => d.Employee)
.WithMany(m => m.EmpToPhns)
.HasForeignKey(d => d.EmpIdRef)
.HasPrincipalKey(p => p.Id)
.IsRequired(true)
.OnDelete(DeleteBehavior.NoAction);
builder.Entity<TenantConnectionString>().HasOne(d => d.Tnnt)
.WithMany(m => m.ConnectionStrings)
.HasForeignKey(d => d.TenantId)
.HasPrincipalKey(p => p.Id)
.IsRequired(true)
.OnDelete(DeleteBehavior.NoAction);
#endif
}
- Repeat the steps of the 404 First View (PhoneType). Wizard repository
- It is important to note that using default DTO-naming is not the case for
TenantConnectionString
-entity. The wizard generatesTenantConnectionStringDto
-name. But the calss with the same name is declared in theVolo.Abp.TenantManagement.Application.Contracts.csproj
-prject. Even though the class created by our wizard has a different namespace, it is much better if different Dto classes have different names. We changeTenantConnectionStringDto
toTenantConnectionStringExDto
. - In the
rupbes.firstapp.Application.Contracts.csproj
-rpoject we create theTm
-folder and define Dto as follows: - The second page of the wizard will be as follows:
Click to show the picture
- The fourth page of the wizard will be as follows:
Click to show the picture
- We define the foreign key:
Click to show the picture
- Repeat the steps of the 405 First Web Api Service (PhoneType)
- Please note that
-
Use only root props for select method
-checkbox is ON. - Even though the TenantManagement module may have a web service for
TenantConnectionString
, we generate our own copy of the classes and interfaces: [ITenantConnectionStringExDtoRepo.cs, TenantConnectionStringExDtoRepo.cs, ITenantConnectionStringExDtoService, TenantConnectionStringExDtoService.cs, TenantConnectionStringExDtoWebApiController.cs]
-
Click to show the picture
- It is important to note that in the
updateone
-method of theTenantConnectionStringExDtoService
-class we had to replace one line of code:- we commented the code
resultEntity.Value = viewToUpdate.Value;
- we added
resultEntity.SetValue(viewToUpdate.Value);
- in the article we introduce
ChangeVals
-method which used to change properties. - Abp developers use
SetXXX()
-methods to modify properties - as we mentioned above, we leave Abp-classes to be unchanged at build time
- in the article we introduce
- you do not need to remember of which code you must change. Visual Studio shows it.
- we commented the code
Click to show the code
[Authorize(firstappPermissions.TenantConnectionStringExDto.Edit)]
public async Task<TenantConnectionStringExDto?> updateone(TenantConnectionStringExDto viewToUpdate)
{
// using(repo.EnableTracking())
{
var appQr = (await repo.GetQueryableAsync()) // .TenantConnectionStrings
.Where(p => p.TenantId == viewToUpdate.TenantId)
.Where(p => p.Name == viewToUpdate.Name)
;
TenantConnectionString resultEntity = await AsyncExecuter.FirstOrDefaultAsync(appQr);
if(resultEntity == null) {
return null;
}
//resultEntity.Value = viewToUpdate.Value;
resultEntity.SetValue(viewToUpdate.Value);
await repo.UpdateAsync(resultEntity);
await UnitOfWorkManager.Current.SaveChangesAsync();
var appQrEx = (await repo.GetQueryableAsync()) // .TenantConnectionStrings
.Where(p => p.TenantId == viewToUpdate.TenantId)
.Where(p => p.Name == viewToUpdate.Name)
.Select(itm => new TenantConnectionStringExDto() {
TenantId = itm.TenantId
, Name = itm.Name
, Value = itm.Value
});
return await AsyncExecuter.FirstOrDefaultAsync(appQrEx);
} // using(repo.EnableTracking()) { ... }
}
-
Repeat the steps of the 408 Typescript Classes for the First View of ABP framework.
-
Repeat the steps of the 409 Navigation aware typescript components for the First View of ABP framework.
-
UI List properties is as follows:
Click to show the picture
- UI Form properties is as follows:
Click to show the picture
- After generating typescript classes for
TenantConnectionString
we must to regenerate the following classes forTenant
:- services (014400-AbpWebApiService.json-batch)
- rv-form
- rlist-form
- rdlist-form
- this is a scipt (not a batch) 01980-R-lazy.routes.ts/abp.r-lazy.routes.ts.t4
- this is a scipt (not a batch) 02060-Rdl-lazy.routes.ts/abp.rdl-lazy.routes.ts.t4
Here is a result: