416.2 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 forPhbkPhoneMtm
-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, 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
- Using Dbcontext Wizard we define 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<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
- 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);
#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 theConfigurefirstappDbContext(this ModelBuilder builder)
-method of thefirstappDbContextCreatingExtensions
-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 keyb.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 therupbes.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 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 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 meansPPhoneTypeName
-prop will be generated for DTO-class.
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 thePPhoneTypeName
-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
- services (
- Regenerate
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)