<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:a10="http://www.w3.org/2005/Atom" version="2.0">
  <channel xmlns:media="http://search.yahoo.com/mrss/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <title>ABP.IO Stories</title>
    <link>https://abp.io/community/articles</link>
    <description>A hub for ABP Framework, .NET, and software development. Access articles, tutorials, news, and contribute to the ABP community.</description>
    <lastBuildDate>Sat, 24 Oct 2026 11:12:40 Z</lastBuildDate>
    <generator>Community - ABP.IO</generator>
    <image>
      <url>https://abp.io/assets/favicon.ico/favicon-32x32.png</url>
      <title>ABP.IO Stories</title>
      <link>https://abp.io/community/articles</link>
    </image>
    <a10:link rel="self" type="application/rss+xml" title="self" href="https://abp.io/community/rss?member=jevonsflash%40qq.com" />
    <item>
      <guid isPermaLink="true">https://abp.io/community/posts/li-yong-volo.abpyou-ya-di-zeng-shan-cha-gai-3dla71tl</guid>
      <link>https://abp.io/community/posts/li-yong-volo.abpyou-ya-di-zeng-shan-cha-gai-3dla71tl</link>
      <a10:author>
        <a10:name>jevonsflash@qq.com</a10:name>
        <a10:uri>https://abp.io/community/members/jevonsflash@qq.com</a10:uri>
      </a10:author>
      <title>利用Volo.Abp优雅地增删查改</title>
      <description>这是一个使用Volo.Abp框架搭建的通用查询接口，应用层基于AbpCrudAppService，对业务常用的查询功能进行扩展，抽象这些业务并封装成独立的AppService以及Controller</description>
      <pubDate>Fri, 21 Jul 2023 09:35:42 Z</pubDate>
      <a10:updated>2026-04-27T11:47:42Z</a10:updated>
      <content:encoded><![CDATA[<p>目录</p>
<ul>
<li><a href="#%E9%A1%B9%E7%9B%AE%E4%BB%8B%E7%BB%8D">项目介绍</a>
<ul>
<li><a href="#%E6%A8%A1%E5%9D%97%E5%8C%96">模块化</a></li>
<li><a href="#%E7%94%B1%E6%A1%86%E6%9E%B6%E5%AE%9E%E7%8E%B0%E7%9A%84">由框架实现的</a></li>
<li><a href="#%E9%9C%80%E8%A6%81%E5%AE%9E%E7%8E%B0%E7%9A%84">需要实现的</a></li>
</ul>
</li>
<li><a href="#%E9%A1%B9%E7%9B%AE%E6%90%AD%E5%BB%BA">项目搭建</a>
<ul>
<li><a href="#%E5%88%9B%E5%BB%BA%E9%A1%B9%E7%9B%AE">创建项目</a></li>
<li><a href="#%E5%88%9B%E5%BB%BA%E4%B8%9A%E5%8A%A1%E6%A8%A1%E5%9D%97">创建业务模块</a></li>
<li><a href="#%E9%85%8D%E7%BD%AE%E5%BC%95%E7%94%A8%E5%92%8C%E4%BE%9D%E8%B5%96">配置引用和依赖</a></li>
<li><a href="#%E9%85%8D%E7%BD%AEdbcontext">配置DbContext</a></li>
<li><a href="#%E5%88%9B%E5%BB%BA%E5%AE%9E%E4%BD%93%E5%92%8Cdto">创建实体和Dto</a></li>
<li><a href="#%E9%85%8D%E7%BD%AEautomapper">配置AutoMapper</a></li>
</ul>
</li>
<li><a href="#%E6%89%A9%E5%B1%95%E8%BA%AB%E4%BB%BD%E7%AE%A1%E7%90%86%E6%A8%A1%E5%9D%97">扩展身份管理模块</a>
<ul>
<li><a href="#%E7%94%A8%E6%88%B7%E5%85%B3%E7%B3%BB%E7%AE%A1%E7%90%86">用户关系管理</a></li>
<li><a href="#%E6%89%A9%E5%B1%95%E7%BB%84%E7%BB%87%E7%AE%A1%E7%90%86%E5%8A%9F%E8%83%BD">扩展组织管理功能</a></li>
<li><a href="#%E5%AE%9E%E7%8E%B0%E6%8E%A7%E5%88%B6%E5%99%A8">实现控制器</a></li>
<li><a href="#%E6%B5%8B%E8%AF%95%E6%8E%A5%E5%8F%A3">测试接口：</a></li>
</ul>
</li>
<li><a href="#%E4%B8%9A%E5%8A%A1%E7%94%A8%E6%88%B7%E7%9A%84%E5%A2%9E%E5%88%A0%E6%9F%A5%E6%94%B9">业务用户的增删查改</a>
<ul>
<li><a href="#%E5%88%9B%E5%BB%BA%E4%B8%9A%E5%8A%A1%E7%94%A8%E6%88%B7%E5%90%8C%E6%AD%A5%E5%99%A8">创建业务用户同步器</a></li>
<li><a href="#%E5%88%9B%E5%BB%BA%E4%B8%9A%E5%8A%A1%E7%94%A8%E6%88%B7%E5%BA%94%E7%94%A8%E6%9C%8D%E5%8A%A1">创建业务用户应用服务</a>
<ul>
<li><a href="#%E5%A2%9E">增</a></li>
<li><a href="#%E5%88%A0">删</a></li>
<li><a href="#%E6%94%B9">改</a></li>
<li><a href="#%E6%9F%A5">查</a></li>
</ul>
</li>
<li><a href="#%E5%88%9B%E5%BB%BA%E6%8E%A7%E5%88%B6%E5%99%A8">创建控制器</a></li>
<li><a href="#%E6%B5%8B%E8%AF%95">测试</a>
<ul>
<li><a href="#%E6%8C%89%E7%BB%84%E7%BB%87%E6%9E%B6%E6%9E%84%E6%9F%A5%E8%AF%A2">按组织架构查询</a></li>
<li><a href="#%E6%8C%89%E8%81%8C%E7%A7%B0%E6%9F%A5%E8%AF%A2">按职称查询</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#%E5%88%9B%E5%BB%BA%E9%80%9A%E7%94%A8%E6%9F%A5%E8%AF%A2%E5%9F%BA%E7%B1%BB">创建通用查询基类</a>
<ul>
<li><a href="#%E5%88%9B%E5%BB%BA%E9%80%9A%E7%94%A8%E6%9F%A5%E8%AF%A2%E6%8A%BD%E8%B1%A1%E5%B1%82">创建通用查询抽象层</a></li>
<li><a href="#%E5%88%9B%E5%BB%BA%E9%80%9A%E7%94%A8%E6%9F%A5%E8%AF%A2%E5%BA%94%E7%94%A8%E5%B1%82%E5%9F%BA%E7%B1%BB">创建通用查询应用层基类</a></li>
<li><a href="#%E5%88%9B%E5%BB%BA%E9%80%9A%E7%94%A8%E6%9F%A5%E8%AF%A2%E6%8E%A7%E5%88%B6%E5%99%A8%E5%9F%BA%E7%B1%BB">创建通用查询控制器基类</a></li>
<li><a href="#%E5%8F%AF%E9%80%89%E6%9B%BF%E6%8D%A2restfulapi">[可选]替换RESTfulApi</a></li>
<li><a href="#%E6%89%A9%E5%B1%95%E6%B3%9B%E5%9E%8B%E5%8F%82%E6%95%B0">扩展泛型参数</a></li>
<li><a href="#%E6%9C%8D%E5%8A%A1%E7%9A%84%E6%B8%90%E8%BF%9B%E5%BC%8F">服务的“渐进式”</a></li>
<li><a href="#%E4%BD%BF%E7%94%A8">使用</a></li>
</ul>
</li>
<li><a href="#%E6%8C%89%E7%BB%84%E7%BB%87%E6%9E%B6%E6%9E%84%E6%9F%A5%E8%AF%A2-1">按组织架构查询</a>
<ul>
<li><a href="#%E5%8E%9F%E7%90%86">原理</a></li>
<li><a href="#%E5%AE%9E%E7%8E%B0">实现</a></li>
<li><a href="#%E4%BD%BF%E7%94%A8-1">使用</a></li>
<li><a href="#%E6%B5%8B%E8%AF%95-1">测试</a></li>
</ul>
</li>
<li><a href="#%E6%8C%89%E4%BB%BB%E6%84%8F%E5%AD%97%E6%AE%B5%E5%85%B3%E9%94%AE%E5%AD%97%E6%9F%A5%E8%AF%A2">按任意字段关键字查询</a>
<ul>
<li><a href="#%E5%AE%9E%E7%8E%B0-1">实现</a></li>
<li><a href="#%E5%BA%94%E7%94%A8">应用</a></li>
<li><a href="#%E6%B5%8B%E8%AF%95-2">测试</a></li>
</ul>
</li>
<li><a href="#%E6%8C%89%E7%94%A8%E6%88%B7%E6%9F%A5%E8%AF%A2">按用户查询</a>
<ul>
<li><a href="#%E5%AE%9E%E7%8E%B0-2">实现</a></li>
<li><a href="#%E4%BD%BF%E7%94%A8-2">使用</a></li>
<li><a href="#%E6%B5%8B%E8%AF%95-3">测试</a></li>
</ul>
</li>
<li><a href="#%E6%8C%89%E7%94%A8%E6%88%B7%E5%85%B3%E7%B3%BB%E6%9F%A5%E8%AF%A2">按用户关系查询</a>
<ul>
<li><a href="#%E5%8E%9F%E7%90%86-1">原理</a></li>
<li><a href="#%E5%AE%9E%E7%8E%B0-3">实现</a>
<ul>
<li><a href="#%E6%AD%A3%E5%90%91%E7%94%A8%E6%88%B7%E5%85%B3%E7%B3%BB">正向用户关系</a></li>
<li><a href="#%E5%8F%8D%E5%90%91%E7%94%A8%E6%88%B7%E5%85%B3%E7%B3%BB">反向用户关系</a></li>
</ul>
</li>
<li><a href="#%E4%BD%BF%E7%94%A8-3">使用</a></li>
<li><a href="#%E6%B5%8B%E8%AF%95-4">测试</a></li>
</ul>
</li>
<li><a href="#%E6%8C%89%E6%97%A5%E6%9C%9F%E8%8C%83%E5%9B%B4%E6%9F%A5%E8%AF%A2">按日期范围查询</a>
<ul>
<li><a href="#%E5%AE%9E%E7%8E%B0-4">实现</a>
<ul>
<li><a href="#%E6%8C%89%E5%BC%80%E5%A7%8B%E6%97%A5%E6%9C%9F%E6%9F%A5%E8%AF%A2">按开始日期查询</a></li>
<li><a href="#%E6%8C%89%E7%BB%93%E6%9D%9F%E6%97%A5%E6%9C%9F%E6%9F%A5%E8%AF%A2">按结束日期查询</a></li>
</ul>
</li>
<li><a href="#%E4%BD%BF%E7%94%A8-4">使用</a></li>
<li><a href="#%E9%A1%B9%E7%9B%AE%E5%9C%B0%E5%9D%80">项目地址</a></li>
<li><a href="#%E5%9C%A8%E7%BA%BF%E6%BC%94%E7%A4%BA">在线演示：</a></li>
</ul>
</li>
</ul>
<h1>项目介绍</h1>
<p>本项目是基于一个简单的用户健康数据管理系统，我们将对业务常用的查询功能进行扩展，抽象这些业务并封装成接口，称之为通用查询接口（GeneralCurdInterfaces），本项目关注的是基础设施层，但大部分实现还是围绕业务，对于普适性有待研究，所以我还是决定以Sample为名。</p>
<h2>模块化</h2>
<p>Abp模块是可以供主模块重用的独立功能单元，每个模块可以包含应用服务、领域层、数据访问层、Web API等，模块可以被其他模块引用，也可以被主模块引用。</p>
<p>本项目模块化的目的除了可重用，更多是为微服务架构做准备。微服务架构不在本博文的讨论范围，为了简化，还是使用单体应用架构。</p>
<h2>由框架实现的</h2>
<p>Volo.Abp 为我们实现了<code>CrudAppService</code>，（在旧版本的AbpBoilerplate中称Crud为Curd，在我看来两者没有什么区别，本项目还是以Curd命名）</p>
<p><code>CrudAppService</code>为我们提供了基本的增删改查，以及分页、排序的实现</p>
<p><img src="https://img-blog.csdnimg.cn/8665e43911e9403893c62344144fd103.png" alt="在这里插入图片描述" /> </p>
<h2>需要实现的</h2>
<ul>
<li><p>按任意字段关键字查询</p>
</li>
<li><p>按任意字段排序</p>
</li>
<li><p>按组织架构查询</p>
</li>
<li><p>按用户查询</p>
</li>
<li><p>按用户关系查询</p>
</li>
<li><p>按创建日期查询（起始日期，结束日期）</p>
</li>
</ul>
<p>本项目虽然是用Volo.Abp实现，但对于旧版本的AbpBoilerplate仍然可以方便的移植，可以看我之前的博文：<a href="https://blog.csdn.net/jevonsflash/article/details/129756995">[Volo.Abp升级笔记]使用旧版Api规则替换RESTful Api以兼容老程序</a>，如何以最大限度保持接口的兼容性。</p>
<h1>项目搭建</h1>
<h2>创建项目</h2>
<p>创建空白文件夹，在文件夹内打开命令行</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-2.png" alt="Alt text" /></p>
<p>使用AbpCli创建一个无UI的项目 拆分Auth Server，执行以下命令</p>
<pre><code>abp new Matoapp -u none --separate-auth-server -csf
</code></pre>
<p>等待项目创建成功</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-3.png" alt="Alt text" /></p>
<h2>创建业务模块</h2>
<p>作为命名空间前缀，Matoapp是一个虚构的企业名称。</p>
<p>在解决方案目录中创建新目录<code>src/modules</code>，在该目录下创建员工健康管理模块Health，公共业务模块Common，以及扩展了Volo.Abp.Indentity的Identity模块</p>
<p>在modules目录下打开命令行，分别执行以下命令</p>
<pre><code>abp new Matoapp.Health -t module --no-ui
abp new Matoapp.Common -t module --no-ui
abp new Matoapp.Identity -t module --no-ui
</code></pre>
<p>等待模块创建完成</p>
<p>打开解决方案，将业务模块中的各个项目添加到解决方案中，我们只需要添加各模块的<code>Application</code>，<code>Application.Contracts</code>，<code>Domain</code>，<code>Domain.Shared</code>，<code>EntityFrameworkCore</code>，<code>HttpApi</code>以及<code>HttpApi.Client</code>。</p>
<p>添加完成后的解决方案结构看上去像这样：</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-5.png" alt="Alt text" /></p>
<h2>配置引用和依赖</h2>
<p>将<code>Volo.Abp.Identity.Application</code>添加到Application项目的引用中</p>
<pre><code>dotnet add package Volo.Abp.Identity.Application
</code></pre>
<p>将<code>Volo.Abp.Identity.Application.Contracts</code>添加到Application.Contracts项目的引用中</p>
<pre><code>dotnet add package Volo.Abp.Identity.Application.Contracts
</code></pre>
<p>将<code>Volo.Abp.Identity.Domain</code>,<code>Volo.Abp.PermissionManagement.Domain</code>添加到Domain项目的引用中</p>
<pre><code>dotnet add package Volo.Abp.Identity.Domain
dotnet add package Volo.Abp.PermissionManagement.Domain
</code></pre>
<p>将<code>Volo.Abp.Identity.EntityFrameworkCore</code>添加到EntityFrameworkCore项目的引用中</p>
<pre><code>dotnet add package Volo.Abp.Identity.EntityFrameworkCore
</code></pre>
<p>Application层</p>
<p>Application层添加对各模块的引用，
ApplicationModule中添加对各模块的依赖</p>
<pre><code>[DependsOn(
    ...
    typeof(CommonApplicationModule),
    typeof(HealthApplicationModule),
    typeof(IdentityApplicationModule)
    )]
public class MatoappApplicationModule : AbpModule
{

}
</code></pre>
<p>AuthServer添加Identity数据访问层引用，并配置依赖关系</p>
<pre><code>[DependsOn(
    ...
    typeof(IdentityDomainModule),
    typeof(IdentityEntityFrameworkCoreModule)
)]
public class MatoappAuthServerModule : AbpModule
{

}
</code></pre>
<p>HttpApi层添加对各模块的引用，
HttpApiModule中添加对各模块的依赖</p>
<p>[DependsOn(
...
typeof(CommonHttpApiModule),
typeof(HealthHttpApiModule),
typeof(IdentityHttpApiModule)
)]
public class MatoappHttpApiModule : AbpModule
{</p>
<p>}</p>
<h2>配置DbContext</h2>
<p>用CodeFirst方式创建一些业务表，比如员工表，客户表，报警表等，这些表都是在Health模块中创建的，</p>
<p>Tag相关的表放入Common模块中，Relation表放入Identity模块中。</p>
<p>这些业务表按照业务模块的划分，放入各自的DbContext中。</p>
<pre><code>public interface IIdentityDbContext : IEfCoreDbContext
{

    DbSet&lt;Relation.Relation&gt; Relation { get; set; }

}
</code></pre>
<pre><code>public interface IHealthDbContext : IEfCoreDbContext
{

    DbSet&lt;Client.Client&gt; Client { get; set; }
    DbSet&lt;Employee.Employee&gt; Employee { get; set; }

    DbSet&lt;Alarm.Alarm&gt; Alarm { get; set; }

    DbSet&lt;SimpleValueRecord&gt; SimpleValueRecord { get; set; }
}
</code></pre>
<pre><code>public interface ICommonDbContext : IEfCoreDbContext
{
    DbSet&lt;DataEnum.DataEnum&gt; DataEnum { get; set; }
    DbSet&lt;DataEnumCategory.DataEnumCategory&gt; DataEnumCategory { get; set; }
    DbSet&lt;Tag.Tag&gt; Tag { get; set; }
}
</code></pre>
<p>各业务模块的DbContextModelCreatingExtensions中添加对各表的字段，约束，索引等的配置。以便在DbContext的OnModelCreating中调用</p>
<pre><code>builder.ConfigureCommon();
builder.ConfigureHealth();
builder.ConfigureMatoIdentity();

</code></pre>
<p>EntityFrameworkCore层中改写MatoappDbContext如下：</p>
<pre><code>[ReplaceDbContext(typeof(Matoapp.Identity.EntityFrameworkCore.IIdentityDbContext))]
[ReplaceDbContext(typeof(IHealthDbContext))]
[ReplaceDbContext(typeof(ICommonDbContext))]
[ReplaceDbContext(typeof(ITenantManagementDbContext))]
[ConnectionStringName(&quot;Default&quot;)]
public class MatoappDbContext :
    AbpDbContext&lt;MatoappDbContext&gt;,
    Matoapp.Identity.EntityFrameworkCore.IIdentityDbContext,
    IHealthDbContext,
    ICommonDbContext,
    ITenantManagementDbContext
{

    #region Entities from the modules

    public DbSet&lt;Relation&gt; Relation { get; set; }

    // Tenant Management
    public DbSet&lt;Tenant&gt; Tenants { get; set; }
    public DbSet&lt;TenantConnectionString&gt; TenantConnectionStrings { get; set; }

    public DbSet&lt;Client&gt; Client { get; set; }
    public DbSet&lt;Employee&gt; Employee { get; set; }

    public DbSet&lt;Alarm&gt; Alarm { get; set; }

    public DbSet&lt;SimpleValueRecord&gt; SimpleValueRecord { get; set; }

    public DbSet&lt;DataEnum&gt; DataEnum { get; set; }
    public DbSet&lt;DataEnumCategory&gt; DataEnumCategory { get; set; }
    public DbSet&lt;Tag&gt; Tag { get; set; }


    #endregion

    public MatoappDbContext(DbContextOptions&lt;MatoappDbContext&gt; 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&lt;YourEntity&gt;(b =&gt;
        //{
        //    b.ToTable(MatoappConsts.DbTablePrefix + &quot;YourEntities&quot;, MatoappConsts.DbSchema);
        //    b.ConfigureByConvention(); //auto configure for the base class props
        //    //...
        //});
    }
}

</code></pre>
<p>在AuthServer创建AuthServerDbContextFactory，AuthServerDbContext。</p>
<p>AuthServerDbContext.cs代码如下</p>
<pre><code>public class AuthServerDbContext : AbpDbContext&lt;AuthServerDbContext&gt;
{
    public AuthServerDbContext(DbContextOptions&lt;AuthServerDbContext&gt; 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();

    }
}
</code></pre>
<h2>创建实体和Dto</h2>
<p>在各业务模块中创建实体类，以及对应的Dto类
此处以Health模块为例，创建以下实体类</p>
<ul>
<li>Employee 员工</li>
<li>Client 客户</li>
<li>Alarm 报警</li>
<li>SimpleValueRecord 简单值记录</li>
</ul>
<h2>配置AutoMapper</h2>
<p>根据实际业务需求，配置AutoMapper，将实体类映射到DTO类。此处以Health模块为例。</p>
<pre><code>public HealthApplicationAutoMapperProfile()
{
    CreateMap&lt;Client.Client, ClientDto&gt;().Ignore(c =&gt; c.EntityVersion);
    CreateMap&lt;Employee.Employee, EmployeeDto&gt;().Ignore(c =&gt; c.EntityVersion);

    CreateMap&lt;ClientDto, Client.Client&gt;();
    CreateMap&lt;EmployeeDto, Employee.Employee&gt;();

    CreateMap&lt;Alarm.Alarm, AlarmDto&gt;();
    CreateMap&lt;Alarm.Alarm, AlarmBriefDto&gt;();

    CreateMap&lt;AlarmDto, Alarm.Alarm&gt;().Ignore(c =&gt; c.TenantId)
            .Ignore(c =&gt; c.ConcurrencyStamp);
    CreateMap&lt;CreateAlarmInput, Alarm.Alarm&gt;().IgnoreFullAuditedObjectProperties()
            .IgnoreSoftDeleteProperties()
            .Ignore(c =&gt; c.TenantId)
            .Ignore(c =&gt; c.User)
            .Ignore(c =&gt; c.ConcurrencyStamp)
            .Ignore(c =&gt; c.Id);

    CreateMap&lt;UpdateAlarmInput, Alarm.Alarm&gt;().IgnoreFullAuditedObjectProperties()
            .IgnoreSoftDeleteProperties()
            .Ignore(c =&gt; c.TenantId)
            .Ignore(c =&gt; c.User)
            .Ignore(c =&gt; c.ConcurrencyStamp);

    

    CreateMap&lt;SimpleValueRecord, SimpleValueRecordBriefDto&gt;();
    CreateMap&lt;SimpleValueRecord, SimpleValueRecordDto&gt;();
    CreateMap&lt;SimpleValueRecordDto, SimpleValueRecord&gt;().Ignore(c =&gt; c.TenantId)
        .Ignore(c =&gt; c.Alarm)
            .Ignore(c =&gt; c.ConcurrencyStamp);


    CreateMap&lt;CreateClientInput, Client.Client&gt;()
        .ForAllMembers(opt =&gt; opt.Condition((src, dest, srcMember, destMember) =&gt; srcMember != null));

    CreateMap&lt;CreateClientWithUserInput, Client.Client&gt;()
        .IgnoreFullAuditedObjectProperties()
        .IgnoreSoftDeleteProperties()
        .Ignore(c =&gt; c.LockoutEnabled)
        .Ignore(c =&gt; c.LockoutEnd)
        .Ignore(c =&gt; c.TenantId)
        .Ignore(c =&gt; c.ConcurrencyStamp)
        .Ignore(c =&gt; c.EmailConfirmed)
        .Ignore(c =&gt; c.PhoneNumberConfirmed)

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


    CreateMap&lt;CreateEmployeeInput, Employee.Employee&gt;()
        .ForAllMembers(opt =&gt; opt.Condition((src, dest, srcMember, destMember) =&gt; srcMember != null));

    CreateMap&lt;CreateEmployeeWithUserInput, Employee.Employee&gt;()

        .IgnoreFullAuditedObjectProperties()
        .IgnoreSoftDeleteProperties()
        .Ignore(c =&gt; c.LockoutEnabled)
        .Ignore(c =&gt; c.LockoutEnd)
        .Ignore(c =&gt; c.TenantId)
        .Ignore(c =&gt; c.ConcurrencyStamp)
        .Ignore(c =&gt; c.EmailConfirmed)
        .Ignore(c =&gt; c.PhoneNumberConfirmed)

        .Ignore(c =&gt; c.Id)
        .ForAllMembers(opt =&gt; opt.Condition((src, dest, srcMember, destMember) =&gt; srcMember != null));
}
</code></pre>
<p>至此，我们有了基础的数据库，实体类，Dto类。下一步我们将创建Curd应用服务，以及统一查询接口。</p>
<h1>扩展身份管理模块</h1>
<p>身份管理模块（Identity模块）为通用查询接口的按组织架构查询和按户关系查询提供查询依据。</p>
<p>身份管理模块的领域层依赖Volo.Abp.Identity.Domain</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-11.png" alt="Alt text" /></p>
<p>Abp为我们实现了一套身份管理模块，此模块包含用户管理、角色管理、组织管理、权限管理等功能。详细请参考<a href="https://docs.abp.io/zh-Hans/abp/latest/Modules/Identity">身份管理模块</a>。</p>
<p>我们将基于Volo.Abp.Identity模块按需求扩展。将为其扩展组织管理功能的接口，以及人员关系（Relation）功能。</p>
<h2>用户关系管理</h2>
<p>Relation是人员之间的关系，比如：签约、关注，或者朋友关系等。人员之间的关系是单项的，也就是说可以A是B的好友，但B不一定是A的好友。</p>
<p>关系类型由Type来定义</p>
<p>正向关系：User -&gt; RelatedUser，由查询GetRelatedToUsersAsync实现；</p>
<p>反向关系：RelatedUser -&gt; User，由查询GetRelatedFromUsersAsync实现。</p>
<p>添加Relation实体：</p>
<pre><code>public class Relation : FullAuditedAggregateRoot&lt;long&gt;
{
    public Guid? TenantId { get; set; }

    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public override long Id { get; protected set; }

    public Guid UserId { get; set; }

    [ForeignKey(&quot;UserId&quot;)]
    public IdentityUser User { get; set; }

    public Guid RelatedUserId { get; set; }

    [ForeignKey(&quot;RelatedUserId&quot;)]
    public IdentityUser RelatedUser { get; set; }

    public string Type { get; set; }

}
</code></pre>
<p>在模块配置中添加</p>
<pre><code>public class IdentityEntityFrameworkCoreModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        context.Services.AddAbpDbContext&lt;IdentityDbContext&gt;(options =&gt;
        {
            options.AddRepository&lt;IdentityUserOrganizationUnit, EfCoreRepository&lt;IdentityDbContext, IdentityUserOrganizationUnit&gt;&gt;();
            options.AddRepository&lt;Relation.Relation, EfCoreRepository&lt;IdentityDbContext, Relation.Relation&gt;&gt;();
        });
    }
}

</code></pre>
<p>创建RelationManager，实现人员关系的正向和反向查询</p>
<pre><code>public async Task&lt;List&lt;Relation&gt;&gt; GetRelatedToUsersAsync(Guid userId, string type)
{
    var query = (await Repository.GetQueryableAsync())
        .WhereIf(userId != null, c =&gt; userId == c.UserId)
        .WhereIf(!string.IsNullOrEmpty(type), c =&gt; c.Type == type);
    var items = query.ToList();
    return items;

}

public async Task&lt;List&lt;Relation&gt;&gt; GetRelatedFromUsersAsync(Guid userId, string type)
{
    var query = (await Repository.GetQueryableAsync())
        .Where(c =&gt; userId == c.RelatedUserId)
        .WhereIf(!string.IsNullOrEmpty(type), c =&gt; c.Type == type);
    var items = query.ToList();
    return items;
}
</code></pre>
<h2>扩展组织管理功能</h2>
<p>组织（OrganizationUnit）是身份管理模块的核心概念，组织是树形结构，组织之间存在父子关系。</p>
<p>我们对功能模块的接口进行扩展：</p>
<ol>
<li><p>增加OrganizationUnit的增删查改接口</p>
</li>
<li><p>增加OrganizationUnit的移动接口</p>
</li>
<li><p>增加人员与组织架构管理接口，如添加/删除人员到组织架构，查询组织架构下的人员，查询未分配组织的人员等</p>
</li>
<li><p>增加查询根组织（GetRootOrganizationUnit）接口。</p>
</li>
</ol>
<p>完整的应用层接口如下：</p>
<pre><code>public interface IOrganizationUnitAppService : IBasicCurdAppService&lt;OrganizationUnitDto, Guid, CreateOrganizationUnitInput, UpdateOrganizationUnitInput&gt;, IApplicationService
{
    Task AddToOrganizationUnitAsync(UserToOrganizationUnitInput input);
    Task&lt;List&lt;OrganizationUnitDto&gt;&gt; GetCurrentOrganizationUnitsAsync();
    Task&lt;PagedResultDto&lt;IdentityUserDto&gt;&gt; GetOrganizationUnitUsersByPageAsync(GetOrganizationUnitUsersInput input);
    Task&lt;List&lt;IdentityUserDto&gt;&gt; GetOrganizationUnitUsersAsync(GetOrganizationUnitUsersInput input);
    Task&lt;OrganizationUnitDto&gt; GetRootOrganizationUnitAsync(Guid id);
    Task&lt;List&lt;OrganizationUnitDto&gt;&gt; GetRootOrganizationUnitsAsync(IEnumerable&lt;Guid&gt; ids);
    Task&lt;OrganizationUnitDto&gt; GetRootOrganizationUnitByDisplayNameAsync(GetRootOrganizationUnitByDisplayName input);
    Task&lt;List&lt;OrganizationUnitDto&gt;&gt; GetRootOrganizationUnitsByParentAsync(GetRootOrganizationUnitsByParentInput input);
    Task&lt;bool&gt; IsInOrganizationUnitAsync(UserToOrganizationUnitInput input);
    Task MoveOrganizationUnitAsync(MoveOrganizationUnitInput input);
    Task RemoveUserFromOrganizationUnitAsync(UserToOrganizationUnitInput input);
    Task&lt;List&lt;IdentityUserDto&gt;&gt; GetUsersWithoutOrganizationAsync(GetUserWithoutOrganizationInput input);
    Task&lt;PagedResultDto&lt;IdentityUserDto&gt;&gt; GetUsersWithoutOrganizationByPageAsync(GetUserWithoutOrganizationInput input);
}
</code></pre>
<p>创建可查询仓储</p>
<p>通用查询接口过滤条件需要对IQueryable进行拼接，由于Volo.Abp.Identity.IIdentityUserRepository继承自IBasicRepository，我们需要重新编写一个IdentityUser的可查询仓储：QueryableIdentityUserRepository</p>
<p>其实现接口IQueryableIdentityUserRepository的定义如下：</p>
<pre><code>public interface IQueryableIdentityUserRepository : IIdentityUserRepository
{
    Task&lt;IQueryable&lt;OrganizationUnit&gt;&gt; GetOrganizationUnitsQueryableAsync(Guid id, bool includeDetails = false);
    Task&lt;IQueryable&lt;IdentityUser&gt;&gt; GetOrganizationUnitUsersAsync(
        Guid id, string keyword, string[] type,
        bool includeDetails = false);
    Task&lt;IQueryable&lt;IdentityUser&gt;&gt; GetUsersWithoutOrganizationAsync(string keyword, string[] type);
}
</code></pre>
<h2>实现控制器</h2>
<p>为OrganizationUnitAppService 以及 RelationAppService 创建。</p>
<pre><code>namespace Matoapp.Identity.OrganizationUnit
{
    [Area(IdentityRemoteServiceConsts.ModuleName)]
    [RemoteService(Name = IdentityRemoteServiceConsts.RemoteServiceName)]
    [Route(&quot;api/identity/organizationUnit&quot;)]
    public class OrganizationUnitController : IdentityController, IOrganizationUnitAppService
    {
        private readonly IOrganizationUnitAppService _organizationUnitAppService;

        public OrganizationUnitController(IOrganizationUnitAppService organizationUnitAppService)
        {
            _organizationUnitAppService = organizationUnitAppService;
        }

        [HttpPost]
        [Route(&quot;AddToOrganizationUnit&quot;)]
        
        public async Task AddToOrganizationUnitAsync(UserToOrganizationUnitInput input)
        {
            await _organizationUnitAppService.AddToOrganizationUnitAsync(input);
        }

        [HttpPost]
        [Route(&quot;Create&quot;)]
        
        public async Task&lt;OrganizationUnitDto&gt; CreateAsync(CreateOrganizationUnitInput input)
        {
            return await _organizationUnitAppService.CreateAsync(input);
        }

        [HttpDelete]
        [Route(&quot;Delete&quot;)]
        
        public async Task DeleteAsync(Guid id)
        {
            await _organizationUnitAppService.DeleteAsync(id);
        }


        [HttpGet]
        [Route(&quot;Get&quot;)]
        
        public async Task&lt;OrganizationUnitDto&gt; GetAsync(Guid id)
        {
            return await _organizationUnitAppService.GetAsync(id);

        }

        [HttpGet]
        [Route(&quot;GetCurrentOrganizationUnits&quot;)]

        
        public async Task&lt;List&lt;OrganizationUnitDto&gt;&gt; GetCurrentOrganizationUnitsAsync()
        {
            return await _organizationUnitAppService.GetCurrentOrganizationUnitsAsync();
        }


        [HttpGet]
        [Route(&quot;GetOrganizationUnitUsers&quot;)]
        
        public async Task&lt;List&lt;IdentityUserDto&gt;&gt; GetOrganizationUnitUsersAsync(GetOrganizationUnitUsersInput input)
        {
            return await _organizationUnitAppService.GetOrganizationUnitUsersAsync(input);
        }

        [HttpGet]
        [Route(&quot;GetOrganizationUnitUsersByPage&quot;)]
        
        public async Task&lt;PagedResultDto&lt;IdentityUserDto&gt;&gt; GetOrganizationUnitUsersByPageAsync(GetOrganizationUnitUsersInput input)
        {
            return await _organizationUnitAppService.GetOrganizationUnitUsersByPageAsync(input);
        }

        [HttpGet]
        [Route(&quot;GetRootOrganizationUnit&quot;)]
        
        public async Task&lt;OrganizationUnitDto&gt; GetRootOrganizationUnitAsync(Guid id)
        {
            return await _organizationUnitAppService.GetRootOrganizationUnitAsync(id);
        }

        [HttpGet]
        [Route(&quot;GetRootOrganizationUnits&quot;)]
        
        public async Task&lt;List&lt;OrganizationUnitDto&gt;&gt; GetRootOrganizationUnitsAsync(IEnumerable&lt;Guid&gt; ids)
        {
            return await _organizationUnitAppService.GetRootOrganizationUnitsAsync(ids);
        }

        [HttpGet]
        [Route(&quot;GetRootOrganizationUnitByDisplayName&quot;)]
        
        public async Task&lt;OrganizationUnitDto&gt; GetRootOrganizationUnitByDisplayNameAsync(GetRootOrganizationUnitByDisplayName input)
        {
            return await _organizationUnitAppService.GetRootOrganizationUnitByDisplayNameAsync(input);
        }

        [HttpGet]
        [Route(&quot;GetRootOrganizationUnitsByParent&quot;)]
        
        public async Task&lt;List&lt;OrganizationUnitDto&gt;&gt; GetRootOrganizationUnitsByParentAsync(GetRootOrganizationUnitsByParentInput input)
        {
            return await _organizationUnitAppService.GetRootOrganizationUnitsByParentAsync(input);
        }

        [HttpGet]
        [Route(&quot;GetUsersWithoutOrganization&quot;)]
        
        public async Task&lt;List&lt;IdentityUserDto&gt;&gt; GetUsersWithoutOrganizationAsync(GetUserWithoutOrganizationInput input)
        {
            return await _organizationUnitAppService.GetUsersWithoutOrganizationAsync(input);
        }

        [HttpGet]
        [Route(&quot;GetUsersWithoutOrganizationByPage&quot;)]
        
        public async Task&lt;PagedResultDto&lt;IdentityUserDto&gt;&gt; GetUsersWithoutOrganizationByPageAsync(GetUserWithoutOrganizationInput input)
        {
            return await _organizationUnitAppService.GetUsersWithoutOrganizationByPageAsync(input);
        }

        [HttpGet]
        [Route(&quot;IsInOrganizationUnit&quot;)]
        
        public async Task&lt;bool&gt; IsInOrganizationUnitAsync(UserToOrganizationUnitInput input)
        {
            return await _organizationUnitAppService.IsInOrganizationUnitAsync(input);
        }

        [HttpPost]
        [Route(&quot;MoveOrganizationUnit&quot;)]
        
        public async Task MoveOrganizationUnitAsync(MoveOrganizationUnitInput input)
        {
            await _organizationUnitAppService.MoveOrganizationUnitAsync(input);
        }

        [HttpPost]
        [Route(&quot;RemoveUserFromOrganizationUnit&quot;)]
        
        public async Task RemoveUserFromOrganizationUnitAsync(UserToOrganizationUnitInput input)
        {
            await _organizationUnitAppService.RemoveUserFromOrganizationUnitAsync(input);
        }

        [HttpPut]
        [Route(&quot;Update&quot;)]
        
        public async Task&lt;OrganizationUnitDto&gt; UpdateAsync(UpdateOrganizationUnitInput input)
        {
            return await _organizationUnitAppService.UpdateAsync(input);
        }

    }
</code></pre>
<h2>测试接口：</h2>
<p>上一章节我们已经将三个模组的依赖添加到MatoappHttpApiModule中，直接启动HttpApi.Host就可以访问接口了。</p>
<pre><code>[DependsOn(
    ...
    typeof(CommonHttpApiModule),
    typeof(HealthHttpApiModule),
    typeof(IdentityHttpApiModule)
    )]
public class MatoappHttpApiModule : AbpModule
</code></pre>
<p>Relation相关接口：</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-13.png" alt="Alt text" /></p>
<p>OrganizationUnit相关接口：</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-14.png" alt="Alt text" /></p>
<h1>业务用户的增删查改</h1>
<p>区别于身份管理模块（Identity模块）的鉴权用户IdentityUser，业务用户（BusinessUser）是围绕业务系统中“用户”这一定义的领域模型。如：在一个医院系统中，业务用户可以是医生、护士、患者；在一个OA系统中，业务用户可以是员工、管理员、客户等。</p>
<p>业务用户和鉴权用户由同步机制关联，业务用户通过分布式事件（DistributedEvent）的同步器（Synchronizer）与鉴权用户关联同步。</p>
<p>在Health业务模块中，定义两种业务用户：</p>
<p>Client: 客户；</p>
<p>Employee: 员工。</p>
<p>这些业务用户继承自HealthUser，HealthUser是业务用户的基类，包含了业务用户的基本信息，如姓名，性别，出生日期，身份证号等。并且需要实现IUpdateUserData接口，以便在同步鉴权用户信息时，更新业务用户的基本信息。</p>
<p>Employee包含工号，职称，简介等信息。其领域模型定义如下：</p>
<pre><code>public class Employee : HealthUser&lt;Guid&gt;, IUser, IUpdateUserData
{
    [StringLength(12)]
    public string EmployeeNumber { get; set; }

    [StringLength(64)]
    public string EmployeeTitle { get; set; }

    public string Introduction { get; set; }

    ...
}

</code></pre>
<p>Client包含客户号，身高，体重，婚姻状况等信息。其领域模型定义如下：</p>
<pre><code>public class Client : HealthUser&lt;Guid&gt;, IUser, IUpdateUserData
{

    //unique

    [StringLength(12)]
    public string ClientNumber { get; set; }

    public string ClientNumberType { get; set; }

    [Range(0.0, 250.0)]
    public double? Height { get; set; }


    [Range(0.0, 1000.0)]
    public double? Weight { get; set; }

    public string Marriage { get; set; }

    public string Status { get; set; }
}

</code></pre>
<h2>创建业务用户同步器</h2>
<p>以Client为例，ClientLookupService是业务用户的查询服务，其基类UserLookupService定义了关联用户的查询接口，包括按ID查询，按用户名查询，按组织架构查询，按户关系查询等。</p>
<p>创建ClientLookupService， 代码如下</p>
<pre><code>public class ClientLookupService : UserLookupService&lt;Client, IClientRepository&gt;, IClientLookupService
{
    public ClientLookupService(
        IClientRepository userRepository,
        IUnitOfWorkManager unitOfWorkManager)
        : base(
            userRepository,
            unitOfWorkManager)
    {

    }

    protected override Client CreateUser(IUserData externalUser)
    {
        return new Client(externalUser);
    }
}

</code></pre>
<p>同步器订阅了分布式事件EntityUpdatedEto<UserEto>，当鉴权用户更新时，同步器将更新业务用户的基本信息。</p>
<p>创建ClientSynchronizer，代码如下</p>
<pre><code>public class ClientSynchronizer :
        IDistributedEventHandler&lt;EntityUpdatedEto&lt;UserEto&gt;&gt;,
    ITransientDependency
{
    protected IClientRepository UserRepository { get; }
    protected IClientLookupService UserLookupService { get; }

    public ClientSynchronizer(
        IClientRepository userRepository,
        IClientLookupService userLookupService)
    {
        UserRepository = userRepository;
        UserLookupService = userLookupService;
    }

    public async Task HandleEventAsync(EntityUpdatedEto&lt;UserEto&gt; eventData)
    {
        var user = await UserRepository.FindAsync(eventData.Entity.Id);
        if (user != null)
        {
            if (user.Update(eventData.Entity))
            {
                await UserRepository.UpdateAsync(user);
            }
        }
    }
}
</code></pre>
<h2>创建业务用户应用服务</h2>
<p>以Employee为例</p>
<p>在应用层中创建EmployeeAppService，在这里我们实现对业务用户的增删改查操作。</p>
<p>EmployeeAppService继承自CrudAppService，它是ABP框架提供的增删改查的基类，其基类定义了增删改查的接口，包括GetAsync，GetListAsync，CreateAsync，UpdateAsync，DeleteAsync等。</p>
<p>OrganizationUnit为业务用户的查询接口的按组织架构查询提供查询依据。OrganizationUnitAppService注入到EmployeeAppService中。</p>
<pre><code>public class EmployeeAppService : CrudAppService&lt;Employee, EmployeeDto, Guid, GetAllEmployeeInput, CreateEmployeeInput&gt;, IEmployeeAppService
{
    private readonly IOrganizationUnitAppService organizationUnitAppService;

}
</code></pre>
<h3>增</h3>
<p>创建CreateWithUserAsync方法，用于创建业务用户。</p>
<pre><code>public async Task&lt;EmployeeDto&gt; CreateWithUserAsync(CreateEmployeeWithUserInput input)
{

    var createdUser = await identityUserAppService.CreateAsync(input);
    await CurrentUnitOfWork.SaveChangesAsync();
    var currentEmployee = await userLookupService.FindByIdAsync(createdUser.Id);
    ObjectMapper.Map(input, currentEmployee);
    var updatedEmployee = await Repository.UpdateAsync(currentEmployee);
    var result = ObjectMapper.Map&lt;Employee, EmployeeDto&gt;(updatedEmployee);

    if (input.OrganizationUnitId.HasValue)
    {
        await organizationUnitAppService.AddToOrganizationUnitAsync(
            new UserToOrganizationUnitInput()
            { UserId = createdUser.Id, OrganizationUnitId = input.OrganizationUnitId.Value });
    }
    return result;
}
</code></pre>
<h3>删</h3>
<p>删除接口由CrudAppService提供默认实现，无需重写。</p>
<h3>改</h3>
<p>创建UpdateWithUserAsync方法，用于更新业务用户。</p>
<pre><code>public async Task&lt;EmployeeDto&gt; UpdateWithUserAsync(CreateEmployeeInput input)
{

    var currentEmployee = await userLookupService.FindByIdAsync(input.Id);
    if (currentEmployee == null)
    {
        throw new UserFriendlyException(&quot;没有找到对应的用户&quot;);
    }
    ObjectMapper.Map(input, currentEmployee);
    var updatedEmployee = await Repository.UpdateAsync(currentEmployee);
    var result = ObjectMapper.Map&lt;Employee, EmployeeDto&gt;(updatedEmployee);

    return result;
}

</code></pre>
<h3>查</h3>
<p>查询单个实体接口由CrudAppService提供默认实现，无需重写。</p>
<p>查询集合：</p>
<p>以Employee为例，查询接口所需要的入参为：</p>
<p>OrganizationUnitId：按组织架构查询用户
IsWithoutOrganization：查询不属于任何组织架构的用户
EmployeeTitle：按职称查询用户</p>
<p>创建GetAllEmployeeInput，代码如下</p>
<pre><code>public class GetAllEmployeeInput : PagedAndSortedResultRequestDto
{
    public string EmployeeTitle { get; set; }

    public Guid? OrganizationUnitId { get; set; }
    public bool IsWithoutOrganization { get; set; }

}
</code></pre>
<p>重写CreateFilteredQueryAsync</p>
<pre><code>
protected override async Task&lt;IQueryable&lt;Employee&gt;&gt; CreateFilteredQueryAsync(GetAllEmployeeInput input)
{
    var query = await ReadOnlyRepository.GetQueryableAsync().ConfigureAwait(continueOnCapturedContext: false);

    if (input.OrganizationUnitId.HasValue &amp;&amp; !input.IsWithoutOrganization)
    {
        var organizationUnitUsers = await organizationUnitAppService.GetOrganizationUnitUsersAsync(new GetOrganizationUnitUsersInput()
        {
            Id = input.OrganizationUnitId.Value
        });
        if (organizationUnitUsers.Count() &gt; 0)
        {
            var ids = organizationUnitUsers.Select(c =&gt; c.Id);
            query = query.Where(t =&gt; ids.Contains(t.Id));
        }
        else
        {
            query = query.Where(c =&gt; false);
        }
    }
    else if (input.IsWithoutOrganization)
    {
        var organizationUnitUsers = await organizationUnitAppService.GetUsersWithoutOrganizationAsync(new GetUserWithoutOrganizationInput());
        if (organizationUnitUsers.Count() &gt; 0)
        {
            var ids = organizationUnitUsers.Select(c =&gt; c.Id);
            query = query.Where(t =&gt; ids.Contains(t.Id));
        }
        else
        {
            query = query.Where(c =&gt; false);
        }
    }
    query = query.WhereIf(!string.IsNullOrEmpty(input.EmployeeTitle), c =&gt; c.EmployeeTitle == input.EmployeeTitle);
    return query;
}

</code></pre>
<p>至此，我们已完成了对业务用户的增删改查功能实现。</p>
<h2>创建控制器</h2>
<p>在HttpApi项目中创建EmployeeController，代码如下：</p>
<pre><code>[Area(HealthRemoteServiceConsts.ModuleName)]
[RemoteService(Name = HealthRemoteServiceConsts.RemoteServiceName)]
[Route(&quot;api/Health/employee&quot;)]
public class EmployeeController : AbpControllerBase, IEmployeeAppService
{
    private readonly IEmployeeAppService _employeeAppService;

    public EmployeeController(IEmployeeAppService employeeAppService)
    {
        _employeeAppService = employeeAppService;
    }

    [HttpPost]
    [Route(&quot;CreateWithUser&quot;)]

    public Task&lt;EmployeeDto&gt; CreateWithUserAsync(CreateEmployeeWithUserInput input)
    {
        return _employeeAppService.CreateWithUserAsync(input);
    }

    [HttpDelete]
    [Route(&quot;Delete&quot;)]
    public Task DeleteAsync(Guid id)
    {
        return _employeeAppService.DeleteAsync(id);
    }

    [HttpPut]
    [Route(&quot;UpdateWithUser&quot;)]

    public Task&lt;EmployeeDto&gt; UpdateWithUserAsync(CreateEmployeeInput input)
    {
        return _employeeAppService.UpdateWithUserAsync(input);
    }

    [HttpGet]
    [Route(&quot;Get&quot;)]
    public Task&lt;EmployeeDto&gt; GetAsync(Guid id)
    {
        return _employeeAppService.GetAsync(id);
    }

    [HttpGet]
    [Route(&quot;GetAll&quot;)]
    public Task&lt;PagedResultDto&lt;EmployeeDto&gt;&gt; GetAllAsync(GetAllEmployeeInput input)
    {
        return _employeeAppService.GetAllAsync(input);
    }
}
</code></pre>
<h2>测试</h2>
<p>运行项目</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-15.png" alt="Alt text" /></p>
<p>在Web端，进入组织机构</p>
<p>我们随意创建几个部门，如下图所示：</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-16.png" alt="Alt text" /></p>
<p>创建几个员工用户，并将他们分配到“研发组A”，“研发组B”中，</p>
<h3>按组织架构查询</h3>
<p>通过点击不同的组织架构，可以查看不同的用户：</p>
<p>“研发组A”中的用户：</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-17.png" alt="Alt text" /></p>
<p>“研发组B”中的用户：</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-18.png" alt="Alt text" /></p>
<p>“未分配”中的用户：</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-19.png" alt="Alt text" /></p>
<h3>按职称查询</h3>
<p>在某个组别中点击“筛选”，选择职称-中级，点击查询</p>
<p>将查询所有职称为中级的员工</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-20.png" alt="Alt text" /></p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-23.png" alt="Alt text" /></p>
<p>组合查询的报文Payload如下图：</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-21.png" alt="Alt text" /></p>
<h1>创建通用查询基类</h1>
<p>我们将封装通用的应用层，接口以及控制器基类。</p>
<h2>创建通用查询抽象层</h2>
<p>创建接口ICurdAppService，在这里我们定义了通用的增删改查接口。</p>
<p>其中的泛型参数：</p>
<ul>
<li>TGetOutputDto： Get方法返回的实体Dto</li>
<li>TGetListOutputDto:  GetAll方法返回的实体Dto</li>
<li>TKey: 实体的主键</li>
<li>TGetListInput: GetAll方法接收的输入参数</li>
<li>TCreateInput: Create方法接收的输入参数</li>
<li>TUpdateInput: Update方法接收的输入参数</li>
</ul>
<pre><code>public interface ICurdAppService&lt;TGetOutputDto, TGetListOutputDto, in TKey, in TGetListInput, in TCreateInput, in TUpdateInput&gt;

{
    Task&lt;TGetOutputDto&gt; GetAsync(TKey id);

    Task&lt;PagedResultDto&lt;TGetListOutputDto&gt;&gt; GetAllAsync(TGetListInput input);

    Task&lt;TGetOutputDto&gt; CreateAsync(TCreateInput input);

    Task&lt;TGetOutputDto&gt; UpdateAsync(TUpdateInput input);

    Task DeleteAsync(TKey id);
}

</code></pre>
<h2>创建通用查询应用层基类</h2>
<p>创建应用层服务CurdAppServiceBase，它是一个抽象类，继承自Abp.Application.Services.CrudAppService。</p>
<p>代码如下：</p>
<pre><code>public abstract class CurdAppServiceBase&lt;TEntity, TGetOutputDto, TGetListOutputDto,  TKey, TGetListInput,  TCreateInput, TUpdateInput&gt;
: CrudAppService&lt;TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput&gt;
where TEntity : class, IEntity&lt;TKey&gt;
    where TGetOutputDto : IEntityDto&lt;TKey&gt;
where TGetListOutputDto : IEntityDto&lt;TKey&gt;
{
    protected CurdAppServiceBase(IRepository&lt;TEntity, TKey&gt; repository)
: base(repository)
    {

    }
}
</code></pre>
<h2>创建通用查询控制器基类</h2>
<p>创建控制器类CurdController，继承自AbpControllerBase。并实现ICurdAppService接口。</p>
<p>代码如下：</p>
<pre><code>public abstract class CurdController&lt;ITAppService, TGetOutputDto, TGetListOutputDto,  TKey, TGetListInput,  TCreateInput, TUpdateInput&gt;
    : AbpControllerBase
where ITAppService : ICurdAppService&lt;TGetOutputDto, TGetListOutputDto,  TKey, TGetListInput,  TCreateInput, TUpdateInput&gt;
where TGetOutputDto : IEntityDto&lt;TKey&gt;
where TGetListOutputDto : IEntityDto&lt;TKey&gt;
{

    private readonly ITAppService _recipeAppService;

    public CurdController(ITAppService recipeAppService)
    {
        _recipeAppService = recipeAppService;
    }

    [HttpPost]
    [Route(&quot;Create&quot;)]
    
    public virtual async Task&lt;TGetOutputDto&gt; CreateAsync(TCreateInput input)
    {
        return await _recipeAppService.CreateAsync(input);
    }

    [HttpDelete]
    [Route(&quot;Delete&quot;)]
    
    public virtual async Task DeleteAsync(TKey id)
    {
        await _recipeAppService.DeleteAsync(id);
    }

    [HttpGet]
    [Route(&quot;GetAll&quot;)]
    
    public virtual async Task&lt;PagedResultDto&lt;TGetListOutputDto&gt;&gt; GetAllAsync(TGetListInput input)
    {
        return await _recipeAppService.GetAllAsync(input);
    }

    [HttpGet]
    [Route(&quot;Get&quot;)]
    
    public virtual async Task&lt;TGetOutputDto&gt; GetAsync(TKey id)
    {
        return await _recipeAppService.GetAsync(id);
    }

    [HttpPut]
    [Route(&quot;Update&quot;)]
    
    public virtual async Task&lt;TGetOutputDto&gt; UpdateAsync(TUpdateInput input)
    {
        return await _recipeAppService.UpdateAsync(input);
    }
}
</code></pre>
<h2>[可选]替换RESTfulApi</h2>
<p>为了兼容旧版Abp，需更改增删查改服务（CrudAppService）的方法签名，可参考<a href="https://blog.csdn.net/jevonsflash/article/details/129756995">[Volo.Abp升级笔记]使用旧版Api规则替换RESTful Api以兼容老程序</a>，此处不再赘述。</p>
<p>将UpdateAsync，GetListAsync方法封闭：</p>
<pre><code>private new Task&lt;TGetOutputDto&gt; UpdateAsync(TKey id, TUpdateInput input)
{
    return base.UpdateAsync(id, input);
}
private new Task&lt;PagedResultDto&lt;TGetListOutputDto&gt;&gt; GetListAsync(TGetListInput input)
{
    return base.GetListAsync(input);
}


</code></pre>
<p>封闭原有UpdateAsync， 新增的UpdateAsync方法更改了方法签名：</p>
<pre><code>
public virtual async Task&lt;TGetOutputDto&gt; UpdateAsync(TUpdateInput input)
{
    await CheckUpdatePolicyAsync();
    var entity = await GetEntityByIdAsync((input as IEntityDto&lt;TKey&gt;).Id);
    MapToEntity(input, entity);
    await Repository.UpdateAsync(entity, autoSave: true);
    return await MapToGetOutputDtoAsync(entity);

}

</code></pre>
<p>Brief是一种简化的查询实体集合的方法，其返回的Dto不包含导航属性，以减少数据传输量。</p>
<p>新增GetAllAsync和GetAllBriefAsync方法：</p>
<pre><code>public virtual Task&lt;PagedResultDto&lt;TGetListOutputDto&gt;&gt; GetAllAsync(TGetListInput input)
{
    return this.GetListAsync(input);
}


public async Task&lt;PagedResultDto&lt;TGetListBriefOutputDto&gt;&gt; GetAllBriefAsync(TGetListBriefInput input)
{
    await CheckGetListPolicyAsync();

    var query = await CreateBriefFilteredQueryAsync(input);
    var totalCount = await AsyncExecuter.CountAsync(query);

    var entities = new List&lt;TEntity&gt;();
    var entityDtos = new List&lt;TGetListBriefOutputDto&gt;();

    if (totalCount &gt; 0)
    {
        query = ApplySorting(query, input);
        query = ApplyPaging(query, input);

        entities = await AsyncExecuter.ToListAsync(query);

        entityDtos = ObjectMapper.Map&lt;List&lt;TEntity&gt;, List&lt;TGetListBriefOutputDto&gt;&gt;(entities);

    }

    return new PagedResultDto&lt;TGetListBriefOutputDto&gt;(
        totalCount,
        entityDtos
    );

}

</code></pre>
<h2>扩展泛型参数</h2>
<p>目前为止，我们的应用层基类继承于Abp.Application.Services.CrudAppService</p>
<p>为了更好的代码重用，我们对泛型参数进行扩展，使用CurdAppServiceBase的类可根据实际业务需求选择泛型参数</p>
<p>其中的泛型参数：</p>
<ul>
<li>TEntity: CRUD操作对应的实体类</li>
<li>TEntityDto: GetAll方法返回的实体Dto</li>
<li>TKey: 实体的主键</li>
<li>TGetListBriefInput: GetAllBrief方法的输入参数</li>
<li>TGetListBriefOutputDto: GetAllBrief方法的输出参数</li>
</ul>
<p>首先扩展ICurdAppService：</p>
<pre><code>
public interface ICurdAppService&lt;TEntityDto, in TKey&gt;
    : ICurdAppService&lt;TEntityDto, TKey, PagedAndSortedResultRequestDto&gt;
{

}

public interface ICurdAppService&lt;TEntityDto, in TKey, in TGetListInput&gt;
    : ICurdAppService&lt;TEntityDto, TKey, TGetListInput, TEntityDto&gt;
{

}

public interface ICurdAppService&lt;TEntityDto, in TKey, in TGetListInput, in TCreateInput&gt;
    : ICurdAppService&lt;TEntityDto, TKey, TGetListInput, TCreateInput, TCreateInput&gt;
{

}


public interface ICurdAppService&lt;TEntityDto, in TKey, in TGetListInput, in TCreateInput, in TUpdateInput&gt;
    : ICurdAppService&lt;TEntityDto, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput&gt;
{

}


public interface ICurdAppService&lt;TGetOutputDto, TGetListOutputDto, in TKey, in TGetListInput, in TCreateInput, in TUpdateInput&gt;
: ICurdAppService&lt;TGetOutputDto,  TGetListOutputDto, TKey, TGetListInput, TGetListInput, TCreateInput, TUpdateInput&gt;
{

}



public interface ICurdAppService&lt;TGetOutputDto, TGetListOutputDto, in TKey, in TGetListInput, in TGetListBriefInput, in TCreateInput, in TUpdateInput&gt;
: ICurdAppService&lt;TGetOutputDto, TGetListOutputDto, TGetListOutputDto, TKey, TGetListInput, TGetListBriefInput, TCreateInput, TUpdateInput&gt;
{

}



public interface ICurdAppService&lt;TGetOutputDto, TGetListOutputDto, TGetListBriefOutputDto, in TKey, in TGetListInput, in TGetListBriefInput, in TCreateInput, in TUpdateInput&gt;

{
    Task&lt;TGetOutputDto&gt; GetAsync(TKey id);

    Task&lt;PagedResultDto&lt;TGetListOutputDto&gt;&gt; GetAllAsync(TGetListInput input);

    Task&lt;TGetOutputDto&gt; CreateAsync(TCreateInput input);

    Task&lt;TGetOutputDto&gt; UpdateAsync(TUpdateInput input);

    Task DeleteAsync(TKey id);

    Task&lt;PagedResultDto&lt;TGetListBriefOutputDto&gt;&gt; GetAllBriefAsync(TGetListInput input);


}




</code></pre>
<p>扩展CurdAppServiceBase：</p>
<pre><code>
public abstract class CurdAppServiceBase&lt;TEntity, TEntityDto, TKey&gt;
    : CurdAppServiceBase&lt;TEntity, TEntityDto, TKey, PagedAndSortedResultRequestDto&gt;
    where TEntity : class, IEntity&lt;TKey&gt;
    where TEntityDto : IEntityDto&lt;TKey&gt;
{
    protected CurdAppServiceBase(IRepository&lt;TEntity, TKey&gt; repository)
        : base(repository)
    {

    }
}

public abstract class CurdAppServiceBase&lt;TEntity, TEntityDto, TKey, TGetListInput&gt;
    : CurdAppServiceBase&lt;TEntity, TEntityDto, TKey, TGetListInput, TEntityDto&gt;
    where TEntity : class, IEntity&lt;TKey&gt;
    where TEntityDto : IEntityDto&lt;TKey&gt;
{
    protected CurdAppServiceBase(IRepository&lt;TEntity, TKey&gt; repository)
        : base(repository)
    {

    }
}


public abstract class CurdAppServiceBase&lt;TEntity, TEntityDto, TKey, TGetListInput, TCreateInput&gt;
    : CurdAppServiceBase&lt;TEntity, TEntityDto, TKey, TGetListInput, TCreateInput, TCreateInput&gt;
    where TEntity : class, IEntity&lt;TKey&gt;
    where TEntityDto : IEntityDto&lt;TKey&gt;
{
    protected CurdAppServiceBase(IRepository&lt;TEntity, TKey&gt; repository)
        : base(repository)
    {

    }
}

public abstract class CurdAppServiceBase&lt;TEntity, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput&gt;
: CurdAppServiceBase&lt;TEntity, TEntityDto, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput&gt;
where TEntity : class, IEntity&lt;TKey&gt;
where TEntityDto : IEntityDto&lt;TKey&gt;
{
    protected CurdAppServiceBase(IRepository&lt;TEntity, TKey&gt; repository)
        : base(repository)
    {

    }

    protected override Task&lt;TEntityDto&gt; MapToGetListOutputDtoAsync(TEntity entity)
    {
        return MapToGetOutputDtoAsync(entity);
    }

    protected override TEntityDto MapToGetListOutputDto(TEntity entity)
    {
        return MapToGetOutputDto(entity);
    }
}

public abstract class CurdAppServiceBase&lt;TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput&gt;
: CurdAppServiceBase&lt;TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TGetListInput, TCreateInput, TUpdateInput&gt;
where TEntity : class, IEntity&lt;TKey&gt;
where TGetOutputDto : IEntityDto&lt;TKey&gt;
where TGetListOutputDto : IEntityDto&lt;TKey&gt;
{
    protected CurdAppServiceBase(IRepository&lt;TEntity, TKey&gt; repository)
        : base(repository)
    {

    }
}


public abstract class CurdAppServiceBase&lt;TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput,  TCreateInput, TUpdateInput&gt;
: CurdAppServiceBase&lt;TEntity, TGetOutputDto, TGetListOutputDto, TGetListOutputDto, TKey, TGetListInput,  TCreateInput, TUpdateInput&gt;
where TEntity : class, IEntity&lt;TKey&gt;
where TGetOutputDto : IEntityDto&lt;TKey&gt;
where TGetListOutputDto : IEntityDto&lt;TKey&gt;
{
    protected CurdAppServiceBase(IRepository&lt;TEntity, TKey&gt; repository)
        : base(repository)
    {

    }
}

</code></pre>
<p>扩展CurdController</p>
<pre><code>public abstract class CurdController&lt;ITAppService, TEntityDto, TKey&gt;
        : CurdController&lt;ITAppService, TEntityDto, TKey, PagedAndSortedResultRequestDto&gt;
        where ITAppService : ICurdAppService&lt;TEntityDto, TKey&gt;
        where TEntityDto : IEntityDto&lt;TKey&gt;
{
    protected CurdController(ITAppService appService)
        : base(appService)
    {

    }
}

public abstract class CurdController&lt;ITAppService, TEntityDto, TKey, TGetListInput&gt;
    : CurdController&lt;ITAppService, TEntityDto, TKey, TGetListInput, TEntityDto&gt;
    where ITAppService : ICurdAppService&lt;TEntityDto, TKey, TGetListInput&gt;
    where TEntityDto : IEntityDto&lt;TKey&gt;
{
    protected CurdController(ITAppService appService)
        : base(appService)
    {

    }
}


public abstract class CurdController&lt;ITAppService, TEntityDto, TKey, TGetListInput, TCreateInput&gt;
    : CurdController&lt;ITAppService, TEntityDto, TKey, TGetListInput, TCreateInput, TCreateInput&gt;
    where ITAppService : ICurdAppService&lt;TEntityDto, TKey, TGetListInput, TCreateInput&gt;
    where TEntityDto : IEntityDto&lt;TKey&gt;
{
    protected CurdController(ITAppService appService)
        : base(appService)
    {

    }
}



public abstract class CurdController&lt;ITAppService, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput&gt;
: CurdController&lt;ITAppService, TEntityDto, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput&gt;
where ITAppService : ICurdAppService&lt;TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput&gt;
    where TEntityDto : IEntityDto&lt;TKey&gt;
{
    protected CurdController(ITAppService appService)
        : base(appService)
    {

    }

}




public abstract class CurdController&lt;ITAppService, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput&gt;
: CurdController&lt;ITAppService, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TGetListInput, TCreateInput, TUpdateInput&gt;
where ITAppService : ICurdAppService&lt;TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput&gt;
where TGetOutputDto : IEntityDto&lt;TKey&gt;
where TGetListOutputDto : IEntityDto&lt;TKey&gt;
{
    protected CurdController(ITAppService appService)
        : base(appService)
    {

    }

}



public abstract class CurdController&lt;ITAppService, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TGetListBriefInput, TCreateInput, TUpdateInput&gt;
: CurdController&lt;ITAppService, TGetOutputDto, TGetListOutputDto, TGetListOutputDto, TKey, TGetListInput, TGetListBriefInput, TCreateInput, TUpdateInput&gt;
where ITAppService : ICurdAppService&lt;TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TGetListBriefInput, TCreateInput, TUpdateInput&gt;
where TGetOutputDto : IEntityDto&lt;TKey&gt;
where TGetListOutputDto : IEntityDto&lt;TKey&gt;
where TGetListBriefInput : TGetListInput
{
    protected CurdController(ITAppService appService)
        : base(appService)
    {

    }

}


public abstract class CurdController&lt;ITAppService, TGetOutputDto, TGetListOutputDto, TGetListBriefOutputDto, TKey, TGetListInput, TGetListBriefInput, TCreateInput, TUpdateInput&gt;
    : AbpControllerBase
where ITAppService : ICurdAppService&lt;TGetOutputDto, TGetListOutputDto, TGetListBriefOutputDto, TKey, TGetListInput, TGetListBriefInput, TCreateInput, TUpdateInput&gt;
where TGetOutputDto : IEntityDto&lt;TKey&gt;
where TGetListOutputDto : IEntityDto&lt;TKey&gt;
where TGetListBriefInput : TGetListInput
{

    private readonly ITAppService _recipeAppService;

    public CurdController(ITAppService recipeAppService)
    {
        _recipeAppService = recipeAppService;
    }

    [HttpPost]
    [Route(&quot;Create&quot;)]
    
    public virtual async Task&lt;TGetOutputDto&gt; CreateAsync(TCreateInput input)
    {
        return await _recipeAppService.CreateAsync(input);
    }

    [HttpDelete]
    [Route(&quot;Delete&quot;)]
    
    public virtual async Task DeleteAsync(TKey id)
    {
        await _recipeAppService.DeleteAsync(id);
    }

    [HttpGet]
    [Route(&quot;GetAll&quot;)]
    
    public virtual async Task&lt;PagedResultDto&lt;TGetListOutputDto&gt;&gt; GetAllAsync(TGetListInput input)
    {
        return await _recipeAppService.GetAllAsync(input);
    }

    [HttpGet]
    [Route(&quot;Get&quot;)]
    
    public virtual async Task&lt;TGetOutputDto&gt; GetAsync(TKey id)
    {
        return await _recipeAppService.GetAsync(id);
    }

    [HttpPut]
    [Route(&quot;Update&quot;)]
    
    public virtual async Task&lt;TGetOutputDto&gt; UpdateAsync(TUpdateInput input)
    {
        return await _recipeAppService.UpdateAsync(input);
    }

    [HttpGet]
    [Route(&quot;GetAllBrief&quot;)]
    
    public virtual async Task&lt;PagedResultDto&lt;TGetListBriefOutputDto&gt;&gt; GetAllBriefAsync(TGetListBriefInput input)
    {
        return await _recipeAppService.GetAllBriefAsync(input);
    }
}


</code></pre>
<h2>服务的“渐进式”</h2>
<p>在开发业务模块时，我们可以先使用简单的方式提供Curd服务，随着UI复杂度增加，逐步的使用更加复杂的Curd服务。某种程度上来说，即所谓“渐进式”的开发方式。</p>
<ul>
<li>BaseCurd: 基础型，仅包含Create、Update、Delete、Get</li>
<li>SimpleCurd: 简单型，包含BaseCurd的所有功能，同时包含GetAll</li>
<li>Curd：完整型，包含SimpleCurd的所有功能，同时包含GetAllBrief，GetAllBrief是一种简化的查询实体集合的方法，其返回的Dto不包含导航属性，以减少数据传输量。是最常用的服务类型。</li>
<li>ExtendedCurd：扩展型，包含Curd的所有功能，同时包含GetAllBriefWithoutPage，GetAllBriefWithoutPage 适合一些非分页场景，如日历视图，Echarts图表控件等。使用此接口需要注意：由于没有分页限制，需要其他的查询约束条件（比如日期范围），否则会返回大量的数据，影响性能。</li>
</ul>
<p>我们扩展应用层基类，控制器及其接口</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-25.png" alt="Alt text" /></p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-24.png" alt="Alt text" /></p>
<h2>使用</h2>
<p>以Alarm为例。来实现扩展型Curd服务（ExtendedCurd）。</p>
<p>假设你已完成创建实体、Dto以及配置完成AutoMapper映射</p>
<ol>
<li>在Health模块的抽象层中，创建接口IAlarmAppService，继承自IExtendedCurdAppService和IApplicationService。</li>
</ol>
<p>IApplicationService是ABP框架的接口，所有的应用服务都需要继承自此接口。</p>
<pre><code>public interface IAlarmAppService : IExtendedCurdAppService&lt;AlarmDto, AlarmDto, AlarmBriefDto, long, GetAllAlarmInput, GetAllAlarmInput,  CreateAlarmInput, UpdateAlarmInput&gt;, IApplicationService
{
}
</code></pre>
<ol start="2">
<li>在Health模块的应用层中，创建AlarmAppService</li>
</ol>
<pre><code>public class AlarmAppService : ExtendedCurdAppServiceBase&lt;CAH.Health.Alarm.Alarm, AlarmDto, AlarmDto, AlarmBriefDto, long, GetAllAlarmInput, GetAllAlarmInput, CreateAlarmInput, UpdateAlarmInput&gt;, IAlarmAppService
{
    public AlarmAppService(IRepository&lt;CAH.Health.Alarm.Alarm, long&gt; basicInventoryRepository) : base(basicInventoryRepository)
    {
    }
}

</code></pre>
<ol start="3">
<li>在Health模块的HttpApi中，创建AlarmController，并实现IAlarmAppService接口</li>
</ol>
<pre><code>[Area(HealthRemoteServiceConsts.ModuleName)]
[RemoteService(Name = HealthRemoteServiceConsts.RemoteServiceName)]
[Route(&quot;api/Health/alarm&quot;)]
public class AlarmController : ExtendedCurdController&lt;IAlarmAppService, AlarmDto, AlarmDto, AlarmBriefDto, long, GetAllAlarmInput, GetAllAlarmInput, CreateAlarmInput, UpdateAlarmInput&gt;, IAlarmAppService
{
    private readonly IAlarmAppService _alarmAppService;

    public AlarmController(IAlarmAppService alarmAppService) : base(alarmAppService)
    {
        _alarmAppService = alarmAppService;
    }


}
</code></pre>
<p>运行程序</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-26.png" alt="Alt text" /></p>
<p>可以看到，我们的接口已经包含所有扩展型Curd方法。</p>
<h1>按组织架构查询</h1>
<p>之前我们实现了Employee，Alarm管理模块以及通用查询应用层。</p>
<p>Employee的集合查询业务，是通过重写CreateFilteredQueryAsync方法，来实现按组织架构查询的过滤条件。</p>
<p>我们将这段逻辑代码提取到通用查询应用层中，便可实现在任何业务的按组织架构查询。</p>
<h2>原理</h2>
<p>EmployeeAppService中，CreateFilteredQueryAsync方法组织架构的过滤条件代码如下：</p>
<pre><code>var organizationUnitUsers = await organizationUnitAppService.GetOrganizationUnitUsersAsync(new GetOrganizationUnitUsersInput()
{
    Id = input.OrganizationUnitId.Value
});
if (organizationUnitUsers.Count() &gt; 0)
{
    var ids = organizationUnitUsers.Select(c =&gt; c.Id);
    query = query.Where(t =&gt; ids.Contains(t.Id));
}
else
{
    query = query.Where(c =&gt; false);
}
</code></pre>
<p>CreateFilteredQueryAsync是通过业务用户的IRepository获取实体的IQueryable 然后通过query.Where()实现了按组织架构的过滤条件。</p>
<p>IQueryable是一泛型类接口，泛型参数是实体类。要想在任意实体实现Where的过滤条件，我们使用动态拼接语言集成查询 (LINQ) 的方式实现通用查询接口，有关LINQ表达式，请阅读 <a href="https://learn.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/linq">LINQ 教程</a>和有关 <a href="https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/operators/lambda-expressions">Lambda 表达式</a>的文章。</p>
<h2>实现</h2>
<p>定义按组织架构查询过滤器（IOrganizationOrientedFilter）接口，查询实体列表Dto若实现该接口，将筛选指定 OrganizationUnitId 下的用户关联的实体。</p>
<pre><code>public interface IOrganizationOrientedFilter
{
    Guid? OrganizationUnitId { get; set; }
}
</code></pre>
<p>重写CreateFilteredQueryAsync方法，代码如下</p>
<pre><code>
protected override async Task&lt;IQueryable&lt;TEntity&gt;&gt; CreateFilteredQueryAsync(TGetListInput input)
{
    var query = await ReadOnlyRepository.GetQueryableAsync();

    query = await ApplyOrganizationOrientedFiltered(query,input);

    return query;
}
</code></pre>
<p>对于OrganizationUnit服务，其依赖关系在应用层，查找指定组织架构的用户将在CurdAppServiceBase的子类实现。创建一个抽象方法GetUserIdsByOrganizationAsync</p>
<pre><code>protected abstract Task&lt;IEnumerable&lt;Guid&gt;&gt; GetUserIdsByOrganizationAsync(Guid organizationUnitId)
</code></pre>
<p>创建应用过滤条件方法：ApplyOrganizationOrientedFiltered，在此实现拼接LINQ表达式，代码如下：</p>
<pre><code>protected virtual async Task&lt;IQueryable&lt;TEntity&gt;&gt; ApplyOrganizationOrientedFiltered(IQueryable&lt;TEntity&gt; query, TGetListInput input)
{
    if (input is IOrganizationOrientedFilter &amp;&amp; HasProperty&lt;TEntity&gt;(&quot;UserId&quot;))
    {
        var property = typeof(TEntity).GetProperty(&quot;UserId&quot;);
        var filteredInput = input as IOrganizationOrientedFilter;
        if (filteredInput != null &amp;&amp; filteredInput.OrganizationUnitId.HasValue)
        {

            var ids = await GetUserIdsByOrganizationAsync(filteredInput.OrganizationUnitId.Value);
            Expression originalExpression = null;
            var parameter = Expression.Parameter(typeof(TEntity), &quot;p&quot;);
            foreach (var id in ids)
            {
                var keyConstantExpression = Expression.Constant(id, typeof(Guid));
                var propertyAccess = Expression.MakeMemberAccess(parameter, property);
                var expressionSegment = Expression.Equal(propertyAccess, keyConstantExpression);

                if (originalExpression == null)
                {
                    originalExpression = expressionSegment;
                }
                else
                {
                    originalExpression = Expression.Or(originalExpression, expressionSegment);
                }
            }

            var equalExpression = originalExpression != null ?
                    Expression.Lambda&lt;Func&lt;TEntity, bool&gt;&gt;(originalExpression, parameter)
                    : p =&gt; false;

            query = query.Where(equalExpression);

        }

    }
    return query;
}
</code></pre>
<p>请注意，可应用过滤的条件为：</p>
<ol>
<li>input需实现IOrganizationOrientedFilter接口</li>
<li>实体必须包含UserId字段</li>
</ol>
<p>否则将原封不动返回IQueryable对象。</p>
<h2>使用</h2>
<p>在上一章Alarm管理模块中，我们已经写好了AlarmAppService，我们需要为其实现GetUserIdsByOrganizationAsync方法。改造AlarmAppService代码如下：</p>
<pre><code>public class AlarmAppService : ExtendedCurdAppServiceBase&lt;Matoapp.Health.Alarm.Alarm, AlarmDto, AlarmDto, AlarmBriefDto, long, GetAllAlarmInput, GetAllAlarmInput, CreateAlarmInput, UpdateAlarmInput&gt;, IAlarmAppService
{
    private readonly IOrganizationUnitAppService organizationUnitAppService;

    public AlarmAppService(
        IOrganizationUnitAppService organizationUnitAppService,
        IRepository&lt;Matoapp.Health.Alarm.Alarm, long&gt; basicInventoryRepository) : base(basicInventoryRepository)
    {
        this.organizationUnitAppService = organizationUnitAppService;
    }

    protected override async Task&lt;IEnumerable&lt;Guid&gt;&gt; GetUserIdsByOrganizationAsync(Guid organizationUnitId)
    {
        var organizationUnitUsers = await organizationUnitAppService.GetOrganizationUnitUsersAsync(new GetOrganizationUnitUsersInput()
        {
            Id = organizationUnitId
        });

        var ids = organizationUnitUsers.Select(c =&gt; c.Id);
        return ids;
    }
}
</code></pre>
<h2>测试</h2>
<p>创建一些组织架构，命名“群组”</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-28.png" alt="Alt text" /></p>
<p>在不同“群组”下创建一些客户（Client）</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-27.png" alt="Alt text" /></p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-29.png" alt="Alt text" /></p>
<p>在告警管理页面中，创建一些告警，并将这些告警分配给不同的客户</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-30.png" alt="Alt text" /></p>
<p>在客户管理中，通过选择不同的组织架构，查询当前“群组”下的客户告警</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-32.png" alt="Alt text" /></p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-31.png" alt="Alt text" /></p>
<h1>按任意字段关键字查询</h1>
<h2>实现</h2>
<p>定义按任意字段关键字查询过滤器（IKeywordOrientedFilter）接口，查询实体列表Dto若实现该接口，将筛选指定的目标字段（TargetFields）包含指定的关键字（Keyword）的实体。</p>
<pre><code>public interface IKeywordOrientedFilter
{
    public string Keyword { get; set; }

    public string TargetFields { get; set; }
}

</code></pre>
<p>创建应用过滤条件方法：ApplySearchFiltered，代码如下：</p>
<pre><code>protected virtual IQueryable&lt;TEntity&gt; ApplySearchFiltered(IQueryable&lt;TEntity&gt; query, TGetListInput input)
{
    if (input is IKeywordOrientedFilter)
    {
        var filteredInput = input as IKeywordOrientedFilter;
        if (filteredInput != null)
        {
            var targetFields = new string[] { &quot;Name&quot;, &quot;Title&quot; };
            if (!string.IsNullOrEmpty(filteredInput.TargetFields))
            {
                targetFields = filteredInput.TargetFields.Split(',');
            }

            return query.WhereIf(!filteredInput.Keyword.IsNullOrWhiteSpace(),
                FilterByKeywordDynamic&lt;TEntity&gt;(filteredInput.Keyword, targetFields));
        }
    }
    return query;
}
</code></pre>
<p>请注意，可应用过滤的条件为：</p>
<ol>
<li>input需实现IKeywordOrientedFilter接口，且Keyword不为空；</li>
<li>若filteredInput.TargetFields为空，则默认使用Name和Title字段进行筛选。</li>
</ol>
<p>对于每一个TargetField，需要在实体中找到对应字段（属性）。若找到，则为此实体字段创建条件筛选的表达式，然后将这些表达式通过Or连接起来，最终返回一个包含多段关键字筛选的Lambda表达式。</p>
<p>创建FilterByKeywordDynamic方法，代码如下：</p>
<pre><code>private Expression&lt;Func&lt;TEntity, bool&gt;&gt; FilterByKeywordDynamic&lt;T&gt;(string keyword, params string[] sortColumns)
{
    var parameter = Expression.Parameter(typeof(T), &quot;p&quot;);
    var propertys = sortColumns.Select(sortColumn =&gt; typeof(T).GetProperty(sortColumn));

    var method = typeof(string)
        .GetMethods()
        .FirstOrDefault(x =&gt; x.Name == &quot;Contains&quot;);

    var keyConstantExpression = Expression.Constant(keyword, typeof(string));
    Expression originalExpression = null;
    foreach (var property in propertys)
    {
        if (property != null)
        {
            var propertyAccess = Expression.MakeMemberAccess(parameter, property);
            var expression = Expression.Call(propertyAccess, method, keyConstantExpression);
            if (originalExpression == null)
            {
                originalExpression = expression;
            }
            else
            {
                originalExpression = Expression.Or(originalExpression, expression);
            }
        }
    }

    var result = originalExpression != null ?
            Expression.Lambda&lt;Func&lt;TEntity, bool&gt;&gt;(originalExpression, parameter)
            : p =&gt; true;
    return result;


}

</code></pre>
<p>创建默认的应用过滤规则DefaultConvention，将之前的按组织架构查询和按关键字查询的代码提取到DefaultConvention方法中，此类可派生，使用virtual关键字以便在子类中重写，代码如下：</p>
<pre><code>protected virtual async Task&lt;IQueryable&lt;TEntity&gt;&gt; DefaultConvention(TGetListInput input, IQueryable&lt;TEntity&gt; query)
{
    query = ApplySearchFiltered(query, input);
    query = ApplyUserOrientedFiltered(query, input);
    return query;
}
</code></pre>
<p>在CreateBriefFilteredQueryAsync和CreateFilteredQueryAsync方法中调用DefaultConvention方法，代码如下：</p>
<pre><code>protected virtual async Task&lt;IQueryable&lt;TEntity&gt;&gt; CreateBriefFilteredQueryAsync(TGetListBriefInput input)
{
    var query = await ReadOnlyRepository.GetQueryableAsync();

    query = await DefaultConvention(input, query);

    return query;
}



protected override async Task&lt;IQueryable&lt;TEntity&gt;&gt; CreateFilteredQueryAsync(TGetListInput input)
{
    var query = await ReadOnlyRepository.GetQueryableAsync();

    query = await DefaultConvention(input, query);

    return query;
}


</code></pre>
<h2>应用</h2>
<p>无需在应用层中更改代码，</p>
<p>在GetAllAlarmInput中实现IKeywordOrientedFilter接口，代码如下：</p>
<pre><code>public class GetAllAlarmInput : PagedAndSortedResultRequestDto,   IKeywordOrientedFilter
{
    //keyword
    public string Keyword { get; set; }
    public string TargetFields { get; set; }

    ...
}

</code></pre>
<h2>测试</h2>
<p>在告警管理页面建立一些告警</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-33.png" alt="Alt text" /></p>
<p>在筛选中输入关键字“3”，点击查询</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-34.png" alt="Alt text" /></p>
<p>可以看到将筛选出标题包含关键字“3”的告警</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-35.png" alt="Alt text" /></p>
<p>查询的报文Payload如下图：</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-36.png" alt="Alt text" /></p>
<h1>按用户查询</h1>
<h2>实现</h2>
<p>定义按用户查询（IUserOrientedFilter）接口</p>
<pre><code>public interface IUserOrientedFilter
{
    public string EntityUserIdIdiom { get; }
    Guid? UserId { get; set; }
}

</code></pre>
<ul>
<li>EntityUserIdIdiom：语义上的UserId，用于指定业务实体中用于描述“用户Id”字段的名称，若不指定，则默认为“UserId”</li>
<li>UserId：用户Id，若为Guid.Empty，则使用当前登录用户的Id</li>
</ul>
<p>查询实体列表Dto若实现该接口，将筛选指定 UserId 下的关联的实体。</p>
<p>若指定 UserId 为 Guid.Empty，则使用当前登录用户的 UserId。</p>
<p>ICurrentUser是Abp的一个服务，用于获取当前登录用户的信息</p>
<p>创建应用过滤条件方法：ApplyUserOrientedFiltered，在此实现拼接LINQ表达式，代码如下：</p>
<pre><code>protected virtual IQueryable&lt;TEntity&gt; ApplyUserOrientedFiltered(IQueryable&lt;TEntity&gt; query, TGetListInput input)
{
    if (input is IUserOrientedFilter)
    {
        var filteredInput = input as IUserOrientedFilter;
        var entityUserIdIdiom = filteredInput.EntityUserIdIdiom;
        if (string.IsNullOrEmpty(entityUserIdIdiom))
        {
            entityUserIdIdiom = &quot;UserId&quot;;
        }
        if (HasProperty&lt;TEntity&gt;(entityUserIdIdiom))
        {
            var property = typeof(TEntity).GetProperty(entityUserIdIdiom);
            if (filteredInput != null &amp;&amp; filteredInput.UserId.HasValue)
            {
                Guid userId = default;
                if (filteredInput.UserId.Value == Guid.Empty)
                {
                    using (var scope = ServiceProvider.CreateScope())
                    {
                        var currentUser = scope.ServiceProvider.GetRequiredService&lt;ICurrentUser&gt;();
                        if (currentUser != null)
                        {
                            userId = currentUser.GetId();
                        }
                    }
                }
                else
                {
                    userId = filteredInput.UserId.Value;
                }

                var parameter = Expression.Parameter(typeof(TEntity), &quot;p&quot;);
                var keyConstantExpression = Expression.Constant(userId, typeof(Guid));

                var propertyAccess = Expression.MakeMemberAccess(parameter, property);
                var expression = Expression.Equal(propertyAccess, keyConstantExpression);

                var equalExpression = expression != null ?
                        Expression.Lambda&lt;Func&lt;TEntity, bool&gt;&gt;(expression, parameter)
                        : p =&gt; false;

                query = query.Where(equalExpression);
            }
        }
    }
    return query;
}


</code></pre>
<p>请注意，可应用过滤的条件为：</p>
<ol>
<li>input需实现IUserOrientedFilter接口；</li>
<li>实体必须关联用户。</li>
</ol>
<p>否则将原封不动返回IQueryable对象。</p>
<h2>使用</h2>
<p>无需在应用层中更改代码，</p>
<p>在GetAllAlarmInput中实现IUserOrientedFilter接口，代码如下：</p>
<pre><code>public class GetAllAlarmInput : PagedAndSortedResultRequestDto, IUserOrientedFilter
{
    Guid? UserId { get; set; }
    
    public string EntityUserIdIdiom { get; }      
    // 或显式实现   
    // public string EntityUserIdIdiom =&gt; &quot;UserId&quot;;
    
    ...
}

</code></pre>
<h2>测试</h2>
<p>创建一些组织架构，命名“群组”</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-28.png" alt="Alt text" /></p>
<p>在不同“群组”下创建一些客户（Client）</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-27.png" alt="Alt text" /></p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-29.png" alt="Alt text" /></p>
<p>在告警管理页面中，创建一些告警，并将这些告警分配给不同的客户</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-37.png" alt="Alt text" /></p>
<p>告警创建完成后，进入客户管理，在右侧客户列表中点击“查看详情”</p>
<p>打开客户详情页面，点击“告警”标签页，可以看到该客户下的告警列表</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-38.png" alt="Alt text" /></p>
<h1>按用户关系查询</h1>
<p>用户关系（Relation）是描述业务系统中人员与人员之间的关系，如：签约、关注，或者朋友关系。</p>
<p>之前我们在扩展身份管理模块的时候，已经实现了用户关系管理，可以查看本系列博文之前的内容。<a href="https://blog.csdn.net/jevonsflash/article/details/131602982">怎样优雅地增删查改（二）：扩展身份管理模块</a></p>
<h2>原理</h2>
<p><strong>查询依据</strong></p>
<p>用户之间的关系通过Relation表来存储。模型如下图所示：</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-39.png" alt="Alt text" /></p>
<ul>
<li><p>关系类型由Type来定义</p>
</li>
<li><p>关系指向由UserId与RelatedUserId来描述</p>
<p>人员之间的关系是单项的，也就是说可以A是B的好友，但B不一定是A的好友</p>
<p>正向关系：User -&gt; RelatedUser</p>
<p>反向关系：RelatedUser -&gt; User</p>
</li>
</ul>
<p>查询目标业务对象HealthAlarm关联了业务用户HealthClient，因业务用户与鉴权用户IdentityUser共享同一个Id，因此可以通过查询用户关系关联的User，查询到业务对象。</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/screenshot20230718.png" alt="Alt text" /></p>
<h2>实现</h2>
<h3>正向用户关系</h3>
<p>定义按正向用户关系查询（IRelationToOrientedFilter）接口</p>
<pre><code>public interface IRelationToOrientedFilter
{
    Guid? RelationToUserId { get; set; }
    
    public string EntityUserIdIdiom { get; }

    string RelationType { get; set; }

}

</code></pre>
<ul>
<li>EntityUserIdIdiom：语义上的UserId，用于指定业务实体中用于描述“用户Id”字段的名称，若不指定，则默认为“UserId”；</li>
<li>RelationToUserId：正向关系用户Id，若为Guid.Empty，则使用当前登录用户的Id；</li>
<li>RelationType：关系类型，如：“attach”为签约，“follow”为关注，可自定义。</li>
</ul>
<p>对于Relation服务，其依赖关系在应用层，查找指定用户的关系用户将在CurdAppServiceBase的子类实现。创建一个抽象方法GetUserIdsByRelatedToAsync</p>
<pre><code>protected abstruct Task&lt;IEnumerable&lt;Guid&gt;&gt; GetUserIdsByRelatedToAsync(Guid userId, string relationType);
</code></pre>
<p>创建应用过滤条件方法：ApplyRelationToOrientedFiltered，在此实现拼接LINQ表达式，</p>
<p>ICurrentUser是Abp的一个服务，用于获取当前登录用户的信息</p>
<p>代码如下：</p>
<pre><code>protected virtual async Task&lt;IQueryable&lt;TEntity&gt;&gt; ApplyRelationToOrientedFiltered(IQueryable&lt;TEntity&gt; query, TGetListInput input)
{
    if (input is IRelationToOrientedFilter)
    {
        var filteredInput = input as IRelationToOrientedFilter;
        var entityUserIdIdiom = filteredInput.EntityUserIdIdiom;
        if (string.IsNullOrEmpty(entityUserIdIdiom))
        {
            entityUserIdIdiom = &quot;UserId&quot;;
        }
        if (HasProperty&lt;TEntity&gt;(entityUserIdIdiom))
        {
            var property = typeof(TEntity).GetProperty(entityUserIdIdiom);
            if (filteredInput != null &amp;&amp; filteredInput.RelationToUserId.HasValue &amp;&amp; !string.IsNullOrEmpty(filteredInput.RelationType))
            {

                Guid userId = default;
                if (filteredInput.RelationToUserId.Value == Guid.Empty)
                {
                    using (var scope = ServiceProvider.CreateScope())
                    {
                        var currentUser = scope.ServiceProvider.GetRequiredService&lt;ICurrentUser&gt;();
                        if (currentUser != null)
                        {
                            userId = currentUser.GetId();
                        }
                    }
                }
                else
                {
                    userId = filteredInput.RelationToUserId.Value;
                }

                var ids = await GetUserIdsByRelatedToAsync(userId, filteredInput.RelationType);
                Expression originalExpression = null;
                var parameter = Expression.Parameter(typeof(TEntity), &quot;p&quot;);
                foreach (var id in ids)
                {
                    var keyConstantExpression = Expression.Constant(id, typeof(Guid));
                    var propertyAccess = Expression.MakeMemberAccess(parameter, property);
                    var expressionSegment = Expression.Equal(propertyAccess, keyConstantExpression);

                    if (originalExpression == null)
                    {
                        originalExpression = expressionSegment;
                    }
                    else
                    {
                        originalExpression = Expression.Or(originalExpression, expressionSegment);
                    }
                }

                var equalExpression = originalExpression != null ?
                        Expression.Lambda&lt;Func&lt;TEntity, bool&gt;&gt;(originalExpression, parameter)
                        : p =&gt; false;

                query = query.Where(equalExpression);

            }

        }
    }
    return query;
}


</code></pre>
<h3>反向用户关系</h3>
<p>定义按反向用户关系查询（IRelationFromOrientedFilter）接口</p>
<pre><code>public interface IRelationFromOrientedFilter
{
    Guid? RelationFromUserId { get; set; }
    
    public string EntityUserIdIdiom { get; }

    string RelationType { get; set; }

}

</code></pre>
<ul>
<li>EntityUserIdIdiom：语义上的UserId，用于指定业务实体中用于描述“用户Id”字段的名称，若不指定，则默认为“UserId”；</li>
<li>RelationFromUserId：反向关系用户Id，若为Guid.Empty，则使用当前登录用户的Id；</li>
<li>RelationType：关系类型，如：“attach”为签约，“follow”为关注，可自定义。</li>
</ul>
<p>对于Relation服务，其依赖关系在应用层，查找指定用户的关系用户将在CurdAppServiceBase的子类实现。创建一个抽象方法GetUserIdsByRelatedFromAsync</p>
<pre><code>protected abstruct Task&lt;IEnumerable&lt;Guid&gt;&gt; GetUserIdsByRelatedFromAsync(Guid userId, string relationType);
</code></pre>
<p>创建应用过滤条件方法：ApplyRelationFromOrientedFiltered，在此实现拼接LINQ表达式，</p>
<p>ICurrentUser是Abp的一个服务，用于获取当前登录用户的信息</p>
<p>代码如下：</p>
<pre><code>protected virtual async Task&lt;IQueryable&lt;TEntity&gt;&gt; ApplyRelationFromOrientedFiltered(IQueryable&lt;TEntity&gt; query, TGetListInput input)
{
    if (input is IRelationFromOrientedFilter)
    {
        var filteredInput = input as IRelationFromOrientedFilter;
        var entityUserIdIdiom = filteredInput.EntityUserIdIdiom;
        if (string.IsNullOrEmpty(entityUserIdIdiom))
        {
            entityUserIdIdiom = &quot;UserId&quot;;
        }
        if (HasProperty&lt;TEntity&gt;(entityUserIdIdiom))
        {
            var property = typeof(TEntity).GetProperty(entityUserIdIdiom);
            if (filteredInput != null &amp;&amp; filteredInput.RelationFromUserId.HasValue &amp;&amp; !string.IsNullOrEmpty(filteredInput.RelationType))
            {

                Guid userId = default;
                if (filteredInput.RelationFromUserId.Value == Guid.Empty)
                {
                    using (var scope = ServiceProvider.CreateScope())
                    {
                        var currentUser = scope.ServiceProvider.GetRequiredService&lt;ICurrentUser&gt;();
                        if (currentUser != null)
                        {
                            userId = currentUser.GetId();
                        }
                    }
                }
                else
                {
                    userId = filteredInput.RelationFromUserId.Value;
                }

                var ids = await GetUserIdsByRelatedFromAsync(userId, filteredInput.RelationType);
                Expression originalExpression = null;
                var parameter = Expression.Parameter(typeof(TEntity), &quot;p&quot;);
                foreach (var id in ids)
                {
                    var keyConstantExpression = Expression.Constant(id, typeof(Guid));
                    var propertyAccess = Expression.MakeMemberAccess(parameter, property);
                    var expressionSegment = Expression.Equal(propertyAccess, keyConstantExpression);

                    if (originalExpression == null)
                    {
                        originalExpression = expressionSegment;
                    }
                    else
                    {
                        originalExpression = Expression.Or(originalExpression, expressionSegment);
                    }
                }

                var equalExpression = originalExpression != null ?
                        Expression.Lambda&lt;Func&lt;TEntity, bool&gt;&gt;(originalExpression, parameter)
                        : p =&gt; false;

                query = query.Where(equalExpression);

            }
        }
    }
    return query;
}

</code></pre>
<p>IRelationToOrientedFilter 和 IRelationFromOrientedFilter接口实现上并非互斥。</p>
<p>请注意，可应用过滤的条件为：</p>
<ol>
<li>input需实现IRelationToOrientedFilter接口；</li>
<li>实体必须关联用户。</li>
</ol>
<p>否则将原封不动返回IQueryable对象。</p>
<h2>使用</h2>
<p>在应用层中，实现GetUserIdsByRelatedToAsync</p>
<pre><code>protected override async Task&lt;IEnumerable&lt;Guid&gt;&gt; GetUserIdsByRelatedToAsync(Guid userId, string relationType)
{
    var ids = await relationAppService.GetRelatedToUserIdsAsync(new GetRelatedUsersInput()
    {
        UserId = userId,
        Type = relationType
    });
    return ids;

}
</code></pre>
<p>或GetUserIdsByRelatedFromAsync</p>
<pre><code>protected override async Task&lt;IEnumerable&lt;Guid&gt;&gt; GetUserIdsByRelatedFromAsync(Guid userId, string relationType)
{
    var ids = await relationAppService.GetRelatedFromUserIdsAsync(new GetRelatedUsersInput()
    {
        UserId = userId,
        Type = relationType
    });
    return ids;

}
</code></pre>
<p>在GetAllAlarmInput中实现IRelationToOrientedFilter或GetUserIdsByRelatedFromAsync接口，代码如下：</p>
<pre><code>public class GetAllAlarmInput : PagedAndSortedResultRequestDto, IRelationToOrientedFilter
{ 
    public Guid? RelationToUserId { get ; set ; }
    public string RelationType { get; set; }
    public string EntityUserIdIdiom { get; }

    ...
}

</code></pre>
<h2>测试</h2>
<p>创建一些客户（Client）</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-27.png" alt="Alt text" /></p>
<p>进入客户管理，在右侧客户列表中点击“查看详情”</p>
<p>打开客户详情页面，点击管理 - 设置签约员工</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-41.png" alt="Alt text" /></p>
<p>选择一个用户，此时该客户会签约至该用户账号下，这里我们将客户1和客户3签约至当前账号admin下。</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-40.png" alt="Alt text" /></p>
<p>登录签约用户(admin)的账号，点击“我的” - 客户 - 签约客户</p>
<p>在客户列表中可见，客户1和客户3已签约至当前账号下。</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-42.png" alt="Alt text" /></p>
<p>组合查询的报文Payload如下图：</p>
<p><img src="https://raw.githubusercontent.com/jevonsflash/posts/master/./assets/image-43.png" alt="Alt text" /></p>
<h1>按日期范围查询</h1>
<p>使用数据库的创建时间作为查询依据，在Abp框架中，实体类实现ICreationAuditedObject接口，或继承CreationAuditedEntity类，使用仓储创建记录时将自动生成CreationTime。</p>
<h2>实现</h2>
<p>定义按创建日期范围查询（IDateSpanOrientedFilter）接口。</p>
<p>遵守接口隔离原则，将IDateSpanOrientedFilter接口拆分为IStartDateOrientedFilter和IEndDateOrientedFilter接口。</p>
<pre><code>public interface IDateSpanOrientedFilter : IStartDateOrientedFilter, IEndDateOrientedFilter
{

}
</code></pre>
<p>按开始日期查询接口定义如下：</p>
<pre><code>public interface IStartDateOrientedFilter
{
    DateTime? StartDate { get; set; }
}
</code></pre>
<p>结束日期接口定义如下：</p>
<pre><code>public interface IEndDateOrientedFilter
{
    DateTime? EndDate { get; set; }
}
</code></pre>
<ul>
<li>StartDate：开始日期，记录的CreationTime <strong>大于等于</strong> 该日期的记录将被筛选</li>
<li>EndDate：用户Id，记录的CreationTime <strong>小于</strong> 该日期的记录将被筛选</li>
</ul>
<h3>按开始日期查询</h3>
<p>创建应用过滤条件方法：ApplyStartDateOrientedFiltered，在此实现拼接LINQ表达式，代码如下：</p>
<pre><code>protected virtual IQueryable&lt;TEntity&gt; ApplyStartDateOrientedFiltered(IQueryable&lt;TEntity&gt; query, TGetListInput input)
{
    if (input is IStartDateOrientedFilter &amp;&amp; HasProperty&lt;TEntity&gt;(&quot;CreationTime&quot;))
    {
        var property = typeof(TEntity).GetProperty(&quot;CreationTime&quot;);
        var filteredInput = input as IStartDateOrientedFilter;
        if (filteredInput != null &amp;&amp; filteredInput.StartDate.HasValue)
        {
            Expression originalExpression = null;
            var parameter = Expression.Parameter(typeof(TEntity), &quot;p&quot;);

            var dateConstantExpression = Expression.Constant(filteredInput.StartDate.Value, typeof(DateTime));

            var propertyAccess = Expression.MakeMemberAccess(parameter, property);
            var expression = Expression.GreaterThanOrEqual(propertyAccess, dateConstantExpression);

            var equalExpression = expression != null ?
                    Expression.Lambda&lt;Func&lt;TEntity, bool&gt;&gt;(expression, parameter)
                    : p =&gt; false;


            query = query.Where(equalExpression);

        }

    }
    return query;
}


</code></pre>
<h3>按结束日期查询</h3>
<p>创建应用过滤条件方法：ApplyEndDateOrientedFiltered，在此实现拼接LINQ表达式，代码如下：</p>
<pre><code>protected virtual IQueryable&lt;TEntity&gt; ApplyEndDateOrientedFiltered(IQueryable&lt;TEntity&gt; query, TGetListInput input)
{
    if (input is IEndDateOrientedFilter &amp;&amp; HasProperty&lt;TEntity&gt;(&quot;CreationTime&quot;))
    {
        var property = typeof(TEntity).GetProperty(&quot;CreationTime&quot;);
        var filteredInput = input as IEndDateOrientedFilter;
        if (filteredInput != null &amp;&amp; filteredInput.EndDate.HasValue)
        {
            Expression originalExpression = null;
            var parameter = Expression.Parameter(typeof(TEntity), &quot;p&quot;);

            var dateConstantExpression = Expression.Constant(filteredInput.EndDate.Value, typeof(DateTime));

            var propertyAccess = Expression.MakeMemberAccess(parameter, property);
            var expression = Expression.LessThan(propertyAccess, dateConstantExpression);

            var equalExpression = expression != null ?
                    Expression.Lambda&lt;Func&lt;TEntity, bool&gt;&gt;(expression, parameter)
                    : p =&gt; false;


            query = query.Where(equalExpression);

        }

    }
    return query;
}
</code></pre>
<p>请注意，可应用过滤的条件为：</p>
<ol>
<li>input需实现IDateSpanOrientedFilter或子接口；</li>
<li>实体必须包含“CreationTime”字段。</li>
</ol>
<p>否则将原封不动返回IQueryable对象。</p>
<h2>使用</h2>
<p>无需在应用层中更改代码，</p>
<p>在GetAllAlarmInput中实现IDateSpanOrientedFilter接口，代码如下：</p>
<pre><code>public class GetAllAlarmInput : PagedAndSortedResultRequestDto, IDateSpanOrientedFilter
{
    public DateTime? StartDate { get; set; }
    public DateTime? EndDate { get; set; }

    ...
}

</code></pre>
<p>至此，所有的通用查询接口已实现完成。在这个项目中，我实现了适合我的联合查询方式，你可以根据实际业务需求，扩展和调整查询实现。</p>
<h2>项目地址</h2>
<p><a href="https://github.com/jevonsflash/general-curd-sample">Github:general-curd-sample</a></p>
<h2>在线演示：</h2>
<p>https://www.matoapp.net:3012/  用户名admin 密码1q2w3E*</p>
]]></content:encoded>
      <media:thumbnail url="https://abp.io/api/posts/cover-picture-source/3a0c89fa-eef9-b632-572d-2104e3303e3f" />
      <media:content url="https://abp.io/api/posts/cover-picture-source/3a0c89fa-eef9-b632-572d-2104e3303e3f" medium="image" />
    </item>
  </channel>
</rss>