using DigitalData.Core.Contracts.Application; using DigitalData.Core.Contracts.Infrastructure; using DigitalData.Core.Contracts.CultureServices; using AutoMapper; using System.Reflection; using System.ComponentModel.DataAnnotations; 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 : ServiceBase, ICRUDService, IServiceBase where TCRUDRepository : ICRUDRepository where TCreateDto : class where TReadDto : class where TUpdateDto : class where TEntity : class { protected readonly TCRUDRepository _repository; protected readonly IMapper _mapper; protected readonly IKeyTranslationService _translationService; 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 service for translating messages based on culture. /// The AutoMapper instance for mapping between DTOs and entity objects. public CRUDService(TCRUDRepository repository, IKeyTranslationService translationService, IMapper mapper) { _repository = repository; _translationService = translationService; _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.MapOrThrow(createDto); var createdEntity = await _repository.CreateAsync(entity); if(createdEntity is null) return Failed(); else return Successful(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); if (entity is null) { var translatedMessage = _translationService.Translate(MessageKey.EntityDoesNotExist); return Failed(); } else return Successful(_mapper.MapOrThrow(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.MapOrThrow>(entities); return Successful(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 entity = _mapper.MapOrThrow(updateDto); bool isUpdated = await _repository.UpdateAsync(entity); if (isUpdated) return Successful(); else { var translatedMessage = _translationService.Translate(MessageKey.UpdateFailed); return Failed(translatedMessage); } } /// /// 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) { var deletionFailedMessage = _translationService.Translate(MessageKey.DeletionFailed); var entityDoesNotExistMessage = _translationService.Translate(MessageKey.EntityDoesNotExist); return new ServiceMessage(isSuccess: false, deletionFailedMessage, entityDoesNotExistMessage); } bool isDeleted = await _repository.DeleteAsync(entity); if (isDeleted) return Successful(); else { var deletionFailedMessage = _translationService.Translate(MessageKey.DeletionFailed); return Failed(deletionFailedMessage); } } /// /// 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}"; } } }