using DigitalData.Core.Abstractions.Application; using DigitalData.Core.Abstractions.Infrastructure; using AutoMapper; using System.Reflection; using System.ComponentModel.DataAnnotations; using DigitalData.Core.DTO; using DigitalData.Core.Abstractions; using Microsoft.Extensions.Logging; namespace DigitalData.Core.Application { /// /// Provides generic CRUD (Create, Read, Update, Delete) operations for a specified type of entity. /// /// The DTO type for create operations. /// The DTO type for read operations. /// The DTO type for update operations. /// The entity type. /// The type of the identifier for the entity. public class CRUDService : ICRUDService where TCRUDRepository : ICRUDRepository where TCreateDto : class where TReadDto : class where TUpdateDto : IUnique where TEntity : class { protected readonly TCRUDRepository _repository; protected readonly IMapper _mapper; protected readonly PropertyInfo? _keyPropertyInfo; /// /// Initializes a new instance of the CRUDService class with the specified repository, translation service, and mapper. /// /// The CRUD repository for accessing the database. /// The AutoMapper instance for mapping between DTOs and entity objects. public CRUDService(TCRUDRepository repository, IMapper mapper) { _repository = repository; _mapper = mapper; _keyPropertyInfo = typeof(TEntity).GetProperties() .FirstOrDefault(prop => Attribute.IsDefined(prop, typeof(KeyAttribute))); } /// /// Asynchronously creates an entity based on the provided create DTO. /// /// The DTO to create an entity from. /// A service result indicating success or failure, including the entity DTO. public virtual async Task> CreateAsync(TCreateDto createDto) { var entity = _mapper.Map(createDto); var createdEntity = await _repository.CreateAsync(entity); return createdEntity is null ? Result.Fail() : Result.Success(KeyValueOf(createdEntity)); } /// /// Asynchronously reads an entity by its identifier and maps it to a read DTO. /// /// The identifier of the entity to read. /// A service result indicating success or failure, including the read DTO if successful. public virtual async Task> ReadByIdAsync(TId id) { var entity = await _repository.ReadByIdAsync(id); return entity is null ? Result.Fail() : Result.Success(_mapper.Map(entity)); } /// /// Asynchronously reads all entities and maps them to read DTOs. /// /// A service result including a collection of read DTOs. public virtual async Task>> ReadAllAsync() { var entities = await _repository.ReadAllAsync(); var readDto = _mapper.Map>(entities); return Result.Success(readDto); } /// /// Asynchronously updates an entity based on the provided update DTO. /// /// The DTO to update an entity from. /// A service message indicating success or failure. public virtual async Task UpdateAsync(TUpdateDto updateDto) { var currentEntitiy = await _repository.ReadByIdAsync(updateDto.Id); if (currentEntitiy is null) return Result.Fail().Notice(LogLevel.Warning, Flag.NotFound, $"{updateDto.Id} is not found in update process of {GetType()} entity."); var entity = _mapper.Map(updateDto, currentEntitiy); return await _repository.UpdateAsync(entity) ? Result.Success() : Result.Fail(); } /// /// Asynchronously deletes an entity by its identifier. /// /// The identifier of the entity to delete. /// A service message indicating success or failure. public virtual async Task DeleteAsyncById(TId id) { TEntity? entity = await _repository.ReadByIdAsync(id); if (entity is null) return Result.Fail(); bool isDeleted = await _repository.DeleteAsync(entity); return isDeleted ? Result.Success() : Result.Fail(); } /// /// Asynchronously checks if an entity with the specified identifier exists. /// /// The identifier of the entity to check. /// A Task that represents the asynchronous operation. The task result contains a boolean value indicating whether the entity exists. public virtual async Task HasEntity(TId id) { var entity = await _repository.ReadByIdAsync(id); return entity is not null; } /// /// Retrieves the ID value of an entity based on the defined [Key] attribute. /// /// The entity from which to extract the ID. /// The ID of the entity. protected virtual TId KeyValueOf(TEntity entity) { if (_keyPropertyInfo is null) throw new InvalidOperationException($"No property with [Key] attribute found on {typeof(TEntity).Name} entity."); object idObj = _keyPropertyInfo?.GetValue(entity) ?? throw new InvalidOperationException($"The ID property of {typeof(TEntity).Name} entity cannot be null."); if (idObj is TId id) return id; else throw new InvalidCastException($"The ID of {typeof(TEntity).Name} entity must be type of {typeof(TId).Name}, but it is type of {idObj.GetType().Name}."); } /// /// Handles exceptions that occur during CRUD operations, providing a structured string. /// /// The exception that was caught during CRUD operations. /// A containing information about the failure, including a user-friendly error message and additional error details. public virtual string HandleException(Exception ex) { return $"An unexpected error occurred on the server side. Please inform the IT support team.\n{ex.GetType().Name}\n{ex.Message}"; } } }