diff --git a/.github/workflows/liquid-ci-cd-adapter-dataverse.yml b/.github/workflows/liquid-ci-cd-dataverse.yml similarity index 73% rename from .github/workflows/liquid-ci-cd-adapter-dataverse.yml rename to .github/workflows/liquid-ci-cd-dataverse.yml index f4a234e7..11d65912 100644 --- a/.github/workflows/liquid-ci-cd-adapter-dataverse.yml +++ b/.github/workflows/liquid-ci-cd-dataverse.yml @@ -1,17 +1,17 @@ # CI & CD workflow -name: CI/CD - Liquid.Adapter.Dataverse component for Liquid Application Framework +name: CI/CD - Liquid.Dataverse component for Liquid Application Framework on: push: branches: [ main, releases/v2.X.X, releases/v6.X.X ] paths: - - 'src/Liquid.Adapter.Dataverse/**' + - 'src/Liquid.Dataverse/**' pull_request: branches: [ main, releases/** ] types: [opened, synchronize, reopened] paths: - - 'src/Liquid.Adapter.Dataverse/**' + - 'src/Liquid.Dataverse/**' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -20,7 +20,7 @@ jobs: call-reusable-build-workflow: uses: Avanade/Liquid-Application-Framework/.github/workflows/base-liquid-ci-and-cd.yml@main with: - component_name: Liquid.Adapter.Dataverse + component_name: Liquid.Dataverse secrets: sonar_token: ${{ secrets.SONAR_TOKEN_DATAVERSE }} nuget_token: ${{ secrets.PUBLISH_TO_NUGET_ORG }} diff --git a/Liquid.Application.Framework.sln b/Liquid.Application.Framework.sln index 7ee44681..32d4701e 100644 --- a/Liquid.Application.Framework.sln +++ b/Liquid.Application.Framework.sln @@ -63,9 +63,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.WebApi.Http.Tests", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Storage.AzureStorage", "src\Liquid.Storage.AzureStorage\Liquid.Storage.AzureStorage.csproj", "{F599C512-7224-4A27-A474-3AA9510D2A14}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Adapter.Dataverse", "src\Liquid.Adapter.Dataverse\Liquid.Adapter.Dataverse.csproj", "{CD31C6F6-F8DD-4B56-B08C-D9E8D5BA3E73}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Dataverse", "src\Liquid.Dataverse\Liquid.Dataverse.csproj", "{CD31C6F6-F8DD-4B56-B08C-D9E8D5BA3E73}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Adapter.Dataverse.Tests", "test\Liquid.Adapter.Dataverse.Tests\Liquid.Adapter.Dataverse.Tests.csproj", "{5B0DC38B-5BC9-4DAC-8527-8D1C33E97247}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Dataverse.Tests", "test\Liquid.Dataverse.Tests\Liquid.Dataverse.Tests.csproj", "{5B0DC38B-5BC9-4DAC-8527-8D1C33E97247}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Storage.AzureStorage.Tests", "test\Liquid.Storage.AzureStorage.Tests\Liquid.Storage.AzureStorage.Tests.csproj", "{53341B04-6D30-4137-943B-20D8706351E8}" EndProject diff --git a/src/Liquid.Adapter.Dataverse/DataMappingException.cs b/src/Liquid.Adapter.Dataverse/DataMappingException.cs deleted file mode 100644 index dd735104..00000000 --- a/src/Liquid.Adapter.Dataverse/DataMappingException.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Runtime.Serialization; - -namespace Liquid.Adapter.Dataverse -{ - [Serializable] - [ExcludeFromCodeCoverage] - internal class DataMappingException : Exception - { - public DataMappingException() - { - } - - public DataMappingException(string? message) : base(message) - { - } - - public DataMappingException(string? message, Exception? innerException) : base(message, innerException) - { - } - - protected DataMappingException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } - } -} \ No newline at end of file diff --git a/src/Liquid.Adapter.Dataverse/DataverseAdapter.cs b/src/Liquid.Adapter.Dataverse/DataverseAdapter.cs deleted file mode 100644 index bf7137a1..00000000 --- a/src/Liquid.Adapter.Dataverse/DataverseAdapter.cs +++ /dev/null @@ -1,182 +0,0 @@ -using Microsoft.Crm.Sdk.Messages; -using Microsoft.PowerPlatform.Dataverse.Client; -using Microsoft.PowerPlatform.Dataverse.Client.Extensions; -using Microsoft.Xrm.Sdk; -using Microsoft.Xrm.Sdk.Messages; -using Microsoft.Xrm.Sdk.Metadata; -using Microsoft.Xrm.Sdk.Query; - -namespace Liquid.Adapter.Dataverse -{ - /// - public class DataverseAdapter : ILiquidDataverseAdapter - { - private readonly IDataverseClientFactory _serviceFactory; - private readonly IOrganizationServiceAsync _client; - - /// - /// Initialize a new instance of - /// - /// - /// - public DataverseAdapter(IDataverseClientFactory serviceFactory) - { - _serviceFactory = serviceFactory ?? throw new ArgumentNullException(nameof(serviceFactory)); - _client = _serviceFactory.GetClient(); - } - - public async Task GetById(Guid id, string entityName, ColumnSet? columns = null) - { - if (columns == null) - columns = new ColumnSet(true); - - var result = await _client.RetrieveAsync(entityName, id, columns); - - return result; - } - - /// - public async Task> ListByFilter(string entityName, FilterExpression? filter = null, ColumnSet? columns = null) - { - List results = new List(); - - QueryExpression queryData = new QueryExpression(entityName); - - if (filter != null) - queryData.Criteria = filter; - - if (columns != null) - queryData.ColumnSet = columns; - - var result = await _client.RetrieveMultipleAsync(queryData); - if (result?.Entities != null) - { - foreach (var item in result.Entities) - { - results.Add(item); - } - } - - return results; - } - - /// - public async Task> ListByFilter(string entityName, QueryExpression query) - { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } - - List results = new List(); - - var result = await _client.RetrieveMultipleAsync(query); - - if (result?.Entities != null) - { - foreach (var item in result.Entities) - { - results.Add(item); - } - } - - return results; - } - - /// - public async Task GetMetadata(string entityName) - { - var retrieveEntityRequest = new RetrieveEntityRequest - { - EntityFilters = EntityFilters.All, - LogicalName = entityName - }; - var response = await _client.ExecuteAsync(retrieveEntityRequest); - var metadata = (RetrieveEntityResponse)response; - return metadata.EntityMetadata; - } - - /// - public async Task SetState(EntityReference entity, string state, string status) - { - var setStateRequest = new SetStateRequest() - { - EntityMoniker = new EntityReference - { - Id = entity.Id, - LogicalName = entity.LogicalName, - }, - State = new OptionSetValue(int.Parse(state)), - Status = new OptionSetValue(int.Parse(status)) - }; - - await _client.ExecuteAsync(setStateRequest); - } - - /// - public async Task Upsert(Entity entity) - { - var request = new UpsertRequest() - { - Target = entity - }; - - await _client.ExecuteAsync(request); - } - - /// - public Task Create(Entity targetEntity, bool bypassSynchronousCustomLogic = false, bool suppressPowerAutomateTrigger = false, bool suppressDuplicateDetectionRules = true) - { - var createRequest = new CreateRequest - { - Target = targetEntity - }; - - createRequest.Parameters.Add("BypassCustomPluginExecution", bypassSynchronousCustomLogic); - createRequest.Parameters.Add("SuppressCallbackRegistrationExpanderJob", suppressPowerAutomateTrigger); - createRequest.Parameters.Add("SuppressDuplicateDetection", suppressDuplicateDetectionRules); - - var resultCreate = (CreateResponse)_client.Execute(createRequest); - - return Task.FromResult(resultCreate.id); - } - - /// - public async Task Update(Entity entity, bool useOptimisticConcurrency = false, bool bypassSynchronousCustomLogic = false, bool suppressPowerAutomateTrigger = false, bool suppressDuplicateDetectionRules = true) - { - var updateRequest = new UpdateRequest - { - Target = entity - }; - - if (useOptimisticConcurrency) - { - updateRequest.ConcurrencyBehavior = ConcurrencyBehavior.IfRowVersionMatches; - } - updateRequest.Parameters.Add("BypassCustomPluginExecution", bypassSynchronousCustomLogic); - updateRequest.Parameters.Add("SuppressCallbackRegistrationExpanderJob", suppressPowerAutomateTrigger); - updateRequest.Parameters.Add("SuppressDuplicateDetection", suppressDuplicateDetectionRules); - - await _client.ExecuteAsync(updateRequest); - } - - /// - public async Task Delete(Guid id, string entityName, bool bypassSynchronousLogic = false, bool useOptimisticConcurrency = false) - { - var deleteRequest = new DeleteRequest - { - Target = new EntityReference(entityName, id) - }; - - if (useOptimisticConcurrency) - { - deleteRequest.ConcurrencyBehavior = ConcurrencyBehavior.IfRowVersionMatches; - } - - deleteRequest.Parameters.Add("BypassCustomPluginExecution", bypassSynchronousLogic); - - await _client.ExecuteAsync(deleteRequest); - } - - } -} \ No newline at end of file diff --git a/src/Liquid.Adapter.Dataverse/ILiquidDataverseAdapter.cs b/src/Liquid.Adapter.Dataverse/ILiquidDataverseAdapter.cs deleted file mode 100644 index 3df0b9fe..00000000 --- a/src/Liquid.Adapter.Dataverse/ILiquidDataverseAdapter.cs +++ /dev/null @@ -1,86 +0,0 @@ -using Microsoft.Xrm.Sdk; -using Microsoft.Xrm.Sdk.Metadata; -using Microsoft.Xrm.Sdk.Query; - -namespace Liquid.Adapter.Dataverse -{ - /// - /// Dataverse integration service definition. - /// - public interface ILiquidDataverseAdapter - { - /// - /// Insert an using additional parameters to optionally prevent custom synchronous logic execution, suppress Power Automate trigger and enforce duplicate detection rules evaluation. - /// - /// entity definition. - /// - /// - /// - /// created entity Id. - Task Create(Entity targetEntity, bool bypassSynchronousCustomLogic = false, bool suppressPowerAutomateTrigger = false, bool suppressDuplicateDetectionRules = true); - - /// - /// Update an record using parameters to enforce optimistic concurrency, prevent custom synchronous logic execution, suppress Power Automate trigger, and enforce duplicate detection rules evaluation. - /// - /// entity definition. - /// - /// - /// - /// - Task Update(Entity entity, bool useOptimisticConcurrency = false, bool bypassSynchronousCustomLogic = false, bool suppressPowerAutomateTrigger = false, bool suppressDuplicateDetectionRules = true); - - /// - /// Read by . - /// - /// primarykey value. - /// table name. - /// column set should return. - Task GetById(Guid id, string entityName, ColumnSet? columns = null); - - /// - /// Read table according filter conditions. - /// - /// table name. - /// query conditions. - /// conlumn se should return. - Task> ListByFilter(string entityName, FilterExpression filter, ColumnSet? columns = null); - - /// - /// Exclude an item from table by primarykey optionally using parameters to prevent custom synchronous logic execution and enforce optimistic concurrency. - /// - /// primarykey value - /// table name. - /// - /// - Task Delete(Guid id, string entityName, bool bypassSynchronousLogic = false, bool useOptimisticConcurrency = false); - - /// - /// Read table according query conditions. - /// - /// table name. - /// query conditions - Task> ListByFilter(string entityName, QueryExpression query); - - /// - /// Read table properties. - /// - /// table name. - /// set. - Task GetMetadata(string entityName); - - /// - /// Update state and status from an . - /// - /// entity reference. - /// new state value. - /// new status value. - Task SetState(EntityReference entity, string state, string status); - - /// - /// Insert or update an . - /// - /// entity definition. - Task Upsert(Entity entity); - - } -} diff --git a/src/Liquid.Adapter.Dataverse/ILiquidMapper.cs b/src/Liquid.Adapter.Dataverse/ILiquidMapper.cs deleted file mode 100644 index c9a7e866..00000000 --- a/src/Liquid.Adapter.Dataverse/ILiquidMapper.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Liquid.Adapter.Dataverse -{ - /// - /// Defines object that map data between two instance types. - /// - /// type of data source object. - /// results object type. - public interface ILiquidMapper - { - /// - /// Create a new instance of - /// with values obtained from . - /// - /// data source object instance. - Task Map(TFrom dataObject, string? entityName = null); - } -} diff --git a/src/Liquid.Adapter.Dataverse/LiquidMapper.cs b/src/Liquid.Adapter.Dataverse/LiquidMapper.cs deleted file mode 100644 index 14fb5727..00000000 --- a/src/Liquid.Adapter.Dataverse/LiquidMapper.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Diagnostics.CodeAnalysis; - -namespace Liquid.Adapter.Dataverse -{ - /// - [ExcludeFromCodeCoverage] - public abstract class LiquidMapper : ILiquidMapper - { - private readonly string _mapperName; - - /// - /// Create a new instance of - /// - /// Mapper implementation name. - public LiquidMapper(string mapperName) - { - _mapperName = mapperName; - } - /// - public async Task Map(TFrom dataObject, string? entityName = null) - { - if (dataObject is null) - { - throw new ArgumentNullException(nameof(dataObject)); - } - - try - { - return await MapImpl(dataObject, entityName); - } - catch (Exception e) - { - var msg = $"{_mapperName} throw data mapping error: '{e.Message}'"; - - throw new DataMappingException(msg, e); - } - } - /// - /// - /// - /// - /// - /// - protected abstract Task MapImpl(TFrom dataObject, string? entityName = null); - } -} diff --git a/src/Liquid.Adapter.Dataverse/DataverseClientFactory.cs b/src/Liquid.Dataverse/DataverseClientFactory.cs similarity index 85% rename from src/Liquid.Adapter.Dataverse/DataverseClientFactory.cs rename to src/Liquid.Dataverse/DataverseClientFactory.cs index 7e97dac2..23aa552c 100644 --- a/src/Liquid.Adapter.Dataverse/DataverseClientFactory.cs +++ b/src/Liquid.Dataverse/DataverseClientFactory.cs @@ -2,7 +2,7 @@ using Microsoft.PowerPlatform.Dataverse.Client; using System.Diagnostics.CodeAnalysis; -namespace Liquid.Adapter.Dataverse +namespace Liquid.Dataverse { /// public class DataverseClientFactory : IDataverseClientFactory @@ -16,16 +16,13 @@ public class DataverseClientFactory : IDataverseClientFactory /// public DataverseClientFactory(IOptions options) { - if (options is null) - { - throw new ArgumentNullException(nameof(options)); - } + ArgumentNullException.ThrowIfNull(options); _options = options; } + /// [ExcludeFromCodeCoverage] - /// public IOrganizationServiceAsync GetClient() { var settings = _options.Value; diff --git a/src/Liquid.Adapter.Dataverse/DataverseEntityMapper.cs b/src/Liquid.Dataverse/DataverseEntityMapper.cs similarity index 84% rename from src/Liquid.Adapter.Dataverse/DataverseEntityMapper.cs rename to src/Liquid.Dataverse/DataverseEntityMapper.cs index ee70548c..ba889a31 100644 --- a/src/Liquid.Adapter.Dataverse/DataverseEntityMapper.cs +++ b/src/Liquid.Dataverse/DataverseEntityMapper.cs @@ -1,10 +1,11 @@ -using Microsoft.Xrm.Sdk; +using Liquid.Core.AbstractMappers; +using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Metadata; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System.Diagnostics.CodeAnalysis; -namespace Liquid.Adapter.Dataverse +namespace Liquid.Dataverse { /// /// Implementation of that @@ -13,7 +14,7 @@ namespace Liquid.Adapter.Dataverse [ExcludeFromCodeCoverage] public class DataverseEntityMapper : LiquidMapper { - private readonly ILiquidDataverseAdapter _dataverseAdapter; + private readonly ILiquidDataverse _dataverseAdapter; private Dictionary _entitiesMetadata = new Dictionary(); /// @@ -21,7 +22,7 @@ public class DataverseEntityMapper : LiquidMapper /// /// /// - public DataverseEntityMapper(ILiquidDataverseAdapter adapter) : base(nameof(DataverseEntityMapper)) + public DataverseEntityMapper(ILiquidDataverse adapter) : base(nameof(DataverseEntityMapper)) { _dataverseAdapter = adapter ?? throw new ArgumentNullException(nameof(adapter)); } @@ -29,16 +30,19 @@ public DataverseEntityMapper(ILiquidDataverseAdapter adapter) : base(nameof(Data /// protected override async Task MapImpl(string jsonString, string? entityName = null) { - if (entityName == null) { throw new ArgumentNullException(nameof(entityName)); } + ArgumentNullException.ThrowIfNull(entityName); var entityAttributes = await GetEntityAttributes(entityName); + if (entityAttributes == null) + throw new ArgumentNullException(nameof(entityAttributes), $"Entity {entityName} not found."); + var entity = JsonToEntity(entityAttributes, jsonString); return entity; } - private async Task> GetEntityAttributes(string entityName, List? noMappingFields = null) + private async Task?> GetEntityAttributes(string entityName, List? noMappingFields = null) { var entityMetadata = _entitiesMetadata.FirstOrDefault(x => x.Key == entityName).Value; @@ -66,15 +70,21 @@ private async Task> GetEntityAttributes(string entityNam return listAttributes; } - private Entity JsonToEntity(List attributes, string values) + private static Entity JsonToEntity(List attributes, string values) { var entidade = new Entity(); var valuesObject = JsonConvert.DeserializeObject(values); + if (valuesObject == null) + return entidade; foreach (var atrribute in attributes) { var logicalName = atrribute.LogicalName.ToUpper(); - if (valuesObject[logicalName] != null && valuesObject[logicalName].ToString() != "") + + if (valuesObject[logicalName] == null) + continue; + + if (valuesObject[logicalName].ToString() != "") { switch (atrribute.AttributeType.ToString()) diff --git a/src/Liquid.Adapter.Dataverse/DataverseSettings.cs b/src/Liquid.Dataverse/DataverseSettings.cs similarity index 94% rename from src/Liquid.Adapter.Dataverse/DataverseSettings.cs rename to src/Liquid.Dataverse/DataverseSettings.cs index 04444d1a..518242ff 100644 --- a/src/Liquid.Adapter.Dataverse/DataverseSettings.cs +++ b/src/Liquid.Dataverse/DataverseSettings.cs @@ -1,6 +1,6 @@ using System.Diagnostics.CodeAnalysis; -namespace Liquid.Adapter.Dataverse +namespace Liquid.Dataverse { /// /// Set of dataverse connection configs. diff --git a/src/Liquid.Adapter.Dataverse/Extensions/DependencyInjection/IServiceCollectionExtensions.cs b/src/Liquid.Dataverse/Extensions/DependencyInjection/IServiceCollectionExtensions.cs similarity index 81% rename from src/Liquid.Adapter.Dataverse/Extensions/DependencyInjection/IServiceCollectionExtensions.cs rename to src/Liquid.Dataverse/Extensions/DependencyInjection/IServiceCollectionExtensions.cs index 34730a67..9f90df54 100644 --- a/src/Liquid.Adapter.Dataverse/Extensions/DependencyInjection/IServiceCollectionExtensions.cs +++ b/src/Liquid.Dataverse/Extensions/DependencyInjection/IServiceCollectionExtensions.cs @@ -1,9 +1,10 @@ -using Microsoft.Extensions.Configuration; +using Liquid.Core.Interfaces; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Xrm.Sdk; using System.Diagnostics.CodeAnalysis; -namespace Liquid.Adapter.Dataverse.Extensions.DependencyInjection +namespace Liquid.Dataverse.Extensions.DependencyInjection { /// /// Extension methods of @@ -12,7 +13,7 @@ namespace Liquid.Adapter.Dataverse.Extensions.DependencyInjection public static class IServiceCollectionExtensions { /// - /// Registers service, it's dependency + /// Registers service, it's dependency /// , and also set configuration /// option . /// Also register service. @@ -29,7 +30,7 @@ public static IServiceCollection AddLiquidDataverseAdapter(this IServiceCollecti services.AddTransient(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton, DataverseEntityMapper>(); diff --git a/src/Liquid.Adapter.Dataverse/Extensions/EntityExtensions.cs b/src/Liquid.Dataverse/Extensions/EntityExtensions.cs similarity index 94% rename from src/Liquid.Adapter.Dataverse/Extensions/EntityExtensions.cs rename to src/Liquid.Dataverse/Extensions/EntityExtensions.cs index 7180c26f..dc24970c 100644 --- a/src/Liquid.Adapter.Dataverse/Extensions/EntityExtensions.cs +++ b/src/Liquid.Dataverse/Extensions/EntityExtensions.cs @@ -2,7 +2,7 @@ using System.Diagnostics.CodeAnalysis; using System.Text.Json; -namespace Liquid.Adapter.Dataverse.Extensions +namespace Liquid.Dataverse.Extensions { /// /// Extension methods of . diff --git a/src/Liquid.Adapter.Dataverse/IDataverseClientFactory.cs b/src/Liquid.Dataverse/IDataverseClientFactory.cs similarity index 92% rename from src/Liquid.Adapter.Dataverse/IDataverseClientFactory.cs rename to src/Liquid.Dataverse/IDataverseClientFactory.cs index fb054afb..96c2726e 100644 --- a/src/Liquid.Adapter.Dataverse/IDataverseClientFactory.cs +++ b/src/Liquid.Dataverse/IDataverseClientFactory.cs @@ -1,6 +1,6 @@ using Microsoft.PowerPlatform.Dataverse.Client; -namespace Liquid.Adapter.Dataverse +namespace Liquid.Dataverse { /// /// Defines Dataverse provider. diff --git a/src/Liquid.Dataverse/ILiquidDataverse.cs b/src/Liquid.Dataverse/ILiquidDataverse.cs new file mode 100644 index 00000000..7a3ddf6e --- /dev/null +++ b/src/Liquid.Dataverse/ILiquidDataverse.cs @@ -0,0 +1,86 @@ +using Microsoft.Xrm.Sdk; +using Microsoft.Xrm.Sdk.Metadata; +using Microsoft.Xrm.Sdk.Query; + +namespace Liquid.Dataverse +{ + /// + /// Dataverse integration service definition. + /// + public interface ILiquidDataverse + { + /// + /// Insert an using additional parameters to optionally prevent custom synchronous logic execution, suppress Power Automate trigger and enforce duplicate detection rules evaluation. + /// + /// entity definition. + /// + /// + /// + /// created entity Id. + Task Create(Entity targetEntity, bool bypassSynchronousCustomLogic = false, bool suppressPowerAutomateTrigger = false, bool suppressDuplicateDetectionRules = true); + + /// + /// Update an record using parameters to enforce optimistic concurrency, prevent custom synchronous logic execution, suppress Power Automate trigger, and enforce duplicate detection rules evaluation. + /// + /// entity definition. + /// + /// + /// + /// + Task Update(Entity entity, bool useOptimisticConcurrency = false, bool bypassSynchronousCustomLogic = false, bool suppressPowerAutomateTrigger = false, bool suppressDuplicateDetectionRules = true); + + /// + /// Read by . + /// + /// primarykey value. + /// table name. + /// column set should return. + Task GetById(Guid id, string entityName, ColumnSet? columns = null); + + /// + /// Read table according filter conditions. + /// + /// table name. + /// query conditions. + /// conlumn se should return. + Task> ListByFilter(string entityName, FilterExpression filter, ColumnSet? columns = null); + + /// + /// Exclude an item from table by primarykey optionally using parameters to prevent custom synchronous logic execution and enforce optimistic concurrency. + /// + /// primarykey value + /// table name. + /// + /// + Task Delete(Guid id, string entityName, bool bypassSynchronousLogic = false, bool useOptimisticConcurrency = false); + + /// + /// Read table according query conditions. + /// + /// table name. + /// query conditions + Task> ListByFilter(string entityName, QueryExpression query); + + /// + /// Read table properties. + /// + /// table name. + /// set. + Task GetMetadata(string entityName); + + /// + /// Update state and status from an . + /// + /// entity reference. + /// new state value. + /// new status value. + Task SetState(EntityReference entity, string state, string status); + + /// + /// Insert or update an . + /// + /// entity definition. + Task Upsert(Entity entity); + + } +} diff --git a/src/Liquid.Adapter.Dataverse/Liquid.Adapter.Dataverse.csproj b/src/Liquid.Dataverse/Liquid.Dataverse.csproj similarity index 84% rename from src/Liquid.Adapter.Dataverse/Liquid.Adapter.Dataverse.csproj rename to src/Liquid.Dataverse/Liquid.Dataverse.csproj index 69e4e768..b975fda6 100644 --- a/src/Liquid.Adapter.Dataverse/Liquid.Adapter.Dataverse.csproj +++ b/src/Liquid.Dataverse/Liquid.Dataverse.csproj @@ -1,14 +1,14 @@  - net6.0 + net8.0 enable enable Avanade Brazil Avanade Inc. Liquid - Modern Application Framework Avanade 2019 - 6.0.0 + 8.0.0-rc-01 true true Adapter for Microsoft Dataverse integrations. @@ -18,9 +18,10 @@ This component is part of Liquid Application Framework. - - - + + + + diff --git a/src/Liquid.Dataverse/LiquidDataverse.cs b/src/Liquid.Dataverse/LiquidDataverse.cs new file mode 100644 index 00000000..7458c2fa --- /dev/null +++ b/src/Liquid.Dataverse/LiquidDataverse.cs @@ -0,0 +1,181 @@ +using Microsoft.Crm.Sdk.Messages; +using Microsoft.PowerPlatform.Dataverse.Client; +using Microsoft.Xrm.Sdk; +using Microsoft.Xrm.Sdk.Messages; +using Microsoft.Xrm.Sdk.Metadata; +using Microsoft.Xrm.Sdk.Query; + +namespace Liquid.Dataverse +{ + /// + public class LiquidDataverse : ILiquidDataverse + { + private readonly IDataverseClientFactory _serviceFactory; + private readonly IOrganizationServiceAsync _client; + + /// + /// Initialize a new instance of + /// + /// + /// + public LiquidDataverse(IDataverseClientFactory serviceFactory) + { + _serviceFactory = serviceFactory ?? throw new ArgumentNullException(nameof(serviceFactory)); + _client = _serviceFactory.GetClient(); + } + + public async Task GetById(Guid id, string entityName, ColumnSet? columns = null) + { + if (columns == null) + columns = new ColumnSet(true); + + var result = await _client.RetrieveAsync(entityName, id, columns); + + return result; + } + + /// + public async Task> ListByFilter(string entityName, FilterExpression? filter = null, ColumnSet? columns = null) + { + List results = new List(); + + QueryExpression queryData = new QueryExpression(entityName); + + if (filter != null) + queryData.Criteria = filter; + + if (columns != null) + queryData.ColumnSet = columns; + + var result = await _client.RetrieveMultipleAsync(queryData); + if (result?.Entities != null) + { + foreach (var item in result.Entities) + { + results.Add(item); + } + } + + return results; + } + + /// + public async Task> ListByFilter(string entityName, QueryExpression query) + { + if (query is null) + { + throw new ArgumentNullException(nameof(query)); + } + + List results = new List(); + + var result = await _client.RetrieveMultipleAsync(query); + + if (result?.Entities != null) + { + foreach (var item in result.Entities) + { + results.Add(item); + } + } + + return results; + } + + /// + public async Task GetMetadata(string entityName) + { + var retrieveEntityRequest = new RetrieveEntityRequest + { + EntityFilters = EntityFilters.All, + LogicalName = entityName + }; + var response = await _client.ExecuteAsync(retrieveEntityRequest); + var metadata = (RetrieveEntityResponse)response; + return metadata.EntityMetadata; + } + + /// + public async Task SetState(EntityReference entity, string state, string status) + { + var setStateRequest = new SetStateRequest() + { + EntityMoniker = new EntityReference + { + Id = entity.Id, + LogicalName = entity.LogicalName, + }, + State = new OptionSetValue(int.Parse(state)), + Status = new OptionSetValue(int.Parse(status)) + }; + + await _client.ExecuteAsync(setStateRequest); + } + + /// + public async Task Upsert(Entity entity) + { + var request = new UpsertRequest() + { + Target = entity + }; + + await _client.ExecuteAsync(request); + } + + /// + public Task Create(Entity targetEntity, bool bypassSynchronousCustomLogic = false, bool suppressPowerAutomateTrigger = false, bool suppressDuplicateDetectionRules = true) + { + var createRequest = new CreateRequest + { + Target = targetEntity + }; + + createRequest.Parameters.Add("BypassCustomPluginExecution", bypassSynchronousCustomLogic); + createRequest.Parameters.Add("SuppressCallbackRegistrationExpanderJob", suppressPowerAutomateTrigger); + createRequest.Parameters.Add("SuppressDuplicateDetection", suppressDuplicateDetectionRules); + + var resultCreate = (CreateResponse)_client.Execute(createRequest); + + return Task.FromResult(resultCreate.id); + } + + /// + public async Task Update(Entity entity, bool useOptimisticConcurrency = false, bool bypassSynchronousCustomLogic = false, bool suppressPowerAutomateTrigger = false, bool suppressDuplicateDetectionRules = true) + { + var updateRequest = new UpdateRequest + { + Target = entity + }; + + if (useOptimisticConcurrency) + { + updateRequest.ConcurrencyBehavior = ConcurrencyBehavior.IfRowVersionMatches; + } + updateRequest.Parameters.Add("BypassCustomPluginExecution", bypassSynchronousCustomLogic); + updateRequest.Parameters.Add("SuppressCallbackRegistrationExpanderJob", suppressPowerAutomateTrigger); + updateRequest.Parameters.Add("SuppressDuplicateDetection", suppressDuplicateDetectionRules); + + await _client.ExecuteAsync(updateRequest); + } + + /// + public async Task Delete(Guid id, string entityName, bool bypassSynchronousLogic = false, bool useOptimisticConcurrency = false) + { + var deleteRequest = new DeleteRequest + { + Target = new EntityReference(entityName, id) + }; + + if (useOptimisticConcurrency) + { + deleteRequest.ConcurrencyBehavior = ConcurrencyBehavior.IfRowVersionMatches; + } + + deleteRequest.Parameters.Add("BypassCustomPluginExecution", bypassSynchronousLogic); + + await _client.ExecuteAsync(deleteRequest); + } + + } +} \ No newline at end of file diff --git a/src/Liquid.Adapter.Dataverse/logo.png b/src/Liquid.Dataverse/logo.png similarity index 100% rename from src/Liquid.Adapter.Dataverse/logo.png rename to src/Liquid.Dataverse/logo.png diff --git a/test/Liquid.Adapter.Dataverse.Tests/DataverseClientFactoryTests.cs b/test/Liquid.Dataverse.Tests/DataverseClientFactoryTests.cs similarity index 88% rename from test/Liquid.Adapter.Dataverse.Tests/DataverseClientFactoryTests.cs rename to test/Liquid.Dataverse.Tests/DataverseClientFactoryTests.cs index 80cc6345..dd4131ae 100644 --- a/test/Liquid.Adapter.Dataverse.Tests/DataverseClientFactoryTests.cs +++ b/test/Liquid.Dataverse.Tests/DataverseClientFactoryTests.cs @@ -1,8 +1,7 @@ using Microsoft.Extensions.Options; -using Microsoft.PowerPlatform.Dataverse.Client; using NSubstitute; -namespace Liquid.Adapter.Dataverse.Tests +namespace Liquid.Dataverse.Tests { public class DataverseClientFactoryTests { @@ -12,10 +11,10 @@ public class DataverseClientFactoryTests public DataverseClientFactoryTests() { _options = Substitute.For>(); - _options.Value.ReturnsForAnyArgs(new DataverseSettings() { ClientId = "4erewgewgh", ClientSecret = "greggrbnte", Url = "https://test"}); + _options.Value.ReturnsForAnyArgs(new DataverseSettings() { ClientId = "4erewgewgh", ClientSecret = "greggrbnte", Url = "https://test" }); _sut = new DataverseClientFactory(_options); - } - + } + [Fact] public void Ctor_WhenOptionsIsNull_ThenReturnArgumentNullException() diff --git a/test/Liquid.Adapter.Dataverse.Tests/Liquid.Adapter.Dataverse.Tests.csproj b/test/Liquid.Dataverse.Tests/Liquid.Dataverse.Tests.csproj similarity index 83% rename from test/Liquid.Adapter.Dataverse.Tests/Liquid.Adapter.Dataverse.Tests.csproj rename to test/Liquid.Dataverse.Tests/Liquid.Dataverse.Tests.csproj index 5f91d323..50180b65 100644 --- a/test/Liquid.Adapter.Dataverse.Tests/Liquid.Adapter.Dataverse.Tests.csproj +++ b/test/Liquid.Dataverse.Tests/Liquid.Dataverse.Tests.csproj @@ -1,7 +1,7 @@ - + - net6.0 + net8.0 enable enable @@ -23,7 +23,7 @@ - + diff --git a/test/Liquid.Adapter.Dataverse.Tests/DataverseAdapterTests.cs b/test/Liquid.Dataverse.Tests/LiquidDataverseTests.cs similarity index 77% rename from test/Liquid.Adapter.Dataverse.Tests/DataverseAdapterTests.cs rename to test/Liquid.Dataverse.Tests/LiquidDataverseTests.cs index 5d536b9a..13043af1 100644 --- a/test/Liquid.Adapter.Dataverse.Tests/DataverseAdapterTests.cs +++ b/test/Liquid.Dataverse.Tests/LiquidDataverseTests.cs @@ -6,38 +6,33 @@ using Microsoft.Xrm.Sdk.Query; using NSubstitute; using NSubstitute.ReceivedExtensions; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace Liquid.Adapter.Dataverse.Tests +namespace Liquid.Dataverse.Tests { - public class DataverseAdapterTests + public class LiquidDataverseTests { private readonly IOrganizationServiceAsync _client; - private readonly ILiquidDataverseAdapter _sut; - public DataverseAdapterTests() + private readonly ILiquidDataverse _sut; + public LiquidDataverseTests() { var clientFactory = Substitute.For(); _client = Substitute.For(); clientFactory.GetClient().Returns(_client); - _sut = new DataverseAdapter(clientFactory); + _sut = new LiquidDataverse(clientFactory); } [Fact] public void Ctor_WhenClientFactoryIsNull_ThrowArgumentNullException() { - Assert.Throws(() => new DataverseAdapter(null)); + Assert.Throws(() => new LiquidDataverse(null)); } [Fact] public async Task GetById_WhenClientReturnResults_ReturnEntity() { - _client.RetrieveAsync(Arg.Any(), Arg.Any(),Arg.Any()).Returns(new Entity()); + _client.RetrieveAsync(Arg.Any(), Arg.Any(), Arg.Any()).Returns(new Entity()); var guidId = Guid.NewGuid(); @@ -89,7 +84,7 @@ public async Task SetState_WhenCallResultSucessfully_ExecuteAsyncMethodCalled() _client.ExecuteAsync(Arg.Any()).Returns(new OrganizationResponse()); var entity = new EntityReference(); - + await _sut.SetState(entity, "1234", "1212"); await _client.Received(1).ExecuteAsync(Arg.Any()); @@ -107,35 +102,35 @@ public async Task Upsert_WhenCallResultSucessfully_ExecuteAsyncMethodCalled() await _client.Received(1).ExecuteAsync(Arg.Any()); } - [Fact] - public async Task Update_WithOptions_UseRightOrganizationRequestType_RequestParameters() - { - _client.UpdateAsync(Arg.Any()).Returns(Task.CompletedTask); + [Fact] + public async Task Update_WithOptions_UseRightOrganizationRequestType_RequestParameters() + { + _client.UpdateAsync(Arg.Any()).Returns(Task.CompletedTask); - var updatedEntity = new Entity(); + var updatedEntity = new Entity(); - await _sut.Update(updatedEntity, true, true, true); + await _sut.Update(updatedEntity, true, true, true); - await _client.Received(1).ExecuteAsync(Arg.Is(ur => + await _client.Received(1).ExecuteAsync(Arg.Is(ur => ur.Parameters.ContainsKey("BypassCustomPluginExecution") && ur.Parameters.ContainsKey("SuppressCallbackRegistrationExpanderJob") - && ur.Parameters.ContainsKey("SuppressDuplicateDetection") - )); - } + && ur.Parameters.ContainsKey("SuppressDuplicateDetection") + )); + } - [Fact] - public async Task DeleteById_WithOptions_UseRightOrganizationRequestType_RequestParameters() - { + [Fact] + public async Task DeleteById_WithOptions_UseRightOrganizationRequestType_RequestParameters() + { _client.DeleteAsync(Arg.Any(), Arg.Any()).Returns(Task.CompletedTask); - var guidId = Guid.NewGuid(); + var guidId = Guid.NewGuid(); - await _sut.Delete(guidId, "entityname", true, true); + await _sut.Delete(guidId, "entityname", true, true); - await _client.Received(1).ExecuteAsync(Arg.Is(dr => + await _client.Received(1).ExecuteAsync(Arg.Is(dr => dr.Parameters.ContainsKey("BypassCustomPluginExecution") )); - } + } [Fact] public async Task Create_WithOptions_UseRightOrganizationRequestType_RequestParameters() @@ -144,8 +139,8 @@ public async Task Create_WithOptions_UseRightOrganizationRequestType_RequestPara var result = await _sut.Create(new Entity(), false, false, true); - _client.Received(1).Execute(Arg.Is(cr => - cr.Parameters.ContainsKey("BypassCustomPluginExecution") + _client.Received(1).Execute(Arg.Is(cr => + cr.Parameters.ContainsKey("BypassCustomPluginExecution") && cr.Parameters.ContainsKey("SuppressCallbackRegistrationExpanderJob") && cr.Parameters.ContainsKey("SuppressDuplicateDetection") )); diff --git a/test/Liquid.Adapter.Dataverse.Tests/Usings.cs b/test/Liquid.Dataverse.Tests/Usings.cs similarity index 100% rename from test/Liquid.Adapter.Dataverse.Tests/Usings.cs rename to test/Liquid.Dataverse.Tests/Usings.cs