怎樣優雅地增刪查改(四):建立通用查詢基礎類別

2023-07-13 12:00:29

@


上一章我們實現了Employee管理模組,Employee的增刪改查是通過其應用服務類,繼承自Abp.Application.Services.CrudAppService實現的。

我們將封裝通用的應用層,介面以及控制器基礎類別。

建立通用查詢抽象層

建立介面ICurdAppService,在這裡我們定義了通用的增刪改查介面。

其中的泛型引數:

  • TGetOutputDto: Get方法返回的實體Dto
  • TGetListOutputDto: GetAll方法返回的實體Dto
  • TKey: 實體的主鍵
  • TGetListInput: GetAll方法接收的輸入引數
  • TCreateInput: Create方法接收的輸入引數
  • TUpdateInput: Update方法接收的輸入引數
public interface ICurdAppService<TGetOutputDto, TGetListOutputDto, in TKey, in TGetListInput, in TCreateInput, in TUpdateInput>

{
    Task<TGetOutputDto> GetAsync(TKey id);

    Task<PagedResultDto<TGetListOutputDto>> GetAllAsync(TGetListInput input);

    Task<TGetOutputDto> CreateAsync(TCreateInput input);

    Task<TGetOutputDto> UpdateAsync(TUpdateInput input);

    Task DeleteAsync(TKey id);
}

建立通用查詢應用層基礎類別

建立應用層服務CurdAppServiceBase,它是一個抽象類,繼承自Abp.Application.Services.CrudAppService。

程式碼如下:

public abstract class CurdAppServiceBase<TEntity, TGetOutputDto, TGetListOutputDto,  TKey, TGetListInput,  TCreateInput, TUpdateInput>
: CrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where TEntity : class, IEntity<TKey>
    where TGetOutputDto : IEntityDto<TKey>
where TGetListOutputDto : IEntityDto<TKey>
{
    protected CurdAppServiceBase(IRepository<TEntity, TKey> repository)
: base(repository)
    {

    }
}

建立通用查詢控制器基礎類別

建立控制器類CurdController,繼承自AbpControllerBase。並實現ICurdAppService介面。

程式碼如下:

public abstract class CurdController<ITAppService, TGetOutputDto, TGetListOutputDto,  TKey, TGetListInput,  TCreateInput, TUpdateInput>
    : AbpControllerBase
where ITAppService : ICurdAppService<TGetOutputDto, TGetListOutputDto,  TKey, TGetListInput,  TCreateInput, TUpdateInput>
where TGetOutputDto : IEntityDto<TKey>
where TGetListOutputDto : IEntityDto<TKey>
{

    private readonly ITAppService _recipeAppService;

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

    [HttpPost]
    [Route("Create")]
    
    public virtual async Task<TGetOutputDto> CreateAsync(TCreateInput input)
    {
        return await _recipeAppService.CreateAsync(input);
    }

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

    [HttpGet]
    [Route("GetAll")]
    
    public virtual async Task<PagedResultDto<TGetListOutputDto>> GetAllAsync(TGetListInput input)
    {
        return await _recipeAppService.GetAllAsync(input);
    }

    [HttpGet]
    [Route("Get")]
    
    public virtual async Task<TGetOutputDto> GetAsync(TKey id)
    {
        return await _recipeAppService.GetAsync(id);
    }

    [HttpPut]
    [Route("Update")]
    
    public virtual async Task<TGetOutputDto> UpdateAsync(TUpdateInput input)
    {
        return await _recipeAppService.UpdateAsync(input);
    }
}

[可選]替換RESTfulApi

為了相容舊版Abp,需更改增刪查改服務(CrudAppService)的方法簽名,可參考[Volo.Abp升級筆記]使用舊版Api規則替換RESTful Api以相容老程式,此處不再贅述。

將UpdateAsync,GetListAsync方法封閉:

private new Task<TGetOutputDto> UpdateAsync(TKey id, TUpdateInput input)
{
    return base.UpdateAsync(id, input);
}
private new Task<PagedResultDto<TGetListOutputDto>> GetListAsync(TGetListInput input)
{
    return base.GetListAsync(input);
}


封閉原有UpdateAsync, 新增的UpdateAsync方法更改了方法簽名:


public virtual async Task<TGetOutputDto> UpdateAsync(TUpdateInput input)
{
    await CheckUpdatePolicyAsync();
    var entity = await GetEntityByIdAsync((input as IEntityDto<TKey>).Id);
    MapToEntity(input, entity);
    await Repository.UpdateAsync(entity, autoSave: true);
    return await MapToGetOutputDtoAsync(entity);

}

Brief是一種簡化的查詢實體集合的方法,其返回的Dto不包含導航屬性,以減少資料傳輸量。

新增GetAllAsync和GetAllBriefAsync方法:

public virtual Task<PagedResultDto<TGetListOutputDto>> GetAllAsync(TGetListInput input)
{
    return this.GetListAsync(input);
}


public async Task<PagedResultDto<TGetListBriefOutputDto>> GetAllBriefAsync(TGetListBriefInput input)
{
    await CheckGetListPolicyAsync();

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

    var entities = new List<TEntity>();
    var entityDtos = new List<TGetListBriefOutputDto>();

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

        entities = await AsyncExecuter.ToListAsync(query);

        entityDtos = ObjectMapper.Map<List<TEntity>, List<TGetListBriefOutputDto>>(entities);

    }

    return new PagedResultDto<TGetListBriefOutputDto>(
        totalCount,
        entityDtos
    );

}

擴充套件泛型引數

目前為止,我們的應用層基礎類別繼承於Abp.Application.Services.CrudAppService

為了更好的程式碼重用,我們對泛型引數進行擴充套件,使用CurdAppServiceBase的類可根據實際業務需求選擇泛型引數

其中的泛型引數:

  • TEntity: CRUD操作對應的實體類
  • TEntityDto: GetAll方法返回的實體Dto
  • TKey: 實體的主鍵
  • TGetListBriefInput: GetAllBrief方法的輸入引數
  • TGetListBriefOutputDto: GetAllBrief方法的輸出引數

首先擴充套件ICurdAppService:


public interface ICurdAppService<TEntityDto, in TKey>
    : ICurdAppService<TEntityDto, TKey, PagedAndSortedResultRequestDto>
{

}

public interface ICurdAppService<TEntityDto, in TKey, in TGetListInput>
    : ICurdAppService<TEntityDto, TKey, TGetListInput, TEntityDto>
{

}

public interface ICurdAppService<TEntityDto, in TKey, in TGetListInput, in TCreateInput>
    : ICurdAppService<TEntityDto, TKey, TGetListInput, TCreateInput, TCreateInput>
{

}


public interface ICurdAppService<TEntityDto, in TKey, in TGetListInput, in TCreateInput, in TUpdateInput>
    : ICurdAppService<TEntityDto, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
{

}


public interface ICurdAppService<TGetOutputDto, TGetListOutputDto, in TKey, in TGetListInput, in TCreateInput, in TUpdateInput>
: ICurdAppService<TGetOutputDto,  TGetListOutputDto, TKey, TGetListInput, TGetListInput, TCreateInput, TUpdateInput>
{

}



public interface ICurdAppService<TGetOutputDto, TGetListOutputDto, in TKey, in TGetListInput, in TGetListBriefInput, in TCreateInput, in TUpdateInput>
: ICurdAppService<TGetOutputDto, TGetListOutputDto, TGetListOutputDto, TKey, TGetListInput, TGetListBriefInput, TCreateInput, TUpdateInput>
{

}



public interface ICurdAppService<TGetOutputDto, TGetListOutputDto, TGetListBriefOutputDto, in TKey, in TGetListInput, in TGetListBriefInput, in TCreateInput, in TUpdateInput>

{
    Task<TGetOutputDto> GetAsync(TKey id);

    Task<PagedResultDto<TGetListOutputDto>> GetAllAsync(TGetListInput input);

    Task<TGetOutputDto> CreateAsync(TCreateInput input);

    Task<TGetOutputDto> UpdateAsync(TUpdateInput input);

    Task DeleteAsync(TKey id);

    Task<PagedResultDto<TGetListBriefOutputDto>> GetAllBriefAsync(TGetListInput input);


}




擴充套件CurdAppServiceBase:


public abstract class CurdAppServiceBase<TEntity, TEntityDto, TKey>
    : CurdAppServiceBase<TEntity, TEntityDto, TKey, PagedAndSortedResultRequestDto>
    where TEntity : class, IEntity<TKey>
    where TEntityDto : IEntityDto<TKey>
{
    protected CurdAppServiceBase(IRepository<TEntity, TKey> repository)
        : base(repository)
    {

    }
}

public abstract class CurdAppServiceBase<TEntity, TEntityDto, TKey, TGetListInput>
    : CurdAppServiceBase<TEntity, TEntityDto, TKey, TGetListInput, TEntityDto>
    where TEntity : class, IEntity<TKey>
    where TEntityDto : IEntityDto<TKey>
{
    protected CurdAppServiceBase(IRepository<TEntity, TKey> repository)
        : base(repository)
    {

    }
}


public abstract class CurdAppServiceBase<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput>
    : CurdAppServiceBase<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput, TCreateInput>
    where TEntity : class, IEntity<TKey>
    where TEntityDto : IEntityDto<TKey>
{
    protected CurdAppServiceBase(IRepository<TEntity, TKey> repository)
        : base(repository)
    {

    }
}

public abstract class CurdAppServiceBase<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
: CurdAppServiceBase<TEntity, TEntityDto, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where TEntity : class, IEntity<TKey>
where TEntityDto : IEntityDto<TKey>
{
    protected CurdAppServiceBase(IRepository<TEntity, TKey> repository)
        : base(repository)
    {

    }

    protected override Task<TEntityDto> MapToGetListOutputDtoAsync(TEntity entity)
    {
        return MapToGetOutputDtoAsync(entity);
    }

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

public abstract class CurdAppServiceBase<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
: CurdAppServiceBase<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TGetListInput, TCreateInput, TUpdateInput>
where TEntity : class, IEntity<TKey>
where TGetOutputDto : IEntityDto<TKey>
where TGetListOutputDto : IEntityDto<TKey>
{
    protected CurdAppServiceBase(IRepository<TEntity, TKey> repository)
        : base(repository)
    {

    }
}


public abstract class CurdAppServiceBase<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput,  TCreateInput, TUpdateInput>
: CurdAppServiceBase<TEntity, TGetOutputDto, TGetListOutputDto, TGetListOutputDto, TKey, TGetListInput,  TCreateInput, TUpdateInput>
where TEntity : class, IEntity<TKey>
where TGetOutputDto : IEntityDto<TKey>
where TGetListOutputDto : IEntityDto<TKey>
{
    protected CurdAppServiceBase(IRepository<TEntity, TKey> repository)
        : base(repository)
    {

    }
}

擴充套件CurdController

public abstract class CurdController<ITAppService, TEntityDto, TKey>
        : CurdController<ITAppService, TEntityDto, TKey, PagedAndSortedResultRequestDto>
        where ITAppService : ICurdAppService<TEntityDto, TKey>
        where TEntityDto : IEntityDto<TKey>
{
    protected CurdController(ITAppService appService)
        : base(appService)
    {

    }
}

public abstract class CurdController<ITAppService, TEntityDto, TKey, TGetListInput>
    : CurdController<ITAppService, TEntityDto, TKey, TGetListInput, TEntityDto>
    where ITAppService : ICurdAppService<TEntityDto, TKey, TGetListInput>
    where TEntityDto : IEntityDto<TKey>
{
    protected CurdController(ITAppService appService)
        : base(appService)
    {

    }
}


public abstract class CurdController<ITAppService, TEntityDto, TKey, TGetListInput, TCreateInput>
    : CurdController<ITAppService, TEntityDto, TKey, TGetListInput, TCreateInput, TCreateInput>
    where ITAppService : ICurdAppService<TEntityDto, TKey, TGetListInput, TCreateInput>
    where TEntityDto : IEntityDto<TKey>
{
    protected CurdController(ITAppService appService)
        : base(appService)
    {

    }
}



public abstract class CurdController<ITAppService, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
: CurdController<ITAppService, TEntityDto, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where ITAppService : ICurdAppService<TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
    where TEntityDto : IEntityDto<TKey>
{
    protected CurdController(ITAppService appService)
        : base(appService)
    {

    }

}




public abstract class CurdController<ITAppService, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
: CurdController<ITAppService, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TGetListInput, TCreateInput, TUpdateInput>
where ITAppService : ICurdAppService<TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where TGetOutputDto : IEntityDto<TKey>
where TGetListOutputDto : IEntityDto<TKey>
{
    protected CurdController(ITAppService appService)
        : base(appService)
    {

    }

}



public abstract class CurdController<ITAppService, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TGetListBriefInput, TCreateInput, TUpdateInput>
: CurdController<ITAppService, TGetOutputDto, TGetListOutputDto, TGetListOutputDto, TKey, TGetListInput, TGetListBriefInput, TCreateInput, TUpdateInput>
where ITAppService : ICurdAppService<TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TGetListBriefInput, TCreateInput, TUpdateInput>
where TGetOutputDto : IEntityDto<TKey>
where TGetListOutputDto : IEntityDto<TKey>
where TGetListBriefInput : TGetListInput
{
    protected CurdController(ITAppService appService)
        : base(appService)
    {

    }

}


public abstract class CurdController<ITAppService, TGetOutputDto, TGetListOutputDto, TGetListBriefOutputDto, TKey, TGetListInput, TGetListBriefInput, TCreateInput, TUpdateInput>
    : AbpControllerBase
where ITAppService : ICurdAppService<TGetOutputDto, TGetListOutputDto, TGetListBriefOutputDto, TKey, TGetListInput, TGetListBriefInput, TCreateInput, TUpdateInput>
where TGetOutputDto : IEntityDto<TKey>
where TGetListOutputDto : IEntityDto<TKey>
where TGetListBriefInput : TGetListInput
{

    private readonly ITAppService _recipeAppService;

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

    [HttpPost]
    [Route("Create")]
    
    public virtual async Task<TGetOutputDto> CreateAsync(TCreateInput input)
    {
        return await _recipeAppService.CreateAsync(input);
    }

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

    [HttpGet]
    [Route("GetAll")]
    
    public virtual async Task<PagedResultDto<TGetListOutputDto>> GetAllAsync(TGetListInput input)
    {
        return await _recipeAppService.GetAllAsync(input);
    }

    [HttpGet]
    [Route("Get")]
    
    public virtual async Task<TGetOutputDto> GetAsync(TKey id)
    {
        return await _recipeAppService.GetAsync(id);
    }

    [HttpPut]
    [Route("Update")]
    
    public virtual async Task<TGetOutputDto> UpdateAsync(TUpdateInput input)
    {
        return await _recipeAppService.UpdateAsync(input);
    }

    [HttpGet]
    [Route("GetAllBrief")]
    
    public virtual async Task<PagedResultDto<TGetListBriefOutputDto>> GetAllBriefAsync(TGetListBriefInput input)
    {
        return await _recipeAppService.GetAllBriefAsync(input);
    }
}


服務的「漸進式」

在開發業務模組時,我們可以先使用簡單的方式提供Curd服務,隨著UI複雜度增加,逐步的使用更加複雜的Curd服務。某種程度上來說,即所謂「漸進式」的開發方式。

  • BaseCurd: 基礎型,僅包含Create、Update、Delete、Get
  • SimpleCurd: 簡單型,包含BaseCurd的所有功能,同時包含GetAll
  • Curd:完整型,包含SimpleCurd的所有功能,同時包含GetAllBrief,GetAllBrief是一種簡化的查詢實體集合的方法,其返回的Dto不包含導航屬性,以減少資料傳輸量。是最常用的服務型別。
  • ExtendedCurd:擴充套件型,包含Curd的所有功能,同時包含GetAllBriefWithoutPage,GetAllBriefWithoutPage 適合一些非分頁場景,如日曆檢視,Echarts圖表控制元件等。使用此介面需要注意:由於沒有分頁限制,需要其他的查詢約束條件(比如日期範圍),否則會返回大量的資料,影響效能。

我們擴充套件應用層基礎類別,控制器及其介面

使用

以Alarm為例。來實現擴充套件型Curd服務(ExtendedCurd)。

假設你已完成建立實體、Dto以及設定完成AutoMapper對映

  1. 在Health模組的抽象層中,建立介面IAlarmAppService,繼承自IExtendedCurdAppService和IApplicationService。

IApplicationService是ABP框架的介面,所有的應用服務都需要繼承自此介面。

public interface IAlarmAppService : IExtendedCurdAppService<AlarmDto, AlarmDto, AlarmBriefDto, long, GetAllAlarmInput, GetAllAlarmInput,  CreateAlarmInput, UpdateAlarmInput>, IApplicationService
{
}
  1. 在Health模組的應用層中,建立AlarmAppService
public class AlarmAppService : ExtendedCurdAppServiceBase<CAH.Health.Alarm.Alarm, AlarmDto, AlarmDto, AlarmBriefDto, long, GetAllAlarmInput, GetAllAlarmInput, CreateAlarmInput, UpdateAlarmInput>, IAlarmAppService
{
    public AlarmAppService(IRepository<CAH.Health.Alarm.Alarm, long> basicInventoryRepository) : base(basicInventoryRepository)
    {
    }
}

  1. 在Health模組的HttpApi中,建立AlarmController,並實現IAlarmAppService介面
[Area(HealthRemoteServiceConsts.ModuleName)]
[RemoteService(Name = HealthRemoteServiceConsts.RemoteServiceName)]
[Route("api/Health/alarm")]
public class AlarmController : ExtendedCurdController<IAlarmAppService, AlarmDto, AlarmDto, AlarmBriefDto, long, GetAllAlarmInput, GetAllAlarmInput, CreateAlarmInput, UpdateAlarmInput>, IAlarmAppService
{
    private readonly IAlarmAppService _alarmAppService;

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


}

執行程式

可以看到,我們的介面已經包含所有擴充套件型Curd方法。

下一章我們將實現通用查詢介面的按組織架構查詢。