Refactor namespaces and enhance application structure
This commit reorganizes namespaces from `DigitalData.Core.Abstractions` and `DigitalData.Core.DTO` to `DigitalData.Core.Application.Interfaces` and `DigitalData.Core.Application.DTO`, improving maintainability and clarity. Updated using directives across multiple files to reflect the new structure, ensuring functionality remains intact. Project references in `DigitalData.Core.API.csproj` have been consolidated to include the new Application project. Introduced new classes and interfaces such as `BaseDTO`, `CookieConsentSettings`, `DataResult`, `Notice`, and `Result` to enhance data transfer and service result handling. Updated `IRepository`, `ICRUDRepository`, and `IEntityMapper` interfaces to facilitate CRUD operations and entity mapping. Added extension methods in `Extensions.cs` to improve repository usability. New interfaces for HTTP client services have been added, enhancing external API call handling. Overall, these changes reflect a significant restructuring aimed at improving organization and preparing for future development.
This commit is contained in:
@@ -1,22 +0,0 @@
|
||||
using DigitalData.Core.Abstractions.Infrastructure;
|
||||
|
||||
namespace DigitalData.Core.Abstractions.Application
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements a simplified CRUD service interface that uses a single Data Transfer Object (DTO) type for all CRUD operations,
|
||||
/// streamlining the process for entities where the same DTO can be used for creating, reading, updating, and deleting entities.
|
||||
/// This interface inherits from the ICRUDService interface, applying the same DTO type for all generic type parameters,
|
||||
/// thereby simplifying the usage for cases where a single DTO is sufficient for all operations on an entity.
|
||||
/// </summary>
|
||||
/// <typeparam name="TDto">The type of the Data Transfer Object used for all CRUD operations.</typeparam>
|
||||
/// <typeparam name="TEntity">The type of the entity this service maps to.</typeparam>
|
||||
/// <typeparam name="TId">The type of the identifier for the entity.</typeparam>
|
||||
/// <remarks>
|
||||
/// This interface is useful for entities that do not require different DTOs for different operations,
|
||||
/// allowing for a more concise and maintainable codebase when implementing services for such entities.
|
||||
/// </remarks>
|
||||
public interface IBasicCRUDService<TDto, TEntity, TId> : ICRUDService<TDto, TDto, TEntity, TId>
|
||||
where TDto : class, IUnique<TId> where TEntity : class, IUnique<TId>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
using DigitalData.Core.DTO;
|
||||
|
||||
namespace DigitalData.Core.Abstractions.Application
|
||||
{
|
||||
public interface ICRUDService<TCreateDto, TReadDto, TEntity, TId> : IReadService<TReadDto, TEntity, TId>
|
||||
where TCreateDto : class where TReadDto : class where TEntity : class, IUnique<TId>
|
||||
{
|
||||
/// <summary>
|
||||
/// Asynchronously creates a new entity based on the provided <paramref name="createDto"/> and returns the identifier of the created entity wrapped in a <see cref="DataResult{TId}"/>.
|
||||
/// The <see cref="DataResult{TId}"/> contains the identifier of the newly created entity on success or an error message on failure.
|
||||
/// </summary>
|
||||
/// <param name="createDto">The data transfer object containing the information for the new entity.</param>
|
||||
/// <returns>A task representing the asynchronous operation, with a <see cref="DataResult{TId}"/> containing the identifier of the created entity or an error message.</returns>
|
||||
Task<DataResult<TId>> CreateAsync(TCreateDto createDto);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Updates an existing entity based on the provided updateDTO and returns the result wrapped in an IServiceMessage,
|
||||
/// indicating the success or failure of the operation, including the error messages on failure.
|
||||
/// </summary>
|
||||
/// <param name="updateDto">The updateDTO with updated values for the entity.</param>
|
||||
/// <returns>An Result indicating the outcome of the update operation, with an appropriate message.</returns>
|
||||
Task<Result> UpdateAsync<TUpdateDto>(TUpdateDto updateDto) where TUpdateDto : IUnique<TId>;
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
using DigitalData.Core.DTO;
|
||||
using System.DirectoryServices;
|
||||
|
||||
namespace DigitalData.Core.Abstractions.Application
|
||||
{
|
||||
public interface IDirectorySearchService
|
||||
{
|
||||
public string ServerName { get; }
|
||||
|
||||
public string Root { get; }
|
||||
|
||||
string SearchRootPath { get; }
|
||||
|
||||
Dictionary<string, string> CustomSearchFilters { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates the connections to the server and returns a Boolean value that specifies
|
||||
/// whether the specified username and password are valid.
|
||||
/// </summary>
|
||||
/// <param name="userName">The username that is validated on the server. See the Remarks section
|
||||
/// for more information on the format of userName.</param>
|
||||
/// <param name="password">The password that is validated on the server.</param>
|
||||
/// <returns>True if the credentials are valid; otherwise, false.</returns>
|
||||
bool ValidateCredentials(string userName, string password);
|
||||
|
||||
/// <summary>
|
||||
/// Creates the connections to the server asynchronously and returns a Boolean value that specifies
|
||||
/// whether the specified username and password are valid.
|
||||
/// </summary>
|
||||
/// <param name="userName">The username that is validated on the server. See the Remarks section
|
||||
/// for more information on the format of userName.</param>
|
||||
/// <param name="password">The password that is validated on the server.</param>
|
||||
/// <returns>True if the credentials are valid; otherwise, false.</returns>
|
||||
Task<bool> ValidateCredentialsAsync(string userName, string password);
|
||||
|
||||
/// <summary>
|
||||
/// Finds all directory entries matching the specified filter.
|
||||
/// </summary>
|
||||
/// <param name="searchRoot">The search root.</param>
|
||||
/// <param name="filter">The search filter.</param>
|
||||
/// <param name="searchScope">The search scope.</param>
|
||||
/// <param name="sizeLimit">The size limit.</param>
|
||||
/// <param name="properties">The properties to load.</param>
|
||||
/// <returns>A <see cref="DataResult{T}"/> containing the results.</returns>
|
||||
DataResult<IEnumerable<ResultPropertyCollection>> FindAll(DirectoryEntry searchRoot, string filter, SearchScope searchScope = SearchScope.Subtree, int sizeLimit = 5000, params string[] properties);
|
||||
|
||||
/// <summary>
|
||||
/// Finds all directory entries matching the specified filter asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="searchRoot">The search root.</param>
|
||||
/// <param name="filter">The search filter.</param>
|
||||
/// <param name="searchScope">The search scope.</param>
|
||||
/// <param name="sizeLimit">The size limit.</param>
|
||||
/// <param name="properties">The properties to load.</param>
|
||||
/// <returns>A <see cref="DataResult{T}"/> containing the results.</returns>
|
||||
Task<DataResult<IEnumerable<ResultPropertyCollection>>> FindAllAsync(DirectoryEntry searchRoot, string filter, SearchScope searchScope = SearchScope.Subtree, int sizeLimit = 5000, params string[] properties);
|
||||
|
||||
/// <summary>
|
||||
/// Finds all directory entries matching the specified filter, using the user cache.
|
||||
/// </summary>
|
||||
/// <param name="username">The username.</param>
|
||||
/// <param name="filter">The search filter.</param>
|
||||
/// <param name="searchScope">The search scope.</param>
|
||||
/// <param name="sizeLimit">The size limit.</param>
|
||||
/// <param name="properties">The properties to load.</param>
|
||||
/// <returns>A <see cref="DataResult{T}"/> containing the results.</returns>
|
||||
DataResult<IEnumerable<ResultPropertyCollection>> FindAllByUserCache(string username, string filter, SearchScope searchScope = SearchScope.Subtree, int sizeLimit = 5000, params string[] properties);
|
||||
|
||||
/// <summary>
|
||||
/// Finds all directory entries matching the specified filter asynchronously, using the user cache.
|
||||
/// </summary>
|
||||
/// <param name="username">The username.</param>
|
||||
/// <param name="filter">The search filter.</param>
|
||||
/// <param name="searchScope">The search scope.</param>
|
||||
/// <param name="sizeLimit">The size limit.</param>
|
||||
/// <param name="properties">The properties to load.</param>
|
||||
/// <returns>A <see cref="DataResult{T}"/> containing the results.</returns>
|
||||
Task<DataResult<IEnumerable<ResultPropertyCollection>>> FindAllByUserCacheAsync(string username, string filter, SearchScope searchScope = SearchScope.Subtree, int sizeLimit = 5000, params string[] properties);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the search root in the cache.
|
||||
/// </summary>
|
||||
/// <param name="username">The directory entry username.</param>
|
||||
/// <param name="password">The directory entry password.</param>
|
||||
void SetSearchRootCache(string username, string password);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the search root from the cache.
|
||||
/// </summary>
|
||||
/// <param name="username">The directory entry username.</param>
|
||||
/// <returns>The cached <see cref="DirectoryEntry"/> if found; otherwise, null.</returns>
|
||||
DirectoryEntry? GetSearchRootCache(string username);
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace DigitalData.Core.Application
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the operations for JWT service handling claims of type <typeparamref name="TClaimValue"/>.
|
||||
/// </summary>
|
||||
public interface IJWTService<TClaimValue>
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a symmetric security key with the specified byte size.
|
||||
/// </summary>
|
||||
/// <param name="byteSize">The size of the security key in bytes. Default is 32 bytes.</param>
|
||||
/// <returns>A new instance of <see cref="SymmetricSecurityKey"/>.</returns>
|
||||
public static SymmetricSecurityKey GenerateSecurityKey(int byteSize = 32)
|
||||
{
|
||||
using var rng = RandomNumberGenerator.Create();
|
||||
var randomBytes = new byte[byteSize];
|
||||
rng.GetBytes(randomBytes);
|
||||
var securityKey = new SymmetricSecurityKey(randomBytes);
|
||||
|
||||
return securityKey;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a token based on the specified claim value.
|
||||
/// </summary>
|
||||
/// <param name="claimValue">The claim value to encode in the token.</param>
|
||||
/// <returns>A JWT as a string.</returns>
|
||||
string GenerateToken(TClaimValue claimValue);
|
||||
|
||||
/// <summary>
|
||||
/// Reads and validates a security token from a string representation.
|
||||
/// </summary>
|
||||
/// <param name="token">The JWT to read.</param>
|
||||
/// <returns>A <see cref="JwtSecurityToken"/> if the token is valid; otherwise, null.</returns>
|
||||
JwtSecurityToken? ReadSecurityToken(string token);
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
using DigitalData.Core.DTO;
|
||||
|
||||
namespace DigitalData.Core.Abstractions.Application
|
||||
{
|
||||
public interface IReadService<TReadDto, TEntity, TId>
|
||||
where TReadDto : class where TEntity : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves an entity by its identifier and returns its readDTO representation wrapped in an IServiceResult,
|
||||
/// including the readDTO on success or null and an error message on failure.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier of the entity to retrieve.</param>
|
||||
/// <returns>An DataResult containing the readDTO representing the found entity or null, with an appropriate message.</returns>
|
||||
Task<DataResult<TReadDto>> ReadByIdAsync(TId id);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all entities and returns their readDTO representations wrapped in an IServiceResult,
|
||||
/// including a collection of readDTOs on success or an error message on failure.
|
||||
/// </summary>
|
||||
/// <returns>An DataResult containing a collection of readDTOs representing all entities or an error message.</returns>
|
||||
Task<DataResult<IEnumerable<TReadDto>>> ReadAllAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Deletes an entity by its identifier and returns the result wrapped in an IServiceMessage,
|
||||
/// indicating the success or failure of the operation, including the error messages on failure.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier of the entity to delete.</param>
|
||||
/// <returns>An Result indicating the outcome of the delete operation, with an appropriate message.</returns>
|
||||
Task<Result> DeleteAsyncById(TId id);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously checks if an entity with the specified identifier exists within the data store.
|
||||
/// </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>
|
||||
Task<bool> HasEntity(TId id);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
using System.Net;
|
||||
|
||||
namespace DigitalData.Core.Abstractions.Client
|
||||
{
|
||||
public interface IBaseHttpClientService
|
||||
{
|
||||
CookieCollection GetCookies(string path = "");
|
||||
|
||||
Task<HttpResponseMessage> FetchAsync(
|
||||
string? scheme = null,
|
||||
int? port = null,
|
||||
string path = "",
|
||||
IEnumerable<KeyValuePair<string, object?>>? queryParams = null,
|
||||
HttpMethod? method = null,
|
||||
HttpContent? body = null,
|
||||
IEnumerable<KeyValuePair<string, object>>? form = null,
|
||||
IEnumerable<KeyValuePair<string, object>>? headers = null,
|
||||
bool sendWithCookie = true,
|
||||
bool saveCookie = true
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
namespace DigitalData.Core.Abstractions.Client
|
||||
{
|
||||
public interface IHttpClientOptions
|
||||
{
|
||||
string Uri { get; init; }
|
||||
|
||||
string? Path { get; init; }
|
||||
|
||||
Dictionary<string, object>? Headers { get; init; }
|
||||
|
||||
Dictionary<string, object?>? QueryParams { get; init; }
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace DigitalData.Core.Abstractions.Client
|
||||
{
|
||||
public interface IHttpClientService<TClientOptions> : IBaseHttpClientService where TClientOptions : IHttpClientOptions
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -37,8 +37,4 @@
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.5.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DigitalData.Core.DTO\DigitalData.Core.DTO.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,34 +0,0 @@
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace DigitalData.Core.Abstractions.Infrastructure;
|
||||
|
||||
public static class Extensions
|
||||
{
|
||||
#region Create
|
||||
public static Task<TEntity> CreateAsync<TEntity, TDto>(this IRepository<TEntity> repository, TDto dto, CancellationToken ct = default)
|
||||
{
|
||||
var entity = repository.Mapper.Map(dto);
|
||||
return repository.CreateAsync(entity, ct);
|
||||
}
|
||||
|
||||
public static Task<IEnumerable<TEntity>> CreateAsync<TEntity, TDto>(this IRepository<TEntity> repository, IEnumerable<TDto> dtos, CancellationToken ct = default)
|
||||
{
|
||||
var entities = dtos.Select(dto => repository.Mapper.Map(dto));
|
||||
return repository.CreateAsync(entities, ct);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Read
|
||||
public static async Task<TEntity?> ReadFirstOrDefaultAsync<TEntity>(this IRepository<TEntity> repository, Expression<Func<TEntity, bool>>? expression = null)
|
||||
=> (await repository.ReadAllAsync(expression)).FirstOrDefault();
|
||||
|
||||
public static async Task<TEntity> ReadFirstAsync<TEntity>(this IRepository<TEntity> repository, Expression<Func<TEntity, bool>>? expression = null)
|
||||
=> (await repository.ReadAllAsync(expression)).First();
|
||||
|
||||
public static async Task<TEntity?> ReadSingleOrDefaultAsync<TEntity>(this IRepository<TEntity> repository, Expression<Func<TEntity, bool>>? expression = null)
|
||||
=> (await repository.ReadAllAsync(expression)).SingleOrDefault();
|
||||
|
||||
public static async Task<TEntity> ReadSingleAsync<TEntity>(this IRepository<TEntity> repository, Expression<Func<TEntity, bool>>? expression = null)
|
||||
=> (await repository.ReadAllAsync(expression)).Single();
|
||||
#endregion
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
namespace DigitalData.Core.Abstractions.Infrastructure
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the contract for CRUD operations on a repository for entities of type TEntity.
|
||||
/// </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, IUnique<TId>
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a new entity to the repository.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity to add.</param>
|
||||
/// <returns>The added entity, or null if the entity cannot be added.</returns>
|
||||
Task<TEntity?> CreateAsync(TEntity entity);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an entity by its identifier from the repository.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier of the entity to retrieve.</param>
|
||||
/// <returns>The entity found, or null if no entity is found.</returns>
|
||||
Task<TEntity?> ReadByIdAsync(TId id);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all entities from the repository.
|
||||
/// </summary>
|
||||
/// <returns>A collection of all entities.</returns>
|
||||
Task<IEnumerable<TEntity>> ReadAllAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Updates an existing entity in the repository.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity to update.</param>
|
||||
/// <returns>The updated entity.</returns>
|
||||
Task<bool> UpdateAsync(TEntity entity);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes an entity from the repository.
|
||||
/// </summary>
|
||||
/// <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,50 +0,0 @@
|
||||
namespace DigitalData.Core.Abstractions.Infrastructure
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines methods for mapping between entities and Data Transfer Objects (DTOs).
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">The type of the entity to be mapped.</typeparam>
|
||||
public interface IEntityMapper<TEntity>
|
||||
{
|
||||
/// <summary>
|
||||
/// Maps an entity to a DTO.
|
||||
/// </summary>
|
||||
/// <typeparam name="TDto">The type of the DTO to map to.</typeparam>
|
||||
/// <param name="entity">The entity to be mapped.</param>
|
||||
/// <returns>The mapped DTO.</returns>
|
||||
TDto Map<TDto>(TEntity entity);
|
||||
|
||||
/// <summary>
|
||||
/// Maps an entity list to a DTO list.
|
||||
/// </summary>
|
||||
/// <typeparam name="TDto">The type of the DTO to map to.</typeparam>
|
||||
/// <param name="entities">The entity list to be mapped.</param>
|
||||
/// <returns>The mapped DTO list.</returns>
|
||||
IEnumerable<TDto> Map<TDto>(IEnumerable<TEntity> entities);
|
||||
|
||||
/// <summary>
|
||||
/// Maps a DTO to an entity.
|
||||
/// </summary>
|
||||
/// <typeparam name="TDto">The type of the DTO to be mapped.</typeparam>
|
||||
/// <param name="dto">The DTO to be mapped.</param>
|
||||
/// <returns>The mapped entity.</returns>
|
||||
TEntity Map<TDto>(TDto dto);
|
||||
|
||||
/// <summary>
|
||||
/// Maps a DTO list to an entity list.
|
||||
/// </summary>
|
||||
/// <typeparam name="TDto">The type of the DTO to be mapped.</typeparam>
|
||||
/// <param name="dtos">The DTO list to be mapped.</param>
|
||||
/// <returns>The mapped entity list.</returns>
|
||||
IEnumerable<TEntity> Map<TDto>(IEnumerable<TDto> dtos);
|
||||
|
||||
/// <summary>
|
||||
/// Maps a DTO to an existing entity.
|
||||
/// </summary>
|
||||
/// <typeparam name="TDto">The type of the DTO to be mapped.</typeparam>
|
||||
/// <param name="dto">The DTO to be mapped.</param>
|
||||
/// <param name="entity">The existing entity to be updated with the mapped values.</param>
|
||||
/// <returns>The updated entity.</returns>
|
||||
TEntity Map<TDto>(TDto dto, TEntity entity);
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace DigitalData.Core.Abstractions.Infrastructure;
|
||||
|
||||
public interface IRepository<TEntity>
|
||||
{
|
||||
public IEntityMapper<TEntity> Mapper { get; }
|
||||
|
||||
public Task<TEntity> CreateAsync(TEntity entity, CancellationToken ct = default);
|
||||
|
||||
public Task<IEnumerable<TEntity>> CreateAsync(IEnumerable<TEntity> entities, CancellationToken ct = default);
|
||||
|
||||
public Task<IEnumerable<TEntity>> ReadAllAsync(Expression<Func<TEntity, bool>>? expression = null, CancellationToken ct = default);
|
||||
|
||||
public Task<TEntity?> ReadOrDefaultAsync(Expression<Func<TEntity, bool>> expression, bool single = true, CancellationToken ct = default);
|
||||
|
||||
public Task<IEnumerable<TDto>> ReadAllAsync<TDto>(Expression<Func<TEntity, bool>>? expression = null, CancellationToken ct = default);
|
||||
|
||||
public Task<TDto?> ReadOrDefaultAsync<TDto>(Expression<Func<TEntity, bool>> expression, bool single = true, CancellationToken ct = default);
|
||||
|
||||
public Task UpdateAsync<TDto>(TDto dto, Expression<Func<TEntity, bool>> expression, CancellationToken ct = default);
|
||||
|
||||
public Task DeleteAsync(Expression<Func<TEntity, bool>> expression, CancellationToken ct = default);
|
||||
}
|
||||
Reference in New Issue
Block a user