ABP.NET创建项目(二)

发布时间 2023-08-11 12:28:28作者: 动量不守恒的浩

ABP.NET 创建项目(二)(进阶部分)

不再以Task类说明

相关文档3_一对一
相关文档4_一对多

7.表设计

类需求:

Student.cs:"学生"的基础字段类(主键Id,为基类所自动生成)
StudentExtra.cs:"学生"的额外字段类(一对一,与Student.cs连接键StudentId)

StudnetConst.cs:存储所有有关"学生"字段的最大数据长度

ClassGroup.cs:"班级"/"小组"的基础字段(主键Id,为基类所自动生成)
StudentClassGroup.cs:"班级"/"小组"的完全字段(有连接键StudentId,ClassGroupId)

ClassGroupConst.cs:存储所有有关"班级"/"小组"字段的最大数据长度
ClassGroupType.cs:枚举类,由于把"班级"与"小组"概念混成一体,因此需要字段说明

image
using Abp.Domain.Entities.Auditing;
using System.Collections.Generic;
using System.Linq;

namespace Practice2023.Students
{
    /// <summary>
    /// 学生信息
    /// </summary>
    public class Student : FullAuditedEntity<long>
    {
        /// <summary>
        /// 姓名
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// 性别
        /// </summary>
        public string Sex { get; set; }
        /// <summary>
        /// 身份证号码
        /// </summary>
        public string IdCardNumber { get; set; }
        /// <summary>
        /// 学号
        /// </summary>
        public string Number { get; set; }
        /// <summary>
        /// 额外信息
        /// </summary>
        public virtual StudentExtra Extra { get; set; }
        /// <summary>
        /// 所在班组
        /// </summary>
        public virtual ICollection<StudentClassGroup> ClassGroups { get; set; } = new List<StudentClassGroup>();
        public Student()
        {
        }
        public Student(string name, string sex = null, string idCardNumber = null, string number = null)
        {
            this.Name = name;
            this.Sex = sex;
            this.IdCardNumber = idCardNumber;
            this.Number = number;
        }
        public void AddExtra(string extra1 = null, string extra2 = null, string extra3 = null)
        {
            this.Extra = new StudentExtra(this.Id, extra1, extra2, extra3);
        }
        public void AddToClassGroup(long classGroupId)
        {
            var classGroup = this.ClassGroups.FirstOrDefault(x => x.ClassGroupId == classGroupId);
            if (classGroup != null) return;
            this.ClassGroups.Add(new StudentClassGroup(this.Id, classGroupId));
        }
        //移除出组
        public void RemoveToClassGroup(long classGroupId)
        {
            var classGroup = this.ClassGroups.FirstOrDefault(x => x.ClassGroupId == classGroupId);
            if (classGroup == null) return;
            this.ClassGroups = this.ClassGroups.Where(x => x.ClassGroupId != classGroupId).ToList();
        }
    }
}

这里看到ClassGroupICollection<StudentClassGroup>。因此1个学生可以参加多个班级/小组

using Abp.Domain.Entities.Auditing;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Practice2023.Students
{
    public class StudentExtra : FullAuditedEntity<long>
    {
        public long StudentId { get; set; }
        public virtual Student Student { get; set; }
        public string Extra1 { get; set; }
        public string Extra2 { get; set; }
        public string Extra3 { get; set; }
        public StudentExtra()
        {
        }
        public StudentExtra(long studentId, string extra1 = null, string extra2 = null, string extra3 = null)
        {
            this.StudentId = studentId;
            this.Extra1 = extra1;
            this.Extra2 = extra2;
            this.Extra3 = extra3;
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Practice2023.Students
{
    public class StudentConsts
    {
        public const int MaxNameLength = 32;
        public const int MaxSexLength = 8;
        public const int MaxIdCardNumberLength = 32;
        public const int MaxNumberLength = 32;
        public const int MaxExtra1Length = 64;
        public const int MaxExtra2Length = 64;
        public const int MaxExtra3Length = 64;
    }
}
using Abp.Domain.Entities.Auditing;
using Practice2023.ClassGroups;
using System;

namespace Practice2023.Students
{
    //我可以理解为ClassGroupExtra?
    public class StudentClassGroup : CreationAuditedEntity<long>, IDeletionAudited
    {
        //小组/班级创始人?
        public long StudentId { get; set; }
        public virtual Student Student { get; set; }
        //小组/班级的主键
        public long ClassGroupId { get; set; }
        public virtual ClassGroup ClassGroup { get; set; }
        public DateTime? DeletionTime { get; set; }
        public bool IsDeleted { get; set; }
        public long? DeleterUserId { get; set; }
        public StudentClassGroup(long studentId, long classGroupId)
        {
            this.StudentId = studentId;
            this.ClassGroupId = classGroupId;
        }
    }
}
using Abp.Domain.Entities.Auditing;

namespace Practice2023.ClassGroups
{
    /// <summary>
    /// 班组
    /// </summary>
    public class ClassGroup : FullAuditedEntity<long>
    {
        /// <summary>
        /// 班组名称
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// 班组类型
        /// </summary>
        public ClassGroupType Type { get; set; }
        public ClassGroup(string name, ClassGroupType type)
        {
            this.Name = name;
            this.Type = type;
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Practice2023.ClassGroups
{
    public class ClassGroupConsts
    {
        public const int MaxNameLength = 128;
    }
}
using System.ComponentModel;

namespace Practice2023.ClassGroups
{
    public enum ClassGroupType
    {
        /// <summary>
        /// 班级
        /// </summary>
        [Description("班级")] Class = 1,
        /// <summary>
        /// 小组
        /// </summary>
        [Description("小组")] Group = 2
    }
}

这里注意为了使用 GetDescription()方法,你需要在程序中引入 System.ComponentModel 命名空间,以便使用 DescriptionAttribute 类。可用于获取上面的"班级"或"小组"Description


8:服务端


一:IClassGroupAppService.cs,ClassGroupAppService:关于班级/小组类的数接口

using Abp.Application.Services;
using Abp.Application.Services.Dto;
using Practice2023.ClassGroups.Dto;
using System.Threading.Tasks;

namespace Practice2023.ClassGroups
{
    public interface IClassGroupAppService : IApplicationService
    {
        Task<ClassGroupDto> GetAsync(long id);
        Task<PagedResultDto<ClassGroupDto>> GetPagedListAsync(PagedClassGroupResultRequestDto input);
        Task CreateAsync(CreateClassGroupDto input);
        Task UpdateAsync(UpdateClassGroupDto input);
        Task DeleteAsync(long id);
    }
}

接口定义了查询(GetAsync),分页列表(GetPagedListAsync),创建班组(CreateAsync),更新班组(UpdateAsync),删除班组(DeleteAsync)。在下面的实例中实现
这里注意

using Abp.Application.Services.Dto;
using Abp.Domain.Repositories;
using Abp.Extensions;
using Abp.Linq.Extensions;
using Abp.UI;
using Microsoft.EntityFrameworkCore;
using Practice2023.ClassGroups.Dto;
using Practice2023.Students;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Practice2023.ClassGroups
{
    public class ClassGroupAppService : Practice2023AppServiceBase, IClassGroupAppService
    {
        //输入ClassGroup作为查询单条数据的类型?
        public readonly IRepository<ClassGroup, long> _classGroupRepository;
        public readonly IRepository<StudentClassGroup, long> _studentClassGroupRepository;

        public ClassGroupAppService(
            IRepository<ClassGroup, long> classGroupRepository,
            IRepository<StudentClassGroup, long> studentClassGroupRepository)
        {
            _classGroupRepository = classGroupRepository;
            _studentClassGroupRepository = studentClassGroupRepository;
        }
        /// <summary>
        /// 获取班组信息
        /// </summary>
        public async Task<ClassGroupDto> GetAsync(long id)
        {
            //FirstOrDefaultAsync只找符合条件的第一个数据
            var entity = await _classGroupRepository.FirstOrDefaultAsync(x => x.Id == id);

            if (entity == null) return null;//return null的话不会报错,而是Swagger上显示null

            //利用自动map把ClassGroup-->ClassGroupDto并return到Swagger上去
            return ObjectMapper.Map<ClassGroupDto>(entity);
        }
        /// <summary>
        /// 获取班组信息分页列表
        /// </summary>
        public async Task<PagedResultDto<ClassGroupDto>> GetPagedListAsync(PagedClassGroupResultRequestDto input)
        {
            //PagedClassGroupResultRequestDto有Keyword,ClassGroupType
            //Keyword是希望部分字符串找Name
            //通过这个Keyword字段告诉我们可以自己添加随意关键字用于后面辅助查询
            var query = _classGroupRepository.GetAll()
                .WhereIf(input.Type.HasValue, x => x.Type == input.Type)
                .WhereIf(!input.Keyword.IsNullOrEmpty(), x => x.Name.Contains(input.Keyword));

            var totalCount = await query.CountAsync();
            var queryResult = await query.OrderByDescending(x => x.Id).PageBy(input).ToListAsync();
            //queryResult是List<ClassGroup>类型
            //PagedResultDto<T>.把泛型类型认定为ClassGroupDto,Map把List<ClassGroup>转为了List<ClassGroupDto>
            //最终return PagedResultDto类型.Swagger应该能识别....
            return new PagedResultDto<ClassGroupDto>(totalCount, ObjectMapper.Map<List<ClassGroupDto>>(queryResult));
        }
        private async Task CheckNameAsync(string name, long? id = null)
        {
            if (name.IsNullOrWhiteSpace()) throw new UserFriendlyException("名称不能为空!");//会显示在Swagger界面
            //下面的WhereIf等价于.Where(id.HasValue ? x => x.Id != id : (Func<Item, bool>)null)
            //当id.HasValue时进行Where操作.筛选出x.Id!=(NUll),(只可能是NULL).的所有数据即已存在于表中的数据
            var isExists = await _classGroupRepository.GetAll().WhereIf(id.HasValue, x => x.Id != id).AnyAsync(x => x.Name == name);
            if (isExists) throw new UserFriendlyException($"名称“{name}”已存在!");//会显示在Swagger界面
        }
        /// <summary>
        /// 创建班组
        /// </summary>
        public async Task CreateAsync(CreateClassGroupDto input)
        {
            //Swagger界面只允许输入`Name`和`Type`<--CreateClassGroupDto.cs所导致的
            await CheckNameAsync(input.Name);
            //这种写法也可以
            // var entity = new ClassGroup(input.Name, input.Type);
            //或者使用自动Map函数将CreateClassGroupDto -->  ClassGroup
            var entity = ObjectMapper.Map<ClassGroup>(input);
            //ClassGroup类型就可以直接Insert了?!
            await _classGroupRepository.InsertAsync(entity);
        }
        /// <summary>
        /// 更新班组
        /// </summary>
        public async Task UpdateAsync(UpdateClassGroupDto input)
        {
            //input只有Name和ClassGroupType<---UpdateClassGroupDto
            await CheckNameAsync(input.Name, input.Id);
            //这里的x是ClassGroup类型
            var entity = await _classGroupRepository.FirstOrDefaultAsync(x => x.Id == input.Id);

            if (entity == null) throw new UserFriendlyException("找不到信息!");
            //Map方法用于将一个对象(input)UpdateClassDto的属性值映射到另一个对象(entity)ClassGroup的属性上。
            ObjectMapper.Map(input, entity);
            //entity是ClassGroup类型
            await _classGroupRepository.UpdateAsync(entity);
        }
        /// <summary>
        /// 删除班组
        /// </summary>
        public async Task DeleteAsync(long id)
        {
            var entity = await _classGroupRepository.FirstOrDefaultAsync(x => x.Id == id);
            if (entity == null) return;
            //先删掉副表
            await _studentClassGroupRepository.DeleteAsync(x => x.ClassGroupId == id);
            //再删主表
            await _classGroupRepository.DeleteAsync(x => x.Id == id);
        }
    }
}

  使用了Abp.Domain.Repositories;命名空间的IRepository。使用其带的各类操作.由于这里我们并不没有像Task类一样自定义了一个操作所以不需要再建之前的ITaskRepository.cs,TaskRepository.cs.
  请注意每个函数的注释解释,可用于理解函数
需要再注意到

/// <summary>
/// 更新班组
/// </summary>

这个语句,可以在SwaggerUI界面添加中文注释:

image

二:CreateClassGroupDto.cs,UpdateClassGroupDto,PagedClassGroupResultRequestDto.cs,ClassGroupMapProfile,ClassGroupDto:关于班级/小组类的数接口。前三者是用于Swagger的输入,第四者用于第五者与前三者的map操作。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Practice2023.ClassGroups.Dto
{
    public class CreateClassGroupDto
    {
        public string Name { get; set; }

        public ClassGroupType Type { get; set; }
    }
}
using Abp.Application.Services.Dto;

namespace Practice2023.ClassGroups.Dto
{
    public class UpdateClassGroupDto : EntityDto<long>
    {
        public string Name { get; set; }
        public ClassGroupType Type { get; set; }
    }
}
using Abp.Application.Services.Dto;

namespace Practice2023.ClassGroups.Dto
{
    public class PagedClassGroupResultRequestDto : PagedResultRequestDto
    {
        public string Keyword { get; set; }
        public ClassGroupType? Type { get; set; }
    }
}

这里Keyword是自定义的Swagger输入字段,可用于辅助自定义函数,可随意增加或命名,详情见ClassGroupAppService.cs的注释

using Abp.Application.Services.Dto;

namespace Practice2023.ClassGroups.Dto
{
    public class PagedClassGroupResultRequestDto : PagedResultRequestDto
    {
        public string Keyword { get; set; }
        public ClassGroupType? Type { get; set; }
    }
}
using AutoMapper;
using Practice2023.Helpers;

namespace Practice2023.ClassGroups.Dto
{
    public class ClassGroupMapProfile : Profile
    {
        public ClassGroupMapProfile()
        {
            //ClassGroup-->ClassGroup
            //ClassGroup.Type.GetDescription()-->ClassGroupDto.TypeDecription
            CreateMap<ClassGroup, ClassGroupDto>()
                .ForMember(x => x.TypeDecription, e => e.MapFrom(x => x.Type.GetDescription()));
            //CreateClassGroupDto-->ClassGroup
            CreateMap<CreateClassGroupDto, ClassGroup>();
            //UpdateClassGroupDto-->ClassGroup
            CreateMap<UpdateClassGroupDto, ClassGroup>();
        }
    }
}

这里注意GetDescription()是调用了ABP框架自建的EUNM函数(using Practice2023.Helpers;命名空间)

using Abp.Application.Services.Dto;
using System;

namespace Practice2023.ClassGroups.Dto
{
    public class ClassGroupDto : EntityDto<long>
    {
        public string Name { get; set; }
        public ClassGroupType Type { get; set; }
        public string TypeDecription { get; set; }
        public DateTime CreationTime { get; set; }
    }
}

三:IStudentAppService.cs,StudentAppService:关于班级/小组类的数接口

using Abp.Application.Services;
using Abp.Application.Services.Dto;
using Practice2023.Students.Dto;
using System.Threading.Tasks;

namespace Practice2023.Students
{
    public interface IStudentAppService: IApplicationService
    {
        Task<StudentDto> GetAsync(long id);
        Task<PagedResultDto<StudentDto>> GetPagedListAsync(PagedStudentResultRequestDto input);
        Task CreateAsync(CreateStudentDto input);
        Task UpdateAsync(UpdateStudentDto input);
        Task DeleteAsync(long id);
    }
}
using Abp.Application.Services.Dto;
using Abp.Domain.Repositories;
using Abp.Extensions;
using Abp.Linq.Extensions;
using Abp.UI;
using Microsoft.EntityFrameworkCore;
using Practice2023.Students.Dto;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Practice2023.Students
{
    public class StudentAppService : Practice2023AppServiceBase, IStudentAppService
    {
        private readonly IRepository<Student, long> _studentRepository;
        private readonly IRepository<StudentExtra, long> _studentExtraRepository;
        private readonly IRepository<StudentClassGroup, long> _studentClassGroupRepository;

        public StudentAppService(
            IRepository<Student, long> studentRepository,
            IRepository<StudentExtra, long> studentExtraRepository,
            IRepository<StudentClassGroup, long> studentClassGroupRepository)
        {
            _studentRepository = studentRepository;
            _studentExtraRepository = studentExtraRepository;
            _studentClassGroupRepository = studentClassGroupRepository;
        }

        /// <summary>
        /// 获取学生信息
        /// </summary>
        public async Task<StudentDto> GetAsync(long id)
        {
            //_studentRepository是由Student类生成,当然有.Extra和.ClassGroups
            //ThenInclude应该是基于ICollection等可迭代对象的Include操作,换在mysql语句中为InnerJoin
            //ClassGroups在Student中定义为ICollection类型
            var entity = await _studentRepository.GetAll()
                .Include(x => x.Extra)
                .Include(x => x.ClassGroups).ThenInclude(x => x.ClassGroup)
                .FirstOrDefaultAsync(x => x.Id == id);

            if (entity == null) return null;
            //Student-->StudentDto-->Swagger自识别
            return ObjectMapper.Map<StudentDto>(entity);
        }

        /// <summary>
        /// 获取学生信息分页列表
        /// </summary>
        public async Task<PagedResultDto<StudentDto>> GetPagedListAsync(PagedStudentResultRequestDto input)
        {
            //PagedStudentResultRequestDto仅含
            //KeyWord是筛查Name/Number/IdCardNumber可能某一部分可能含有的关键词
            var query = _studentRepository.GetAll()
                .Include(x => x.Extra)
                .Include(x => x.ClassGroups).ThenInclude(x => x.ClassGroup)
                .WhereIf(!input.Keyword.IsNullOrWhiteSpace(), x =>
                    x.Name.Contains(input.Keyword) ||
                    x.Number.Contains(input.Keyword) ||
                    x.IdCardNumber.Contains(input.Keyword));

            //var test=query.ToQueryString();此语句用于输出query等价的MySql语句,可加断点查看test的内容

            var totalCount = await query.CountAsync();
            var queryResult = await query.OrderByDescending(x => x.Id).PageBy(input).ToListAsync();

            return new PagedResultDto<StudentDto>(totalCount, ObjectMapper.Map<List<StudentDto>>(queryResult));
        }

        private async Task CheckIdCardNumberAsync(string idCardNumber, long? id = null)
        {
            if (idCardNumber.IsNullOrWhiteSpace()) return;
            var isExists = await _studentRepository.GetAll().WhereIf(id.HasValue, x => x.Id != id).AnyAsync(x => x.IdCardNumber == idCardNumber);
            if (isExists) throw new UserFriendlyException($"身份证号码“{idCardNumber}”已存在!");
        }

        private async Task CheckNumberAsync(string number, long? id = null)
        {
            if (number.IsNullOrWhiteSpace()) return;
            var isExists = await _studentRepository.GetAll().WhereIf(id.HasValue, x => x.Id != id).AnyAsync(x => x.Number == number);
            if (isExists) throw new UserFriendlyException($"学号“{number}”已存在!");
        }

        /// <summary>
        /// 创建学生
        /// </summary>
        public async Task CreateAsync(CreateStudentDto input)
        {
            await CheckIdCardNumberAsync(input.IdCardNumber);
            await CheckNumberAsync(input.Number);

            var entity = new Student(input.Name, input.Sex, input.IdCardNumber, input.Number); // 也可以使用AutoMapper

            entity.AddExtra(input.Extra.Extra1, input.Extra.Extra2, input.Extra.Extra3);

            //input.ClassGroupIds类型是List<Long>
            foreach (var classGroupId in input.ClassGroupIds)
            {
                entity.AddToClassGroup(classGroupId);
            }

            await _studentRepository.InsertAsync(entity);
        }

        /// <summary>
        /// 更新学生
        /// </summary>
        public async Task UpdateAsync(UpdateStudentDto input)
        {
            await CheckIdCardNumberAsync(input.IdCardNumber, input.Id);
            await CheckNumberAsync(input.Number, input.Id);

            var entity = await _studentRepository.GetAll()
                .Include(x => x.Extra)
                .Include(x => x.ClassGroups).ThenInclude(x => x.ClassGroup)
                .FirstOrDefaultAsync(x => x.Id == input.Id);

            if (entity == null) return;

            entity.Name = input.Name;
            entity.Sex = input.Sex;
            entity.IdCardNumber = input.IdCardNumber;
            entity.Number = input.Number;

            ObjectMapper.Map(input.Extra, entity.Extra);

            var oriClassGroupIds = entity.ClassGroups.Select(x => x.ClassGroupId).ToList();
            var newClassGroupIds = input.ClassGroupIds.Except(oriClassGroupIds);
            var delClassGroupIds = oriClassGroupIds.Except(input.ClassGroupIds);

            foreach (var newClassGroupId in newClassGroupIds)
            {
                entity.AddToClassGroup(newClassGroupId);
            }

            foreach(var delClassGroupId in delClassGroupIds)
            {
                entity.RemoveToClassGroup(delClassGroupId);
            }

            await _studentRepository.UpdateAsync(entity);
        }

        /// <summary>
        /// 删除学生
        /// </summary>
        public async Task DeleteAsync(long id)
        {
            var entity = await _studentRepository.FirstOrDefaultAsync(x => x.Id == id);
            if (entity == null) return;

            await _studentClassGroupRepository.DeleteAsync(x => x.StudentId == entity.Id);
            await _studentExtraRepository.DeleteAsync(x => x.StudentId == entity.Id);
            await _studentRepository.DeleteAsync(x => x.Id == entity.Id);
        }
    }
}

四:CreateStudentDto.cs,UpdateStudentDto.cs,PagedStudentResultRequestDto.cs,StudentMapProfile.cs,StudentDto.cs,StudentExtraDto.cs,StudentClassGroupDto.cs:类似

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace Practice2023.Students.Dto
{
    public class CreateStudentDto
    {
        /// <summary>
        /// 姓名
        /// </summary>
        [Required]
        public string Name { get; set; }

        /// <summary>
        /// 性别
        /// </summary>
        public string Sex { get; set; }

        /// <summary>
        /// 身份证号码
        /// </summary>
        public string IdCardNumber { get; set; }

        /// <summary>
        /// 学号
        /// </summary>
        public string Number { get; set; }

        /// <summary>
        /// 额外信息
        /// </summary>
        public StudentExtraDto Extra { get; set; } = new StudentExtraDto();

        /// <summary>
        /// 所在班组
        /// </summary>
        public List<long> ClassGroupIds { get; set; } = new List<long>();
    }
}
using Abp.Application.Services.Dto;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace Practice2023.Students.Dto
{
    public class UpdateStudentDto : EntityDto<long>
    {
        /// <summary>
        /// 姓名
        /// </summary>
        [Required]
        public string Name { get; set; }

        /// <summary>
        /// 性别
        /// </summary>
        public string Sex { get; set; }

        /// <summary>
        /// 身份证号码
        /// </summary>
        public string IdCardNumber { get; set; }

        /// <summary>
        /// 学号
        /// </summary>
        public string Number { get; set; }

        /// <summary>
        /// 额外信息
        /// </summary>
        public StudentExtraDto Extra { get; set; } = new StudentExtraDto();

        /// <summary>
        /// 所在班组
        /// </summary>
        public List<long> ClassGroupIds { get; set; } = new List<long>();
    }
}
using Abp.Application.Services.Dto;

namespace Practice2023.Students.Dto
{
    public class PagedStudentResultRequestDto : PagedResultRequestDto
    {
        public string Keyword { get; set; }
    }
}
using AutoMapper;
using Practice2023.Helpers;

namespace Practice2023.Students.Dto
{
    public class StudentMapProfile : Profile
    {
        public StudentMapProfile()
        {
            CreateMap<Student, StudentDto>();
            CreateMap<StudentExtra, StudentExtraDto>().ReverseMap();
            CreateMap<StudentClassGroup, StudentClassGroupDto>()
                .ForMember(x => x.Name, e => e.MapFrom(x => x.ClassGroup.Name))
                .ForMember(x => x.Type, e => e.MapFrom(x => x.ClassGroup.Type))
                .ForMember(x => x.TypeDescription, e => e.MapFrom(x => x.ClassGroup.Type.GetDescription()));
        }
    }
}
using Abp.Application.Services.Dto;
using System;
using System.Collections.Generic;

namespace Practice2023.Students.Dto
{
    public class StudentDto : EntityDto<long>
    {
        /// <summary>
        /// 姓名
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 性别
        /// </summary>
        public string Sex { get; set; }

        /// <summary>
        /// 身份证号码
        /// </summary>
        public string IdCardNumber { get; set; }

        /// <summary>
        /// 学号
        /// </summary>
        public string Number { get; set; }

        /// <summary>
        /// 创建时间
        /// </summary>
        public DateTime CreationTime { get; set; }

        /// <summary>
        /// 额外信息
        /// </summary>
        public StudentExtraDto Extra { get; set; } = new StudentExtraDto();

        /// <summary>
        /// 所在班组
        /// </summary>
        public List<StudentClassGroupDto> ClassGroups { get; set; } = new List<StudentClassGroupDto>();

    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Practice2023.Students.Dto
{
    public class StudentExtraDto
    {
        public string Extra1 { get; set; }

        public string Extra2 { get; set; }

        public string Extra3 { get; set; }
    }
}
using Practice2023.ClassGroups;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Practice2023.Students.Dto
{
    public class StudentClassGroupDto
    {
        public string Name { get; set; }

        public ClassGroupType Type { get; set; }

        public string TypeDescription { get; set; }
    }
}


9:Practice2023DbContext.cs:这是用于数据入库的文件

using Microsoft.EntityFrameworkCore;
using Abp.Zero.EntityFrameworkCore;
using Practice2023.Authorization.Roles;
using Practice2023.Authorization.Users;
using Practice2023.MultiTenancy;
using Practice2023.Students;
using Practice2023.ClassGroups;

namespace Practice2023.EntityFrameworkCore
{
    public class Practice2023DbContext : AbpZeroDbContext<Tenant, Role, User, Practice2023DbContext>
    {
        /* Define a DbSet for each entity of the application */

        public DbSet<Student> Students { get; set; }

        public DbSet<StudentExtra> StudentExtras { get; set; }

        public DbSet<ClassGroup> ClassGroups { get; set; }

        public DbSet<StudentClassGroup> StudentClassGroups { get; set; }
        
        public Practice2023DbContext(DbContextOptions<Practice2023DbContext> options)
            : base(options)
        {
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Entity<Student>(b =>
            {
                b.ToTable("Students");

                b.Property(x => x.Name).IsRequired().HasMaxLength(StudentConsts.MaxNameLength);
                b.Property(x => x.Sex).HasMaxLength(StudentConsts.MaxSexLength);
                b.Property(x => x.IdCardNumber).HasMaxLength(StudentConsts.MaxIdCardNumberLength);
                b.Property(x => x.Number).HasMaxLength(StudentConsts.MaxNumberLength);

                b.HasOne(x => x.Extra).WithOne(x => x.Student).HasForeignKey<StudentExtra>(x => x.StudentId); // 一对一关系
                b.HasMany(x => x.ClassGroups).WithOne(x => x.Student).HasForeignKey(x => x.StudentId); // 一对多关系

                b.HasIndex(x => x.Name);
                b.HasIndex(x => x.IdCardNumber);
                b.HasIndex(x => x.Number);
            });

            modelBuilder.Entity<StudentExtra>(b =>
            {
                b.ToTable("StudentExtras");

                b.Property(x => x.Extra1).HasMaxLength(StudentConsts.MaxExtra1Length);
                b.Property(x => x.Extra2).HasMaxLength(StudentConsts.MaxExtra2Length);
                b.Property(x => x.Extra3).HasMaxLength(StudentConsts.MaxExtra3Length);
            });

            modelBuilder.Entity<ClassGroup>(b =>
            {
                b.ToTable("ClassGroups");

                b.Property(x => x.Name).IsRequired().HasMaxLength(ClassGroupConsts.MaxNameLength);

                b.HasIndex(x => x.Name);
            });

            modelBuilder.Entity<StudentClassGroup>(b =>
            {
                b.ToTable("StudentClassGroups");

                b.HasKey(x => new { x.StudentId, x.ClassGroupId });
            });
        }
    }
}

这里在上面定义Dbset<T>后就能自动更新数据。但是入库的表头名是用的原来类中的字段名。
modelBuilder.Entity可以更加体系性地入库.
这里注意语句

b.HasOne(x => x.Extra).WithOne(x => x.Student).HasForeignKey<StudentExtra>(x => x.StudentId); // 一对一关系
b.HasMany(x => x.ClassGroups).WithOne(x => x.Student).HasForeignKey(x => x.StudentId); // 一对多关系

涉及到了表间连接关系


10.DeBug与问题解答

Q1:泛型的用法
A1:

public class MyGenericClass<T>
{
    private T value;

    public MyGenericClass(T value)
    {
        this.value = value;
    }

    public T GetValue()
    {
        return value;
    }
}

public class Program
{
    static void Main(string[] args)
    {
        MyGenericClass<int> intClass = new MyGenericClass<int>(10);
        int intValue = intClass.GetValue();

        MyGenericClass<string> stringClass = new MyGenericClass<string>("Hello");
        string stringValue = stringClass.GetValue();

        Console.WriteLine(intValue); // 输出:10
        Console.WriteLine(stringValue); // 输出:Hello
    }
}


Q2:Inner join

A2:
  内连接(Inner Join)是一种SQL操作,用于将两个或多个表中的行连接起来,只返回匹配的行。
假设我们有两个表:表A和表B。内连接将返回同时在表A和表B中有匹配的行。任何一边有NULL的都不会返回