From 8bab5cb9b3ca46a91a83b76d7c42f5ee525ce1ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Tue, 17 Mar 2020 15:18:31 +0300 Subject: [PATCH] Support composite keys for CrudAppService --- .../Mvc/Conventions/AbpServiceConvention.cs | 38 +- .../Application/Services/ICrudAppService.cs | 6 - .../Services/AbstractKeyCrudAppService.cs | 341 ++++++++++++++++++ .../Application/Services/CrudAppService.cs | 262 +------------- .../Entities/EntityNotFoundException.cs | 6 +- .../TestApp/Application/DistrictAppService.cs | 30 ++ .../Abp/TestApp/Application/DistrictKey.cs | 11 + .../TestApp/Application/Dto/DistrictDto.cs | 14 + 8 files changed, 449 insertions(+), 259 deletions(-) create mode 100644 framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyCrudAppService.cs create mode 100644 framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/DistrictAppService.cs create mode 100644 framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/DistrictKey.cs create mode 100644 framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/DistrictDto.cs diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs index 9875ccd808..0254a6022b 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs @@ -12,6 +12,7 @@ using Volo.Abp.Application.Services; using Volo.Abp.DependencyInjection; using Volo.Abp.Http; using Volo.Abp.Http.Modeling; +using Volo.Abp.Http.ProxyScripting.Generators; using Volo.Abp.Reflection; namespace Volo.Abp.AspNetCore.Mvc.Conventions @@ -81,7 +82,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions continue; } - if (!TypeHelper.IsPrimitiveExtended(prm.ParameterInfo.ParameterType)) + if (!TypeHelper.IsPrimitiveExtended(prm.ParameterInfo.ParameterType, includeEnums: true)) { if (CanUseFormBodyBinding(action, prm)) { @@ -94,7 +95,15 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions protected virtual bool CanUseFormBodyBinding(ActionModel action, ParameterModel parameter) { - if (_options.ConventionalControllers.FormBodyBindingIgnoredTypes.Any(t => t.IsAssignableFrom(parameter.ParameterInfo.ParameterType))) + //We want to use "id" as path parameter, not body! + if (parameter.ParameterName == "id") + { + return false; + } + + if (_options.ConventionalControllers + .FormBodyBindingIgnoredTypes + .Any(t => t.IsAssignableFrom(parameter.ParameterInfo.ParameterType))) { return false; } @@ -251,7 +260,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions if (!selector.ActionConstraints.OfType().Any()) { - selector.ActionConstraints.Add(new HttpMethodActionConstraint(new[] {httpMethod})); + selector.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { httpMethod })); } } } @@ -295,9 +304,24 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions var url = $"api/{rootPath}/{controllerNameInUrl.ToCamelCase()}"; //Add {id} path if needed - if (action.Parameters.Any(p => p.ParameterName == "id")) + var idParameterModel = action.Parameters.FirstOrDefault(p => p.ParameterName == "id"); + if (idParameterModel != null) { - url += "/{id}"; + if (TypeHelper.IsPrimitiveExtended(idParameterModel.ParameterType, includeEnums: true)) + { + url += "/{id}"; + } + else + { + var properties = idParameterModel + .ParameterType + .GetProperties(BindingFlags.Instance | BindingFlags.Public); + + foreach (var property in properties) + { + url += "/{" + property.Name + "}"; + } + } } //Add action name if needed @@ -341,7 +365,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions protected virtual string NormalizeUrlControllerName(string rootPath, string controllerName, ActionModel action, string httpMethod, [CanBeNull] ConventionalControllerSetting configuration) { - if(configuration?.UrlControllerNameNormalizer == null) + if (configuration?.UrlControllerNameNormalizer == null) { return controllerName; } @@ -364,7 +388,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions protected virtual bool IsEmptySelector(SelectorModel selector) { - return selector.AttributeRouteModel == null + return selector.AttributeRouteModel == null && selector.ActionConstraints.IsNullOrEmpty() && selector.EndpointMetadata.IsNullOrEmpty(); } diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/ICrudAppService.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/ICrudAppService.cs index 7816bd20eb..12f3b51d11 100644 --- a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/ICrudAppService.cs +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/ICrudAppService.cs @@ -5,36 +5,30 @@ namespace Volo.Abp.Application.Services { public interface ICrudAppService : ICrudAppService - where TEntityDto : IEntityDto { } public interface ICrudAppService : ICrudAppService - where TEntityDto : IEntityDto { } public interface ICrudAppService : ICrudAppService - where TEntityDto : IEntityDto { } public interface ICrudAppService : ICrudAppService - where TEntityDto : IEntityDto { } public interface ICrudAppService : IApplicationService - where TGetOutputDto : IEntityDto - where TGetListOutputDto : IEntityDto { Task GetAsync(TKey id); diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyCrudAppService.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyCrudAppService.cs new file mode 100644 index 0000000000..45915826e4 --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyCrudAppService.cs @@ -0,0 +1,341 @@ +using System; +using System.Linq; +using System.Linq.Dynamic.Core; +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Auditing; +using Volo.Abp.Domain.Entities; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.Linq; +using Volo.Abp.MultiTenancy; +using Volo.Abp.ObjectMapping; + +namespace Volo.Abp.Application.Services +{ + public abstract class AbstractKeyCrudAppService + : AbstractKeyCrudAppService + where TEntity : class, IEntity + { + protected AbstractKeyCrudAppService(IRepository repository) + : base(repository) + { + + } + } + + public abstract class AbstractKeyCrudAppService + : AbstractKeyCrudAppService + where TEntity : class, IEntity + { + protected AbstractKeyCrudAppService(IRepository repository) + : base(repository) + { + + } + } + + public abstract class AbstractKeyCrudAppService + : AbstractKeyCrudAppService + where TEntity : class, IEntity + { + protected AbstractKeyCrudAppService(IRepository repository) + : base(repository) + { + + } + } + + public abstract class AbstractKeyCrudAppService + : AbstractKeyCrudAppService + where TEntity : class, IEntity + { + protected AbstractKeyCrudAppService(IRepository repository) + : base(repository) + { + + } + + protected override TEntityDto MapToGetListOutputDto(TEntity entity) + { + return MapToGetOutputDto(entity); + } + } + + public abstract class AbstractKeyCrudAppService + : ApplicationService, + ICrudAppService + where TEntity : class, IEntity + { + public IAsyncQueryableExecuter AsyncQueryableExecuter { get; set; } + + protected IRepository Repository { get; } + + protected virtual string GetPolicyName { get; set; } + + protected virtual string GetListPolicyName { get; set; } + + protected virtual string CreatePolicyName { get; set; } + + protected virtual string UpdatePolicyName { get; set; } + + protected virtual string DeletePolicyName { get; set; } + + protected AbstractKeyCrudAppService(IRepository repository) + { + Repository = repository; + AsyncQueryableExecuter = DefaultAsyncQueryableExecuter.Instance; + } + + public virtual async Task GetAsync(TKey id) + { + await CheckGetPolicyAsync(); + + var entity = await GetEntityByIdAsync(id); + return MapToGetOutputDto(entity); + } + + public virtual async Task> GetListAsync(TGetListInput input) + { + await CheckGetListPolicyAsync(); + + var query = CreateFilteredQuery(input); + + var totalCount = await AsyncQueryableExecuter.CountAsync(query); + + query = ApplySorting(query, input); + query = ApplyPaging(query, input); + + var entities = await AsyncQueryableExecuter.ToListAsync(query); + + return new PagedResultDto( + totalCount, + entities.Select(MapToGetListOutputDto).ToList() + ); + } + + public virtual async Task CreateAsync(TCreateInput input) + { + await CheckCreatePolicyAsync(); + + var entity = MapToEntity(input); + + TryToSetTenantId(entity); + + await Repository.InsertAsync(entity, autoSave: true); + + return MapToGetOutputDto(entity); + } + + public virtual async Task UpdateAsync(TKey id, TUpdateInput input) + { + await CheckUpdatePolicyAsync(); + + var entity = await GetEntityByIdAsync(id); + //TODO: Check if input has id different than given id and normalize if it's default value, throw ex otherwise + MapToEntity(input, entity); + await Repository.UpdateAsync(entity, autoSave: true); + + return MapToGetOutputDto(entity); + } + + public virtual async Task DeleteAsync(TKey id) + { + await CheckDeletePolicyAsync(); + + await DeleteByIdAsync(id); + } + + protected abstract Task DeleteByIdAsync(TKey id); + + protected abstract Task GetEntityByIdAsync(TKey id); + + protected virtual async Task CheckGetPolicyAsync() + { + await CheckPolicyAsync(GetPolicyName); + } + + protected virtual async Task CheckGetListPolicyAsync() + { + await CheckPolicyAsync(GetListPolicyName); + } + + protected virtual async Task CheckCreatePolicyAsync() + { + await CheckPolicyAsync(CreatePolicyName); + } + + protected virtual async Task CheckUpdatePolicyAsync() + { + await CheckPolicyAsync(UpdatePolicyName); + } + + protected virtual async Task CheckDeletePolicyAsync() + { + await CheckPolicyAsync(DeletePolicyName); + } + + /// + /// Should apply sorting if needed. + /// + /// The query. + /// The input. + protected virtual IQueryable ApplySorting(IQueryable query, TGetListInput input) + { + //Try to sort query if available + if (input is ISortedResultRequest sortInput) + { + if (!sortInput.Sorting.IsNullOrWhiteSpace()) + { + return query.OrderBy(sortInput.Sorting); + } + } + + //IQueryable.Task requires sorting, so we should sort if Take will be used. + if (input is ILimitedResultRequest) + { + return ApplyDefaultSorting(query); + } + + //No sorting + return query; + } + + /// + /// Applies sorting if no sorting specified but a limited result requested. + /// + /// The query. + protected virtual IQueryable ApplyDefaultSorting(IQueryable query) + { + if (typeof(TEntity).IsAssignableTo()) + { + return query.OrderByDescending(e => ((ICreationAuditedObject)e).CreationTime); + } + + throw new AbpException("No sorting specified but this query requires sorting. Override the ApplyDefaultSorting method for your application service derived from AbstractKeyCrudAppService!"); + } + + /// + /// Should apply paging if needed. + /// + /// The query. + /// The input. + protected virtual IQueryable ApplyPaging(IQueryable query, TGetListInput input) + { + //Try to use paging if available + if (input is IPagedResultRequest pagedInput) + { + return query.PageBy(pagedInput); + } + + //Try to limit query result if available + if (input is ILimitedResultRequest limitedInput) + { + return query.Take(limitedInput.MaxResultCount); + } + + //No paging + return query; + } + + /// + /// This method should create based on given input. + /// It should filter query if needed, but should not do sorting or paging. + /// Sorting should be done in and paging should be done in + /// methods. + /// + /// The input. + protected virtual IQueryable CreateFilteredQuery(TGetListInput input) + { + return Repository; + } + + /// + /// Maps to . + /// It uses by default. + /// It can be overriden for custom mapping. + /// + protected virtual TGetOutputDto MapToGetOutputDto(TEntity entity) + { + return ObjectMapper.Map(entity); + } + + /// + /// Maps to . + /// It uses by default. + /// It can be overriden for custom mapping. + /// + protected virtual TGetListOutputDto MapToGetListOutputDto(TEntity entity) + { + return ObjectMapper.Map(entity); + } + + /// + /// Maps to to create a new entity. + /// It uses by default. + /// It can be overriden for custom mapping. + /// + protected virtual TEntity MapToEntity(TCreateInput createInput) + { + var entity = ObjectMapper.Map(createInput); + SetIdForGuids(entity); + return entity; + } + + /// + /// Sets Id value for the entity if is . + /// It's used while creating a new entity. + /// + protected virtual void SetIdForGuids(TEntity entity) + { + var entityWithGuidId = entity as IEntity; + + if (entityWithGuidId == null || entityWithGuidId.Id != Guid.Empty) + { + return; + } + + EntityHelper.TrySetId( + entityWithGuidId, + () => GuidGenerator.Create(), + true + ); + } + + /// + /// Maps to to update the entity. + /// It uses by default. + /// It can be overriden for custom mapping. + /// + protected virtual void MapToEntity(TUpdateInput updateInput, TEntity entity) + { + ObjectMapper.Map(updateInput, entity); + } + + protected virtual void TryToSetTenantId(TEntity entity) + { + if (entity is IMultiTenant && HasTenantIdProperty(entity)) + { + var tenantId = CurrentTenant.Id; + + if (!tenantId.HasValue) + { + return; + } + + var propertyInfo = entity.GetType().GetProperty(nameof(IMultiTenant.TenantId)); + + if (propertyInfo == null || propertyInfo.GetSetMethod(true) == null) + { + return; + } + + propertyInfo.SetValue(entity, tenantId); + } + } + + protected virtual bool HasTenantIdProperty(TEntity entity) + { + return entity.GetType().GetProperty(nameof(IMultiTenant.TenantId)) != null; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs index 4134b59a4c..a2af0592a4 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs @@ -1,13 +1,10 @@ using System; using System.Linq; -using System.Linq.Dynamic.Core; using System.Threading.Tasks; using Volo.Abp.Application.Dtos; +using Volo.Abp.Auditing; using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Repositories; -using Volo.Abp.Linq; -using Volo.Abp.MultiTenancy; -using Volo.Abp.ObjectMapping; namespace Volo.Abp.Application.Services { @@ -65,274 +62,49 @@ namespace Volo.Abp.Application.Services } public abstract class CrudAppService - : ApplicationService, - ICrudAppService - where TEntity : class, IEntity + : AbstractKeyCrudAppService + where TEntity : class, IEntity where TGetOutputDto : IEntityDto where TGetListOutputDto : IEntityDto { - public IAsyncQueryableExecuter AsyncQueryableExecuter { get; set; } - - protected IRepository Repository { get; } - - protected virtual string GetPolicyName { get; set; } - - protected virtual string GetListPolicyName { get; set; } - - protected virtual string CreatePolicyName { get; set; } - - protected virtual string UpdatePolicyName { get; set; } - - protected virtual string DeletePolicyName { get; set; } + protected new IRepository Repository { get; } protected CrudAppService(IRepository repository) + : base(repository) { Repository = repository; - AsyncQueryableExecuter = DefaultAsyncQueryableExecuter.Instance; } - public virtual async Task GetAsync(TKey id) + protected override async Task DeleteByIdAsync(TKey id) { - await CheckGetPolicyAsync(); - - var entity = await GetEntityByIdAsync(id); - return MapToGetOutputDto(entity); - } - - public virtual async Task> GetListAsync(TGetListInput input) - { - await CheckGetListPolicyAsync(); - - var query = CreateFilteredQuery(input); - - var totalCount = await AsyncQueryableExecuter.CountAsync(query); - - query = ApplySorting(query, input); - query = ApplyPaging(query, input); - - var entities = await AsyncQueryableExecuter.ToListAsync(query); - - return new PagedResultDto( - totalCount, - entities.Select(MapToGetListOutputDto).ToList() - ); - } - - public virtual async Task CreateAsync(TCreateInput input) - { - await CheckCreatePolicyAsync(); - - var entity = MapToEntity(input); - - TryToSetTenantId(entity); - - await Repository.InsertAsync(entity, autoSave: true); - - return MapToGetOutputDto(entity); - } - - public virtual async Task UpdateAsync(TKey id, TUpdateInput input) - { - await CheckUpdatePolicyAsync(); - - var entity = await GetEntityByIdAsync(id); - //TODO: Check if input has id different than given id and normalize if it's default value, throw ex otherwise - MapToEntity(input, entity); - await Repository.UpdateAsync(entity, autoSave: true); - - return MapToGetOutputDto(entity); - } - - public virtual async Task DeleteAsync(TKey id) - { - await CheckDeletePolicyAsync(); - await Repository.DeleteAsync(id); } - protected virtual Task GetEntityByIdAsync(TKey id) - { - return Repository.GetAsync(id); - } - - protected virtual async Task CheckGetPolicyAsync() - { - await CheckPolicyAsync(GetPolicyName); - } - - protected virtual async Task CheckGetListPolicyAsync() - { - await CheckPolicyAsync(GetListPolicyName); - } - - protected virtual async Task CheckCreatePolicyAsync() - { - await CheckPolicyAsync(CreatePolicyName); - } - - protected virtual async Task CheckUpdatePolicyAsync() - { - await CheckPolicyAsync(UpdatePolicyName); - } - - protected virtual async Task CheckDeletePolicyAsync() + protected override async Task GetEntityByIdAsync(TKey id) { - await CheckPolicyAsync(DeletePolicyName); + return await Repository.GetAsync(id); } - /// - /// Should apply sorting if needed. - /// - /// The query. - /// The input. - protected virtual IQueryable ApplySorting(IQueryable query, TGetListInput input) - { - //Try to sort query if available - if (input is ISortedResultRequest sortInput) - { - if (!sortInput.Sorting.IsNullOrWhiteSpace()) - { - return query.OrderBy(sortInput.Sorting); - } - } - - //IQueryable.Task requires sorting, so we should sort if Take will be used. - if (input is ILimitedResultRequest) - { - return query.OrderByDescending(e => e.Id); - } - - //No sorting - return query; - } - - /// - /// Should apply paging if needed. - /// - /// The query. - /// The input. - protected virtual IQueryable ApplyPaging(IQueryable query, TGetListInput input) - { - //Try to use paging if available - if (input is IPagedResultRequest pagedInput) - { - return query.PageBy(pagedInput); - } - - //Try to limit query result if available - if (input is ILimitedResultRequest limitedInput) - { - return query.Take(limitedInput.MaxResultCount); - } - - //No paging - return query; - } - - /// - /// This method should create based on given input. - /// It should filter query if needed, but should not do sorting or paging. - /// Sorting should be done in and paging should be done in - /// methods. - /// - /// The input. - protected virtual IQueryable CreateFilteredQuery(TGetListInput input) - { - return Repository; - } - - /// - /// Maps to . - /// It uses by default. - /// It can be overriden for custom mapping. - /// - protected virtual TGetOutputDto MapToGetOutputDto(TEntity entity) - { - return ObjectMapper.Map(entity); - } - - /// - /// Maps to . - /// It uses by default. - /// It can be overriden for custom mapping. - /// - protected virtual TGetListOutputDto MapToGetListOutputDto(TEntity entity) - { - return ObjectMapper.Map(entity); - } - - /// - /// Maps to to create a new entity. - /// It uses by default. - /// It can be overriden for custom mapping. - /// - protected virtual TEntity MapToEntity(TCreateInput createInput) - { - var entity = ObjectMapper.Map(createInput); - SetIdForGuids(entity); - return entity; - } - - /// - /// Sets Id value for the entity if is . - /// It's used while creating a new entity. - /// - protected virtual void SetIdForGuids(TEntity entity) - { - var entityWithGuidId = entity as IEntity; - - if (entityWithGuidId == null || entityWithGuidId.Id != Guid.Empty) - { - return; - } - - EntityHelper.TrySetId( - entityWithGuidId, - () => GuidGenerator.Create(), - true - ); - } - - /// - /// Maps to to update the entity. - /// It uses by default. - /// It can be overriden for custom mapping. - /// - protected virtual void MapToEntity(TUpdateInput updateInput, TEntity entity) + protected override void MapToEntity(TUpdateInput updateInput, TEntity entity) { if (updateInput is IEntityDto entityDto) { entityDto.Id = entity.Id; } - ObjectMapper.Map(updateInput, entity); + base.MapToEntity(updateInput, entity); } - protected virtual void TryToSetTenantId(TEntity entity) + protected override IQueryable ApplyDefaultSorting(IQueryable query) { - if (entity is IMultiTenant && HasTenantIdProperty(entity)) + if (typeof(TEntity).IsAssignableTo()) { - var tenantId = CurrentTenant.Id; - - if (!tenantId.HasValue) - { - return; - } - - var propertyInfo = entity.GetType().GetProperty(nameof(IMultiTenant.TenantId)); - - if (propertyInfo == null || propertyInfo.GetSetMethod(true) == null) - { - return; - } - - propertyInfo.SetValue(entity, tenantId); + return query.OrderByDescending(e => ((ICreationAuditedObject)e).CreationTime); + } + else + { + return query.OrderByDescending(e => e.Id); } - } - - protected virtual bool HasTenantIdProperty(TEntity entity) - { - return entity.GetType().GetProperty(nameof(IMultiTenant.TenantId)) != null; } } } diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityNotFoundException.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityNotFoundException.cs index 510d0384c2..85466ccf0e 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityNotFoundException.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityNotFoundException.cs @@ -47,7 +47,11 @@ namespace Volo.Abp.Domain.Entities /// Creates a new object. /// public EntityNotFoundException(Type entityType, object id, Exception innerException) - : base($"There is no such an entity. Entity type: {entityType.FullName}, id: {id}", innerException) + : base( + id == null + ? $"There is no such an entity given given id. Entity type: {entityType.FullName}" + : $"There is no such an entity. Entity type: {entityType.FullName}, id: {id}", + innerException) { EntityType = entityType; Id = id; diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/DistrictAppService.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/DistrictAppService.cs new file mode 100644 index 0000000000..81052d17cc --- /dev/null +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/DistrictAppService.cs @@ -0,0 +1,30 @@ +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.TestApp.Domain; +using Volo.Abp.Application.Services; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.TestApp.Application.Dto; + +namespace Volo.Abp.TestApp.Application +{ + //This is especially used to test the AbstractKeyCrudAppService + public class DistrictAppService : AbstractKeyCrudAppService + { + public DistrictAppService(IRepository repository) + : base(repository) + { + } + + protected override async Task DeleteByIdAsync(DistrictKey id) + { + await Repository.DeleteAsync(d => d.CityId == id.CityId && d.Name == id.Name); + } + + protected override async Task GetEntityByIdAsync(DistrictKey id) + { + return await AsyncQueryableExecuter.FirstOrDefaultAsync( + Repository.Where(d => d.CityId == id.CityId && d.Name == id.Name) + ); + } + } +} diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/DistrictKey.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/DistrictKey.cs new file mode 100644 index 0000000000..a7d5c9b965 --- /dev/null +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/DistrictKey.cs @@ -0,0 +1,11 @@ +using System; + +namespace Volo.Abp.TestApp.Application +{ + public class DistrictKey + { + public Guid CityId { get; set; } + + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/DistrictDto.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/DistrictDto.cs new file mode 100644 index 0000000000..3b5f3e5f83 --- /dev/null +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/DistrictDto.cs @@ -0,0 +1,14 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace Volo.Abp.TestApp.Application.Dto +{ + public class DistrictDto : EntityDto + { + public Guid CityId { get; set; } + + public string Name { get; set; } + + public int Population { get; set; } + } +}