abp-vnext-pro 实战(八,聚合根的写法,客户M:N地址)

发布时间 2023-08-19 23:26:14作者: Gu

参考数据字典模块的写法

/// <summary>
/// 地址
/// </summary>
public class Address : FullAuditedAggregateRoot<Guid>,IMultiTenant
{

    public Guid? TenantId { get; protected set; }

    /// <summary>
    /// 公司名
    /// </summary>
    public string Company { get; private set; }
    /// <summary>
    /// 城市
    /// </summary>
    public string City { get; private set; }
    /// <summary>
    /// 地址
    /// </summary>
    public string Address1 { get; private set; }
    /// <summary>
    /// 邮编
    /// </summary>
    public string ZipPostalCode { get; private set; }
    /// <summary>
    /// 电话
    /// </summary>
    public string PhoneNumber { get; private set; }

    public List<Customer> Customers { get; private set; }  


}

public class Customer : FullAuditedAggregateRoot<Guid>, IMultiTenant
{

    public Guid? TenantId { get; protected set; }
    /// <summary>
    /// 客户名字
    /// </summary>
    public string Name { get; private set; }
    /// <summary>
    /// 客户编号
    /// </summary>
    public string Code { get; private set; }
    /// <summary>
    /// 客户简称
    /// </summary>
    public string ShortName { get; private set; }
    /// <summary>
    /// 组织代码
    /// </summary>
    public string OrgCode { get; private set; }


    public List<Address> CustAddresses { get; private set; }
}

application.contract 项目里的PageAddressInput,增加一个客户字段custId用于查询关联, nswag/refresh.bat 要执行刷新。

public class PageAddressInput: PagingBase
{
    public Guid CustId { get; set; }
}

 对应的CreateAddressInput 也要加一个CustId的字段,而更新地址时不需要改变CustId

 

EntityFrameworkCore 项目里 ERPDbContextModelCreatingExtensions 多对多的默认写法

                b.HasMany(b => b.CustAddresses).WithMany(c=>c.Customers)
                .UsingEntity("CustAddresses");

对应生成的数据库结构是这样

            migrationBuilder.CreateTable(
                name: "CustAddresses",
                columns: table => new
                {
                    CustAddressesId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
                    CustomersId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci")
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_CustAddresses", x => new { x.CustAddressesId, x.CustomersId });
                    table.ForeignKey(
                        name: "FK_CustAddresses_CrmAddresses_CustAddressesId",
                        column: x => x.CustAddressesId,
                        principalTable: "CrmAddresses",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Cascade);
                    table.ForeignKey(
                        name: "FK_CustAddresses_CrmCustomer_CustomersId",
                        column: x => x.CustomersId,
                        principalTable: "CrmCustomer",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Cascade);
                })
                .Annotation("MySql:CharSet", "utf8mb4");

            migrationBuilder.CreateIndex(
                name: "IX_CustAddresses_CustomersId",
                table: "CustAddresses",
                column: "CustomersId");

 假如要指定中间表的字段名,则2个对象都要指定

 builder.Entity<Customer>(b =>
            {
                b.ToTable(ERPConsts.CRMDbTablePrefix + "Customer", ERPConsts.DbSchema);
                b.ConfigureByConvention(); 
                b.HasMany(b => b.CustAddresses).WithMany(c=>c.Customers)
                .UsingEntity("CustAddresses",
                l => l.HasOne(typeof(Customer)).WithMany().HasForeignKey("Customer_Id").HasPrincipalKey(nameof(Customer.Id)),
                r => r.HasOne(typeof(Address)).WithMany().HasForeignKey("Address_Id").HasPrincipalKey(nameof(Address.Id)),
                j => j.HasKey("Customer_Id", "Address_Id"));

                //The skip navigation 'Address.Customers' doesn't have a foreign key associated with it.
                //Every skip navigation must have a configured foreign key.
            });

            builder.Entity<Address>(b =>
            {
                //b.ToTable(ERPConsts.CRMDbTablePrefix + nameof(Address).Pluralize
                b.ToTable(ERPConsts.CRMDbTablePrefix + "Addresses");

                b.ConfigureByConvention();
                b.HasMany(b => b.Customers).WithMany(c => c.CustAddresses)
                .UsingEntity("CustAddresses",
                l => l.HasOne(typeof(Customer)).WithMany().HasForeignKey("Customer_Id").HasPrincipalKey(nameof(Customer.Id)),
                r => r.HasOne(typeof(Address)).WithMany().HasForeignKey("Address_Id").HasPrincipalKey(nameof(Address.Id)),
                j => j.HasKey("Customer_Id", "Address_Id"));
            });

 

=======================================多对多前端页面=============================

客户列表界面A,点击地址按钮,新开一个tab页面B,显示该客户下面的地址列表。

A页面path= setting 的某个链接或按钮,按下新开tab 页面

1. 先在router/module里定义一个隐藏菜单,path是 preViewCode

 {
      path: 'preViewCode',
      name: 'PreViewCode',
      component: () => import('/@/views/generators/PreViewCode.vue'),
      meta: {
        title: '预览',
        icon: 'ant-design:file-sync-outlined',
        hideMenu: true,
      },

2. A页面index.vue里面 click事件里用router.push传path和参数

<a-button class="!ml-4" type="primary" @click="addTabPage">预览</a-button>  

A页面要引入 useRouter 

import { useRouter } from 'vue-router';

setup() {
const router = useRouter();

}

      //跳转到地址列表页,并传递客户ID
      async function addTabPage(record: Recordable) {
        try {
          await router.push({
            name: 'AddressPage',
            query: { custId: record.id },
          });
        } catch (error) { }
      }

3. 再在B页面的index.vue 页面, 引入useRoute ,而不是useRouter, 差一个字母,很容易看错

  import { useRoute } from 'vue-router';
  setup() {
      const route = useRoute();
      const queryParams = route.query;
      console.log(queryParams.custId);

      // table配置
      const [registerTable, { reload }] = useTable({
        columns: tableColumns,
        formConfig: {
          labelWidth: 70,
          schemas: searchFormSchema,
        },
        api: pageAsync11,  //



       function pageAsync11(a : any) {
         console.log("queryParams.custId传到params: PageAddressInput");
        //params: any
        a.custId=queryParams.custId;
        return pageAsync(a);
      }

Domain 项目的 AddressManager

   private readonly IAddressRepository _addressRepository;
    private readonly ICustomerRepository _customerRepository;
    private readonly IObjectMapper _objectMapper;

    public AddressManager(IAddressRepository addressRepository, IObjectMapper objectMapper, ICustomerRepository customerRepository)
    {
        _addressRepository = addressRepository;
        _objectMapper = objectMapper;
        _customerRepository = customerRepository;
    }
    public async Task<List<AddressDto>> GetListAsync(Guid custId,int maxResultCount = 10, int skipCount = 0)
    {
        var list = await _customerRepository.GetCustAddressListAsync(custId, maxResultCount, skipCount);
        //var list = await _addressRepository.GetListAsync(maxResultCount, skipCount);
        return _objectMapper.Map<List<Address>, List<AddressDto>>(list);
    }

    /// <summary>
    /// 创建地址
    /// </summary>
    public async Task<AddressDto> CreateAsync(
        Guid id,
        int countryId,
        int stateProvinceId,
        string firstName,
        string lastName,
        string email,
        string company,
        string city,
        string address1,
        string zipPostalCode,
        string phoneNumber,
        Guid custId
    )
    {
        var entity = new Address(id, countryId, stateProvinceId, firstName, lastName, email, company, city, address1, zipPostalCode, phoneNumber);
        entity = await _addressRepository.InsertAsync(entity);
        //客户地址增加关联
        var cust = await _customerRepository.GetAsync(custId);
        cust.AddAddress(entity);
        return _objectMapper.Map<Address, AddressDto>(entity);
    }