Compare commits
20 Commits
ed5dd43f37
...
b6ac303c96
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6ac303c96 | ||
|
|
406a41b91f | ||
|
|
adfb0daf7d | ||
|
|
a6d554fbc2 | ||
|
|
c6199cc0be | ||
|
|
ed2a591317 | ||
|
|
f6d5305c22 | ||
|
|
a6230419d8 | ||
|
|
b6cd520b72 | ||
|
|
68bfe93cf2 | ||
|
|
e6849cd9c9 | ||
|
|
d59350174c | ||
|
|
5f18ccd2bd | ||
|
|
58d879aec5 | ||
|
|
c9d07ce7bf | ||
|
|
bb39b97d1e | ||
|
|
b91769d931 | ||
|
|
ee5668a5cb | ||
|
|
67a3c598b1 | ||
|
|
ceb8858dc9 |
@@ -4,7 +4,7 @@ using DigitalData.Core.DTO;
|
||||
namespace DigitalData.Core.Abstractions.Application
|
||||
{
|
||||
public interface ICRUDService<TCreateDto, TReadDto, TUpdateDto, TEntity, TId>
|
||||
where TCreateDto : class where TReadDto : class where TUpdateDto : class where TEntity : class
|
||||
where TCreateDto : class where TReadDto : class where TUpdateDto : IUnique<TId> where TEntity : class, IUnique<TId>
|
||||
{
|
||||
Task<DataResult<TId>> CreateAsync(TCreateDto createDto);
|
||||
|
||||
@@ -45,16 +45,5 @@ namespace DigitalData.Core.Abstractions.Application
|
||||
/// <param name="id">The identifier of the entity to check.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains a boolean value indicating whether the entity exists.</returns>
|
||||
Task<bool> HasEntity(TId id);
|
||||
|
||||
/// <summary>
|
||||
/// Handles exceptions that occur within service actions. This method should log the exception
|
||||
/// and return an String that contains information about the error, which can then be sent to the client.
|
||||
/// The implementation should determine the appropriate level of detail to include in the error message
|
||||
/// based on security and usability considerations.
|
||||
/// </summary>
|
||||
/// <param name="ex">The exception that occurred during the controller action.</param>
|
||||
/// <returns>An string instance representing the outcome of the error handling process.
|
||||
/// This includes a flag indicating the operation was unsuccessful and any relevant error messages.</returns>
|
||||
string HandleException(Exception ex);
|
||||
}
|
||||
}
|
||||
7
DigitalData.Core.Abstractions/IUnique.cs
Normal file
7
DigitalData.Core.Abstractions/IUnique.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace DigitalData.Core.Abstractions
|
||||
{
|
||||
public interface IUnique<T>
|
||||
{
|
||||
public T Id { get; }
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">The type of the entity this repository works with.</typeparam>
|
||||
/// <typeparam name="TId">The type of the identifier for the entity.</typeparam>
|
||||
public interface ICRUDRepository<TEntity, TId> where TEntity : class
|
||||
public interface ICRUDRepository<TEntity, TId> where TEntity : class, IUnique<TId>
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a new entity to the repository.
|
||||
@@ -40,5 +40,23 @@
|
||||
/// <param name="entity">The entity to delete.</param>
|
||||
/// <returns>If entity is deleted, return true othwerwise return false.</returns>
|
||||
Task<bool> DeleteAsync(TEntity entity);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously counts all entities in the repository.
|
||||
/// </summary>
|
||||
/// <returns>The total number of entities in the repository.</returns>
|
||||
Task<int> CountAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously counts the number of entities in the repository that match a specific identifier.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier of the entities to count.</param>
|
||||
/// <returns>The number of entities with the specified identifier.</returns>
|
||||
/// <remarks>
|
||||
/// This method provides a count of entities in the database that match the given identifier.
|
||||
/// If there are multiple entities with the same identifier, they will all be counted.
|
||||
/// The default implementation assumes that the identifier is unique for each entity.
|
||||
/// </remarks>
|
||||
Task<int> CountAsync(TId id);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
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
|
||||
{
|
||||
@@ -16,11 +16,10 @@ namespace DigitalData.Core.Application
|
||||
/// <typeparam name="TEntity">The entity type.</typeparam>
|
||||
/// <typeparam name="TId">The type of the identifier for the entity.</typeparam>
|
||||
public class CRUDService<TCRUDRepository, TCreateDto, TReadDto, TUpdateDto, TEntity, TId> : ICRUDService<TCreateDto, TReadDto, TUpdateDto, TEntity, TId>
|
||||
where TCRUDRepository : ICRUDRepository<TEntity, TId> where TCreateDto : class where TReadDto : class where TUpdateDto : class where TEntity : class
|
||||
where TCRUDRepository : ICRUDRepository<TEntity, TId> where TCreateDto : class where TReadDto : class where TUpdateDto : IUnique<TId> where TEntity : class, IUnique<TId>
|
||||
{
|
||||
protected readonly TCRUDRepository _repository;
|
||||
protected readonly IMapper _mapper;
|
||||
protected readonly PropertyInfo? _keyPropertyInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the CRUDService class with the specified repository, translation service, and mapper.
|
||||
@@ -31,9 +30,6 @@ namespace DigitalData.Core.Application
|
||||
{
|
||||
_repository = repository;
|
||||
_mapper = mapper;
|
||||
|
||||
_keyPropertyInfo = typeof(TEntity).GetProperties()
|
||||
.FirstOrDefault(prop => Attribute.IsDefined(prop, typeof(KeyAttribute)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -43,9 +39,9 @@ namespace DigitalData.Core.Application
|
||||
/// <returns>A service result indicating success or failure, including the entity DTO.</returns>
|
||||
public virtual async Task<DataResult<TId>> CreateAsync(TCreateDto createDto)
|
||||
{
|
||||
var entity = _mapper.MapOrThrow<TEntity>(createDto);
|
||||
var entity = _mapper.Map<TEntity>(createDto);
|
||||
var createdEntity = await _repository.CreateAsync(entity);
|
||||
return createdEntity is null ? Result.Fail<TId>() : Result.Success(KeyValueOf(createdEntity));
|
||||
return createdEntity is null ? Result.Fail<TId>() : Result.Success(createdEntity.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -58,7 +54,7 @@ namespace DigitalData.Core.Application
|
||||
var entity = await _repository.ReadByIdAsync(id);
|
||||
return entity is null
|
||||
? Result.Fail<TReadDto>()
|
||||
: Result.Success(_mapper.MapOrThrow<TReadDto>(entity));
|
||||
: Result.Success(_mapper.Map<TReadDto>(entity));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -68,7 +64,7 @@ namespace DigitalData.Core.Application
|
||||
public virtual async Task<DataResult<IEnumerable<TReadDto>>> ReadAllAsync()
|
||||
{
|
||||
var entities = await _repository.ReadAllAsync();
|
||||
var readDto = _mapper.MapOrThrow<IEnumerable<TReadDto>>(entities);
|
||||
var readDto = _mapper.Map<IEnumerable<TReadDto>>(entities);
|
||||
return Result.Success(readDto);
|
||||
}
|
||||
|
||||
@@ -79,9 +75,16 @@ namespace DigitalData.Core.Application
|
||||
/// <returns>A service message indicating success or failure.</returns>
|
||||
public virtual async Task<Result> UpdateAsync(TUpdateDto updateDto)
|
||||
{
|
||||
var entity = _mapper.MapOrThrow<TEntity>(updateDto);
|
||||
bool isUpdated = await _repository.UpdateAsync(entity);
|
||||
return isUpdated ? Result.Success() : Result.Fail();
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -105,38 +108,6 @@ namespace DigitalData.Core.Application
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier of the entity to check.</param>
|
||||
/// <returns>A Task that represents the asynchronous operation. The task result contains a boolean value indicating whether the entity exists.</returns>
|
||||
public virtual async Task<bool> HasEntity(TId id)
|
||||
{
|
||||
var entity = await _repository.ReadByIdAsync(id);
|
||||
return entity is not null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the ID value of an entity based on the defined [Key] attribute.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity from which to extract the ID.</param>
|
||||
/// <returns>The ID of the entity.</returns>
|
||||
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}.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles exceptions that occur during CRUD operations, providing a structured string.
|
||||
/// </summary>
|
||||
/// <param name="ex">The exception that was caught during CRUD operations.</param>
|
||||
/// <returns>A <see cref="IServiceMessage"/> containing information about the failure, including a user-friendly error message and additional error details.</returns>
|
||||
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}";
|
||||
}
|
||||
public virtual async Task<bool> HasEntity(TId id) => await _repository.CountAsync(id) > 0;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace DigitalData.Core.DTO
|
||||
{
|
||||
public static class AutoMapperExtension
|
||||
{
|
||||
[Obsolete("use mapper.Map<T>")]
|
||||
/// <summary>
|
||||
/// Maps a source object to a destination object, or throws an exception if the mapping result is null.
|
||||
/// </summary>
|
||||
@@ -19,4 +20,4 @@ namespace DigitalData.Core.DTO
|
||||
"Hint: Ensure that the AutoMapper profile configuration for this mapping is correct.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
<Description>This package provides Data Transfer Object (DTO) implementations and related utilities. It includes generic result handling, DTO extension methods, cookie consent settings management, and AutoMapper integration for robust object mapping, all adhering to Clean Architecture principles to ensure separation of concerns and maintainability.</Description>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<PackageId>DigitalData.Core.DTO</PackageId>
|
||||
<Version>1.0.0</Version>
|
||||
<Version>1.0.0.1</Version>
|
||||
<Authors>Digital Data GmbH</Authors>
|
||||
<Company>Digital Data GmbH</Company>
|
||||
<Product>DigitalData.Core.DTO</Product>
|
||||
|
||||
@@ -39,6 +39,12 @@
|
||||
/// Indicates that either a possible security breach, a possible data integrity issue, or both have been detected during the service operation.
|
||||
/// This flag is used when it is uncertain whether the issue is related to security, data integrity, or both.
|
||||
/// </summary>
|
||||
PossibleSecurityBreachOrDataIntegrity
|
||||
PossibleSecurityBreachOrDataIntegrity,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the requested resource or operation could not be found.
|
||||
/// This flag is used when the specified item or condition does not exist or is unavailable.
|
||||
/// </summary>
|
||||
NotFound
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using DigitalData.Core.Abstractions.Infrastructure;
|
||||
using DigitalData.Core.Abstractions;
|
||||
using DigitalData.Core.Abstractions.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace DigitalData.Core.Infrastructure
|
||||
@@ -14,7 +15,7 @@ namespace DigitalData.Core.Infrastructure
|
||||
/// It leverages the EF Core's DbContext and DbSet to perform these operations.
|
||||
/// </remarks>
|
||||
public class CRUDRepository<TEntity, TId, TDbContext> : ICRUDRepository<TEntity, TId>
|
||||
where TEntity : class
|
||||
where TEntity : class, IUnique<TId>
|
||||
where TDbContext : DbContext
|
||||
{
|
||||
protected readonly TDbContext _dbContext;
|
||||
@@ -24,10 +25,11 @@ namespace DigitalData.Core.Infrastructure
|
||||
/// Initializes a new instance of the CRUDRepository with the specified DbContext.
|
||||
/// </summary>
|
||||
/// <param name="dbContext">The DbContext instance to be used by the repository.</param>
|
||||
public CRUDRepository(TDbContext dbContext)
|
||||
/// <param name="dbSet">The DbSet instance to be used by the repository.</param>
|
||||
public CRUDRepository(TDbContext dbContext, DbSet<TEntity> dbSet)
|
||||
{
|
||||
_dbContext = dbContext;
|
||||
_dbSet = dbContext.Set<TEntity>();
|
||||
_dbSet = dbSet;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -49,11 +51,21 @@ namespace DigitalData.Core.Infrastructure
|
||||
/// <returns>The entity found, or null if no entity is found with the specified identifier.</returns>
|
||||
public virtual async Task<TEntity?> ReadByIdAsync(TId id) => await _dbSet.FindAsync(id);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all entities of type <typeparamref name="TEntity"/> from the database.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method returns an <see cref="IQueryable{TEntity}"/> of all entities in the database.
|
||||
/// The result is not tracked by the context, which improves performance when you only need to read data without making modifications.
|
||||
/// </remarks>
|
||||
/// <returns>An <see cref="IQueryable{TEntity}"/> containing all entities of type <typeparamref name="TEntity"/>.</returns>
|
||||
protected virtual IQueryable<TEntity> ReadOnly() => _dbSet.AsNoTracking();
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously retrieves all entities of type TEntity.
|
||||
/// </summary>
|
||||
/// <returns>An enumerable of all entities in the database.</returns>
|
||||
public virtual async Task<IEnumerable<TEntity>> ReadAllAsync() => await _dbSet.ToListAsync();
|
||||
public virtual async Task<IEnumerable<TEntity>> ReadAllAsync() => await ReadOnly().ToListAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously updates an existing entity in the repository.
|
||||
@@ -84,5 +96,17 @@ namespace DigitalData.Core.Infrastructure
|
||||
/// </summary>
|
||||
/// <returns>The total number of entities in the repository.</returns>
|
||||
public virtual async Task<int> CountAsync() => await _dbSet.CountAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously counts the number of entities in the repository that match a specific identifier.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier of the entities to count.</param>
|
||||
/// <returns>The number of entities with the specified identifier.</returns>
|
||||
/// <remarks>
|
||||
/// This method provides a count of entities in the database that match the given identifier.
|
||||
/// If there are multiple entities with the same identifier, they will all be counted.
|
||||
/// The default implementation assumes that the identifier is unique for each entity.
|
||||
/// </remarks>
|
||||
public virtual async Task<int> CountAsync(TId id) => await _dbSet.Where(e => e.Id!.Equals(id)).CountAsync();
|
||||
}
|
||||
}
|
||||
@@ -43,8 +43,8 @@ Global
|
||||
{B54DEF90-C30C-44EA-9875-76F1B330CBB7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B54DEF90-C30C-44EA-9875-76F1B330CBB7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B54DEF90-C30C-44EA-9875-76F1B330CBB7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0B051A5F-BD38-47D1-BAFF-D44BA30D3FB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0B051A5F-BD38-47D1-BAFF-D44BA30D3FB7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0B051A5F-BD38-47D1-BAFF-D44BA30D3FB7}.Debug|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0B051A5F-BD38-47D1-BAFF-D44BA30D3FB7}.Debug|Any CPU.Build.0 = Release|Any CPU
|
||||
{0B051A5F-BD38-47D1-BAFF-D44BA30D3FB7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0B051A5F-BD38-47D1-BAFF-D44BA30D3FB7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6A80FFEC-9B83-40A7-8C78-124440B48B33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
||||
Reference in New Issue
Block a user