416.2 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 PhbkPhoneMtm-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 a class as shown below. We inherit the class from Entity
Click to show the code
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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 PhbkPhoneType PhoneType { get; set; } = null!;-property is declared outside #if (!NOTMODELING) ... #endif, i.e. this property will be avalable at build time (not only at design time).

  • We have to modify PhbkPhoneType-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 PhbkPhoneType : BasicAggregateRoot<int>, IMultiTenant, IHasConcurrencyStamp
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        [Display(Description = "Row id", Name = "Phone Type Id", Prompt = "Enter Phone Type Id", ShortName = "Phone Type Id")]
        [Required]
        public override int Id { get; protected set; } = default!;

        [StringLength(20, MinimumLength = 3, ErrorMessage = "Invalid")]
        [Display(Description = "Name of the Phone Type", Name = "Phone Type Name", Prompt = "Enter Phone Type Name", ShortName = "Phone Type Name")]
        [Required]
        public string PhoneTypeName { get; protected set; } = default!;

        [Display(Description = "Description of the Phone Type", Name = "Phone Type Description", Prompt = "Enter Phone Type Description", ShortName = "Phone Type Description")]
        [StringLength(250, ErrorMessage = "Invalid")]
        public string? PhoneTypeDesc { 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() : base()
        {
            ConcurrencyStamp = Guid.NewGuid().ToString("N");
        }

        public PhbkPhoneType(int id, string phoneTypeName, string? phoneTypeDesc) : base(id)
        {
            Check.NotNullOrWhiteSpace(phoneTypeName, nameof(PhoneTypeName));
            PhoneTypeName = phoneTypeName;
            PhoneTypeDesc = phoneTypeDesc;
            ConcurrencyStamp = Guid.NewGuid().ToString("N");
        }

        public virtual void ChangeVals(string phoneTypeName, string? phoneTypeDesc, string concurrencyStamp)
        {
            Check.NotNullOrWhiteSpace(phoneTypeName, nameof(PhoneTypeName));
            PhoneTypeName = phoneTypeName;
            PhoneTypeDesc = phoneTypeDesc;
            ConcurrencyStamp = concurrencyStamp;
        }

#if (!NOTMODELING)
        public virtual List<PhbkPhone> Phones { get; set; } = null!;
        public virtual List<PhbkPhoneMtm> PhoneMtms { get; set; } = null!;
#endif

    }
}
  • It is important to note that, public virtual List<PhbkPhoneMtm> PhoneMtms { get; set; } = null!; 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.
  • Using Dbcontext Wizard we define Primary key
Click to show the picture

project structure

  • Using Dbcontext Wizard we define 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<PhbkPhoneMtm>().HasKey(p => p.Id); line in the #if (!NOTMODELING) ... #endif-block
    • We put generated builder.Entity<PhbkPhoneMtm>().HasOne(d => d.PhoneType)... 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);
#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);
#endif
    }
  • Now we copy builder.Entity<PhbkPhoneMtm>().HasOne(d => d.PhoneType)... code in the ConfigurefirstappDbContext(this ModelBuilder builder)-method of the firstappDbContextCreatingExtensions-class and modify as follows:
Click to show the code
            builder.Entity<PhbkPhoneMtm>(b =>
            {
                b.ToTable(firstappConsts.DbTablePrefix + "PhoneMtms", firstappConsts.DbSchema);
                b.ConfigureByConvention(); //auto configure for the base class props
                                           //...
                b.HasKey(p => p.Id);

                //builder.Entity<PhbkEmployee>().HasOne(d => d.Division)
                b.HasOne(d => d.PhoneType)
                        // .WithMany(m => m.PhoneMtms)
                        .WithMany()
                        .HasForeignKey(d => d.PhoneTypeIdRef)
                        .HasPrincipalKey(p => p.Id)
                        .IsRequired(true)
                        .OnDelete(DeleteBehavior.NoAction);
            });
  • It is important to note that, PhoneType-property is used to define foreign key b.HasOne(d => d.PhoneType)...

  • 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_PhbkPhoneMtm
  • After Added_PhbkPhoneMtm 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 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 PPhoneTypeName-prop will be generated for DTO-class.
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 PPhoneTypeName-prop.

  • For PhbkPhoneType-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

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:PhbkPhoneMtmDto',
        group: 'firstapp::Psn:PhbkPhoneMtmDto',
        order: 16,
        iconClass: 'fas fa-phone',
      },      
      {
        path: '/RDLPhbkPhoneMtmDto',
        name: 'Mtm Phone Dlg',
        requiredPolicy: 'firstapp.PhbkPhoneMtmDto',
        parentName: 'firstapp::Psn:PhbkPhoneMtmDto',
        iconClass: 'fas fa-phone',
        order: 140,
        layout: eLayoutType.application,
      },
      {
        path: '/PhbkPhoneMtmDto',
        name: 'Mtm Phone',
        requiredPolicy: 'firstapp.PhbkPhoneMtmDto',
        parentName: 'firstapp::Psn:PhbkPhoneMtmDto',
        iconClass: 'fas fa-phone',
        order: 150,
        layout: eLayoutType.application,
      }

  ]);
}
  • Here is a result. Phone type name column is displayed on the list form. Note, it is data of the master table (PhbkPhoneType)
Click to show the picture

project structure

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