ABP.NET创建项目(一)

发布时间 2023-08-10 13:08:51作者: 动量不守恒的浩

ABP.NET 创建项目

相关文档1(下半部分)
相关文档2(MySql部分)

1.按照相关文档1的上半部分下载ABP

2.需要额外安装的NuGet包

3.需要自己建立的文件(Red)&需要更改的原始文件(Green)

3.1:需要更改的原始文件(Green)

一 :MyProjectDbContext.cs:

using Microsoft.EntityFrameworkCore;
using Abp.Zero.EntityFrameworkCore;
using MyProject.Authorization.Roles;
using MyProject.Authorization.Users;
using MyProject.MultiTenancy;
using MyProject.MyTest;

namespace MyProject.EntityFrameworkCore
{
    public class MyProjectDbContext : AbpZeroDbContext<Tenant, Role, User, MyProjectDbContext>
    {
        /* Define a DbSet for each entity of the application */
        public DbSet<Simple> Simples { get; set; }
        public virtual DbSet<Task> Tasks { get; set; }
        public virtual DbSet<Person> People { get; set; }

        public MyProjectDbContext(DbContextOptions<MyProjectDbContext> options)
            : base(options)
        {
        }
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Entity<Simple>(p =>
            {
                p.ToTable("Simples");
                p.Property(x => x.Name).IsRequired(true).HasMaxLength(20);
                p.Property(x => x.Details).HasMaxLength(100);
            });
        }
    }
}
    这里我并没有覆写OnModelCreating方法中显式配置Task和Person实体的映射和属性。但是在Swagger上进行的Task,Person字段操作都成功地作用于本地的MySql库中了。这个问题在[6.DeBug与问题解答](#6)中提到。

二 :MyProjectDbContextConfigurer.cs:

由于使用的是MySql而不是默认的SqlServer,根据相关文档2进行MySql内容的替换。

using System.Data.Common;
using Microsoft.EntityFrameworkCore;
namespace MyProject.EntityFrameworkCore
{
    public static class MyProjectDbContextConfigurer
    {
        public static void Configure(DbContextOptionsBuilder<MyProjectDbContext> builder, string connectionString)
        {
            var serverVersion = ServerVersion.AutoDetect(connectionString);
            builder.UseMySql(connectionString, serverVersion);
        }

        public static void Configure(DbContextOptionsBuilder<MyProjectDbContext> builder, DbConnection connection)
        {
            var serverVersion = ServerVersion.AutoDetect(connection.ConnectionString);
            builder.UseMySql(connection, serverVersion);
        }
    }
}

这里ConnectionString引用的是

因此需要进行更改:

3.2:需要自己建立的文件(Red)

一 :Task.cs:这是一个属性,定义了<任务>的各个属性字段

using Abp.Domain.Entities;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Abp.Application.Services.Dto;

namespace MyProject.MyTest
{
    public class Task : Entity<long>
    {
        [ForeignKey("AssignedPersonId")]
        public virtual Person AssignedPerson { get; set; }
        public virtual int? AssignedPersonId { get; set; }
        public virtual string Description { get; set; }
        public virtual DateTime CreationTime { get; set; }
        public virtual TaskState State { get; set; }
        public Task()
        {
            CreationTime = DateTime.Now;
            State = TaskState.Active;
        }
    }
    public enum TaskState
    {
        Active,
        Inactive,
    }
}

二 :ITaskRepository.cs,TaskRepository.cs:Task的仓储接口以及实现例。用于被TaskAppService调用以进行增删改查或自定义操作

由于ITaskRepository继承了原生的IRepository因此具有基类中早已定义好的各类增删改查函数。

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

namespace MyProject.MyTest.Method
{

    /*通过仓储模式,可以更好把业务代码与数据库操作代码更好的分离,可以针对不同的数据库有不同的实现类,而业务代码不需要修改。
定义仓储接口的代码写到Core项目中,因为仓储接口是领域层的一部分.
我们先定义Task的仓储接口*/
    public interface ITaskRepository : IRepository<Task, long>
    {
        List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state);
    }
    //接口待实现
}

由于TaskRepository继承了原生的MyProjectRepositoryBase因此具有基类中早已定义好的GetAll()等query处理函数

using MyProject.EntityFrameworkCore.Repositories;
using MyProject.MyTest.Method;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Abp.EntityFrameworkCore;
using MyProject.EntityFrameworkCore;

namespace MyProject.MyTest
{
    public class TaskRepository : MyProjectRepositoryBase<Task, long>, ITaskRepository
    {
        public TaskRepository(IDbContextProvider<MyProjectDbContext> dbContextProvider)
        : base(dbContextProvider)
        {
        }
        public List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state)
        {
            //在仓储方法中,不用处理数据库连接、DbContext和数据事务,ABP框架会自动处理。
            var query = GetAll(); //GetAll() 返回一个 IQueryable<T>接口类型
            //添加一些Where条件
            if (assignedPersonId.HasValue)
            {
                query = query.Where(task => task.AssignedPerson.Id == assignedPersonId.Value);
            }
            if (state.HasValue)
            {
                query = query.Where(task => task.State == state);
            }
            return query
                .OrderByDescending(task => task.CreationTime)
                .Include(task => task.AssignedPerson)
                .ToList();
        }
    }
}

三 :ITaskAppService.cs,TaskAppService.cs:前者接口负责定义Task的增(删)改查,后者负责具体实现

using Abp.Application.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MyProject.MyTest.Dto;
namespace MyProject.MyTest
{
    public interface ITaskAppService : IApplicationService
    {
        GetTasksOutput GetTasks(GetTasksInput input);
        void UpdateTask(UpdateTaskInput input);
        void CreateTask(CreateTaskInput input);
    }
}
using Abp.Application.Services;
using Abp.Domain.Repositories;
using AutoMapper;
using Microsoft.Extensions.Logging;
using MyProject.MyTest.Method;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MyProject.MyTest.Dto;

namespace MyProject.MyTest
{
    public class TaskAppService : ApplicationService, ITaskAppService
    {
        private readonly ITaskRepository _taskRepository;
        private readonly IRepository<Person> _personRepository;
        /// <summary>
        /// 构造函数自动注入我们所需要的类或接口
        /// </summary>
        public TaskAppService(ITaskRepository taskRepository, IRepository<Person> personRepository)
        {
            _taskRepository = taskRepository;
            _personRepository = personRepository;
        }
        public GetTasksOutput GetTasks(GetTasksInput input)
        {
            //调用Task仓储的特定方法GetAllWithPeople
            var tasks = _taskRepository.GetAllWithPeople(input.AssignedPersonId, input.State);

            //用AutoMapper自动将List<Task>转换成List<TaskDto>
            return new GetTasksOutput
            {
                Tasks = ObjectMapper.Map<List<TaskDto>>(tasks)
                //Tasks = Mapper.Map<List<TaskDto>>(tasks)
            };
        }
        public void UpdateTask(UpdateTaskInput input)
        {
            //可以直接Logger,它在ApplicationService基类中定义的
            Logger.Info("Updating a task for input: " + input);
            //通过仓储基类的通用方法Get,获取指定Id的Task实体对象
            var task = _taskRepository.Get(input.TaskId);
            //修改task实体的属性值
            if (input.State.HasValue)
            {
                task.State = input.State.Value;
            }
            if (input.AssignedPersonId.HasValue)
            {
                task.AssignedPerson = _personRepository.Load(input.AssignedPersonId.Value);
            }
            //我们都不需要调用Update方法
            //因为应用服务层的方法默认开启了工作单元模式(Unit of Work)
            //ABP框架会工作单元完成时自动保存对实体的所有更改,除非有异常抛出。有异常时会自动回滚,因为工作单元默认开启数据库事务。
        }
        public void CreateTask(CreateTaskInput input)
        {
            // TODO:
            var persons_query_count = _personRepository.GetAll()
                                         .Where(x => x.Id == input.AssignedPersonId)
                                         .Count();
            if (persons_query_count > 0)
            {
                //存在该委托人,可以创建任务
                Logger.Info("Creating a task for input: " + input);
            }
            else
            {
                //不存在该人,不能创建对应任务
                throw new InvalidOperationException();
            }
            //通过输入参数,创建一个新的Task实体
            var task = new Task { Description = input.Description };

            if (input.AssignedPersonId.HasValue)
            {
                task.AssignedPersonId = input.AssignedPersonId.Value;
            }
            //调用仓储基类的Insert方法把实体保存到数据库中
            _taskRepository.Insert(task);
        }
    }
}

  在TaskAppService这个类中每定义一个函数(哪怕空函数),Swagger界面就会显示一个对应的接口。(可能是基类ApplicationService的带来的方法)
  ITaskRepository taskRepository``IRepository<Person> personRepository两个成员用于使用原生或者自制的增删改查操作。

四 :UpdateTasksInput.cs,CreateTasksInput.cs,GetTasksInput,GetTasksOutput:这四个是对接Swagger的输入/输出字段

using Abp.Runtime.Validation;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyProject.MyTest.Dto
{
    public class UpdateTaskInput //, ICustomValidate
    {
        [Range(1, long.MaxValue)]
        public long TaskId { get; set; }
        public int? AssignedPersonId { get; set; }
        public TaskState? State { get; set; }
        //public void AddValidationErrors(CustomValidationContext context)
        //{
        //    if (AssignedPersonId == null && State == null)
        //    {
        //        context.Results.Add(new ValidationResult("AssignedPersonId和State不能同时为空!", new[] { "AssignedPersonId", "State" }));
        //    }
        //    throw new NotImplementedException();
        //}
    }
}
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyProject.MyTest.Dto
{
    public class CreateTaskInput : IInputDto
    {
        public int? AssignedPersonId { get; set; }
        [Required]//说明Description为必填项
        public string Description { get; set; }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyProject.MyTest.Dto
{
    public class GetTasksInput
    {
            public int? AssignedPersonId { get; set; }
            public TaskState? State { get; set; }

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

namespace MyProject.MyTest.Dto
{
    public class GetTasksOutput
    {
        public List<TaskDto> Tasks { get; set; }
        // 其他属性
    }
}

五:TaskDto.cs,TaskMapProfile.cs:Dto的实现用于被后者文件调用,随后即可使用ObjectMapper.Map实现"AutoMapper自动将List转换成List"

using Abp.Application.Services.Dto;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MyProject.MyTest;

namespace MyProject.MyTest.Dto
{
    public class TaskDto : EntityDto<int>
    {
        public virtual Person AssignedPerson { get; set; }
        public int AssignedPersonId { get; set; }
        public string Description { get; set; }
        public virtual DateTime CreationTime { get; set; }
        public TaskState State { get; set; }
        // 其他属性
    }
}
using AutoMapper;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyProject.MyTest.Dto
{
    public class TaskMapProfil : Profile
    {
        public TaskMapProfil()
        {
            CreateMap<Task, TaskDto>();
            CreateMap<TaskDto, Task>();
        }
    }
}

六:其余诸如 Simple,Person类也是同样类似Task的同上方法。

这里需要注意的是外键约束问题(主次表连接键)

5.数据库的连接&Swagger UI

  键入Add-Migration DIY_Name生成数据迁移文件(注意默认项目选择MyProject.EntityFrameworkCore,因为MyProjectDbContext在此类库下)(参考相关文档2)
(若所生成的迁移文件为空,可能是已经生成过了,此时反复进行Add,Remove操作,或者删掉原有的Migration文件夹内的文件即可)。
  键入Update-Database更新数据库数据(若第一次可能会自动建库建表)

  最后运行项目即可打开Swagger网页(浏览器可能会认为是不安全网站.在键盘下按顺序敲下"Thisisunsafe"即可打开)

6.DeBug与问题解答

Q1:这里的Create数据可以有两种方法(以Student类):
(需要提前在Student的定义中写构造函数)

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);

           foreach (var classGroupId in input.ClassGroupIds)
           {
               entity.AddToClassGroup(classGroupId);
           }

           await _studentRepository.InsertAsync(entity);
       }

或者是上面代码中的不写构造函数,用一种特殊的语法直接赋值

public void CreateTask(CreateTaskInput input)
        {
            // TODO:
            var persons_query_count = _personRepository.GetAll()
                                         .Where(x => x.Id == input.AssignedPersonId)
                                         .Count();
            if (persons_query_count > 0)
            {
                //存在该委托人,可以创建任务
                Logger.Info("Creating a task for input: " + input);
            }
            else
            {
                //不存在该人,不能创建对应任务
                throw new InvalidOperationException();
            }
            //通过输入参数,创建一个新的Task实体
            var task = new Task { Description = input.Description };

            if (input.AssignedPersonId.HasValue)
            {
                task.AssignedPersonId = input.AssignedPersonId.Value;
                
            }
            //调用仓储基类的Insert方法把实体保存到数据库中
            _taskRepository.Insert(task);
        }

(不知道是C#的一种方法还是基类Entity的方法,待研究)


Q1:若SWAGGER出错,但是VS内没报错。
A1:
  可尝试去E:\Visual studio\6.5.0\aspnet-core\src\MyProject.Web.Host\App_Data\Logs 日志内查找错误(ctrl+F找ERROR关键词)
  比如我就出错了,在日志上找到了
MySqlConnector.MySqlException (0x80004005): Cannot add or update a child row: a foreign key constraint fails (`myprojectdb`.`tasks`, CONSTRAINT `FK_Tasks_People_AssignedPersonId` FOREIGN KEY (`AssignedPersonId`) REFERENCES `people` (`Id`) ON DELETE RESTRICT)
  结果是外键约束产生的附带问题


Q2:我想知道为什么我没有modelBuilder.Entity用于Task和Person,但我的数据库仍然能接受到数据,即进行正常的数据库操作
A2:
  在这个示例中,尽管你没有在OnModelCreating方法中显式配置Task和Person实体的映射和属性,但EF Core会根据约定进行默认的配置。根据约定,EF Core会将实体类名称作为表名,并使用实体类的属性作为表的列。
  因此,即使你没有提供显式的配置,EF Core仍然可以根据约定将Task和Person实体映射到数据库中的对应表。这也是为什么你能够正常地进行数据库操作。
  但是,如果你希望对这些实体类的映射和属性进行更详细的配置,你可以在OnModelCreating方法中使用modelBuilder.Entity<T>语法进行显式配置,就像你已经对Simple实体进行的配置一样。这样做可以提供更多的灵活性并允许你自定义表名、列名、数据类型,以及其他高级配置选项。


Q3:使用以下办法将仅含一条数据的List<Person>转换为Person类型
A3:

List<Person> personsList = GetPersons();

if (personsList.Count > 0)
{
    // 获取列表中的第一个元素,并将其转换为Person类型
    Person person = personsList[0];

    Console.WriteLine(person.Name); // 输出:John Doe
}
else
{
    Console.WriteLine("未找到匹配的Person对象");
}

Q4:什么叫异步方法
A4:
  而异步方法是一种在执行过程中可以让出线程并异步等待某个操作完成的方法。让出线程:将耗时的任务放在最后执行的意思是为了先执行那些不耗时的任务,以提高整体的执行效率和响应性。通过先执行不耗时的任务,可以让程序快速响应用户的操作或请求,并在后台执行耗时的任务,避免阻塞主线程或其他任务的执行。
  这种方式也可以更好地利用计算资源,因为在执行耗时任务的同时,其他的任务可以继续执行,从而提高系统的并发性和效率。总的执行时长可能不会改变,但整体的用户体验会更好。


Q5:我想知道
接口A.cs在B.cs中实现后,若在C.cs中创建了A的实例,能否调用在B.cs中实现的A内函

A5:
  是的,如果接口 A 在类 B 中实现,而在类 C 中创建了 A 的实例,那么你可以通过该实例调用在类 B 中实现的 A 的内部函数。
  这也是接口文件的实现方式

// A.cs
public interface A
{
    void MyMethod();
}
// B.cs
public class B : A
{
    public void MyMethod()
    {
        Console.WriteLine("Hello from B");
    }
}
// C.cs
public class C
{
    public void DoSomething()
    {
        A instance = new B();
        instance.MyMethod(); // 调用在 B 类中实现的 MyMethod 方法
    }
}