diff --git a/Extensions/XModelExtensions.cs b/Extensions/XModelExtensions.cs
new file mode 100644
index 0000000..17b00bf
--- /dev/null
+++ b/Extensions/XModelExtensions.cs
@@ -0,0 +1,149 @@
+using System.Collections.Generic;
+using System.Linq;
+using xCategoryService.Models.Dtos;
+using xCategoryService.Models.Entities;
+using xCommons.Extensions;
+using xDataService.Extensions;
+using xDataService.Models;
+
+namespace xCategoryService.Extensions
+{
+ public static class XModelExtensions
+ {
+ ///
+ /// Extract References of a Model ...
+ ///
+ ///
+ ///
+ public static IList> GetReferences(
+ this XCategory source
+ )
+ {
+ //
+ var result = new List>();
+
+ //
+ if (
+ !source.IsNullOrDefault() &&
+ !source.References.IsNullOrEmpty()
+ )
+ {
+ //
+ result = source.References
+ .ParseXReferenceList()
+ .ToList();
+ }
+
+ //
+ return result;
+ }
+
+ ///
+ /// Extract References of a Model ...
+ ///
+ ///
+ ///
+ public static IList> GetReferences(
+ this XCategoryDto source
+ )
+ {
+ //
+ var result = new List>();
+
+ //
+ if (
+ !source.IsNullOrDefault() &&
+ !source.References.IsNullOrEmpty()
+ )
+ {
+ //
+ result = source.References
+ .ParseXReferenceList()
+ .ToList();
+ }
+
+ //
+ return result;
+ }
+
+ ///
+ /// Update a Model's References by Providing References List ...
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static XCategory UpdateReferences(
+ this XCategory source,
+ IList> references,
+ bool forceClean = true
+ )
+ {
+ //
+ XCategory result = source;
+
+ //
+ if (!source.IsNullOrDefault())
+ {
+ //
+ result = source;
+
+ //
+ if (forceClean)
+ {
+ result.References = string.Empty;
+ }
+
+ //
+ if (!references.IsNull() &&
+ references.HasChild())
+ {
+ result.References = references.ToXReferenceString();
+ }
+ }
+
+ //
+ return result;
+ }
+
+ ///
+ /// Update a Model's References by Providing References List ...
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static XCategoryDto UpdateReferences(
+ this XCategoryDto source,
+ IList> references,
+ bool forceClean = true
+ )
+ {
+ //
+ XCategoryDto result = source;
+
+ //
+ if (!source.IsNullOrDefault())
+ {
+ //
+ result = source;
+
+ //
+ if (forceClean)
+ {
+ result.References = string.Empty;
+ }
+
+ //
+ if (!references.IsNull() &&
+ references.HasChild())
+ {
+ result.References = references.ToXReferenceString();
+ }
+ }
+
+ //
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Interfaces/IXBaseCategoryProvider.cs b/Interfaces/IXBaseCategoryProvider.cs
new file mode 100644
index 0000000..5fc9460
--- /dev/null
+++ b/Interfaces/IXBaseCategoryProvider.cs
@@ -0,0 +1,8 @@
+using xCategoryService.Models.Dtos;
+using xDataService.Interfaces;
+
+namespace xCategoryService.Interfaces
+{
+ public interface IXBaseCategoryProvider : IXBaseReferencedProvider
+ {}
+}
\ No newline at end of file
diff --git a/Providers/XBaseCategoryProvider.cs b/Providers/XBaseCategoryProvider.cs
new file mode 100644
index 0000000..d75b648
--- /dev/null
+++ b/Providers/XBaseCategoryProvider.cs
@@ -0,0 +1,710 @@
+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 : IXBaseCategoryProvider
+ {
+ //
+ #region Props ...
+ ///
+ /// Provider Identifier ...
+ ///
+ public string Provider { get; }
+
+ ///
+ /// Tag Provider for Maipulating Tags ...
+ ///
+ public IXCategoryServiceProvider CategoryProvider { get; }
+ #endregion
+
+ //
+ #region Constructor ...
+ public XBaseCategoryProvider(
+ string providedFor,
+ IXCategoryServiceProvider categoryProvider
+ )
+ {
+ //
+ Provider = providedFor;
+ CategoryProvider = categoryProvider;
+ }
+ #endregion
+
+ //
+ #region Referenced Actions ...
+ ///
+ /// Get Specified Indexed Reference for Specific Provided ID ...
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task 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 maxIndex = (await GetReferencingIndexes(
+ providedForId: providedForId,
+ cancellationToken: cancellationToken
+ ))
+ .Max();
+ 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;
+ }
+
+ ///
+ /// Retrieve all Exists References of Specific Provided ID ...
+ ///
+ ///
+ ///
+ ///
+ public async Task> 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;
+ }
+
+ ///
+ /// Extract all Referencing Models for Specified Key ...
+ ///
+ ///
+ ///
+ ///
+ public async Task>> 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())
+ .Where(x => x.ProvidedFor == Provider && $"{x.ReferencedTo}" == $"{providedForId}")
+ .OrderBy(x => x.Index)
+ .AsEnumerable();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Count References ...
+ ///
+ ///
+ ///
+ ///
+ public async Task 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;
+ }
+
+ ///
+ /// Retrieve References of Specific Provided ID as Query ...
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task> 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;
+ }
+
+ ///
+ /// Add Reference a Model to Specific Provided ID ...
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task 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
+ {
+ 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;
+ }
+
+ ///
+ /// Add Reference a Model to Specific XRefence ...
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task AddReference(
+ XCategoryDto model,
+ XReference 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;
+ }
+
+ ///
+ /// Remove Reference a Model for Specific Provided ID ...
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task 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;
+ }
+
+ ///
+ /// Remove Reference a Model for Specific XRefence ...
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task RemoveReference(
+ XCategoryDto model,
+ XReference 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;
+ }
+
+ ///
+ /// Remove All References to Specified Key ...
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task 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> GetReferencingIndexes(
+ TKey providedForId,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Validate ...
+ var isValid =
+ !providedForId.IsNull();
+ if (!isValid)
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ var result = new List();
+ 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
+ }
+}
\ No newline at end of file