From 0523d1ae3b3816dcc281f88856bd7959e463846b Mon Sep 17 00:00:00 2001 From: Dmitriy Melnychenko Date: Thu, 31 Aug 2023 18:03:09 +0300 Subject: [PATCH 1/6] Added counter logic for serial attributes --- CloudFabric.EAV.Domain/Counter.cs | 46 +++ CloudFabric.EAV.Enums/CounterActionStatus.cs | 7 + ...rialAttributeConfigurationUpdateRequest.cs | 2 +- CloudFabric.EAV.Service/EAVCategoryService.cs | 6 +- .../EAVEntityInstanceService.cs | 56 ++-- CloudFabric.EAV.Service/EAVService.cs | 280 ++++++++++-------- .../EntitySerialCounterService.cs | 221 ++++++++++++++ .../BaseQueryTests/BaseQueryTests.cs | 12 +- CloudFabric.EAV.Tests/Tests.cs | 218 ++++++++++---- 9 files changed, 644 insertions(+), 204 deletions(-) create mode 100644 CloudFabric.EAV.Domain/Counter.cs create mode 100644 CloudFabric.EAV.Enums/CounterActionStatus.cs create mode 100644 CloudFabric.EAV.Service/EntitySerialCounterService.cs diff --git a/CloudFabric.EAV.Domain/Counter.cs b/CloudFabric.EAV.Domain/Counter.cs new file mode 100644 index 0000000..9412b3c --- /dev/null +++ b/CloudFabric.EAV.Domain/Counter.cs @@ -0,0 +1,46 @@ +namespace CloudFabric.EAV.Domain; + +public class Counter +{ + public long NextValue { get; set; } + + private int? _lastIncrement; + public int? LastIncrement + { + get { return _lastIncrement; } + init { _lastIncrement = value; } + } + + private DateTime _timeStamp; + public DateTime TimeStamp + { + get { return _timeStamp; } + init { _timeStamp = value; } + } + + public Guid AttributeConfidurationId { get; init; } + + // Used for Json deserialization + public Counter() + { + } + + public Counter(long nextValue, DateTime timeStamp, Guid attributeConfigurationId, int? lastIncrement = null) + { + NextValue = nextValue; + TimeStamp = timeStamp; + AttributeConfidurationId = attributeConfigurationId; + LastIncrement = lastIncrement; + } + + public void SetTimeStamp(DateTime timeStamp) + { + _timeStamp = timeStamp; + } + + public long Step(int increment) + { + _lastIncrement = increment; + return this.NextValue += increment; + } +} diff --git a/CloudFabric.EAV.Enums/CounterActionStatus.cs b/CloudFabric.EAV.Enums/CounterActionStatus.cs new file mode 100644 index 0000000..1d6e94f --- /dev/null +++ b/CloudFabric.EAV.Enums/CounterActionStatus.cs @@ -0,0 +1,7 @@ +namespace CloudFabric.EAV.Enums; + +public enum CounterActionStatus +{ + Saved, + Conflict +} diff --git a/CloudFabric.EAV.Models/RequestModels/Attributes/SerialAttributeConfigurationUpdateRequest.cs b/CloudFabric.EAV.Models/RequestModels/Attributes/SerialAttributeConfigurationUpdateRequest.cs index e476a43..75cc2bf 100644 --- a/CloudFabric.EAV.Models/RequestModels/Attributes/SerialAttributeConfigurationUpdateRequest.cs +++ b/CloudFabric.EAV.Models/RequestModels/Attributes/SerialAttributeConfigurationUpdateRequest.cs @@ -1,4 +1,4 @@ -using CloudFabric.EAV.Enums; +using CloudFabric.EAV.Enums; namespace CloudFabric.EAV.Models.RequestModels.Attributes; diff --git a/CloudFabric.EAV.Service/EAVCategoryService.cs b/CloudFabric.EAV.Service/EAVCategoryService.cs index e9cdafa..63a9776 100644 --- a/CloudFabric.EAV.Service/EAVCategoryService.cs +++ b/CloudFabric.EAV.Service/EAVCategoryService.cs @@ -1,4 +1,4 @@ -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; using System.Text.Json; using AutoMapper; @@ -34,13 +34,15 @@ public EAVCategoryService(ILogger? elasticSearchQueryOptions = null) : base(logger, new CategoryFromDictionaryDeserializer(mapper), mapper, jsonSerializerOptions, aggregateRepositoryFactory, projectionRepositoryFactory, - userInfo) + userInfo, + counterService) { _elasticSearchQueryOptions = elasticSearchQueryOptions != null diff --git a/CloudFabric.EAV.Service/EAVEntityInstanceService.cs b/CloudFabric.EAV.Service/EAVEntityInstanceService.cs index 2ceab7c..bfc2284 100644 --- a/CloudFabric.EAV.Service/EAVEntityInstanceService.cs +++ b/CloudFabric.EAV.Service/EAVEntityInstanceService.cs @@ -1,28 +1,25 @@ -using System.Text.Json; +using System.Text.Json; using AutoMapper; +using CloudFabric.EAV.Domain; using CloudFabric.EAV.Domain.Models; -using CloudFabric.EAV.Domain.Models.Attributes; using CloudFabric.EAV.Enums; using CloudFabric.EAV.Models.RequestModels; using CloudFabric.EAV.Models.ViewModels; -using CloudFabric.EAV.Options; using CloudFabric.EAV.Service.Serialization; using CloudFabric.EventSourcing.Domain; -using CloudFabric.EventSourcing.EventStore; using CloudFabric.EventSourcing.EventStore.Persistence; using CloudFabric.Projections; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; using ProjectionDocumentSchemaFactory = CloudFabric.EAV.Domain.Projections.EntityInstanceProjection.ProjectionDocumentSchemaFactory; namespace CloudFabric.EAV.Service; -public class EAVEntityInstanceService: EAVService +public class EAVEntityInstanceService : EAVService { public EAVEntityInstanceService(ILogger> logger, @@ -30,13 +27,15 @@ public EAVEntityInstanceService(ILogger CreateEntityInstance( - EntityInstanceCreateRequest entity, bool dryRun = false, bool requiredAttributesCanBeNull = false, CancellationToken cancellationToken = default + public async Task<(EntityInstanceViewModel?, ProblemDetails?)> CreateEntityInstance( + EntityInstanceCreateRequest entity, + bool dryRun = false, + bool requiredAttributesCanBeNull = false, + CancellationToken cancellationToken = default ) { EntityConfiguration? entityConfiguration = await _entityConfigurationRepository.LoadAsync( @@ -421,6 +422,7 @@ await GetAttributeConfigurationsForEntityConfiguration( ); var validationErrors = new Dictionary(); + List entityCounters = new(); foreach (AttributeConfiguration a in attributeConfigurations) { AttributeInstance? attributeValue = entityInstance.Attributes @@ -432,10 +434,9 @@ await GetAttributeConfigurationsForEntityConfiguration( validationErrors.Add(a.MachineName, attrValidationErrors.ToArray()); } - // Note that this method updates entityConfiguration state (for serial attribute it increments the number - // stored in externalvalues) but does not save entity configuration, we need to do that manually outside of - // the loop - InitializeAttributeInstanceWithExternalValuesFromEntity(entityConfiguration, a, attributeValue); + var counter = await InitializeAttributeInstanceWithCounterValue(entityConfiguration, a, attributeValue); + + entityCounters.Add(counter); } if (validationErrors.Count > 0) @@ -445,15 +446,30 @@ await GetAttributeConfigurationsForEntityConfiguration( if (!dryRun) { - var entityConfigurationSaved = await _entityConfigurationRepository - .SaveAsync(_userInfo, entityConfiguration, cancellationToken) - .ConfigureAwait(false); + // Save counters + var counterResponse = new List(); - if (!entityConfigurationSaved) + foreach (var counter in entityCounters.Where(x => x != null)) { - throw new Exception("Entity was not saved"); + counterResponse.Add(await _counterService.SaveCounter(entityConfiguration.Id, counter.AttributeConfidurationId, counter)); + } + + foreach (var responce in counterResponse.Where(x => x.Status == CounterActionStatus.Conflict)) + { + do + { + var counter = await _counterService.LoadCounter(responce.EntityConfigurationId, responce.AttributeConfigurationId); + + counter!.Step(counter.LastIncrement!.Value); + + var repeatedCounterSaveResponce = await _counterService.SaveCounter(entityConfiguration.Id, counter.AttributeConfidurationId, counter); + + responce.Status = repeatedCounterSaveResponce.Status; + + } while (responce.Status != CounterActionStatus.Saved); } + // Save instance ProjectionDocumentSchema schema = ProjectionDocumentSchemaFactory .FromEntityConfiguration(entityConfiguration, attributeConfigurations); diff --git a/CloudFabric.EAV.Service/EAVService.cs b/CloudFabric.EAV.Service/EAVService.cs index a1fbbb0..632581c 100644 --- a/CloudFabric.EAV.Service/EAVService.cs +++ b/CloudFabric.EAV.Service/EAVService.cs @@ -5,12 +5,13 @@ using AutoMapper; -using CloudFabric.EAV.Enums; +using CloudFabric.EAV.Domain; using CloudFabric.EAV.Domain.Models; using CloudFabric.EAV.Domain.Models.Attributes; using CloudFabric.EAV.Domain.Models.Base; using CloudFabric.EAV.Domain.Projections.AttributeConfigurationProjection; using CloudFabric.EAV.Domain.Projections.EntityConfigurationProjection; +using CloudFabric.EAV.Enums; using CloudFabric.EAV.Models.RequestModels; using CloudFabric.EAV.Models.RequestModels.Attributes; using CloudFabric.EAV.Models.ViewModels; @@ -31,9 +32,9 @@ namespace CloudFabric.EAV.Service; [SuppressMessage("ReSharper", "InconsistentNaming")] -public abstract class EAVService where TViewModel: EntityInstanceViewModel - where TUpdateRequest: EntityInstanceUpdateRequest - where TEntityType: EntityInstanceBase +public abstract class EAVService where TViewModel : EntityInstanceViewModel + where TUpdateRequest : EntityInstanceUpdateRequest + where TEntityType : EntityInstanceBase { private readonly IProjectionRepository _attributeConfigurationProjectionRepository; @@ -50,7 +51,7 @@ private readonly IProjectionRepository internal readonly EntityInstanceCreateUpdateRequestFromJsonDeserializer _entityInstanceCreateUpdateRequestFromJsonDeserializer; internal readonly AggregateRepository _entityInstanceRepository; - private readonly ILogger> _logger; + private readonly ILogger> _logger; internal readonly IMapper _mapper; private readonly JsonSerializerOptions _jsonSerializerOptions; internal readonly ProjectionRepositoryFactory _projectionRepositoryFactory; @@ -60,15 +61,17 @@ internal readonly EntityInstanceCreateUpdateRequestFromJsonDeserializer internal readonly AggregateRepository _categoryTreeRepository; internal readonly AggregateRepository _categoryInstanceRepository; + internal readonly EntitySerialCounterService _counterService; + protected EAVService( - ILogger> logger, + ILogger> logger, InstanceFromDictionaryDeserializer instanceFromDictionaryDeserializer, IMapper mapper, JsonSerializerOptions jsonSerializerOptions, AggregateRepositoryFactory aggregateRepositoryFactory, ProjectionRepositoryFactory projectionRepositoryFactory, - EventUserInfo userInfo - ) + EventUserInfo userInfo, + EntitySerialCounterService counterService) { _logger = logger; _mapper = mapper; @@ -104,6 +107,7 @@ EventUserInfo userInfo .GetAggregateRepository(); _categoryTreeRepository = aggregateRepositoryFactory .GetAggregateRepository(); + _counterService = counterService; } private void EnsureAttributeMachineNameIsAdded(AttributeConfigurationCreateUpdateRequest attributeRequest) @@ -185,93 +189,6 @@ private async Task CheckAttributesListMachineNameUnique( return true; } - - - /// - /// Update entity configuration external values. - /// - /// - /// Specialized method to update entity configuration external value with updating arrtibute instance value - - /// this means new instance value is not out of external value logic, and can be overwritten to it. - /// Intended use: update with a new value an attribute instance - /// whose value was initialized from the external entity configuration values. - /// - /// Note that after exetuting this method entity configuration aggregate repository has uncommited events, - /// use .SaveAsync() for saving. - /// - /// - /// - /// - /// - /// List of validation errors or null if everithing is fine. - /// - private List? UpdateEntityExternalValuesDuringInstanceUpdate( - EntityConfiguration entityConfiguration, - AttributeConfiguration attributeConfiguration, - AttributeInstance? attributeInstance - ) - { - switch (attributeConfiguration.ValueType) - { - case EavAttributeType.Serial: - { - if (attributeInstance == null) - { - return null; - } - - var validationErrors = new List(); - - var serialAttributeConfiguration = attributeConfiguration as SerialAttributeConfiguration; - - var serialInstance = attributeInstance as SerialAttributeInstance; - - if (serialAttributeConfiguration == null || serialInstance == null) - { - validationErrors.Add("Invalid attribute type."); - } - - if (serialInstance != null && !serialInstance.Value.HasValue) - { - validationErrors.Add("Updating serial number value can not be empty."); - } - - EntityConfigurationAttributeReference? entityAttribute = entityConfiguration.Attributes - .FirstOrDefault(x => x.AttributeConfigurationId == attributeConfiguration.Id); - - if (entityAttribute == null) - { - validationErrors.Add("Attribute configuration is not found."); - } - - if (validationErrors.Count > 0) - { - return validationErrors; - } - - var existingAttributeValue = - entityAttribute!.AttributeConfigurationExternalValues.First(); - - long? deserializedValue = JsonSerializer.Deserialize(existingAttributeValue!.ToString()!); - - if (serialInstance!.Value <= deserializedValue!.Value) - { - validationErrors.Add("Serial number value can not be less than the already existing one."); - return validationErrors; - } - - var newExternalValue = serialInstance.Value; - - entityConfiguration.UpdateAttrributeExternalValues(attributeConfiguration.Id, - new List { newExternalValue! } - ); - - return null; - } - } - return null; - } - private async Task> GetAttributesByIds( List attributesIds, CancellationToken cancellationToken) { @@ -534,6 +451,7 @@ CancellationToken cancellationToken } } + var allCreatedAttributes = new List(); var allAttrProblemDetails = new List(); for (var i = 0; i < entityConfigurationCreateRequest.Attributes.Count; i++) { @@ -560,6 +478,7 @@ await CreateAttribute( { AttributeConfigurationId = attrCreated.Id }; + allCreatedAttributes.Add(attrCreated); } } } @@ -590,6 +509,7 @@ await CreateAttribute( new ValidationErrorResponse(entityConfiguration.MachineName, entityValidationErrors.ToArray())); } + await _counterService.InitializeEntityConfigurationCounters(entityConfiguration.Id, allCreatedAttributes); await _entityConfigurationRepository.SaveAsync( _userInfo, @@ -680,6 +600,8 @@ await _entityConfigurationRepository.SaveAsync( } reservedAttributes.Add(attributeConfiguration.Id); + + await _counterService.InitializeEntityConfigurationCounterIfSerialAttribute(entityConfiguration.Id, attributeConfiguration); } } else if (attributeUpdate is AttributeConfigurationCreateUpdateRequest attributeCreateRequest) @@ -713,6 +635,8 @@ await CreateAttribute( { entityConfiguration.AddAttribute(attributeCreated.Id); reservedAttributes.Add(attributeCreated.Id); + + await _counterService.InitializeEntityConfigurationCounterIfSerialAttribute(entityConfiguration.Id, attributeCreated); } } } @@ -815,6 +739,8 @@ private async Task EnsureProjectionIndexForEntityConfiguration(EntityConfigurati await _entityConfigurationRepository.SaveAsync(_userInfo, entityConfiguration, cancellationToken) .ConfigureAwait(false); + await _counterService.InitializeEntityConfigurationCounterIfSerialAttribute(entityConfigurationId, attributeConfiguration); + return (_mapper.Map(entityConfiguration), null); } @@ -860,6 +786,8 @@ await _entityConfigurationRepository.SaveAsync(_userInfo, entityConfiguration, c await _entityConfigurationRepository.SaveAsync(_userInfo, entityConfiguration, cancellationToken); + await _counterService.InitializeEntityConfigurationCounterIfSerialAttribute(entityConfigurationId, createdAttribute); + return (createdAttribute, null)!; } @@ -955,7 +883,7 @@ await _entityConfigurationProjectionRepository.Query( } } } -private async Task CreateArrayElementConfiguration(EavAttributeType type, string machineName, AttributeConfigurationCreateUpdateRequest? attributeConfigurationCreateUpdateRequest, Guid? tenantId, CancellationToken cancellationToken) + private async Task CreateArrayElementConfiguration(EavAttributeType type, string machineName, AttributeConfigurationCreateUpdateRequest? attributeConfigurationCreateUpdateRequest, Guid? tenantId, CancellationToken cancellationToken) { Guid? resultGuid = null; @@ -1156,6 +1084,8 @@ await GetAttributeConfigurationsForEntityConfiguration( } // Add or update attributes + List entityCounters = new(); + foreach (AttributeInstanceCreateUpdateRequest? newAttributeRequest in updateRequest.AttributesToAddOrUpdate) { AttributeConfiguration? attrConfig = entityConfigurationAttributeConfigurations @@ -1178,21 +1108,23 @@ await GetAttributeConfigurationsForEntityConfiguration( { if (!newAttribute.Equals(currentAttribute)) { - var updateExternalValuesErrors = UpdateEntityExternalValuesDuringInstanceUpdate(entityConfiguration, attrConfig, newAttribute); + (Counter? counter, List? counterErrors) = await UpdateCounterValueDuringInstanceUpdate(entityConfiguration, attrConfig, newAttribute); - if (updateExternalValuesErrors == null) + if (counterErrors == null) { entityInstance.UpdateAttributeInstance(newAttribute); + + entityCounters.Add(counter); } else { - validationErrors.Add(newAttribute.ConfigurationAttributeMachineName, updateExternalValuesErrors.ToArray()); + validationErrors.Add(newAttribute.ConfigurationAttributeMachineName, counterErrors.ToArray()); } } } else { - InitializeAttributeInstanceWithExternalValuesFromEntity(entityConfiguration, attrConfig, newAttribute); + entityCounters.Add(await InitializeAttributeInstanceWithCounterValue(entityConfiguration, attrConfig, newAttribute)); entityInstance.AddAttributeInstance( _mapper.Map(newAttributeRequest) @@ -1212,14 +1144,33 @@ await GetAttributeConfigurationsForEntityConfiguration( if (!dryRun) { - var entityConfigurationSaved = await _entityConfigurationRepository - .SaveAsync(_userInfo, entityConfiguration, cancellationToken) - .ConfigureAwait(false); + // Save counters + var counterResponse = new List(); + + foreach (var counter in entityCounters.Where(x => x != null)) + { + counterResponse.Add(await _counterService.SaveCounter(entityConfiguration.Id, counter.AttributeConfidurationId, counter)); + } + + foreach (var responce in counterResponse.Where(x => x.Status == CounterActionStatus.Conflict)) + { + do + { + var counter = await _counterService.LoadCounter(responce.EntityConfigurationId, responce.AttributeConfigurationId); + + counter!.Step(counter.LastIncrement!.Value); + + var repeatedCounterSaveResponce = await _counterService.SaveCounter(entityConfiguration.Id, counter.AttributeConfidurationId, counter); + + responce.Status = repeatedCounterSaveResponce.Status; + + } while (responce.Status != CounterActionStatus.Saved); + } var entityInstanceSaved = await _entityInstanceRepository .SaveAsync(_userInfo, entityInstance, cancellationToken) .ConfigureAwait(false); - if (!entityInstanceSaved || !entityConfigurationSaved) + if (!entityInstanceSaved) { //TODO: Throw a error when ready } @@ -1372,7 +1323,7 @@ public async Task> QueryInstancesJsonSingleL return (null, new ValidationErrorResponse(nameof(entityInstanceId), "Instance not found"))!; } - (var newCategoryPath, var parentId, ProblemDetails? errors) = + (var newCategoryPath, var parentId, ProblemDetails? errors) = await BuildCategoryPath(treeId, newParentId, cancellationToken).ConfigureAwait(false); if (errors != null) @@ -1414,7 +1365,16 @@ await _attributeConfigurationRepository.LoadAsyncOrThrowNotFound( #endregion - internal void InitializeAttributeInstanceWithExternalValuesFromEntity( + /// + /// Initialize attribute instance value with counter value if serial attribute. + /// + /// + /// + /// + /// Current counter with changed values if attribute value was initialized, otherwise null. + /// + /// + internal async Task InitializeAttributeInstanceWithCounterValue( EntityConfiguration entityConfiguration, AttributeConfiguration attributeConfiguration, AttributeInstance? attributeInstance @@ -1426,7 +1386,15 @@ internal void InitializeAttributeInstanceWithExternalValuesFromEntity( { if (attributeInstance == null) { - return; + return null; + } + + EntityConfigurationAttributeReference? entityAttribute = entityConfiguration.Attributes + .FirstOrDefault(x => x.AttributeConfigurationId == attributeConfiguration.Id); + + if (entityAttribute == null) + { + return null; } var serialAttributeConfiguration = attributeConfiguration as SerialAttributeConfiguration; @@ -1435,38 +1403,106 @@ internal void InitializeAttributeInstanceWithExternalValuesFromEntity( if (serialAttributeConfiguration == null || serialInstance == null) { - throw new ArgumentException("Invalid attribute type"); + return null; + } + + var counter = await _counterService.LoadCounter(entityConfiguration.Id, attributeConfiguration.Id); + + if (counter == null) + { + counter = await _counterService.InitializeEntityConfigurationSerialAttributeCounter(entityConfiguration.Id, serialAttributeConfiguration); + } + + serialInstance.Value = counter!.NextValue; + + counter.Step(serialAttributeConfiguration.Increment); + + return counter; + } + } + + return null; + } + + /// + /// Update entity configuration counter. + /// + /// + /// Specialized method to update entity configuration counter value with updating arrtibute instance value - + /// this means new instance value is not out of sequence logic, and can be overwritten to it. + /// Intended use: update next avaliable counter value with a new attribute instance value. + /// + /// + /// + /// + /// + /// List of validation errors or counter with changed values if everithing is okay. + /// + private async Task<(Counter?, List?)> UpdateCounterValueDuringInstanceUpdate( + EntityConfiguration entityConfiguration, + AttributeConfiguration attributeConfiguration, + AttributeInstance? attributeInstance + ) + { + switch (attributeConfiguration.ValueType) + { + case EavAttributeType.Serial: + { + if (attributeInstance == null) + { + return (null, null); } + var validationErrors = new List(); + EntityConfigurationAttributeReference? entityAttribute = entityConfiguration.Attributes .FirstOrDefault(x => x.AttributeConfigurationId == attributeConfiguration.Id); if (entityAttribute == null) { - throw new NotFoundException("Attribute not found"); + validationErrors.Add("Attribute configuration is not found"); + return (null, validationErrors); + } + + var serialAttributeConfiguration = attributeConfiguration as SerialAttributeConfiguration; + + var serialInstance = attributeInstance as SerialAttributeInstance; + + if (serialAttributeConfiguration == null || serialInstance == null) + { + validationErrors.Add("Invalid attribute type."); + } + + if (serialInstance != null && !serialInstance.Value.HasValue) + { + validationErrors.Add("Updating serial number value can not be empty."); } - var existingAttributeValue = - entityAttribute.AttributeConfigurationExternalValues.FirstOrDefault(); + if (validationErrors.Count > 0) + { + return (null, validationErrors); + } - long? deserializedValue = null; + var counter = await _counterService.LoadCounter(entityConfiguration.Id, serialAttributeConfiguration.Id); - if (existingAttributeValue != null) + if (counter == null) { - deserializedValue = JsonSerializer.Deserialize(existingAttributeValue.ToString()!); + validationErrors.Add("Counter is not found."); } - var newExternalValue = existingAttributeValue == null - ? serialAttributeConfiguration.StartingNumber - : deserializedValue += serialAttributeConfiguration.Increment; + if (serialInstance!.Value <= counter!.NextValue) + { + // TO DO: Add validation and possibility to update serial value if value less than existing one + validationErrors.Add("Serial number value can not be less or equal than the already existing one."); + return (null, validationErrors); + } - serialInstance.Value = newExternalValue!.Value; + counter.NextValue = serialInstance.Value.Value + serialAttributeConfiguration.Increment; - entityConfiguration.UpdateAttrributeExternalValues(attributeConfiguration.Id, - new List { newExternalValue } - ); + return (counter, null); } - break; } + return (null, null); } + } diff --git a/CloudFabric.EAV.Service/EntitySerialCounterService.cs b/CloudFabric.EAV.Service/EntitySerialCounterService.cs new file mode 100644 index 0000000..262354d --- /dev/null +++ b/CloudFabric.EAV.Service/EntitySerialCounterService.cs @@ -0,0 +1,221 @@ +using AutoMapper; + +using CloudFabric.EAV.Domain; +using CloudFabric.EAV.Domain.Models; +using CloudFabric.EAV.Domain.Models.Attributes; +using CloudFabric.EAV.Enums; +using CloudFabric.EAV.Models.ViewModels.Attributes; +using CloudFabric.EventSourcing.Domain; + +namespace CloudFabric.EAV.Service; + +public class CounterActionResponce +{ + public CounterActionResponce(Guid entityConfiguration, Guid attributeConfigurationId) + { + EntityConfigurationId = entityConfiguration; + AttributeConfigurationId = attributeConfigurationId; + } + + public CounterActionStatus Status { get; set; } + + public Guid EntityConfigurationId { get; } + + public Guid AttributeConfigurationId { get; } +} + +public class EntitySerialCounterService +{ + private readonly IStoreRepository _storeRepository; + private readonly IMapper _mapper; + + public EntitySerialCounterService(IStoreRepository storeRepository, IMapper mapper) + { + _storeRepository = storeRepository; + _mapper = mapper; + } + + #region Initializers + + /// + /// Initalize counters for all serial attribute confgirurations within entity. + /// Already initialized counters will not be overriten or changed. + /// + /// + /// + public async Task InitializeEntityConfigurationCounters(Guid entityConfigurationId, List attributesConfigurations) + { + var serialAttributeConfigurations = + attributesConfigurations + .Where(x => x.ValueType == EavAttributeType.Serial) + .ToList(); + + foreach (var attribute in serialAttributeConfigurations) + { + var counter = await InitializeEntityConfigurationCounterIfSerialAttribute(entityConfigurationId, attribute); + } + } + + /// + /// Initalize counter for attribute configuration within entity configuration if attribute is serial. + /// Already initialized counter will not be overriten or changed. + /// + /// + /// + /// Initialized counter if it has not been initialized previously and attribute is serial attribute, otherwise null. + public async Task InitializeEntityConfigurationCounterIfSerialAttribute(Guid entityConfigurationId, AttributeConfiguration attributeConfiguration) + { + var serialConfigurationViewModel = _mapper.Map(attributeConfiguration); + + return await InitializeEntityConfigurationCounterIfSerialAttribute(entityConfigurationId, serialConfigurationViewModel); + } + + /// + /// Initalize counter for attribute configuration within entity configuration if attribute is serial. + /// Already initialized counter will not be overriten or changed. + /// + /// + /// + /// Initialized counter if it has not been initialized previously and attribute is serial attribute, otherwise null. + public async Task InitializeEntityConfigurationCounterIfSerialAttribute(Guid entityConfigurationId, AttributeConfigurationViewModel attributeConfiguration) + { + var serialAttributeConfiguration = attributeConfiguration as SerialAttributeConfigurationViewModel; + + if (serialAttributeConfiguration == null) + { + return null; + } + + return await InitializeEntityConfigurationSerialAttributeCounter(entityConfigurationId, serialAttributeConfiguration); + } + + /// + /// Initalize counter for serial attribute configuration withing entity configuration. + /// Already initialized counter will not be overriten or changed. + /// + /// + /// + /// Initialized counter if it has not been initialized previously, otherwise null. + public async Task InitializeEntityConfigurationSerialAttributeCounter(Guid entityConfigurationId, SerialAttributeConfiguration serialAttributeConfiguration) + { + var serialAttributeConfigurationViewModel = _mapper.Map(serialAttributeConfiguration); + + return await InitializeEntityConfigurationSerialAttributeCounter(entityConfigurationId, serialAttributeConfigurationViewModel); + + } + + /// + /// Initalize counter for serial attribute configuration withing entity configuration. + /// Already initialized counter will not be overriten or changed. + /// + /// + /// + /// Initialized counter if it has not been initialized previously, otherwise null. + public async Task InitializeEntityConfigurationSerialAttributeCounter(Guid entityConfigurationId, SerialAttributeConfigurationViewModel serialAttributeConfiguration) + { + var existingCounter = await LoadCounter(entityConfigurationId, serialAttributeConfiguration.Id); + + if (existingCounter != null) + { + return null; + } + + var counter = new Counter(serialAttributeConfiguration.StartingNumber, DateTime.UtcNow, serialAttributeConfiguration.Id); + + await _storeRepository.UpsertItem( + string.Concat(entityConfigurationId, serialAttributeConfiguration.Id), + string.Concat(entityConfigurationId, serialAttributeConfiguration.Id), + counter + ); + + return counter; + } + + #endregion + + /// + /// Load counter for specified attribute configuration within entity configuration. + /// + /// + /// + /// Counter or null if not exists. + public async Task LoadCounter(Guid entityConfigurationId, Guid attributeId) + { + return await _storeRepository.LoadItem( + string.Concat(entityConfigurationId, attributeId), + string.Concat(entityConfigurationId, attributeId) + ); + } + + /// + /// Update and validate counter, related to specified attribute configuration within entity configuration. + /// + /// + /// + /// + /// Flag that idnicates existance of counter and errors, that are null if everything is okay. + public async Task<(bool isExists, List? errors)> UpdateCounterIfExists(Guid entityConfigurationId, Guid attributeId, Counter newCounter) + { + var counter = await LoadCounter(entityConfigurationId, attributeId); + + if (counter == null) + { + return (false, null); + } + + List errors = new(); + + if (newCounter.NextValue < counter.NextValue) + { + errors.Add("Wrong counter sequence"); + return (true, errors); + } + + await _storeRepository.UpsertItem( + string.Concat(entityConfigurationId, attributeId), + string.Concat(entityConfigurationId, attributeId), + counter + ); + + return (true, null); + } + + /// + /// Save counter. + /// + /// + /// + /// + /// Counter action responce with status to indicate result. + /// + public async Task SaveCounter(Guid entityConfigurationId, Guid attributeConfigurationId, Counter updatedCounter) + { + CounterActionResponce response = new(entityConfigurationId, attributeConfigurationId); + + Counter? counter = await LoadCounter(entityConfigurationId, attributeConfigurationId); + + if (counter == null) + { + throw new ArgumentNullException("Failed to save counter - make sure it was initialized"); + } + + if (updatedCounter.TimeStamp != counter.TimeStamp) + { + response.Status = CounterActionStatus.Conflict; + + return response; + } + + counter.SetTimeStamp(DateTime.UtcNow); + + await _storeRepository.UpsertItem( + string.Concat(entityConfigurationId, attributeConfigurationId), + string.Concat(entityConfigurationId, attributeConfigurationId), + updatedCounter + ); + + response.Status = CounterActionStatus.Saved; + + return response; + } +} diff --git a/CloudFabric.EAV.Tests/BaseQueryTests/BaseQueryTests.cs b/CloudFabric.EAV.Tests/BaseQueryTests/BaseQueryTests.cs index f848318..f6e7265 100644 --- a/CloudFabric.EAV.Tests/BaseQueryTests/BaseQueryTests.cs +++ b/CloudFabric.EAV.Tests/BaseQueryTests/BaseQueryTests.cs @@ -24,6 +24,7 @@ public abstract class BaseQueryTests { protected EAVEntityInstanceService _eavEntityInstanceService; protected EAVCategoryService _eavCategoryService; + protected EntitySerialCounterService _counterService; protected IEventStore _eventStore; protected IStore _store; @@ -129,6 +130,11 @@ public async Task SetUp() await ProjectionsRebuildProcessor.RebuildProjectionsThatRequireRebuild(); + _counterService = new EntitySerialCounterService( + new StoreRepository(_store), + eiMapper) + ; + _eavEntityInstanceService = new EAVEntityInstanceService( _eiLogger, eiMapper, @@ -139,7 +145,8 @@ public async Task SetUp() }, aggregateRepositoryFactory, projectionRepositoryFactory, - new EventUserInfo(Guid.NewGuid()) + new EventUserInfo(Guid.NewGuid()), + _counterService ); _eavCategoryService = new EAVCategoryService( @@ -152,7 +159,8 @@ public async Task SetUp() }, aggregateRepositoryFactory, projectionRepositoryFactory, - new EventUserInfo(Guid.NewGuid()) + new EventUserInfo(Guid.NewGuid()), + _counterService ); } } diff --git a/CloudFabric.EAV.Tests/Tests.cs b/CloudFabric.EAV.Tests/Tests.cs index b5417c3..bc3d9be 100644 --- a/CloudFabric.EAV.Tests/Tests.cs +++ b/CloudFabric.EAV.Tests/Tests.cs @@ -28,6 +28,7 @@ using FluentAssertions; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -43,6 +44,8 @@ public class Tests private EAVEntityInstanceService _eavEntityInstanceService; + private EntitySerialCounterService _entitySerialCounterService; + private IEventStore _eventStore; private IStore _store; private ILogger _eiLogger; @@ -131,6 +134,8 @@ public async Task SetUp() await projectionsEngine.StartAsync("TestInstance"); + _entitySerialCounterService = new EntitySerialCounterService(new StoreRepository(_store), _mapper); + _eavEntityInstanceService = new EAVEntityInstanceService( _eiLogger, _mapper, @@ -141,7 +146,8 @@ public async Task SetUp() }, _aggregateRepositoryFactory, _projectionRepositoryFactory, - new EventUserInfo(Guid.NewGuid()) + new EventUserInfo(Guid.NewGuid()), + _entitySerialCounterService ); } @@ -172,7 +178,6 @@ public async Task Cleanup() } } - [TestMethod] public async Task CreateInstance_Success() { @@ -1999,7 +2004,15 @@ public async Task CreateEntityInstanceWithSerialAttributes_Success() (EntityConfigurationViewModel? entityConfig, _) = await _eavEntityInstanceService.CreateEntityConfiguration(entityConfigurationCreateRequest, CancellationToken.None); - (EntityInstanceViewModel result, ProblemDetails _) = await _eavEntityInstanceService.CreateEntityInstance( + // Check counter for attribute was initialized + var counter = await _entitySerialCounterService.LoadCounter( + entityConfig.Id, entityConfig.Attributes.First().AttributeConfigurationId + ); + counter.NextValue.Should().Be(serialAttributeCreateRequest.StartingNumber); + counter.LastIncrement.Should().BeNull(); + + // Create entity instance and check it and counter + (EntityInstanceViewModel? result, ProblemDetails _) = await _eavEntityInstanceService.CreateEntityInstance( new EntityInstanceCreateRequest { EntityConfigurationId = entityConfig.Id, @@ -2012,33 +2025,17 @@ public async Task CreateEntityInstanceWithSerialAttributes_Success() } } ); - result.Attributes.FirstOrDefault().ConfigurationAttributeMachineName.Should() - .Be(serialAttributeCreateRequest.MachineName); - - // check attribute override value in entity configuration - EntityConfiguration entity = await entityRepository.LoadAsyncOrThrowNotFound(entityConfig.Id, - entityConfig.PartitionKey, - CancellationToken.None - ); - var attributeExternalValue = JsonSerializer.Deserialize( - entity.Attributes.FirstOrDefault().AttributeConfigurationExternalValues.FirstOrDefault().ToString() - ); - - attributeExternalValue.Should() - .Be(serialAttributeCreateRequest - .As().StartingNumber - ); - - // check entity instance - EntityInstance entityInstance = - await entityInstanceRepository.LoadAsyncOrThrowNotFound(result.Id, result.PartitionKey); + var serialAttributeInstance = result.Attributes + .FirstOrDefault(x => x.ConfigurationAttributeMachineName == serialAttributeCreateRequest.MachineName) + .As(); + serialAttributeInstance.Should().NotBeNull(); + serialAttributeInstance.Value.Should().Be(counter.NextValue); - entityInstance.Attributes.FirstOrDefault(x => - x.ConfigurationAttributeMachineName == serialAttributeCreateRequest.MachineName - ) - .As().Value.Should().Be(serialAttributeCreateRequest.StartingNumber); + var counterAfterFirstSerial = + await _entitySerialCounterService.LoadCounter(entityConfig.Id, entityConfig.Attributes.First().AttributeConfigurationId); + counterAfterFirstSerial!.NextValue.Should().Be(serialAttributeInstance.Value + serialAttributeCreateRequest.Increment); - // create another entity instance + // Create another entity instance and check it and counter (result, _) = await _eavEntityInstanceService.CreateEntityInstance(new EntityInstanceCreateRequest { EntityConfigurationId = entityConfig.Id, @@ -2051,30 +2048,15 @@ public async Task CreateEntityInstanceWithSerialAttributes_Success() } } ); + serialAttributeInstance = result.Attributes + .FirstOrDefault(x => x.ConfigurationAttributeMachineName == serialAttributeCreateRequest.MachineName) + .As(); + serialAttributeInstance.Should().NotBeNull(); + serialAttributeInstance.Value.Should().Be(counterAfterFirstSerial.NextValue); - // check that override value in entity configuration was updated - entity = await entityRepository.LoadAsyncOrThrowNotFound(entityConfig.Id, - entityConfig.PartitionKey, - CancellationToken.None - ); - attributeExternalValue = JsonSerializer.Deserialize( - entity.Attributes.FirstOrDefault().AttributeConfigurationExternalValues.FirstOrDefault().ToString() - ); - - attributeExternalValue.Should() - .Be(serialAttributeCreateRequest - .As().StartingNumber - + serialAttributeCreateRequest.Increment - ); - - //check another entity instance - entityInstance = await entityInstanceRepository.LoadAsyncOrThrowNotFound(result.Id, result.PartitionKey); - entityInstance.Attributes.FirstOrDefault(x => - x.ConfigurationAttributeMachineName == serialAttributeCreateRequest.MachineName - ) - .As().Value.Should().Be(serialAttributeCreateRequest.StartingNumber - + serialAttributeCreateRequest.Increment - ); + var counterAfterSecondSerial = + await _entitySerialCounterService.LoadCounter(entityConfig.Id, entityConfig.Attributes.First().AttributeConfigurationId); + counterAfterSecondSerial!.NextValue.Should().Be(serialAttributeInstance.Value + serialAttributeCreateRequest.Increment); } [TestMethod] @@ -2133,6 +2115,57 @@ await _eavEntityInstanceService.AddAttributeToEntityConfiguration( .BeTrue(); } + [TestMethod] + public async Task AddSerialAttributeToEntityConfiguration_CounterInitialized() + { + var cultureInfoId = CultureInfo.GetCultureInfo("en-US").LCID; + + var configCreateRequest = new EntityConfigurationCreateRequest + { + MachineName = "test", + Name = new List + { + new() { CultureInfoId = cultureInfoId, String = "test" } + }, + Attributes = new List() + }; + + (EntityConfigurationViewModel? createdEntityConfiguration, _) = + await _eavEntityInstanceService.CreateEntityConfiguration(configCreateRequest, CancellationToken.None); + + var serialAttributeCreateRequest = new SerialAttributeConfigurationCreateRequest + { + MachineName = "serialAttr", + Description = + new List + { + new() { CultureInfoId = cultureInfoId, String = "SerialAttributeDescription" } + }, + Name = new List + { + new() { CultureInfoId = cultureInfoId, String = "serialAttributeName" } + }, + IsRequired = true, + StartingNumber = 100, + Increment = 555 + }; + + (AttributeConfigurationViewModel? createdAttribute, _) = + await _eavEntityInstanceService.CreateAttribute(serialAttributeCreateRequest, CancellationToken.None); + + await _eavEntityInstanceService.AddAttributeToEntityConfiguration( + createdAttribute.Id, + createdEntityConfiguration.Id, + CancellationToken.None + ); + + // Check counter was initialized + var counter = await _entitySerialCounterService.LoadCounter(createdEntityConfiguration.Id, createdAttribute.Id); + counter.Should().NotBeNull(); + counter.NextValue.Should().Be(serialAttributeCreateRequest.StartingNumber); + counter.LastIncrement.Should().BeNull(); + } + [TestMethod] public async Task AddAttributeToEntityConfiguration_MachineNamesAreNotUnique() { @@ -2507,7 +2540,7 @@ public async Task UpdateInstance_RemoveAttribute_FailValidation() } [TestMethod] - public async Task UpdateInstanceSerialAttributeExternalValue_Success() + public async Task UpdateInstanceSerialAttribute_Success() { // create entity configuration with serial attribute var cultureInfoId = CultureInfo.GetCultureInfo("en-US").LCID; @@ -2538,25 +2571,26 @@ public async Task UpdateInstanceSerialAttributeExternalValue_Success() Attributes = new List { serialAttributeCreateRequest } }; - (EntityConfigurationViewModel? created, _) = + (EntityConfigurationViewModel? createdEntityConfiguration, _) = await _eavEntityInstanceService.CreateEntityConfiguration(entityConfigurationCreateRequest, CancellationToken.None); // create entity instance for further update var entityInstanceCreateRequest = new EntityInstanceCreateRequest { - EntityConfigurationId = created.Id, - TenantId = created.TenantId, + EntityConfigurationId = createdEntityConfiguration.Id, + TenantId = createdEntityConfiguration.TenantId, Attributes = new List { new SerialAttributeInstanceCreateUpdateRequest { ConfigurationAttributeMachineName = serialAttributeCreateRequest.MachineName, ValueType = serialAttributeCreateRequest.ValueType, + // this value should be ignored Value = -10 } } }; - (EntityInstanceViewModel createdItem, _) = await _eavEntityInstanceService.CreateEntityInstance(entityInstanceCreateRequest); + (EntityInstanceViewModel createdItem, var smth) = await _eavEntityInstanceService.CreateEntityInstance(entityInstanceCreateRequest); // update entity instance var updateSerialInstanceRequest = new SerialAttributeInstanceCreateUpdateRequest @@ -2580,10 +2614,80 @@ public async Task UpdateInstanceSerialAttributeExternalValue_Success() updatedinstance.Attributes.FirstOrDefault().As().Value .Should().Be(updateSerialInstanceRequest.Value); + + var counter = await _entitySerialCounterService.LoadCounter( + createdEntityConfiguration.Id, + createdEntityConfiguration.Attributes.First().AttributeConfigurationId + ); + + counter.NextValue.Should().Be(updateSerialInstanceRequest.Value + serialAttributeCreateRequest.Increment); + } + + [TestMethod] + public async Task CreateEntityInstance_EnsureCounterOneForAll() + { + // create entity configuration with serial attribute + var cultureInfoId = CultureInfo.GetCultureInfo("en-US").LCID; + var serialAttributeCreateRequest = new SerialAttributeConfigurationCreateRequest + { + MachineName = "serialAttr", + Description = + new List + { + new() { CultureInfoId = cultureInfoId, String = "SerialAttributeDescription" } + }, + Name = new List + { + new() { CultureInfoId = cultureInfoId, String = "serialAttributeName" } + }, + IsRequired = true, + StartingNumber = 10, + Increment = 1 + }; + + var entityConfigurationCreateRequest = new EntityConfigurationCreateRequest + { + MachineName = $"test", + Name = new List + { + new() { CultureInfoId = cultureInfoId, String = "test" } + }, + Attributes = new List { serialAttributeCreateRequest } + }; + + (EntityConfigurationViewModel? createdEntityConfiguration, _) = + await _eavEntityInstanceService.CreateEntityConfiguration(entityConfigurationCreateRequest, CancellationToken.None); + + int instanceCount = 50; + + for (int i = 0; i < instanceCount; i++) + { + var entityInstanceCreateRequest = new EntityInstanceCreateRequest + { + EntityConfigurationId = createdEntityConfiguration.Id, + TenantId = createdEntityConfiguration.TenantId, + Attributes = new List + { + new SerialAttributeInstanceCreateUpdateRequest + { + ConfigurationAttributeMachineName = serialAttributeCreateRequest.MachineName, + ValueType = serialAttributeCreateRequest.ValueType + } + } + }; + await _eavEntityInstanceService.CreateEntityInstance(entityInstanceCreateRequest); + } + + var counter = await _entitySerialCounterService.LoadCounter( + createdEntityConfiguration.Id, + createdEntityConfiguration.Attributes.First().AttributeConfigurationId + ); + + counter.NextValue.Should().Be(serialAttributeCreateRequest.StartingNumber + instanceCount); } [TestMethod] - public async Task UpdateInstanceSerialExternalValue_WrongValue() + public async Task UpdateInstanceSerialAttribute_WrongValue() { // create entity configuration with serial attribute var cultureInfoId = CultureInfo.GetCultureInfo("en-US").LCID; From 1273d283fc91b80a8a03134c02e52d7c25668841 Mon Sep 17 00:00:00 2001 From: Dmitriy Melnychenko Date: Thu, 31 Aug 2023 18:14:20 +0300 Subject: [PATCH 2/6] Fixed DB ItemTable naming --- CloudFabric.EAV.Tests/CategoryTests/CategoryTestsPostgresql.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CloudFabric.EAV.Tests/CategoryTests/CategoryTestsPostgresql.cs b/CloudFabric.EAV.Tests/CategoryTests/CategoryTestsPostgresql.cs index 17283c1..cba3a0a 100644 --- a/CloudFabric.EAV.Tests/CategoryTests/CategoryTestsPostgresql.cs +++ b/CloudFabric.EAV.Tests/CategoryTests/CategoryTestsPostgresql.cs @@ -25,7 +25,7 @@ public CategoryTestsPostgresql() _eventStore = new PostgresqlEventStore( connectionString, "eav_tests_event_store", - "eav_tests_items_store" + "eav_tests_item_store" ); _projectionRepositoryFactory = new PostgresqlProjectionRepositoryFactory(new LoggerFactory(), connectionString); From be81367c675e4081e4850ab792eb30d034243596 Mon Sep 17 00:00:00 2001 From: Dmitriy Melnychenko Date: Thu, 31 Aug 2023 18:15:05 +0300 Subject: [PATCH 3/6] Fix DB ItemTable naming --- CloudFabric.EAV.Tests/CategoryTests/CategoryTestsPostgresql.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CloudFabric.EAV.Tests/CategoryTests/CategoryTestsPostgresql.cs b/CloudFabric.EAV.Tests/CategoryTests/CategoryTestsPostgresql.cs index cba3a0a..00175c7 100644 --- a/CloudFabric.EAV.Tests/CategoryTests/CategoryTestsPostgresql.cs +++ b/CloudFabric.EAV.Tests/CategoryTests/CategoryTestsPostgresql.cs @@ -29,7 +29,7 @@ public CategoryTestsPostgresql() ); _projectionRepositoryFactory = new PostgresqlProjectionRepositoryFactory(new LoggerFactory(), connectionString); - _store = new PostgresqlStore(connectionString, "eav_tests_item_store"); + _store = new PostgresqlStore(connectionString, "eav_test_item_store"); using var loggerFactory = new LoggerFactory(); _logger = loggerFactory.CreateLogger(); From a2face8886b71c940816994a02105bc5e2b47f5b Mon Sep 17 00:00:00 2001 From: Dmitriy Melnychenko Date: Sun, 3 Sep 2023 21:34:06 +0300 Subject: [PATCH 4/6] Changed mistakes in naming and code style --- CloudFabric.EAV.Domain/Counter.cs | 16 ++++++++-------- .../EAVEntityInstanceService.cs | 12 ++++++------ CloudFabric.EAV.Service/EAVService.cs | 12 ++++++------ .../EntitySerialCounterService.cs | 14 +++++++------- .../BaseQueryTests/BaseQueryTests.cs | 5 ++--- 5 files changed, 29 insertions(+), 30 deletions(-) diff --git a/CloudFabric.EAV.Domain/Counter.cs b/CloudFabric.EAV.Domain/Counter.cs index 9412b3c..5f83850 100644 --- a/CloudFabric.EAV.Domain/Counter.cs +++ b/CloudFabric.EAV.Domain/Counter.cs @@ -11,11 +11,11 @@ public int? LastIncrement init { _lastIncrement = value; } } - private DateTime _timeStamp; - public DateTime TimeStamp + private DateTime _timestamp; + public DateTime Timestamp { - get { return _timeStamp; } - init { _timeStamp = value; } + get { return _timestamp; } + init { _timestamp = value; } } public Guid AttributeConfidurationId { get; init; } @@ -25,17 +25,17 @@ public Counter() { } - public Counter(long nextValue, DateTime timeStamp, Guid attributeConfigurationId, int? lastIncrement = null) + public Counter(long nextValue, DateTime timestamp, Guid attributeConfigurationId, int? lastIncrement = null) { NextValue = nextValue; - TimeStamp = timeStamp; + Timestamp = timestamp; AttributeConfidurationId = attributeConfigurationId; LastIncrement = lastIncrement; } - public void SetTimeStamp(DateTime timeStamp) + public void SetTimestamp(DateTime timestamp) { - _timeStamp = timeStamp; + _timestamp = timestamp; } public long Step(int increment) diff --git a/CloudFabric.EAV.Service/EAVEntityInstanceService.cs b/CloudFabric.EAV.Service/EAVEntityInstanceService.cs index bfc2284..bb6bcae 100644 --- a/CloudFabric.EAV.Service/EAVEntityInstanceService.cs +++ b/CloudFabric.EAV.Service/EAVEntityInstanceService.cs @@ -447,26 +447,26 @@ await GetAttributeConfigurationsForEntityConfiguration( if (!dryRun) { // Save counters - var counterResponse = new List(); + var counterResponse = new List(); foreach (var counter in entityCounters.Where(x => x != null)) { counterResponse.Add(await _counterService.SaveCounter(entityConfiguration.Id, counter.AttributeConfidurationId, counter)); } - foreach (var responce in counterResponse.Where(x => x.Status == CounterActionStatus.Conflict)) + foreach (var response in counterResponse.Where(x => x.Status == CounterActionStatus.Conflict)) { do { - var counter = await _counterService.LoadCounter(responce.EntityConfigurationId, responce.AttributeConfigurationId); + var counter = await _counterService.LoadCounter(response.EntityConfigurationId, response.AttributeConfigurationId); counter!.Step(counter.LastIncrement!.Value); - var repeatedCounterSaveResponce = await _counterService.SaveCounter(entityConfiguration.Id, counter.AttributeConfidurationId, counter); + var repeatedCounterSaveResponse = await _counterService.SaveCounter(entityConfiguration.Id, counter.AttributeConfidurationId, counter); - responce.Status = repeatedCounterSaveResponce.Status; + response.Status = repeatedCounterSaveResponse.Status; - } while (responce.Status != CounterActionStatus.Saved); + } while (response.Status != CounterActionStatus.Saved); } // Save instance diff --git a/CloudFabric.EAV.Service/EAVService.cs b/CloudFabric.EAV.Service/EAVService.cs index 632581c..5ff76a5 100644 --- a/CloudFabric.EAV.Service/EAVService.cs +++ b/CloudFabric.EAV.Service/EAVService.cs @@ -1145,26 +1145,26 @@ await GetAttributeConfigurationsForEntityConfiguration( if (!dryRun) { // Save counters - var counterResponse = new List(); + var counterResponse = new List(); foreach (var counter in entityCounters.Where(x => x != null)) { counterResponse.Add(await _counterService.SaveCounter(entityConfiguration.Id, counter.AttributeConfidurationId, counter)); } - foreach (var responce in counterResponse.Where(x => x.Status == CounterActionStatus.Conflict)) + foreach (var response in counterResponse.Where(x => x.Status == CounterActionStatus.Conflict)) { do { - var counter = await _counterService.LoadCounter(responce.EntityConfigurationId, responce.AttributeConfigurationId); + var counter = await _counterService.LoadCounter(response.EntityConfigurationId, response.AttributeConfigurationId); counter!.Step(counter.LastIncrement!.Value); - var repeatedCounterSaveResponce = await _counterService.SaveCounter(entityConfiguration.Id, counter.AttributeConfidurationId, counter); + var repeatedCounterSaveResponse = await _counterService.SaveCounter(entityConfiguration.Id, counter.AttributeConfidurationId, counter); - responce.Status = repeatedCounterSaveResponce.Status; + response.Status = repeatedCounterSaveResponse.Status; - } while (responce.Status != CounterActionStatus.Saved); + } while (response.Status != CounterActionStatus.Saved); } var entityInstanceSaved = await _entityInstanceRepository diff --git a/CloudFabric.EAV.Service/EntitySerialCounterService.cs b/CloudFabric.EAV.Service/EntitySerialCounterService.cs index 262354d..0ddcc46 100644 --- a/CloudFabric.EAV.Service/EntitySerialCounterService.cs +++ b/CloudFabric.EAV.Service/EntitySerialCounterService.cs @@ -9,9 +9,9 @@ namespace CloudFabric.EAV.Service; -public class CounterActionResponce +public class CounterActionResponse { - public CounterActionResponce(Guid entityConfiguration, Guid attributeConfigurationId) + public CounterActionResponse(Guid entityConfiguration, Guid attributeConfigurationId) { EntityConfigurationId = entityConfiguration; AttributeConfigurationId = attributeConfigurationId; @@ -186,11 +186,11 @@ await _storeRepository.UpsertItem( /// /// /// - /// Counter action responce with status to indicate result. + /// Counter action response with status to indicate result. /// - public async Task SaveCounter(Guid entityConfigurationId, Guid attributeConfigurationId, Counter updatedCounter) + public async Task SaveCounter(Guid entityConfigurationId, Guid attributeConfigurationId, Counter updatedCounter) { - CounterActionResponce response = new(entityConfigurationId, attributeConfigurationId); + CounterActionResponse response = new(entityConfigurationId, attributeConfigurationId); Counter? counter = await LoadCounter(entityConfigurationId, attributeConfigurationId); @@ -199,14 +199,14 @@ public async Task SaveCounter(Guid entityConfigurationId, throw new ArgumentNullException("Failed to save counter - make sure it was initialized"); } - if (updatedCounter.TimeStamp != counter.TimeStamp) + if (updatedCounter.Timestamp != counter.Timestamp) { response.Status = CounterActionStatus.Conflict; return response; } - counter.SetTimeStamp(DateTime.UtcNow); + counter.SetTimestamp(DateTime.UtcNow); await _storeRepository.UpsertItem( string.Concat(entityConfigurationId, attributeConfigurationId), diff --git a/CloudFabric.EAV.Tests/BaseQueryTests/BaseQueryTests.cs b/CloudFabric.EAV.Tests/BaseQueryTests/BaseQueryTests.cs index f6e7265..bfe3b93 100644 --- a/CloudFabric.EAV.Tests/BaseQueryTests/BaseQueryTests.cs +++ b/CloudFabric.EAV.Tests/BaseQueryTests/BaseQueryTests.cs @@ -3,7 +3,6 @@ using AutoMapper; -using CloudFabric.EAV.Domain.Models; using CloudFabric.EAV.Domain.Projections.AttributeConfigurationProjection; using CloudFabric.EAV.Domain.Projections.EntityConfigurationProjection; using CloudFabric.EAV.Domain.Projections.EntityInstanceProjection; @@ -132,8 +131,8 @@ public async Task SetUp() _counterService = new EntitySerialCounterService( new StoreRepository(_store), - eiMapper) - ; + eiMapper + ); _eavEntityInstanceService = new EAVEntityInstanceService( _eiLogger, From 938cd8068c7f0a7ba54d3cc6c7d8f41484480b37 Mon Sep 17 00:00:00 2001 From: Dmitriy Melnychenko Date: Thu, 7 Sep 2023 15:08:16 +0300 Subject: [PATCH 5/6] Replaced generating values (such as counter) to another abstraction layer. Made additional service to inreact with generated values --- .../CloudFabric.EAV.Domain.csproj | 2 +- .../{ => GeneratingValues}/Counter.cs | 6 +- .../GeneratingValues/IGeneratedValueInfo.cs | 8 + CloudFabric.EAV.Enums/CounterActionStatus.cs | 7 - .../GeneratedValueActionStatus.cs | 10 + .../GeneratedValueActionResponse.cs | 20 ++ .../ValueAttributeServiceOptions.cs | 6 + .../Abstractions/IGeneratedValueService.cs | 14 + CloudFabric.EAV.Service/EAVCategoryService.cs | 4 +- .../EAVEntityInstanceService.cs | 38 +-- CloudFabric.EAV.Service/EAVService.cs | 195 +----------- .../EntitySerialCounterService.cs | 221 ------------- .../SerialCounterService.cs | 105 +++++++ .../ValueAttributeService.cs | 291 ++++++++++++++++++ .../BaseQueryTests/BaseQueryTests.cs | 10 +- CloudFabric.EAV.Tests/Tests.cs | 18 +- 16 files changed, 504 insertions(+), 451 deletions(-) rename CloudFabric.EAV.Domain/{ => GeneratingValues}/Counter.cs (87%) create mode 100644 CloudFabric.EAV.Domain/GeneratingValues/IGeneratedValueInfo.cs delete mode 100644 CloudFabric.EAV.Enums/CounterActionStatus.cs create mode 100644 CloudFabric.EAV.Enums/GeneratedValueActionStatus.cs create mode 100644 CloudFabric.EAV.Models/GeneratedValueActionResponse.cs create mode 100644 CloudFabric.EAV.Options/ValueAttributeServiceOptions.cs create mode 100644 CloudFabric.EAV.Service/Abstractions/IGeneratedValueService.cs delete mode 100644 CloudFabric.EAV.Service/EntitySerialCounterService.cs create mode 100644 CloudFabric.EAV.Service/SerialCounterService.cs create mode 100644 CloudFabric.EAV.Service/ValueAttributeService.cs diff --git a/CloudFabric.EAV.Domain/CloudFabric.EAV.Domain.csproj b/CloudFabric.EAV.Domain/CloudFabric.EAV.Domain.csproj index 58c7d33..edc884f 100644 --- a/CloudFabric.EAV.Domain/CloudFabric.EAV.Domain.csproj +++ b/CloudFabric.EAV.Domain/CloudFabric.EAV.Domain.csproj @@ -1,4 +1,4 @@ - + net7.0 diff --git a/CloudFabric.EAV.Domain/Counter.cs b/CloudFabric.EAV.Domain/GeneratingValues/Counter.cs similarity index 87% rename from CloudFabric.EAV.Domain/Counter.cs rename to CloudFabric.EAV.Domain/GeneratingValues/Counter.cs index 5f83850..370bfc8 100644 --- a/CloudFabric.EAV.Domain/Counter.cs +++ b/CloudFabric.EAV.Domain/GeneratingValues/Counter.cs @@ -1,6 +1,6 @@ -namespace CloudFabric.EAV.Domain; +namespace CloudFabric.EAV.Domain.GeneratedValues; -public class Counter +public class Counter : IGeneratedValueInfo { public long NextValue { get; set; } @@ -41,6 +41,6 @@ public void SetTimestamp(DateTime timestamp) public long Step(int increment) { _lastIncrement = increment; - return this.NextValue += increment; + return NextValue += increment; } } diff --git a/CloudFabric.EAV.Domain/GeneratingValues/IGeneratedValueInfo.cs b/CloudFabric.EAV.Domain/GeneratingValues/IGeneratedValueInfo.cs new file mode 100644 index 0000000..6b7e433 --- /dev/null +++ b/CloudFabric.EAV.Domain/GeneratingValues/IGeneratedValueInfo.cs @@ -0,0 +1,8 @@ +namespace CloudFabric.EAV.Domain.GeneratedValues; + +public interface IGeneratedValueInfo +{ + public Guid AttributeConfidurationId { get; init; } + + public DateTime Timestamp { get; init; } +} diff --git a/CloudFabric.EAV.Enums/CounterActionStatus.cs b/CloudFabric.EAV.Enums/CounterActionStatus.cs deleted file mode 100644 index 1d6e94f..0000000 --- a/CloudFabric.EAV.Enums/CounterActionStatus.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace CloudFabric.EAV.Enums; - -public enum CounterActionStatus -{ - Saved, - Conflict -} diff --git a/CloudFabric.EAV.Enums/GeneratedValueActionStatus.cs b/CloudFabric.EAV.Enums/GeneratedValueActionStatus.cs new file mode 100644 index 0000000..2520b02 --- /dev/null +++ b/CloudFabric.EAV.Enums/GeneratedValueActionStatus.cs @@ -0,0 +1,10 @@ +namespace CloudFabric.EAV.Enums; + +public enum GeneratedValueActionStatus +{ + + NoAction, + Saved, + Conflict, + Failed +} diff --git a/CloudFabric.EAV.Models/GeneratedValueActionResponse.cs b/CloudFabric.EAV.Models/GeneratedValueActionResponse.cs new file mode 100644 index 0000000..056ab87 --- /dev/null +++ b/CloudFabric.EAV.Models/GeneratedValueActionResponse.cs @@ -0,0 +1,20 @@ +using CloudFabric.EAV.Enums; + +namespace CloudFabric.EAV.Models; + +public class GeneratedValueActionResponse +{ + public GeneratedValueActionResponse(Guid entityConfiguration, Guid attributeConfigurationId) + { + EntityConfigurationId = entityConfiguration; + AttributeConfigurationId = attributeConfigurationId; + } + + public Type? GeneratedValueType { get; set; } + + public GeneratedValueActionStatus Status { get; set; } + + public Guid EntityConfigurationId { get; } + + public Guid AttributeConfigurationId { get; } +} diff --git a/CloudFabric.EAV.Options/ValueAttributeServiceOptions.cs b/CloudFabric.EAV.Options/ValueAttributeServiceOptions.cs new file mode 100644 index 0000000..59750e6 --- /dev/null +++ b/CloudFabric.EAV.Options/ValueAttributeServiceOptions.cs @@ -0,0 +1,6 @@ +namespace CloudFabric.EAV.Options; + +public class ValueAttributeServiceOptions +{ + public int ActionMaxCountAttempts { get; set; } = 4; +} diff --git a/CloudFabric.EAV.Service/Abstractions/IGeneratedValueService.cs b/CloudFabric.EAV.Service/Abstractions/IGeneratedValueService.cs new file mode 100644 index 0000000..626dc6e --- /dev/null +++ b/CloudFabric.EAV.Service/Abstractions/IGeneratedValueService.cs @@ -0,0 +1,14 @@ +using CloudFabric.EAV.Domain.GeneratedValues; +using CloudFabric.EAV.Models; +using CloudFabric.EAV.Models.ViewModels.Attributes; + +namespace CloudFabric.EAV.Service.Abstractions; + +public interface IGeneratedValueService where T : IGeneratedValueInfo +{ + Task Create(Guid entityConfigurationId, AttributeConfigurationViewModel attributeConfiguration); + + Task Load(Guid entityConfigurationId, Guid attributeConfigurationId); + + Task Save(Guid entityConfigurationId, Guid attributeConfigurationId, T generatedValueInfo); +} diff --git a/CloudFabric.EAV.Service/EAVCategoryService.cs b/CloudFabric.EAV.Service/EAVCategoryService.cs index 63a9776..4185c6e 100644 --- a/CloudFabric.EAV.Service/EAVCategoryService.cs +++ b/CloudFabric.EAV.Service/EAVCategoryService.cs @@ -34,7 +34,7 @@ public EAVCategoryService(ILogger? elasticSearchQueryOptions = null) : base(logger, new CategoryFromDictionaryDeserializer(mapper), mapper, @@ -42,7 +42,7 @@ public EAVCategoryService(ILogger(); - List entityCounters = new(); + List generatedValues = new(); + foreach (AttributeConfiguration a in attributeConfigurations) { AttributeInstance? attributeValue = entityInstance.Attributes @@ -434,9 +435,9 @@ await GetAttributeConfigurationsForEntityConfiguration( validationErrors.Add(a.MachineName, attrValidationErrors.ToArray()); } - var counter = await InitializeAttributeInstanceWithCounterValue(entityConfiguration, a, attributeValue); + var valueInfo = await _valueAttributeService.GenerateAttributeInstanceValue(entityConfiguration, a, attributeValue); - entityCounters.Add(counter); + generatedValues.Add(await _valueAttributeService.GenerateAttributeInstanceValue(entityConfiguration, a, attributeValue)); } if (validationErrors.Count > 0) @@ -446,30 +447,17 @@ await GetAttributeConfigurationsForEntityConfiguration( if (!dryRun) { - // Save counters - var counterResponse = new List(); - - foreach (var counter in entityCounters.Where(x => x != null)) - { - counterResponse.Add(await _counterService.SaveCounter(entityConfiguration.Id, counter.AttributeConfidurationId, counter)); - } + var response = await _valueAttributeService.SaveValues(entityConfiguration.Id, generatedValues); - foreach (var response in counterResponse.Where(x => x.Status == CounterActionStatus.Conflict)) + foreach (var actionResponse in response.Where(x => x.Status == GeneratedValueActionStatus.Failed)) { - do - { - var counter = await _counterService.LoadCounter(response.EntityConfigurationId, response.AttributeConfigurationId); - - counter!.Step(counter.LastIncrement!.Value); - - var repeatedCounterSaveResponse = await _counterService.SaveCounter(entityConfiguration.Id, counter.AttributeConfidurationId, counter); - - response.Status = repeatedCounterSaveResponse.Status; + var attributeMachineName = attributeConfigurations.First(x => x.Id == actionResponse.AttributeConfigurationId).MachineName; - } while (response.Status != CounterActionStatus.Saved); + validationErrors.Add( + attributeMachineName, + new string[] { $"Failed to generate value: {actionResponse.GeneratedValueType?.Name}" }); } - // Save instance ProjectionDocumentSchema schema = ProjectionDocumentSchemaFactory .FromEntityConfiguration(entityConfiguration, attributeConfigurations); diff --git a/CloudFabric.EAV.Service/EAVService.cs b/CloudFabric.EAV.Service/EAVService.cs index 5ff76a5..976cfac 100644 --- a/CloudFabric.EAV.Service/EAVService.cs +++ b/CloudFabric.EAV.Service/EAVService.cs @@ -5,7 +5,7 @@ using AutoMapper; -using CloudFabric.EAV.Domain; +using CloudFabric.EAV.Domain.GeneratedValues; using CloudFabric.EAV.Domain.Models; using CloudFabric.EAV.Domain.Models.Attributes; using CloudFabric.EAV.Domain.Models.Base; @@ -61,7 +61,7 @@ internal readonly EntityInstanceCreateUpdateRequestFromJsonDeserializer internal readonly AggregateRepository _categoryTreeRepository; internal readonly AggregateRepository _categoryInstanceRepository; - internal readonly EntitySerialCounterService _counterService; + internal readonly ValueAttributeService _valueAttributeService; protected EAVService( ILogger> logger, @@ -71,7 +71,7 @@ protected EAVService( AggregateRepositoryFactory aggregateRepositoryFactory, ProjectionRepositoryFactory projectionRepositoryFactory, EventUserInfo userInfo, - EntitySerialCounterService counterService) + ValueAttributeService valueAttributeService) { _logger = logger; _mapper = mapper; @@ -107,7 +107,7 @@ protected EAVService( .GetAggregateRepository(); _categoryTreeRepository = aggregateRepositoryFactory .GetAggregateRepository(); - _counterService = counterService; + _valueAttributeService = valueAttributeService; } private void EnsureAttributeMachineNameIsAdded(AttributeConfigurationCreateUpdateRequest attributeRequest) @@ -509,7 +509,7 @@ await CreateAttribute( new ValidationErrorResponse(entityConfiguration.MachineName, entityValidationErrors.ToArray())); } - await _counterService.InitializeEntityConfigurationCounters(entityConfiguration.Id, allCreatedAttributes); + await _valueAttributeService.InitializeEntityConfigurationGeneratedValues(entityConfiguration.Id, allCreatedAttributes); await _entityConfigurationRepository.SaveAsync( _userInfo, @@ -601,7 +601,7 @@ await _entityConfigurationRepository.SaveAsync( reservedAttributes.Add(attributeConfiguration.Id); - await _counterService.InitializeEntityConfigurationCounterIfSerialAttribute(entityConfiguration.Id, attributeConfiguration); + await _valueAttributeService.InitializeGeneratedValue(entityConfiguration.Id, attributeConfiguration); } } else if (attributeUpdate is AttributeConfigurationCreateUpdateRequest attributeCreateRequest) @@ -636,7 +636,7 @@ await CreateAttribute( entityConfiguration.AddAttribute(attributeCreated.Id); reservedAttributes.Add(attributeCreated.Id); - await _counterService.InitializeEntityConfigurationCounterIfSerialAttribute(entityConfiguration.Id, attributeCreated); + await _valueAttributeService.InitializeGeneratedValue(entityConfiguration.Id, attributeCreated); } } } @@ -739,7 +739,7 @@ private async Task EnsureProjectionIndexForEntityConfiguration(EntityConfigurati await _entityConfigurationRepository.SaveAsync(_userInfo, entityConfiguration, cancellationToken) .ConfigureAwait(false); - await _counterService.InitializeEntityConfigurationCounterIfSerialAttribute(entityConfigurationId, attributeConfiguration); + await _valueAttributeService.InitializeGeneratedValue(entityConfigurationId, attributeConfiguration); return (_mapper.Map(entityConfiguration), null); } @@ -786,7 +786,7 @@ await _entityConfigurationRepository.SaveAsync(_userInfo, entityConfiguration, c await _entityConfigurationRepository.SaveAsync(_userInfo, entityConfiguration, cancellationToken); - await _counterService.InitializeEntityConfigurationCounterIfSerialAttribute(entityConfigurationId, createdAttribute); + await _valueAttributeService.InitializeGeneratedValue(entityConfigurationId, createdAttribute); return (createdAttribute, null)!; } @@ -1084,7 +1084,7 @@ await GetAttributeConfigurationsForEntityConfiguration( } // Add or update attributes - List entityCounters = new(); + List generatedValues = new(); foreach (AttributeInstanceCreateUpdateRequest? newAttributeRequest in updateRequest.AttributesToAddOrUpdate) { @@ -1108,23 +1108,24 @@ await GetAttributeConfigurationsForEntityConfiguration( { if (!newAttribute.Equals(currentAttribute)) { - (Counter? counter, List? counterErrors) = await UpdateCounterValueDuringInstanceUpdate(entityConfiguration, attrConfig, newAttribute); + (IGeneratedValueInfo? valueInfo, List? valueErrors) = + await _valueAttributeService.UpdateGeneratedValueDuringInstanceUpdate(entityConfiguration, attrConfig, newAttribute); - if (counterErrors == null) + if (valueErrors == null) { entityInstance.UpdateAttributeInstance(newAttribute); - entityCounters.Add(counter); + generatedValues.Add(valueInfo); } else { - validationErrors.Add(newAttribute.ConfigurationAttributeMachineName, counterErrors.ToArray()); + validationErrors.Add(newAttribute.ConfigurationAttributeMachineName, valueErrors.ToArray()); } } } else { - entityCounters.Add(await InitializeAttributeInstanceWithCounterValue(entityConfiguration, attrConfig, newAttribute)); + generatedValues.Add(await _valueAttributeService.GenerateAttributeInstanceValue(entityConfiguration, attrConfig, newAttribute)); entityInstance.AddAttributeInstance( _mapper.Map(newAttributeRequest) @@ -1144,28 +1145,7 @@ await GetAttributeConfigurationsForEntityConfiguration( if (!dryRun) { - // Save counters - var counterResponse = new List(); - - foreach (var counter in entityCounters.Where(x => x != null)) - { - counterResponse.Add(await _counterService.SaveCounter(entityConfiguration.Id, counter.AttributeConfidurationId, counter)); - } - - foreach (var response in counterResponse.Where(x => x.Status == CounterActionStatus.Conflict)) - { - do - { - var counter = await _counterService.LoadCounter(response.EntityConfigurationId, response.AttributeConfigurationId); - - counter!.Step(counter.LastIncrement!.Value); - - var repeatedCounterSaveResponse = await _counterService.SaveCounter(entityConfiguration.Id, counter.AttributeConfidurationId, counter); - - response.Status = repeatedCounterSaveResponse.Status; - - } while (response.Status != CounterActionStatus.Saved); - } + await _valueAttributeService.SaveValues(entityConfiguration.Id, generatedValues); var entityInstanceSaved = await _entityInstanceRepository .SaveAsync(_userInfo, entityInstance, cancellationToken) @@ -1364,145 +1344,4 @@ await _attributeConfigurationRepository.LoadAsyncOrThrowNotFound( } #endregion - - /// - /// Initialize attribute instance value with counter value if serial attribute. - /// - /// - /// - /// - /// Current counter with changed values if attribute value was initialized, otherwise null. - /// - /// - internal async Task InitializeAttributeInstanceWithCounterValue( - EntityConfiguration entityConfiguration, - AttributeConfiguration attributeConfiguration, - AttributeInstance? attributeInstance - ) - { - switch (attributeConfiguration.ValueType) - { - case EavAttributeType.Serial: - { - if (attributeInstance == null) - { - return null; - } - - EntityConfigurationAttributeReference? entityAttribute = entityConfiguration.Attributes - .FirstOrDefault(x => x.AttributeConfigurationId == attributeConfiguration.Id); - - if (entityAttribute == null) - { - return null; - } - - var serialAttributeConfiguration = attributeConfiguration as SerialAttributeConfiguration; - - var serialInstance = attributeInstance as SerialAttributeInstance; - - if (serialAttributeConfiguration == null || serialInstance == null) - { - return null; - } - - var counter = await _counterService.LoadCounter(entityConfiguration.Id, attributeConfiguration.Id); - - if (counter == null) - { - counter = await _counterService.InitializeEntityConfigurationSerialAttributeCounter(entityConfiguration.Id, serialAttributeConfiguration); - } - - serialInstance.Value = counter!.NextValue; - - counter.Step(serialAttributeConfiguration.Increment); - - return counter; - } - } - - return null; - } - - /// - /// Update entity configuration counter. - /// - /// - /// Specialized method to update entity configuration counter value with updating arrtibute instance value - - /// this means new instance value is not out of sequence logic, and can be overwritten to it. - /// Intended use: update next avaliable counter value with a new attribute instance value. - /// - /// - /// - /// - /// - /// List of validation errors or counter with changed values if everithing is okay. - /// - private async Task<(Counter?, List?)> UpdateCounterValueDuringInstanceUpdate( - EntityConfiguration entityConfiguration, - AttributeConfiguration attributeConfiguration, - AttributeInstance? attributeInstance - ) - { - switch (attributeConfiguration.ValueType) - { - case EavAttributeType.Serial: - { - if (attributeInstance == null) - { - return (null, null); - } - - var validationErrors = new List(); - - EntityConfigurationAttributeReference? entityAttribute = entityConfiguration.Attributes - .FirstOrDefault(x => x.AttributeConfigurationId == attributeConfiguration.Id); - - if (entityAttribute == null) - { - validationErrors.Add("Attribute configuration is not found"); - return (null, validationErrors); - } - - var serialAttributeConfiguration = attributeConfiguration as SerialAttributeConfiguration; - - var serialInstance = attributeInstance as SerialAttributeInstance; - - if (serialAttributeConfiguration == null || serialInstance == null) - { - validationErrors.Add("Invalid attribute type."); - } - - if (serialInstance != null && !serialInstance.Value.HasValue) - { - validationErrors.Add("Updating serial number value can not be empty."); - } - - if (validationErrors.Count > 0) - { - return (null, validationErrors); - } - - var counter = await _counterService.LoadCounter(entityConfiguration.Id, serialAttributeConfiguration.Id); - - if (counter == null) - { - validationErrors.Add("Counter is not found."); - } - - if (serialInstance!.Value <= counter!.NextValue) - { - // TO DO: Add validation and possibility to update serial value if value less than existing one - validationErrors.Add("Serial number value can not be less or equal than the already existing one."); - return (null, validationErrors); - } - - counter.NextValue = serialInstance.Value.Value + serialAttributeConfiguration.Increment; - - return (counter, null); - } - } - return (null, null); - } - } diff --git a/CloudFabric.EAV.Service/EntitySerialCounterService.cs b/CloudFabric.EAV.Service/EntitySerialCounterService.cs deleted file mode 100644 index 0ddcc46..0000000 --- a/CloudFabric.EAV.Service/EntitySerialCounterService.cs +++ /dev/null @@ -1,221 +0,0 @@ -using AutoMapper; - -using CloudFabric.EAV.Domain; -using CloudFabric.EAV.Domain.Models; -using CloudFabric.EAV.Domain.Models.Attributes; -using CloudFabric.EAV.Enums; -using CloudFabric.EAV.Models.ViewModels.Attributes; -using CloudFabric.EventSourcing.Domain; - -namespace CloudFabric.EAV.Service; - -public class CounterActionResponse -{ - public CounterActionResponse(Guid entityConfiguration, Guid attributeConfigurationId) - { - EntityConfigurationId = entityConfiguration; - AttributeConfigurationId = attributeConfigurationId; - } - - public CounterActionStatus Status { get; set; } - - public Guid EntityConfigurationId { get; } - - public Guid AttributeConfigurationId { get; } -} - -public class EntitySerialCounterService -{ - private readonly IStoreRepository _storeRepository; - private readonly IMapper _mapper; - - public EntitySerialCounterService(IStoreRepository storeRepository, IMapper mapper) - { - _storeRepository = storeRepository; - _mapper = mapper; - } - - #region Initializers - - /// - /// Initalize counters for all serial attribute confgirurations within entity. - /// Already initialized counters will not be overriten or changed. - /// - /// - /// - public async Task InitializeEntityConfigurationCounters(Guid entityConfigurationId, List attributesConfigurations) - { - var serialAttributeConfigurations = - attributesConfigurations - .Where(x => x.ValueType == EavAttributeType.Serial) - .ToList(); - - foreach (var attribute in serialAttributeConfigurations) - { - var counter = await InitializeEntityConfigurationCounterIfSerialAttribute(entityConfigurationId, attribute); - } - } - - /// - /// Initalize counter for attribute configuration within entity configuration if attribute is serial. - /// Already initialized counter will not be overriten or changed. - /// - /// - /// - /// Initialized counter if it has not been initialized previously and attribute is serial attribute, otherwise null. - public async Task InitializeEntityConfigurationCounterIfSerialAttribute(Guid entityConfigurationId, AttributeConfiguration attributeConfiguration) - { - var serialConfigurationViewModel = _mapper.Map(attributeConfiguration); - - return await InitializeEntityConfigurationCounterIfSerialAttribute(entityConfigurationId, serialConfigurationViewModel); - } - - /// - /// Initalize counter for attribute configuration within entity configuration if attribute is serial. - /// Already initialized counter will not be overriten or changed. - /// - /// - /// - /// Initialized counter if it has not been initialized previously and attribute is serial attribute, otherwise null. - public async Task InitializeEntityConfigurationCounterIfSerialAttribute(Guid entityConfigurationId, AttributeConfigurationViewModel attributeConfiguration) - { - var serialAttributeConfiguration = attributeConfiguration as SerialAttributeConfigurationViewModel; - - if (serialAttributeConfiguration == null) - { - return null; - } - - return await InitializeEntityConfigurationSerialAttributeCounter(entityConfigurationId, serialAttributeConfiguration); - } - - /// - /// Initalize counter for serial attribute configuration withing entity configuration. - /// Already initialized counter will not be overriten or changed. - /// - /// - /// - /// Initialized counter if it has not been initialized previously, otherwise null. - public async Task InitializeEntityConfigurationSerialAttributeCounter(Guid entityConfigurationId, SerialAttributeConfiguration serialAttributeConfiguration) - { - var serialAttributeConfigurationViewModel = _mapper.Map(serialAttributeConfiguration); - - return await InitializeEntityConfigurationSerialAttributeCounter(entityConfigurationId, serialAttributeConfigurationViewModel); - - } - - /// - /// Initalize counter for serial attribute configuration withing entity configuration. - /// Already initialized counter will not be overriten or changed. - /// - /// - /// - /// Initialized counter if it has not been initialized previously, otherwise null. - public async Task InitializeEntityConfigurationSerialAttributeCounter(Guid entityConfigurationId, SerialAttributeConfigurationViewModel serialAttributeConfiguration) - { - var existingCounter = await LoadCounter(entityConfigurationId, serialAttributeConfiguration.Id); - - if (existingCounter != null) - { - return null; - } - - var counter = new Counter(serialAttributeConfiguration.StartingNumber, DateTime.UtcNow, serialAttributeConfiguration.Id); - - await _storeRepository.UpsertItem( - string.Concat(entityConfigurationId, serialAttributeConfiguration.Id), - string.Concat(entityConfigurationId, serialAttributeConfiguration.Id), - counter - ); - - return counter; - } - - #endregion - - /// - /// Load counter for specified attribute configuration within entity configuration. - /// - /// - /// - /// Counter or null if not exists. - public async Task LoadCounter(Guid entityConfigurationId, Guid attributeId) - { - return await _storeRepository.LoadItem( - string.Concat(entityConfigurationId, attributeId), - string.Concat(entityConfigurationId, attributeId) - ); - } - - /// - /// Update and validate counter, related to specified attribute configuration within entity configuration. - /// - /// - /// - /// - /// Flag that idnicates existance of counter and errors, that are null if everything is okay. - public async Task<(bool isExists, List? errors)> UpdateCounterIfExists(Guid entityConfigurationId, Guid attributeId, Counter newCounter) - { - var counter = await LoadCounter(entityConfigurationId, attributeId); - - if (counter == null) - { - return (false, null); - } - - List errors = new(); - - if (newCounter.NextValue < counter.NextValue) - { - errors.Add("Wrong counter sequence"); - return (true, errors); - } - - await _storeRepository.UpsertItem( - string.Concat(entityConfigurationId, attributeId), - string.Concat(entityConfigurationId, attributeId), - counter - ); - - return (true, null); - } - - /// - /// Save counter. - /// - /// - /// - /// - /// Counter action response with status to indicate result. - /// - public async Task SaveCounter(Guid entityConfigurationId, Guid attributeConfigurationId, Counter updatedCounter) - { - CounterActionResponse response = new(entityConfigurationId, attributeConfigurationId); - - Counter? counter = await LoadCounter(entityConfigurationId, attributeConfigurationId); - - if (counter == null) - { - throw new ArgumentNullException("Failed to save counter - make sure it was initialized"); - } - - if (updatedCounter.Timestamp != counter.Timestamp) - { - response.Status = CounterActionStatus.Conflict; - - return response; - } - - counter.SetTimestamp(DateTime.UtcNow); - - await _storeRepository.UpsertItem( - string.Concat(entityConfigurationId, attributeConfigurationId), - string.Concat(entityConfigurationId, attributeConfigurationId), - updatedCounter - ); - - response.Status = CounterActionStatus.Saved; - - return response; - } -} diff --git a/CloudFabric.EAV.Service/SerialCounterService.cs b/CloudFabric.EAV.Service/SerialCounterService.cs new file mode 100644 index 0000000..1eae8e4 --- /dev/null +++ b/CloudFabric.EAV.Service/SerialCounterService.cs @@ -0,0 +1,105 @@ +using CloudFabric.EAV.Domain.GeneratedValues; +using CloudFabric.EAV.Enums; +using CloudFabric.EAV.Models; +using CloudFabric.EAV.Models.ViewModels.Attributes; +using CloudFabric.EAV.Service.Abstractions; +using CloudFabric.EventSourcing.Domain; + +namespace CloudFabric.EAV.Service; + +public class SerialCounterService : IGeneratedValueService +{ + private readonly IStoreRepository _storeRepository; + + public SerialCounterService(IStoreRepository storeRepository) + { + _storeRepository = storeRepository; + } + + /// + /// Create counter for serial attribute configuration withing entity configuration. + /// + /// + /// + /// New counter if it has not been created previously, otherwise null. + public async Task Create(Guid entityConfigurationId, AttributeConfigurationViewModel attributeConfiguration) + { + if (attributeConfiguration is not SerialAttributeConfigurationViewModel serialAttributeConfiguration) + { + return null; + } + + var existingCounter = await Load(entityConfigurationId, serialAttributeConfiguration.Id); + + if (existingCounter != null) + { + return null; + } + + var counter = new Counter(serialAttributeConfiguration.StartingNumber, DateTime.UtcNow, serialAttributeConfiguration.Id); + + await _storeRepository.UpsertItem( + string.Concat(entityConfigurationId, serialAttributeConfiguration.Id), + string.Concat(entityConfigurationId, serialAttributeConfiguration.Id), + counter + ); + + return counter; + } + + /// + /// Load counter for specified attribute configuration within entity configuration. + /// + /// + /// + /// Counter or null if not exists. + public async Task Load(Guid entityConfigurationId, Guid attributeConfigurationId) + { + return await _storeRepository.LoadItem( + string.Concat(entityConfigurationId, attributeConfigurationId), + string.Concat(entityConfigurationId, attributeConfigurationId) + ); + } + + /// + /// Save counter. + /// + /// + /// + /// + /// Action response with status to indicate result. + /// + public async Task Save(Guid entityConfigurationId, Guid attributeConfigurationId, Counter updatedCounter) + { + GeneratedValueActionResponse response = new(entityConfigurationId, attributeConfigurationId) + { + GeneratedValueType = typeof(Counter) + }; + + Counter? counter = await Load(entityConfigurationId, attributeConfigurationId); + + if (counter == null) + { + throw new ArgumentNullException("Failed to save counter - make sure it was initialized"); + } + + if (updatedCounter.Timestamp != counter.Timestamp) + { + response.Status = GeneratedValueActionStatus.Conflict; + + return response; + } + + counter.SetTimestamp(DateTime.UtcNow); + + await _storeRepository.UpsertItem( + string.Concat(entityConfigurationId, attributeConfigurationId), + string.Concat(entityConfigurationId, attributeConfigurationId), + updatedCounter + ); + + response.Status = GeneratedValueActionStatus.Saved; + + return response; + } +} diff --git a/CloudFabric.EAV.Service/ValueAttributeService.cs b/CloudFabric.EAV.Service/ValueAttributeService.cs new file mode 100644 index 0000000..94a818f --- /dev/null +++ b/CloudFabric.EAV.Service/ValueAttributeService.cs @@ -0,0 +1,291 @@ +using AutoMapper; + +using CloudFabric.EAV.Domain.GeneratedValues; +using CloudFabric.EAV.Domain.Models; +using CloudFabric.EAV.Domain.Models.Attributes; +using CloudFabric.EAV.Enums; +using CloudFabric.EAV.Models; +using CloudFabric.EAV.Models.ViewModels.Attributes; +using CloudFabric.EAV.Options; + +using Microsoft.Extensions.Options; + +namespace CloudFabric.EAV.Service; + +public class ValueAttributeService +{ + private readonly SerialCounterService _entitySerialCounterService; + private readonly IMapper _mapper; + private readonly ValueAttributeServiceOptions _options; + + public ValueAttributeService( + SerialCounterService entitySerialCounterService, + IMapper mapper, + IOptions? options = null + ) + { + _entitySerialCounterService = entitySerialCounterService; + _mapper = mapper; + _options = options?.Value ?? new ValueAttributeServiceOptions(); + } + + /// + /// Initalize generating values for all attribute confgirurations within entity based on attribute type. + /// Already initialized values will not be overriten or changed. + /// + /// + /// + internal async Task InitializeEntityConfigurationGeneratedValues(Guid entityConfigurationId, List attributesConfigurations) + { + foreach (var attribute in attributesConfigurations) + { + await InitializeGeneratedValue(entityConfigurationId, attribute); + } + } + + /// + /// Initalize generating value for attribute configuration within entity configuration based on attribute type. + /// Already initialized values will not be overriten or changed. + /// + /// + /// + internal async Task InitializeGeneratedValue(Guid entityConfigurationId, AttributeConfiguration attributeConfiguration) + => await InitializeGeneratedValue(entityConfigurationId, _mapper.Map(attributeConfiguration)); + + /// + /// Initalize generating value for attribute configuration within entity configuration based on attribute type. + /// Already initialized values will not be overriten or changed. + /// + /// + /// + internal async Task InitializeGeneratedValue(Guid entityConfigurationId, AttributeConfigurationViewModel attributeConfiguration) + { + switch (attributeConfiguration.ValueType) + { + case (EavAttributeType.Serial): + { + var serialAttributeConfiguration = attributeConfiguration as SerialAttributeConfigurationViewModel; + + if (serialAttributeConfiguration == null) + { + break; + } + + await _entitySerialCounterService.Create(entityConfigurationId, serialAttributeConfiguration); + + break; + } + } + } + + /// + /// Initialize attribute instance value with entity generated value. + /// + /// + /// Make sure to use this service .Save* method to stock generated value with changed state. + /// + /// + /// + /// + /// Generated value with changed values if attribute value was generated with it, otherwise null. + /// + internal async Task GenerateAttributeInstanceValue( + EntityConfiguration entityConfiguration, + AttributeConfiguration attributeConfiguration, + AttributeInstance? attributeInstance + ) + { + switch (attributeConfiguration.ValueType) + { + case EavAttributeType.Serial: + { + if (attributeInstance == null) + { + return null; + } + + EntityConfigurationAttributeReference? entityAttribute = entityConfiguration.Attributes + .FirstOrDefault(x => x.AttributeConfigurationId == attributeConfiguration.Id); + + if (entityAttribute == null) + { + return null; + } + + var serialAttributeConfiguration = attributeConfiguration as SerialAttributeConfiguration; + + var serialInstance = attributeInstance as SerialAttributeInstance; + + if (serialAttributeConfiguration == null || serialInstance == null) + { + return null; + } + + var counter = await _entitySerialCounterService.Load(entityConfiguration.Id, attributeConfiguration.Id); + + if (counter == null) + { + counter = await _entitySerialCounterService.Create( + entityConfiguration.Id, + _mapper.Map(attributeConfiguration) + ); + } + + serialInstance.Value = counter!.NextValue; + + counter.Step(serialAttributeConfiguration.Increment); + + return counter; + } + } + + return null; + } + + /// + /// Update entity configuration generated value. + /// + /// + /// Specialized method to update entity configuration generated values with updating arrtibute instance value - + /// this means new instance value is not out of the generate value logic, and can be overwritten to it. + /// Make sure to use this service .Save* method to stock generated value with changed state. + /// + /// + /// + /// + /// + /// List of validation errors or counter with changed values if everithing is okay. + /// + internal async Task<(IGeneratedValueInfo?, List?)> UpdateGeneratedValueDuringInstanceUpdate( + EntityConfiguration entityConfiguration, + AttributeConfiguration attributeConfiguration, + AttributeInstance? attributeInstance + ) + { + switch (attributeConfiguration.ValueType) + { + case EavAttributeType.Serial: + { + if (attributeInstance == null) + { + return (null, null); + } + + var validationErrors = new List(); + + EntityConfigurationAttributeReference? entityAttribute = entityConfiguration.Attributes + .FirstOrDefault(x => x.AttributeConfigurationId == attributeConfiguration.Id); + + if (entityAttribute == null) + { + validationErrors.Add("Attribute configuration is not found"); + return (null, validationErrors); + } + + var serialAttributeConfiguration = attributeConfiguration as SerialAttributeConfiguration; + + var serialInstance = attributeInstance as SerialAttributeInstance; + + if (serialAttributeConfiguration == null || serialInstance == null) + { + validationErrors.Add("Invalid attribute type."); + } + + if (serialInstance != null && !serialInstance.Value.HasValue) + { + validationErrors.Add("Updating serial number value can not be empty."); + } + + if (validationErrors.Count > 0) + { + return (null, validationErrors); + } + + var counter = await _entitySerialCounterService.Load(entityConfiguration.Id, attributeConfiguration.Id); + + if (counter == null) + { + validationErrors.Add("Counter is not found."); + } + + if (serialInstance!.Value <= counter!.NextValue) + { + // TO DO: Add validation and possibility to update serial value if value less than existing one + validationErrors.Add("Serial number value can not be less or equal than the already existing one."); + return (null, validationErrors); + } + + counter.NextValue = serialInstance.Value.Value + serialAttributeConfiguration.Increment; + + return (counter, null); + } + } + return (null, null); + } + + /// + /// Save all generated values with changed state within entity. + /// + /// + /// + /// Action responses list with statuses to indicate result. + public async Task> SaveValues(Guid entityConfigurationId, List info) + { + List valueActionReponses = new(); + + foreach (var item in info.Where(x => x != null)) + { + var response = await SaveValue(entityConfigurationId, item!); + valueActionReponses.Add(response); + } + + return valueActionReponses; + } + + /// + /// Save all generated value with changed state within entity. + /// + /// + /// + /// Action response with status to indicate result. + internal async Task SaveValue(Guid entityConfigurationId, IGeneratedValueInfo info) + { + if (info is Counter counter) + { + var response = await _entitySerialCounterService.Save(entityConfigurationId, info.AttributeConfidurationId, counter); + + int loopCounter = 0; + + if (response.Status == GeneratedValueActionStatus.Conflict) + { + do + { + if (loopCounter == _options.ActionMaxCountAttempts) + { + response.Status = GeneratedValueActionStatus.Failed; + + break; + } + + var actualStateCounter = await _entitySerialCounterService.Load(response.EntityConfigurationId, response.AttributeConfigurationId); + + actualStateCounter!.Step(actualStateCounter.LastIncrement!.Value); + + var repeatedCounterSaveResponse = await _entitySerialCounterService.Save(entityConfigurationId, actualStateCounter.AttributeConfidurationId, actualStateCounter); + + response.Status = repeatedCounterSaveResponse.Status; + + loopCounter++; + + } while (response.Status != GeneratedValueActionStatus.Saved); + } + + return response; + } + + return new GeneratedValueActionResponse(entityConfigurationId, info.AttributeConfidurationId) + { + Status = GeneratedValueActionStatus.NoAction + }; + } +} diff --git a/CloudFabric.EAV.Tests/BaseQueryTests/BaseQueryTests.cs b/CloudFabric.EAV.Tests/BaseQueryTests/BaseQueryTests.cs index bfe3b93..326bb69 100644 --- a/CloudFabric.EAV.Tests/BaseQueryTests/BaseQueryTests.cs +++ b/CloudFabric.EAV.Tests/BaseQueryTests/BaseQueryTests.cs @@ -23,7 +23,7 @@ public abstract class BaseQueryTests { protected EAVEntityInstanceService _eavEntityInstanceService; protected EAVCategoryService _eavCategoryService; - protected EntitySerialCounterService _counterService; + protected ValueAttributeService _valueAttributeService; protected IEventStore _eventStore; protected IStore _store; @@ -129,8 +129,8 @@ public async Task SetUp() await ProjectionsRebuildProcessor.RebuildProjectionsThatRequireRebuild(); - _counterService = new EntitySerialCounterService( - new StoreRepository(_store), + _valueAttributeService = new ValueAttributeService( + new SerialCounterService(new StoreRepository(_store)), eiMapper ); @@ -145,7 +145,7 @@ public async Task SetUp() aggregateRepositoryFactory, projectionRepositoryFactory, new EventUserInfo(Guid.NewGuid()), - _counterService + _valueAttributeService ); _eavCategoryService = new EAVCategoryService( @@ -159,7 +159,7 @@ public async Task SetUp() aggregateRepositoryFactory, projectionRepositoryFactory, new EventUserInfo(Guid.NewGuid()), - _counterService + _valueAttributeService ); } } diff --git a/CloudFabric.EAV.Tests/Tests.cs b/CloudFabric.EAV.Tests/Tests.cs index bc3d9be..fdd4a44 100644 --- a/CloudFabric.EAV.Tests/Tests.cs +++ b/CloudFabric.EAV.Tests/Tests.cs @@ -44,7 +44,7 @@ public class Tests private EAVEntityInstanceService _eavEntityInstanceService; - private EntitySerialCounterService _entitySerialCounterService; + private SerialCounterService _entitySerialCounterService; private IEventStore _eventStore; private IStore _store; @@ -134,7 +134,7 @@ public async Task SetUp() await projectionsEngine.StartAsync("TestInstance"); - _entitySerialCounterService = new EntitySerialCounterService(new StoreRepository(_store), _mapper); + _entitySerialCounterService = new SerialCounterService(new StoreRepository(_store)); _eavEntityInstanceService = new EAVEntityInstanceService( _eiLogger, @@ -147,7 +147,7 @@ public async Task SetUp() _aggregateRepositoryFactory, _projectionRepositoryFactory, new EventUserInfo(Guid.NewGuid()), - _entitySerialCounterService + new ValueAttributeService(_entitySerialCounterService, _mapper) ); } @@ -2005,7 +2005,7 @@ public async Task CreateEntityInstanceWithSerialAttributes_Success() await _eavEntityInstanceService.CreateEntityConfiguration(entityConfigurationCreateRequest, CancellationToken.None); // Check counter for attribute was initialized - var counter = await _entitySerialCounterService.LoadCounter( + var counter = await _entitySerialCounterService.Load( entityConfig.Id, entityConfig.Attributes.First().AttributeConfigurationId ); counter.NextValue.Should().Be(serialAttributeCreateRequest.StartingNumber); @@ -2032,7 +2032,7 @@ public async Task CreateEntityInstanceWithSerialAttributes_Success() serialAttributeInstance.Value.Should().Be(counter.NextValue); var counterAfterFirstSerial = - await _entitySerialCounterService.LoadCounter(entityConfig.Id, entityConfig.Attributes.First().AttributeConfigurationId); + await _entitySerialCounterService.Load(entityConfig.Id, entityConfig.Attributes.First().AttributeConfigurationId); counterAfterFirstSerial!.NextValue.Should().Be(serialAttributeInstance.Value + serialAttributeCreateRequest.Increment); // Create another entity instance and check it and counter @@ -2055,7 +2055,7 @@ public async Task CreateEntityInstanceWithSerialAttributes_Success() serialAttributeInstance.Value.Should().Be(counterAfterFirstSerial.NextValue); var counterAfterSecondSerial = - await _entitySerialCounterService.LoadCounter(entityConfig.Id, entityConfig.Attributes.First().AttributeConfigurationId); + await _entitySerialCounterService.Load(entityConfig.Id, entityConfig.Attributes.First().AttributeConfigurationId); counterAfterSecondSerial!.NextValue.Should().Be(serialAttributeInstance.Value + serialAttributeCreateRequest.Increment); } @@ -2160,7 +2160,7 @@ await _eavEntityInstanceService.AddAttributeToEntityConfiguration( ); // Check counter was initialized - var counter = await _entitySerialCounterService.LoadCounter(createdEntityConfiguration.Id, createdAttribute.Id); + var counter = await _entitySerialCounterService.Load(createdEntityConfiguration.Id, createdAttribute.Id); counter.Should().NotBeNull(); counter.NextValue.Should().Be(serialAttributeCreateRequest.StartingNumber); counter.LastIncrement.Should().BeNull(); @@ -2615,7 +2615,7 @@ public async Task UpdateInstanceSerialAttribute_Success() updatedinstance.Attributes.FirstOrDefault().As().Value .Should().Be(updateSerialInstanceRequest.Value); - var counter = await _entitySerialCounterService.LoadCounter( + var counter = await _entitySerialCounterService.Load( createdEntityConfiguration.Id, createdEntityConfiguration.Attributes.First().AttributeConfigurationId ); @@ -2678,7 +2678,7 @@ public async Task CreateEntityInstance_EnsureCounterOneForAll() await _eavEntityInstanceService.CreateEntityInstance(entityInstanceCreateRequest); } - var counter = await _entitySerialCounterService.LoadCounter( + var counter = await _entitySerialCounterService.Load( createdEntityConfiguration.Id, createdEntityConfiguration.Attributes.First().AttributeConfigurationId ); From 8cbdf83f7affb8ef2a7eea8d0245c2b51da15e26 Mon Sep 17 00:00:00 2001 From: Dmitriy Melnychenko Date: Fri, 8 Sep 2023 11:30:00 +0300 Subject: [PATCH 6/6] Fixed bug (duplicate code), made SerializeEntityInstanceToJson* in EAVService public --- .../RequestModels/CategoryInstanceCreateRequest.cs | 1 + CloudFabric.EAV.Service/EAVEntityInstanceService.cs | 2 -- CloudFabric.EAV.Service/EAVService.cs | 4 ++-- .../Serialization/EntityInstanceViewModelToJsonSerializer.cs | 3 +-- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/CloudFabric.EAV.Models/RequestModels/CategoryInstanceCreateRequest.cs b/CloudFabric.EAV.Models/RequestModels/CategoryInstanceCreateRequest.cs index 86f1f55..a1ec5ad 100644 --- a/CloudFabric.EAV.Models/RequestModels/CategoryInstanceCreateRequest.cs +++ b/CloudFabric.EAV.Models/RequestModels/CategoryInstanceCreateRequest.cs @@ -7,6 +7,7 @@ public class CategoryInstanceCreateRequest public Guid CategoryTreeId { get; set; } public string MachineName { get; set; } + public List Attributes { get; set; } public Guid? ParentId { get; set; } diff --git a/CloudFabric.EAV.Service/EAVEntityInstanceService.cs b/CloudFabric.EAV.Service/EAVEntityInstanceService.cs index f4eb9f4..e1ecd58 100644 --- a/CloudFabric.EAV.Service/EAVEntityInstanceService.cs +++ b/CloudFabric.EAV.Service/EAVEntityInstanceService.cs @@ -435,8 +435,6 @@ await GetAttributeConfigurationsForEntityConfiguration( validationErrors.Add(a.MachineName, attrValidationErrors.ToArray()); } - var valueInfo = await _valueAttributeService.GenerateAttributeInstanceValue(entityConfiguration, a, attributeValue); - generatedValues.Add(await _valueAttributeService.GenerateAttributeInstanceValue(entityConfiguration, a, attributeValue)); } diff --git a/CloudFabric.EAV.Service/EAVService.cs b/CloudFabric.EAV.Service/EAVService.cs index 976cfac..392a541 100644 --- a/CloudFabric.EAV.Service/EAVService.cs +++ b/CloudFabric.EAV.Service/EAVService.cs @@ -996,7 +996,7 @@ public async Task GetEntityInstanceJsonSingleLanguage( return SerializeEntityInstanceToJsonSingleLanguage(entityInstanceViewModel, language, fallbackLanguage); } - protected JsonDocument SerializeEntityInstanceToJsonMultiLanguage(TViewModel? entityInstanceViewModel) + public JsonDocument SerializeEntityInstanceToJsonMultiLanguage(TViewModel? entityInstanceViewModel) { var serializerOptions = new JsonSerializerOptions(_jsonSerializerOptions); serializerOptions.Converters.Add(new LocalizedStringMultiLanguageSerializer()); @@ -1005,7 +1005,7 @@ protected JsonDocument SerializeEntityInstanceToJsonMultiLanguage(TViewModel? en return JsonSerializer.SerializeToDocument(entityInstanceViewModel, serializerOptions); } - private JsonDocument SerializeEntityInstanceToJsonSingleLanguage( + public JsonDocument SerializeEntityInstanceToJsonSingleLanguage( TViewModel? entityInstanceViewModel, string language, string fallbackLanguage = "en-US" ) { diff --git a/CloudFabric.EAV.Service/Serialization/EntityInstanceViewModelToJsonSerializer.cs b/CloudFabric.EAV.Service/Serialization/EntityInstanceViewModelToJsonSerializer.cs index fd5f444..a14f888 100644 --- a/CloudFabric.EAV.Service/Serialization/EntityInstanceViewModelToJsonSerializer.cs +++ b/CloudFabric.EAV.Service/Serialization/EntityInstanceViewModelToJsonSerializer.cs @@ -1,12 +1,11 @@ using System.Text.Json; using System.Text.Json.Serialization; -using CloudFabric.EAV.Domain.Models; using CloudFabric.EAV.Models.ViewModels; namespace CloudFabric.EAV.Service.Serialization; -public class EntityInstanceViewModelToJsonSerializer: JsonConverter +public class EntityInstanceViewModelToJsonSerializer : JsonConverter { public override EntityInstanceViewModel? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {