416.3 Many‐to‐many of ABP framework applications - chempkovsky/CS82ANGULAR GitHub Wiki

Note
  • 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# and Typescript code for PhbkEmpToPhn-entity.
  • PhbkPhoneMtm-entity will reference only PhbkPhoneType-entity.
  • We just follow the same steps as for 070 Fifth View (Phone)
Entity
  • In the Phbk folder of the rupbes.firstapp.Domain.csproj project, create PhbkEmpToPhn 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 and public 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 and public 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

project structure

  • Using Dbcontext Wizard we define first foreign key
Click to show the picture

project structure

  • Using Dbcontext Wizard we define second foreign key
Click to show the picture

project structure

  • After Dbcontext Wizard closed we have to modify OnModelCreating(ModelBuilder builder)-method of the firstappDbContext-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
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 the ConfigurefirstappDbContext(this ModelBuilder builder)-method of the firstappDbContextCreatingExtensions-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 key b.HasOne(d => d.PhoneMtm)...

  • It is important to note that, Employee-property is used to define foreign key b.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 the rupbes.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 the rupbes.firstapp.DbMigrator.csproj-project.

  • To returns the solution to modeling mode

    • After rupbes.firstapp.DbMigrator-app finished modify Directory.Build.props-file as follows:
Click to show the code
<Project>
    <PropertyGroup>
        <DefineConstants>XXNOTMODELING</DefineConstants>
    </PropertyGroup>
</Project>
Dto
Click to show the picture

project structure

  • 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

project structure

  • The second prefix is defined as shown below:
Click to show the picture

project structure

Web Api
  • 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 means PPhone-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

project structure

Typescript Classes
Click to show the picture

project structure

  • UI Form properties is as follows:
Click to show the picture

project structure

  • It is important to note that InputType Delete does not contain SeachDialog-switch for the PPPhoneTypeName-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
  • 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
  • Regenerate routing-code again, as the routing items number has changed

    • for PhbkDivision
    • for PhbkEnterprise
    • for PhbkPhoneType

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)
Click to show the picture

project structure

⚠️ **GitHub.com Fallback** ⚠️