416.3 Many‐to‐many of ABP framework applications - chempkovsky/CS82ANGULAR GitHub Wiki
- Please read the 416.1 article first.
- As we mentioned earlier, instead of changing the created entities, we will create two additional ones: PhbkPhoneMtm and PhbkEmpToPhn
- As we mentioned earlier, our approach to modeling needs to change.
- This article describes how to generate the
C#
andTypescript
code forPhbkEmpToPhn
-entity. -
PhbkPhoneMtm
-entity will reference onlyPhbkPhoneType
-entity. - We just follow the same steps as for 070 Fifth View (Phone)
- In the
Phbk
folder of therupbes.firstapp.Domain.csproj
project, createPhbkEmpToPhn
class as shown below. We inherit the class from Entity
Click to show the code
using System;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;
using Volo.Abp.MultiTenancy;
namespace rupbes.firstapp.Phbk
{
public class PhbkEmpToPhn : Entity, IMultiTenant, IHasConcurrencyStamp
{
[Display(Description = "Phone Id", Name = "Id of the Employee", Prompt = "Enter Employee Id", ShortName = "Employee Id")]
[Required]
public int EmpIdRef { get; protected set; }
[Display(Description = "Phone Id", Name = "Phone Id", Prompt = "Enter Phone Id", ShortName = "Phone Id")]
[Required]
public int PhnIdRef { get; protected set; }
[DisableAuditing]
[Display(Description = "Concurrency Stamp", Name = "Concurrency Stamp", Prompt = "Enter Concurrency Stamp", ShortName = "Concurrency Stamp")]
[StringLength(40, MinimumLength = 0, ErrorMessage = "Invalid")]
[Required]
[ConcurrencyCheck]
public virtual string ConcurrencyStamp { get; set; } = default!;
[Display(Description = "Tenant id", Name = "Tenant Id", Prompt = "Enter Tenant Id", ShortName = "Tenant Id")]
public virtual Guid? TenantId { get; protected set; }
public PhbkEmpToPhn() : base()
{
ConcurrencyStamp = Guid.NewGuid().ToString("N");
}
public PhbkEmpToPhn(int empIdRef, int phnIdRef) : base()
{
EmpIdRef = empIdRef;
PhnIdRef = phnIdRef;
ConcurrencyStamp = Guid.NewGuid().ToString("N");
}
public override object[] GetKeys()
{
return new object[] { EmpIdRef, PhnIdRef };
}
#if (!NOTMODELING)
#endif
public PhbkPhoneMtm PhoneMtm { get; set; } = null!;
public PhbkEmployee Employee { get; set; } = null!;
}
}
-
It is important to note that
public PhbkPhoneMtm PhoneMtm { get; set; }
-property andpublic PhbkEmployee Employee { get; set; }
are declared outside#if (!NOTMODELING) ... #endif
, i.e. this property will be avalable at build time (not only at design time). -
We have to modify
PhbkPhoneMtm
-class as well.
Click to show the code
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Volo.Abp;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;
using Volo.Abp.MultiTenancy;
namespace rupbes.firstapp.Phbk
{
public class PhbkPhoneMtm : Entity<int>, IMultiTenant, IHasConcurrencyStamp
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Display(Description = "Row id", Name = "Phone Id", Prompt = "Enter Phone Id", ShortName = "Phone Id")]
[Required]
public override int Id { get; protected set; }
[StringLength(20, MinimumLength = 3, ErrorMessage = "Invalid")]
[Display(Description = "Name of the Phone Type", Name = "Phone", Prompt = "Enter Phone", ShortName = "Phone")]
[Required]
public string Phone { get; protected set; } = null!;
[Display(Description = "Row id", Name = "Phone Type Id", Prompt = "Enter Phone Type Id", ShortName = "Phone Type Id")]
[Required]
public int PhoneTypeIdRef { get; protected set; }
[DisableAuditing]
[Display(Description = "Concurrency Stamp", Name = "Concurrency Stamp", Prompt = "Enter Concurrency Stamp", ShortName = "Concurrency Stamp")]
[StringLength(40, MinimumLength = 0, ErrorMessage = "Invalid")]
[Required]
[ConcurrencyCheck]
public virtual string ConcurrencyStamp { get; set; } = default!;
[Display(Description = "Tenant id", Name = "Tenant Id", Prompt = "Enter Tenant Id", ShortName = "Tenant Id")]
public virtual Guid? TenantId { get; protected set; }
public PhbkPhoneType PhoneType { get; set; } = null!;
#if (!NOTMODELING)
public virtual List<PhbkEmpToPhn> EmpToPhns { get; set; } = null!;
#endif
public PhbkPhoneMtm() : base()
{
ConcurrencyStamp = Guid.NewGuid().ToString("N");
}
public PhbkPhoneMtm(int id, string phone, int phoneTypeIdRef) : base(id)
{
Check.NotNullOrWhiteSpace(phone, nameof(Phone));
Phone = phone;
PhoneTypeIdRef = phoneTypeIdRef;
ConcurrencyStamp = Guid.NewGuid().ToString("N");
}
public virtual void ChangeVals(string phone, int phoneTypeIdRef, string concurrencyStamp)
{
Check.NotNullOrWhiteSpace(phone, nameof(Phone));
Phone = phone;
PhoneTypeIdRef = phoneTypeIdRef;
ConcurrencyStamp = concurrencyStamp;
}
}
}
-
It is important to note that,
public virtual List<PhbkEmpToPhn> EmpToPhns { get; set; }
property is declared inside#if (!NOTMODELING) ... #endif
, i.e. this property will be avalable at at design time and wil not be avalable at build time. -
We have to modify
PhbkEmployee
-class as well.
Click to show the code
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Volo.Abp;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;
using Volo.Abp.MultiTenancy;
namespace rupbes.firstapp.Phbk
{
public class PhbkEmployee : Entity<int>, IMultiTenant, IHasConcurrencyStamp
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Display(Description = "Row id", Name = "Id of the Employee", Prompt = "Enter Employee Id", ShortName = "Employee Id")]
[Required]
public override int Id { get; protected set; }
[Display(Description = "First Name of the Employee", Name = "Employee First Name", Prompt = "Enter Employee First Name", ShortName = "First Name")]
[StringLength(25, MinimumLength = 3, ErrorMessage = "Invalid")]
[Required]
public string EmpFirstName { get; protected set; } = null!;
[Display(Description = "Last Name of the Employee", Name = "Employee Last Name", Prompt = "Enter Employee Last Name", ShortName = "Last Name")]
[StringLength(40, MinimumLength = 3, ErrorMessage = "Invalid")]
[Required]
public string EmpLastName { get; protected set; } = null!;
[Display(Description = "Row id", Name = "Employee Second Name", Prompt = "Enter Employee Second Name", ShortName = "Second Name")]
[StringLength(25, ErrorMessage = "Invalid")]
public string? EmpSecondName { get; protected set; }
[Display(Description = "Row id", Name = "Id of the Division", Prompt = "Enter Division Id", ShortName = "Division Id")]
[Required]
public int DivisionIdRef { get; protected set; }
[DisableAuditing]
[Display(Description = "Concurrency Stamp", Name = "Concurrency Stamp", Prompt = "Enter Concurrency Stamp", ShortName = "Concurrency Stamp")]
[StringLength(40, MinimumLength = 0, ErrorMessage = "Invalid")]
[Required]
[ConcurrencyCheck]
public virtual string ConcurrencyStamp { get; set; } = default!;
[Display(Description = "Tenant id", Name = "Tenant Id", Prompt = "Enter Tenant Id", ShortName = "Tenant Id")]
public virtual Guid? TenantId { get; protected set; }
public PhbkEmployee() : base()
{
ConcurrencyStamp = Guid.NewGuid().ToString("N");
}
public PhbkEmployee(int id, string empFirstName, string? empLastName, string? empSecondName, int divisionIdRef ) : base(id)
{
Check.NotNullOrWhiteSpace(empFirstName, nameof(EmpFirstName));
Check.NotNullOrWhiteSpace(empLastName, nameof(EmpLastName));
EmpFirstName = empFirstName;
EmpLastName = empLastName;
EmpSecondName = empSecondName;
DivisionIdRef = divisionIdRef;
ConcurrencyStamp = Guid.NewGuid().ToString("N");
}
public virtual void ChangeVals(string empFirstName, string? empLastName, string? empSecondName, int divisionIdRef, string concurrencyStamp)
{
Check.NotNullOrWhiteSpace(empFirstName, nameof(EmpFirstName));
Check.NotNullOrWhiteSpace(empLastName, nameof(EmpLastName));
EmpFirstName = empFirstName;
EmpLastName = empLastName;
EmpSecondName = empSecondName;
DivisionIdRef = divisionIdRef;
ConcurrencyStamp = concurrencyStamp;
}
#if (!NOTMODELING)
public PhbkDivision Division { get; set; } = null!;
public virtual List<PhbkPhone> Phones { get; set; } = null!;
public virtual List<PhbkEmpToPhn> EmpToPhns { get; set; } = null!;
#endif
}
}
- It is important to note that,
public virtual List<PhbkPhone> Phones { get; set; }
property andpublic virtual List<PhbkEmpToPhn> EmpToPhns { get; set; }
property are declared inside#if (!NOTMODELING) ... #endif
, i.e. this property will be avalable at at design time and wil not be avalable at build time.
- Using Dbcontext Wizard we define Primary key
Click to show the picture
- Using Dbcontext Wizard we define first foreign key
Click to show the picture
- Using Dbcontext Wizard we define second foreign key
Click to show the picture
- After Dbcontext Wizard closed we have to modify
OnModelCreating(ModelBuilder builder)
-method of thefirstappDbContext
-class:- We put generated
builder.Entity<PhbkEmpToPhn>().HasKey(p => new { p.EmpIdRef, p.PhnIdRef });
line in the#if (!NOTMODELING) ... #endif
-block - We put generated
builder.Entity<PhbkEmpToPhn>().HasOne(d => d.PhoneMtm)...
code in the#if (!NOTMODELING) ... #endif
-block - We put generated
builder.Entity<PhbkEmpToPhn>().HasOne(d => d.Employee)...
code in the#if (!NOTMODELING) ... #endif
-block
- We put generated
Click to show the code
protected override void OnModelCreating(ModelBuilder builder)
{
#if (!NOTMODELING)
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
...
#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);
#endif
}
- Now we copy
builder.Entity<PhbkEmpToPhn>().HasOne(d => d.PhoneMtm)...
code in theConfigurefirstappDbContext(this ModelBuilder builder)
-method of thefirstappDbContextCreatingExtensions
-class and modify as follows:
Click to show the code
builder.Entity<PhbkEmpToPhn>(b =>
{
b.ToTable(firstappConsts.DbTablePrefix + "EmpToPhns", firstappConsts.DbSchema);
b.ConfigureByConvention(); //auto configure for the base class props
//...
b.HasKey(p => new { p.EmpIdRef, p.PhnIdRef });
//builder.Entity<PhbkEmpToPhn>().HasOne(d => d.PhoneMtm)
b.HasOne(d => d.PhoneMtm)
//.WithMany(m => m.EmpToPhns)
.WithMany()
.HasForeignKey(d => d.PhnIdRef)
.HasPrincipalKey(p => p.Id)
.IsRequired(true)
.OnDelete(DeleteBehavior.NoAction);
// builder.Entity<PhbkEmpToPhn>().HasOne(d => d.Employee)
b.HasOne(d => d.Employee)
//.WithMany(m => m.EmpToPhns)
.WithMany()
.HasForeignKey(d => d.EmpIdRef)
.HasPrincipalKey(p => p.Id)
.IsRequired(true)
.OnDelete(DeleteBehavior.NoAction);
});
-
It is important to note that,
PhoneMtm
-property is used to define foreign keyb.HasOne(d => d.PhoneMtm)...
-
It is important to note that,
Employee
-property is used to define foreign keyb.HasOne(d => d.Employee)...
-
Now modify
Directory.Build.props
-file as follows:
Click to show the code
<Project>
<PropertyGroup>
<DefineConstants>NOTMODELING</DefineConstants>
</PropertyGroup>
</Project>
- Run
cmd.exe
, go to directory of therupbes.firstapp.EntityFrameworkCore.csproj
-project and in the terminal run the command:
dotnet ef migrations add Added_PhbkEmpToPhn
-
After
Added_PhbkEmpToPhn
finished build and run app of therupbes.firstapp.DbMigrator.csproj
-project. -
To returns the solution to modeling mode
- After
rupbes.firstapp.DbMigrator
-app finished modifyDirectory.Build.props
-file as follows:
- After
Click to show the code
<Project>
<PropertyGroup>
<DefineConstants>XXNOTMODELING</DefineConstants>
</PropertyGroup>
</Project>
- Repeat the steps of the 404 First View (PhoneType). Wizard repository
Click to show the picture
- It is important to note that
Use only Root Props For Select method
-checkbox is turned OFF!!! - The first prefix is defined as shown below:
Click to show the picture
- The second prefix is defined as shown below:
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 OFF. It meansPPhone
-prop (PPPhoneTypeName
,EEmpFirstName
,EEmpLastName
,EEmpSecondName
) will be generated for DTO-class. - Please note that
Update
method will not be generated, because of the primary key fields.
Click to show the picture
-
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
-
It is important to note that
InputType Delete
does not containSeachDialog
-switch for thePPPhoneTypeName
-prop,PPhone
-prop,EEmpLastName
-prop. -
For
PhbkPhoneMtm
-entity (direct master):- Regenerate
routing
-code again, as the routing items number has changed - Regenerate typescript code again for:
- services (
014400-AbpWebApiService.json
-batch) - rv-form
- rlist-form
- rdlist-form
- services (
- Regenerate
-
For
PhbkEmployee
-entity (direct master):- Regenerate
routing
-code again, as the routing items number has changed - Regenerate typescript code again for:
- services (
014400-AbpWebApiService.json
-batch) - rv-form
- rlist-form
- rdlist-form
- services (
- Regenerate
-
Regenerate
routing
-code again, as the routing items number has changed- for
PhbkDivision
- for
PhbkEnterprise
- for
PhbkPhoneType
- for
Modify route.provider.ts
-file as follows:
Click to show the code
import { RoutesService, eLayoutType } from '@abp/ng.core';
import { inject, provideAppInitializer } from '@angular/core';
export const APP_ROUTE_PROVIDER = [
provideAppInitializer(() => {
configureRoutes();
}),
];
function configureRoutes() {
const routes = inject(RoutesService);
routes.add([
{
...
{
name: 'firstapp::Psn:PhbkEmpToPhnDto',
group: 'firstapp::Psn:PhbkEmpToPhnDto',
order: 17,
iconClass: 'fas fa-phone',
},
{
path: '/RDLPhbkEmpToPhnDto',
name: 'Employee-Phone Pair Dlg',
requiredPolicy: 'firstapp.PhbkEmpToPhnDto',
parentName: 'firstapp::Psn:PhbkEmpToPhnDto',
iconClass: 'fas fa-phone',
order: 160,
layout: eLayoutType.application,
},
{
path: '/PhbkEmpToPhnDto',
name: 'Employee-Phone Pair',
requiredPolicy: 'firstapp.PhbkEmpToPhnDto',
parentName: 'firstapp::Psn:PhbkEmpToPhnDto',
iconClass: 'fas fa-phone',
order: 170,
layout: eLayoutType.application,
}
]);
}
- Here is a result.
-
Phone
,Phone type name
columns are shown. Note, it is data of the master tables (PhbkPhoneType and PhbkPhoneMtm) -
Employee Last Name
,Employee First Name
,Employee Second Name
columns are shown. Note, it is data of the master table (PhbkEmployee)