Files
xCategoryService/Providers/XBaseCategoryProvider.cs
saherelm a029543b72 fix referencing issues ...
fix seeding issues ...
fix SubCategory Refresh and Add Support for handle Category Referesh and Referencing ...
2026-07-24 02:49:46 +03:30

710 lines
22 KiB
C#

using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using xCategoryService.Extensions;
using xCategoryService.Interfaces;
using xCategoryService.Interfaces.Dtos;
using xCategoryService.Models.Dtos;
using xCommons.Extensions;
using xDataService.Extensions;
using xDataService.Models;
using xExceptions.Constants;
using xModels.Dtos;
namespace xCategoryService.Providers
{
public abstract class XBaseCategoryProvider<TKey> : IXBaseCategoryProvider<TKey>
{
//
#region Props ...
/// <summary>
/// Provider Identifier ...
/// </summary>
public string Provider { get; }
/// <summary>
/// Tag Provider for Maipulating Tags ...
/// </summary>
public IXCategoryServiceProvider CategoryProvider { get; }
#endregion
//
#region Constructor ...
public XBaseCategoryProvider(
string providedFor,
IXCategoryServiceProvider categoryProvider
)
{
//
Provider = providedFor;
CategoryProvider = categoryProvider;
}
#endregion
//
#region Referenced Actions ...
/// <summary>
/// Get Specified Indexed Reference for Specific Provided ID ...
/// </summary>
/// <param name="providedForId"></param>
/// <param name="forIndex"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<XCategoryDto> GetReference(
TKey providedForId,
int forIndex = 0,
CancellationToken cancellationToken = default
)
{
//
// Validate ...
if (providedForId.IsNull())
{
XException.InvalidArgs.Throw();
}
//
// Normalize ...
if (forIndex < 0)
{
forIndex = 0;
}
//
// Retrieve Last Index ...
var indexes = await GetReferencingIndexes(
providedForId: providedForId,
cancellationToken: cancellationToken
);
var maxIndex = indexes.HasChild() ? indexes.Max() : -1;
if (forIndex > maxIndex)
{
XException.NotFound.Throw();
}
//
var indexedIdentifier = GetIndexedProvidedID(
forIndex: forIndex,
providedForId: providedForId
);
var result = await CategoryProvider.FindOneAsync(
includeBuilder: null,
ignoreSoftDeleteds: true,
cancellationToken: cancellationToken,
predicate: x => x.References.Contains(indexedIdentifier)
);
if (result.IsNullOrDefault())
{
XException.NotFound.Throw();
}
//
return result;
}
/// <summary>
/// Retrieve all Exists References of Specific Provided ID ...
/// </summary>
/// <param name="providedForId"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<IEnumerable<XCategoryDto>> GetAllReferences(
TKey providedForId,
CancellationToken cancellationToken = default
)
{
//
// Validate ...
if (providedForId.IsNull())
{
XException.InvalidArgs.Throw();
}
//
var identifier = GetProvidedID(providedForId);
var result = await CategoryProvider.FindManyAsync(
orderBuilder: null,
includeBuilder: null,
ignoreSoftDeleteds: true,
cancellationToken: cancellationToken,
predicate: x => x.References.Contains(identifier)
);
//
return result;
}
/// <summary>
/// Extract all Referencing Models for Specified Key ...
/// </summary>
/// <param name="providedForId"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<IEnumerable<XReference<TKey>>> GetAllReferencings(
TKey providedForId,
CancellationToken cancellationToken = default
)
{
//
// Validate ...
var isValid =
!providedForId.IsNull();
if (!isValid)
{
XException.InvalidArgs.Throw();
}
//
var identifier = GetProvidedID(providedForId);
//
var result = (await CategoryProvider.FindManyAsync(
orderBuilder: null,
includeBuilder: null,
ignoreSoftDeleteds: true,
cancellationToken: cancellationToken,
predicate: x => x.References.Contains(identifier)
))
.Select(x => x.References)
.ToList()
.SelectMany(x => x.ParseXReferenceList<TKey>())
.Where(x => x.ProvidedFor == Provider && $"{x.ReferencedTo}" == $"{providedForId}")
.OrderBy(x => x.Index)
.AsEnumerable();
//
return result;
}
/// <summary>
/// Count References ...
/// </summary>
/// <param name="providedForId"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<int> CountReferences(
TKey providedForId,
CancellationToken cancellationToken = default
)
{
//
// Validate ...
var isValid = !providedForId.IsNull();
if (!isValid)
{
XException.InvalidArgs.Throw();
}
//
var identifier = GetProvidedID(providedForId);
var result = (await CategoryProvider.FindManyAsync(
orderBuilder: null,
includeBuilder: null,
ignoreSoftDeleteds: true,
cancellationToken: cancellationToken,
predicate: x => x.References.Contains(identifier)
))
.Count();
//
return result;
}
/// <summary>
/// Retrieve References of Specific Provided ID as Query ...
/// </summary>
/// <param name="query"></param>
/// <param name="providedForId"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<XQueryResult<XCategoryDto>> QueryReferences(
XQuery query,
TKey providedForId,
CancellationToken cancellationToken = default
)
{
//
// Validate ...
if (query.IsNull() || providedForId.IsNull())
{
XException.InvalidArgs.Throw();
}
//
var identifier = GetProvidedID(providedForId);
var result = await CategoryProvider.QueryAsync(
query: query,
includeBuilder: null,
ignoreSoftDeleteds: true,
cancellationToken: cancellationToken,
orderBuilder: x => x.OrderBy(y => y.Id),
predicate: x => x.References.Contains(identifier)
);
//
return result;
}
/// <summary>
/// Add Reference a Model to Specific Provided ID ...
/// </summary>
/// <param name="model"></param>
/// <param name="providedForId"></param>
/// <param name="connectionId"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<bool> AddReference(
XCategoryDto model,
TKey providedForId,
string connectionId = null,
CancellationToken cancellationToken = default
)
{
//
var result = false;
//
// Validate ...
result = !model.IsNullOrDefault() &&
model.Id.IsValidIntId() &&
!providedForId.IsNull();
if (!result)
{
XException.InvalidArgs.Throw();
}
//
// Check Model Exists ...
result = await CategoryProvider.IsExistsAsync(model.Id);
if (!result)
{
XException.NotFound.Throw();
}
//
// Update Model by Retrieving ...
model = await CategoryProvider.GetAsync(
id: model.Id,
includeBuilder: null,
ignoreSoftDeleteds: true,
cancellationToken: cancellationToken
);
//
// Retrieve all References ...
var references = await GetAllReferences(
providedForId: providedForId,
cancellationToken: cancellationToken
);
//
// Check isReferenced or not ...
result = references
.Any(r =>
r.Id == model.Id &&
r.GetReferences()
.Any(rf =>
rf.ProvidedFor == Provider &&
rf.ReferencedTo == $"{providedForId}"));
if (result)
{
return result;
}
//
// Here means there is not any reference to ProvidedForId ...
int forIndex = await CountReferences(
providedForId: providedForId,
cancellationToken: cancellationToken
);
var modelReferences = model.GetReferences();
modelReferences.Add(new XReference<string>
{
Index = forIndex,
ProvidedFor = Provider,
ReferencedTo = $"{providedForId}"
});
modelReferences = modelReferences
.OrderBy(r => r.Index)
.ToList();
model = model.UpdateReferences(modelReferences);
//
// Update Data Base ...
model = await CategoryProvider.UpdateAsync(
id: model.Id,
item: model,
saveChanges: true,
connectionId: connectionId,
cancellationToken: cancellationToken
);
result = !model.IsNullOrDefault();
//
return result;
}
/// <summary>
/// Add Reference a Model to Specific XRefence<TKey/> ...
/// </summary>
/// <param name="model"></param>
/// <param name="reference"></param>
/// <param name="connectionId"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<bool> AddReference(
XCategoryDto model,
XReference<TKey> reference,
string connectionId = null,
CancellationToken cancellationToken = default
)
{
//
// Validate ...
// Since other Validations Handled in Calling, we Ignore them here ...
var result =
!model.IsNull() &&
!reference.IsNullOrDefault() &&
reference.ProvidedFor == Provider &&
!reference.ReferencedTo.IsNull();
if (!result)
{
XException.InvalidArgs.Throw();
}
//
result = await AddReference(
model: model,
connectionId: connectionId,
cancellationToken: cancellationToken,
providedForId: reference.ReferencedTo
);
//
return result;
}
/// <summary>
/// Remove Reference a Model for Specific Provided ID ...
/// </summary>
/// <param name="model"></param>
/// <param name="providedForId"></param>
/// <param name="connectionId"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<bool> RemoveReference(
XCategoryDto model,
TKey providedForId,
string connectionId = null,
CancellationToken cancellationToken = default
)
{
//
// Validate ...
var result =
!providedForId.IsNull() &&
!model.IsNullOrDefault() &&
model.Id.IsValidIntId();
if (!result)
{
XException.InvalidArgs.Throw();
}
//
// Retrieve Model ...
model = await CategoryProvider.GetAsync(
id: model.Id,
includeBuilder: null,
ignoreSoftDeleteds: true,
cancellationToken: cancellationToken
);
result = !model.IsNullOrDefault();
if (!result)
{
XException.NotFound.Throw();
}
//
// Reading Referencing Indexes ...
var indexes = await GetReferencingIndexes(
providedForId: providedForId,
cancellationToken: cancellationToken
);
//
// Check Model Reference to ID ...
var referencesCount = await CountReferences(
providedForId: providedForId,
cancellationToken: cancellationToken
);
result = referencesCount == 0;
if (result)
{
//
// There is not any Reference to Remove ...
return result;
}
//
// Check Model is Trully Referenced to ProvidedForId or not ...
var reference = model
.GetReferences()
.FirstOrDefault(r =>
r.ProvidedFor == Provider &&
r.ReferencedTo == $"{providedForId}");
result = !reference.IsNullOrDefault();
if (!result)
{
XException.ActionFailed.Throw();
}
//
// Remove Referenced Item from Model ...
var modelReferences = model
.GetReferences()
.Where(r =>
!(r.ProvidedFor == Provider &&
r.ReferencedTo == $"{providedForId}"))
.ToList();
model = model.UpdateReferences(modelReferences);
//
// Update DataBase ...
model = await CategoryProvider.UpdateAsync(
item: model,
id: model.Id,
saveChanges: true,
connectionId: connectionId,
cancellationToken: cancellationToken
);
//
result = !model.IsNullOrDefault();
if (!result)
{
XException.ActionFailed.Throw();
}
//
// Get Max Exists Indexes ...
var maxIndex = indexes.Max();
//
// Here We Have to Re Arrange Indexed ...
if (reference.Index < maxIndex)
{
//
var startIndex = reference.Index + 1;
for (int i = startIndex; i <= maxIndex; i++)
{
//
var dto = await GetReference(
forIndex: i,
providedForId: providedForId,
cancellationToken: cancellationToken
);
if (!dto.IsNullOrDefault())
{
//
var dtoReferences = dto.GetReferences();
dtoReferences
.ToList()
.ForEach(iref =>
{
//
if (
iref.Index == i &&
iref.ProvidedFor == Provider &&
iref.ReferencedTo == reference.ReferencedTo
)
{
iref.Index = i - 1;
}
});
dto = dto.UpdateReferences(dtoReferences);
//
dto = await CategoryProvider.UpdateAsync(
item: dto,
id: dto.Id,
saveChanges: true,
connectionId: connectionId,
cancellationToken: cancellationToken
);
}
}
}
//
return result;
}
/// <summary>
/// Remove Reference a Model for Specific XRefence<TKey/> ...
/// </summary>
/// <param name="model"></param>
/// <param name="reference"></param>
/// <param name="connectionId"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<bool> RemoveReference(
XCategoryDto model,
XReference<TKey> reference,
string connectionId = null,
CancellationToken cancellationToken = default
)
{
//
if (model.IsNullOrDefault() ||
reference.IsNullOrDefault() ||
reference.ReferencedTo.IsNull() ||
reference.ProvidedFor != Provider)
{
XException.InvalidArgs.Throw();
}
//
var result = await RemoveReference(
model: model,
connectionId: connectionId,
cancellationToken: cancellationToken,
providedForId: reference.ReferencedTo
);
//
return result;
}
/// <summary>
/// Remove All References to Specified Key ...
/// </summary>
/// <param name="providedForId"></param>
/// <param name="connectionId"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<bool> RemoveReferences(
TKey providedForId,
string connectionId = null,
CancellationToken cancellationToken = default
)
{
//
// Validate ...
if (providedForId.IsNull())
{
XException.InvalidArgs.Throw();
}
//
var result = false;
var references = await GetAllReferences(
providedForId: providedForId,
cancellationToken: cancellationToken
);
if (!references.IsNull() && references.HasChild())
{
//
foreach (var reference in references)
{
//
// Remove Reference ...
var itemReferences = reference.GetReferences()
.Where(r =>
r.ProvidedFor != Provider ||
(r.ProvidedFor == Provider &&
r.ReferencedTo != $"{providedForId}"))
.ToList();
var updatedModel = reference.UpdateReferences(itemReferences);
updatedModel = await CategoryProvider.UpdateAsync(
saveChanges: true,
item: updatedModel,
id: updatedModel.Id,
connectionId: connectionId,
cancellationToken: cancellationToken
);
if (!updatedModel.IsNullOrDefault())
{
result = true;
}
}
}
//
return result;
}
#endregion
//
#region Private ...
private string GetProvidedID(
TKey providedForId,
char splitter = '_'
)
{
//
// Validate ...
if (providedForId.IsNull())
{
XException.InvalidArgs.Throw();
}
//
var result = $"{Provider}{splitter}{providedForId}";
return result;
}
private string GetIndexedProvidedID(
TKey providedForId,
int forIndex,
char splitter = '_'
)
{
//
var result = $"{GetProvidedID(providedForId, splitter)}{splitter}{forIndex}";
//
return result;
}
private async Task<IEnumerable<int>> GetReferencingIndexes(
TKey providedForId,
CancellationToken cancellationToken = default
)
{
//
// Validate ...
var isValid =
!providedForId.IsNull();
if (!isValid)
{
XException.InvalidArgs.Throw();
}
//
var result = new List<int>();
var referencings = await GetAllReferencings(
providedForId: providedForId,
cancellationToken: cancellationToken
);
isValid =
!referencings.IsNull() &&
referencings.HasChild();
if (!isValid)
{
return result;
}
//
result =
referencings
.OrderBy(r => r.Index)
.Select(r => r.Index)
.ToList();
//
return result;
}
#endregion
}
}