怎样优雅地增删查改(一):从0开始搭建Volo.Abp项目

news2025/1/9 15:29:35

软件系统中数据库或者持久层的基本操作功能可以用Curd描述,Curd即 增加(Create)、更新(Update)、读取查询(Retrieve)和删除(Delete), 这4个单词的首字母。

在常见的业务系统中,对数据的大部分操作都是Curd,在实践的过程中对数据的筛选、排序、分页、关联查询等功能抽象和封装。

本系列博文将从0开始,逐步搭建一个基于Volo.Abp + Vue 的前后端分离的,具有Curd通用查询功能的项目。

项目介绍

本项目是基于一个简单的用户健康数据管理系统,我们将对业务常用的查询功能进行扩展,抽象这些业务并封装成接口,称之为通用查询接口(GeneralCurdInterfaces),本项目关注的是基础设施层,但大部分实现还是围绕业务,对于普适性有待研究,所以我还是决定以Sample为名。

模块化

Abp模块是可以供主模块重用的独立功能单元,每个模块可以包含应用服务、领域层、数据访问层、Web API等,模块可以被其他模块引用,也可以被主模块引用。

本项目模块化的目的除了可重用,更多是为微服务架构做准备。微服务架构不在本博文的讨论范围,为了简化,还是使用单体应用架构。

由框架实现的

Volo.Abp 为我们实现了CrudAppService,(在旧版本的AbpBoilerplate中称Crud为Curd,在我看来两者没有什么区别,本项目还是以Curd命名)

CrudAppService为我们提供了基本的增删改查,以及分页、排序的实现

在这里插入图片描述

需要实现的

  • 按任意字段关键字查询

  • 按任意字段排序

  • 按组织架构查询

  • 按用户查询

  • 按用户关系查询

  • 按创建日期查询(起始日期,结束日期)

本项目虽然是用Volo.Abp实现,但对于旧版本的AbpBoilerplate仍然可以方便的移植,可以看我之前的博文:[Volo.Abp升级笔记]使用旧版Api规则替换RESTful Api以兼容老程序,如何以最大限度保持接口的兼容性。

创建项目

创建空白文件夹,在文件夹内打开命令行

在这里插入图片描述

使用AbpCli创建一个无UI的项目 拆分Auth Server,执行以下命令

abp new Matoapp -u none --separate-auth-server -csf

等待项目创建成功

在这里插入图片描述

创建业务模块

作为命名空间前缀,Matoapp是一个虚构的企业名称。

在解决方案目录中创建新目录src/modules,在该目录下创建员工健康管理模块Health,公共业务模块Common,以及扩展了Volo.Abp.Indentity的Identity模块

在modules目录下打开命令行,分别执行以下命令

abp new Matoapp.Health -t module --no-ui
abp new Matoapp.Common -t module --no-ui
abp new Matoapp.Identity -t module --no-ui

等待模块创建完成

打开解决方案,将业务模块中的各个项目添加到解决方案中,我们只需要添加各模块的ApplicationApplication.ContractsDomainDomain.SharedEntityFrameworkCoreHttpApi以及HttpApi.Client

添加完成后的解决方案结构看上去像这样:

在这里插入图片描述

配置引用和依赖

Volo.Abp.Identity.Application添加到Application项目的引用中

dotnet add package Volo.Abp.Identity.Application

Volo.Abp.Identity.Application.Contracts添加到Application.Contracts项目的引用中

dotnet add package Volo.Abp.Identity.Application.Contracts

Volo.Abp.Identity.Domain,Volo.Abp.PermissionManagement.Domain添加到Domain项目的引用中

dotnet add package Volo.Abp.Identity.Domain
dotnet add package Volo.Abp.PermissionManagement.Domain

Volo.Abp.Identity.EntityFrameworkCore添加到EntityFrameworkCore项目的引用中

dotnet add package Volo.Abp.Identity.EntityFrameworkCore

Application层

Application层添加对各模块的引用,
ApplicationModule中添加对各模块的依赖

[DependsOn(
    ...
    typeof(CommonApplicationModule),
    typeof(HealthApplicationModule),
    typeof(IdentityApplicationModule)
    )]
public class MatoappApplicationModule : AbpModule
{

}

AuthServer添加Identity数据访问层引用,并配置依赖关系

[DependsOn(
    ...
    typeof(IdentityDomainModule),
    typeof(IdentityEntityFrameworkCoreModule)
)]
public class MatoappAuthServerModule : AbpModule
{

}

HttpApi层添加对各模块的引用,
HttpApiModule中添加对各模块的依赖

[DependsOn(
    ...
    typeof(CommonHttpApiModule),
    typeof(HealthHttpApiModule),
    typeof(IdentityHttpApiModule)
    )]
public class MatoappHttpApiModule : AbpModule
{

}

配置DbContext

用CodeFirst方式创建一些业务表,比如员工表,客户表,报警表等,这些表都是在Health模块中创建的,

Tag相关的表放入Common模块中,Relation表放入Identity模块中。

这些业务表按照业务模块的划分,放入各自的DbContext中。

public interface IIdentityDbContext : IEfCoreDbContext
{

    DbSet<Relation.Relation> Relation { get; set; }

}
public interface IHealthDbContext : IEfCoreDbContext
{

    DbSet<Client.Client> Client { get; set; }
    DbSet<Employee.Employee> Employee { get; set; }

    DbSet<Alarm.Alarm> Alarm { get; set; }

    DbSet<SimpleValueRecord> SimpleValueRecord { get; set; }
}
public interface ICommonDbContext : IEfCoreDbContext
{
    DbSet<DataEnum.DataEnum> DataEnum { get; set; }
    DbSet<DataEnumCategory.DataEnumCategory> DataEnumCategory { get; set; }
    DbSet<Tag.Tag> Tag { get; set; }
}

各业务模块的DbContextModelCreatingExtensions中添加对各表的字段,约束,索引等的配置。以便在DbContext的OnModelCreating中调用

builder.ConfigureCommon();
builder.ConfigureHealth();
builder.ConfigureMatoIdentity();

EntityFrameworkCore层中改写MatoappDbContext如下:

[ReplaceDbContext(typeof(Matoapp.Identity.EntityFrameworkCore.IIdentityDbContext))]
[ReplaceDbContext(typeof(IHealthDbContext))]
[ReplaceDbContext(typeof(ICommonDbContext))]
[ReplaceDbContext(typeof(ITenantManagementDbContext))]
[ConnectionStringName("Default")]
public class MatoappDbContext :
    AbpDbContext<MatoappDbContext>,
    Matoapp.Identity.EntityFrameworkCore.IIdentityDbContext,
    IHealthDbContext,
    ICommonDbContext,
    ITenantManagementDbContext
{

    #region Entities from the modules

    public DbSet<Relation> Relation { get; set; }

    // Tenant Management
    public DbSet<Tenant> Tenants { get; set; }
    public DbSet<TenantConnectionString> TenantConnectionStrings { get; set; }

    public DbSet<Client> Client { get; set; }
    public DbSet<Employee> Employee { get; set; }

    public DbSet<Alarm> Alarm { get; set; }

    public DbSet<SimpleValueRecord> SimpleValueRecord { get; set; }

    public DbSet<DataEnum> DataEnum { get; set; }
    public DbSet<DataEnumCategory> DataEnumCategory { get; set; }
    public DbSet<Tag> Tag { get; set; }


    #endregion

    public MatoappDbContext(DbContextOptions<MatoappDbContext> options)
        : base(options)
    {

    }

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

        /* Include modules to your migration db context */

        builder.ConfigurePermissionManagement();
        builder.ConfigureSettingManagement();
        builder.ConfigureBackgroundJobs();
        builder.ConfigureAuditLogging();
        builder.ConfigureIdentity();
        builder.ConfigureOpenIddict();
        builder.ConfigureFeatureManagement();
        builder.ConfigureTenantManagement();

        builder.ConfigureCommon();
        builder.ConfigureHealth();
        builder.ConfigureMatoIdentity();

        /* Configure your own tables/entities inside here */

        //builder.Entity<YourEntity>(b =>
        //{
        //    b.ToTable(MatoappConsts.DbTablePrefix + "YourEntities", MatoappConsts.DbSchema);
        //    b.ConfigureByConvention(); //auto configure for the base class props
        //    //...
        //});
    }
}

在AuthServer创建AuthServerDbContextFactory,AuthServerDbContext。

AuthServerDbContext.cs代码如下

public class AuthServerDbContext : AbpDbContext<AuthServerDbContext>
{
    public AuthServerDbContext(DbContextOptions<AuthServerDbContext> options) 
        : base(options)
    {

    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.ConfigureIdentity();
        modelBuilder.ConfigureIdentityServer();
        modelBuilder.ConfigureAuditLogging();
        modelBuilder.ConfigurePermissionManagement();
        modelBuilder.ConfigureSettingManagement();
        modelBuilder.ConfigureTenantManagement();
        modelBuilder.ConfigureFeatureManagement();
        modelBuilder.ConfigureMatoIdentity();

    }
}

创建实体和Dto

在各业务模块中创建实体类,以及对应的Dto类
此处以Health模块为例,创建以下实体类

  • Employee 员工
  • Client 客户
  • Alarm 报警
  • SimpleValueRecord 简单值记录

配置AutoMapper

根据实际业务需求,配置AutoMapper,将实体类映射到DTO类。此处以Health模块为例。

public HealthApplicationAutoMapperProfile()
{
    CreateMap<Client.Client, ClientDto>().Ignore(c => c.EntityVersion);
    CreateMap<Employee.Employee, EmployeeDto>().Ignore(c => c.EntityVersion);

    CreateMap<ClientDto, Client.Client>();
    CreateMap<EmployeeDto, Employee.Employee>();

    CreateMap<Alarm.Alarm, AlarmDto>();
    CreateMap<Alarm.Alarm, AlarmBriefDto>();

    CreateMap<AlarmDto, Alarm.Alarm>().Ignore(c => c.TenantId)
            .Ignore(c => c.ConcurrencyStamp);
    CreateMap<CreateAlarmInput, Alarm.Alarm>().IgnoreFullAuditedObjectProperties()
            .IgnoreSoftDeleteProperties()
            .Ignore(c => c.TenantId)
            .Ignore(c => c.User)
            .Ignore(c => c.ConcurrencyStamp)
            .Ignore(c => c.Id);

    CreateMap<UpdateAlarmInput, Alarm.Alarm>().IgnoreFullAuditedObjectProperties()
            .IgnoreSoftDeleteProperties()
            .Ignore(c => c.TenantId)
            .Ignore(c => c.User)
            .Ignore(c => c.ConcurrencyStamp);

    

    CreateMap<SimpleValueRecord, SimpleValueRecordBriefDto>();
    CreateMap<SimpleValueRecord, SimpleValueRecordDto>();
    CreateMap<SimpleValueRecordDto, SimpleValueRecord>().Ignore(c => c.TenantId)
        .Ignore(c => c.Alarm)
            .Ignore(c => c.ConcurrencyStamp);


    CreateMap<CreateClientInput, Client.Client>()
        .ForAllMembers(opt => opt.Condition((src, dest, srcMember, destMember) => srcMember != null));

    CreateMap<CreateClientWithUserInput, Client.Client>()
        .IgnoreFullAuditedObjectProperties()
        .IgnoreSoftDeleteProperties()
        .Ignore(c => c.LockoutEnabled)
        .Ignore(c => c.LockoutEnd)
        .Ignore(c => c.TenantId)
        .Ignore(c => c.ConcurrencyStamp)
        .Ignore(c => c.EmailConfirmed)
        .Ignore(c => c.PhoneNumberConfirmed)

        .Ignore(c => c.Id)
        .ForAllMembers(opt => opt.Condition((src, dest, srcMember, destMember) => srcMember != null));


    CreateMap<CreateEmployeeInput, Employee.Employee>()
        .ForAllMembers(opt => opt.Condition((src, dest, srcMember, destMember) => srcMember != null));

    CreateMap<CreateEmployeeWithUserInput, Employee.Employee>()

        .IgnoreFullAuditedObjectProperties()
        .IgnoreSoftDeleteProperties()
        .Ignore(c => c.LockoutEnabled)
        .Ignore(c => c.LockoutEnd)
        .Ignore(c => c.TenantId)
        .Ignore(c => c.ConcurrencyStamp)
        .Ignore(c => c.EmailConfirmed)
        .Ignore(c => c.PhoneNumberConfirmed)

        .Ignore(c => c.Id)
        .ForAllMembers(opt => opt.Condition((src, dest, srcMember, destMember) => srcMember != null));
}

至此,我们有了基础的数据库,实体类,Dto类。下一步我们将创建通用Curd应用服务,以及通用查询接口。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/717412.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Elasticsearch(2)——映射关系

1 什么是映射 映射&#xff08;mapping&#xff09;就像数据库中的 Schema &#xff0c;描述了文档可能具有的字段或属性、每个字段的 数据类型&#xff0c;比如 Text&#xff0c;Keyword&#xff0c;Integer 或 Date &#xff0c;以及 Lucene 是如何索引和存储这些字 段的。 …

VS Code安装及配置教程(Windows)

VS Code安装教程&#xff08;Windows&#xff09; 一、下载1、官网下载&#xff1a;2、网盘下载 二、安装 一、下载 1、官网下载&#xff1a; 点击跳转 2、网盘下载 点击跳转 二、安装 双击运行程序&#xff0c;进行安装&#xff0c;同意许可&#xff0c;点击下一步 选择…

轻松拿捏Jenkins(Linux)+Java11+Docker自动化打包部署

注&#xff1a;本文由笔者小白亲手实践写作&#xff0c;涵盖较全&#xff0c;创作不易&#xff0c;且行且珍惜&#xff01;&#xff01; 历时24小时&#xff0c;包含Jenkins环境构建部署过程中的99%问题&#xff0c;确定不来看一下吗&#xff1f; 文章目录 开篇一、软件包准备J…

华为OD机试真题 Python 实现【任务总执行时长】【2023Q1 100分】

目录 一、题目描述二、输入描述三、输出描述四、补充说明五、解题思路六、Python算法源码七、效果展示1、输入2、输出3、说明 一、题目描述 任务编排服务负责对任务进行组合调度。 参与编排的任务有两种类型&#xff0c;其中一种执行时长为taskA&#xff0c;另一种执行时长为…

Linux项目自动化构建工具——make和makefile

make和makefile 一.基本使用二.make并不是每一次都会进行编译三.原理四.特殊符号 一.基本使用 首先创建一个mycode.c文件&#xff0c;接着使用vim写几行代码。 接着创建一个makefile文件&#xff08;这里的m大写小写均可但需要在当前目录下&#xff09;&#xff0c;并使用vim进…

C语言 - unicode编码与字符串互转

概述 最近项目上需要unicode编码与字符串互转&#xff0c;在此做个笔录。 1、编码环境为 visual studio 21&#xff0c;code如下&#xff1a; #include <stdlib.h> #include <stdio.h> #include <string.h> #include <wchar.h> #include <locale.…

Java SpringBoot集成WebSocket

SpringBoot集成WebSocket 引入依赖WebSocket配置拦截器配置总体配置类WebSocket服务类 控制层测试测试使用在线连接工具&#xff08;自行百度&#xff09;连接地址&#xff1a;ws://IP端口号/websocket测试发送消息测试 公众号&#xff1a;一颗星宇宙 引入依赖 <dependency…

液晶铝薄膜的干蚀特性

引言 对于先进的薄膜晶体管液晶显示器(TFT-LCD)制造工艺来说&#xff0c;薄膜层已逐渐取代传统的湿法蚀刻工艺。对于栅电极&#xff0c;一般使用铝(Al)&#xff0c;在Al中加入一些金属&#xff0c;除了可以保持低电阻率和增强Al薄膜的耐腐蚀性外&#xff0c;还可以防止在后退火…

these parameters are deprecated, see docs for addKeyword

在使用json schema中的自定义关键字时发生该报错&#xff0c;因为直接第一个参数直接传keyname&#xff0c;如下 但是这种方法已经弃用&#xff0c;应该用官方支持的写法&#xff0c;不要直接传keyword,以键值对形式写出来&#xff0c;也有可能是其他书写不规范造成的问题&am…

嵌入式系统移植

一、系统分层 移植的目的 不同架构的处理器指令集不兼容&#xff0c;即便是相同的处理器架构&#xff0c;板卡不同驱动代码也不兼容 Linux是一个通用的内核并不是为某一个特定的处理器架构或板卡设计的&#xff0c;所以从官方获取Linux源码后我们要先经过相应的配置使其与我们当…

企业src-白帽赚钱的合理途径

0、什么是src 安全响应中心&#xff08;Security Response Center&#xff09;&#xff1a;SRC通常是一个组织或者公司设立的部门或团队&#xff0c;负责接收、跟踪和响应安全漏洞报告&#xff0c;以及协调修补措施。SRC与安全研究人员和其他报告者合作&#xff0c;以确保及时…

FastDFS【FastDFS概述(简介、核心概念 、上传机制 、下载机制)】(一)-全面详解(学习总结---从入门到深化)

目录 为什么要使用分布式文件系统 FastDFS概述_简介 FastDFS概述_核心概念 FastDFS概述_上传机制 FastDFS概述_下载机制 为什么要使用分布式文件系统 单机时代 初创时期由于时间紧迫&#xff0c;在各种资源有限的情况下&#xff0c;通常就直接在 项目目录下建立静态文件夹…

CentOs7 阿里云如何做云盘扩容?

不罗嗦直接说主题&#xff1a; 在开发中遇到得问题&#xff1a;磁盘满了&#xff0c;删又不知道从何下手&#xff0c;万一删错了那就是巨大得灾难&#xff0c;所以我们就需要磁盘扩容&#xff0c;当然磁盘为什么满&#xff1f;怎么排查以后再说&#xff0c;这里主要是说磁盘如…

Windows PCL GPU版本编译过程

网上教程很少&#xff0c;但是想用PCL GPU功能&#xff0c;于是决定自己踩坑&#xff0c;我有几个不同的环境组合&#xff1a; (1)win10cuda10.0vs2019PCL1.11.1cmake3.18.5&#xff08;失败&#xff09; (2)win10cuda11.2vs2019PCL1.11.1cmake3.18.5&#xff08;成功&#x…

【算法|动态规划系列No.5】leetcode62. 不同路径

个人主页&#xff1a;平行线也会相交 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 平行线也会相交 原创 收录于专栏【手撕算法系列专栏】【LeetCode】 &#x1f354;本专栏旨在提高自己算法能力的同时&#xff0c;记录一下自己的学习过程&#xff0c;希望…

No7.精选前端面试题,享受每天的挑战和学习

文章目录 详细说明下 Composition Api 与 Vue 2.x使用的Options Api详细说明下Proxy和Object.defineProperty详细说明下Loader和Plugin分析下vue2.0和vue3.0的原理vue3和react的异同点 详细说明下 Composition Api 与 Vue 2.x使用的Options Api Composition API 是 Vue 3 中引…

弱网测试(Charles模拟)

一、介绍 移动应用的网络环境多样&#xff0c;而且会出现在不同网络之间切换的场景&#xff0c;即使是在同一网络环境下&#xff0c;也会出现网络连接状态时好时坏的情况&#xff0c;比如时高时低的延迟、经常丢包、频繁断线&#xff0c;在乘坐地铁、穿越隧道&#xff0c;和地…

今日大模型日报

XPhoneBERT&#xff5c;文本到语音转换的音素表示的预训练多语言模型 XPhoneBERT: A Pre-trained Multilingual Model for Phoneme Representations for Text-to-Speech Linh The Nguyen, Thinh Pham, Dat Quoc Nguyen Github地址&#xff1a;https://github.com/VinAIResearch…

IO流~~

就是用来读写数据 2&#xff1a;IO流的分类 3&#xff1a;字节流的使用 1&#xff1a;文件字节输入流&#xff1a;每次读取一个字节 作用&#xff1a;以内存为基准&#xff0c;把磁盘文件中的数据以字节的形式读取到内存中去 性能慢读取中文字符输出无法避免乱码问题 2&…

数字化升级提效85%,箭牌不止家居智能

近年来&#xff0c;随着人们对生活品质的追求不断提升&#xff0c;家居行业也逐渐朝着更加智能化、个性化、环保可持续和互联网的方向发展。有数据显示&#xff0c;2023年中国家居市场规模将达到2万亿元人民币&#xff0c;年复合增长率为8.3%。激烈的市场竞争之下&#xff0c;可…