diff --git a/src/Dataverse.ConfigurationMigrationTool/Console.Tests/Features/Import/Interceptors/BaseEntityReferenceFieldInterceptorTests.cs b/src/Dataverse.ConfigurationMigrationTool/Console.Tests/Features/Import/Interceptors/BaseEntityReferenceFieldInterceptorTests.cs new file mode 100644 index 0000000..f97ecb0 --- /dev/null +++ b/src/Dataverse.ConfigurationMigrationTool/Console.Tests/Features/Import/Interceptors/BaseEntityReferenceFieldInterceptorTests.cs @@ -0,0 +1,90 @@ +using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Interceptors; +using Microsoft.Xrm.Sdk; +using NSubstitute; +using Shouldly; + +namespace Dataverse.ConfigurationMigrationTool.Console.Tests.Features.Import.Interceptors; +public abstract class BaseEntityReferenceFieldInterceptorTests : BaseEntityInterceptorTests + where T : BaseEntityReferenceFieldInterceptor +{ + protected string EntityName { get; } + protected string PrimaryKeyName { get; } + protected BaseEntityReferenceFieldInterceptorTests(string EntityName, string PrimaryKeyName) + { + this.EntityName = EntityName; + this.PrimaryKeyName = PrimaryKeyName; + } + protected abstract Task GetEntityByIdAsync(Guid Id); + protected abstract Task GetEntityByNameAsync(string Name); + [Fact] + public async Task GivenAnEntityWithEntityReferenceField_WhenItsIntercepted_ThenItShouldResolveLookUpByName() + { + //Arrange + var recordName = "Test Team"; + var expectedRecord = new Entity(EntityName) + { + Id = Guid.NewGuid(), + ["name"] = recordName + }; + GetEntityByNameAsync(recordName).Returns(expectedRecord); + + var entity = new Entity("account") + { + Id = Guid.NewGuid(), + ["name"] = "Test Account", + [PrimaryKeyName] = new EntityReference(EntityName, Guid.NewGuid()) { Name = recordName } + }; + + //Act + var result = await InterceptAsync(entity); + //Assert + result.GetAttributeValue(PrimaryKeyName).Id.ShouldBe(expectedRecord.Id); + } + [Fact] + public async Task GivenAnEntityWithEntityReferenceField_WhenItsIntercepted_ThenItShouldResolveLookUpById() + { + //Arrange + var recordName = "Test team"; + var expectedRecord = new Entity(EntityName) + { + Id = Guid.NewGuid(), + ["name"] = recordName + }; + GetEntityByIdAsync(expectedRecord.Id).Returns(expectedRecord); + + var entity = new Entity("account") + { + Id = Guid.NewGuid(), + ["name"] = "Test Account", + [PrimaryKeyName] = expectedRecord.ToEntityReference() + }; + + //Act + var result = await InterceptAsync(entity); + //Assert + result.GetAttributeValue(PrimaryKeyName).Id.ShouldBe(expectedRecord.Id); + } + [Fact] + public async Task GivenAnEntityWithLookUpField_WhenItsInterceptedAndCantBeResolved_ThenItShouldRemoveFieldFromEntity() + { + //Arrange + var recordName = "Test team"; + var expectedRecord = new Entity(EntityName) + { + Id = Guid.NewGuid(), + ["name"] = recordName + }; + + var entity = new Entity("account") + { + Id = Guid.NewGuid(), + ["name"] = "Test Account", + [PrimaryKeyName] = expectedRecord.ToEntityReference() + }; + + //Act + var result = await InterceptAsync(entity); + //Assert + result.Attributes.FirstOrDefault(kv => kv.Key == PrimaryKeyName).ShouldBe(default); + } +} diff --git a/src/Dataverse.ConfigurationMigrationTool/Console.Tests/Features/Import/Interceptors/TargetTransactionCurrencyInterceptorTests.cs b/src/Dataverse.ConfigurationMigrationTool/Console.Tests/Features/Import/Interceptors/TargetTransactionCurrencyInterceptorTests.cs new file mode 100644 index 0000000..244da0b --- /dev/null +++ b/src/Dataverse.ConfigurationMigrationTool/Console.Tests/Features/Import/Interceptors/TargetTransactionCurrencyInterceptorTests.cs @@ -0,0 +1,26 @@ +using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Interceptors; +using Dataverse.ConfigurationMigrationTool.Console.Services.Dataverse; +using Microsoft.Xrm.Sdk; +using NSubstitute; + +namespace Dataverse.ConfigurationMigrationTool.Console.Tests.Features.Import.Interceptors; +public class TargetTransactionCurrencyInterceptorTests : BaseEntityReferenceFieldInterceptorTests +{ + private readonly IProductCatalogService _productCatalogService = Substitute.For(); + + public TargetTransactionCurrencyInterceptorTests() : base("transactioncurrency", "transactioncurrencyid") + { + } + + protected override TargetTransactionCurrencyInterceptor CreateInterceptor() => new TargetTransactionCurrencyInterceptor(_productCatalogService); + + protected override Task GetEntityByIdAsync(Guid Id) + { + return _productCatalogService.GetTransacationCurrencyById(Id); + } + + protected override Task GetEntityByNameAsync(string Name) + { + return _productCatalogService.GetTransacationCurrencyByName(Name); + } +} diff --git a/src/Dataverse.ConfigurationMigrationTool/Console.Tests/Features/Import/Interceptors/TargetUoMInterceptorTests.cs b/src/Dataverse.ConfigurationMigrationTool/Console.Tests/Features/Import/Interceptors/TargetUoMInterceptorTests.cs new file mode 100644 index 0000000..55ca079 --- /dev/null +++ b/src/Dataverse.ConfigurationMigrationTool/Console.Tests/Features/Import/Interceptors/TargetUoMInterceptorTests.cs @@ -0,0 +1,26 @@ +using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Interceptors; +using Dataverse.ConfigurationMigrationTool.Console.Services.Dataverse; +using Microsoft.Xrm.Sdk; +using NSubstitute; + +namespace Dataverse.ConfigurationMigrationTool.Console.Tests.Features.Import.Interceptors; +public class TargetUoMInterceptorTests : BaseEntityReferenceFieldInterceptorTests +{ + private readonly IProductCatalogService _productCatalogService = Substitute.For(); + + public TargetUoMInterceptorTests() : base("uom", "uomid") + { + } + + protected override TargetUoMInterceptor CreateInterceptor() => new TargetUoMInterceptor(_productCatalogService); + + protected override Task GetEntityByIdAsync(Guid Id) + { + return _productCatalogService.GetUoMById(Id); + } + + protected override Task GetEntityByNameAsync(string Name) + { + return _productCatalogService.GetUoMByName(Name); + } +} diff --git a/src/Dataverse.ConfigurationMigrationTool/Console.Tests/Features/Import/Interceptors/TargetUoMScheduleInterceptorTests.cs b/src/Dataverse.ConfigurationMigrationTool/Console.Tests/Features/Import/Interceptors/TargetUoMScheduleInterceptorTests.cs new file mode 100644 index 0000000..5e1fd4f --- /dev/null +++ b/src/Dataverse.ConfigurationMigrationTool/Console.Tests/Features/Import/Interceptors/TargetUoMScheduleInterceptorTests.cs @@ -0,0 +1,26 @@ +using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Interceptors; +using Dataverse.ConfigurationMigrationTool.Console.Services.Dataverse; +using Microsoft.Xrm.Sdk; +using NSubstitute; + +namespace Dataverse.ConfigurationMigrationTool.Console.Tests.Features.Import.Interceptors; +public class TargetUoMScheduleInterceptorTests : BaseEntityReferenceFieldInterceptorTests +{ + private readonly IProductCatalogService _productCatalogService = Substitute.For(); + + public TargetUoMScheduleInterceptorTests() : base("uomschedule", "uomscheduleid") + { + } + + protected override TargetUoMScheduleInterceptor CreateInterceptor() => new TargetUoMScheduleInterceptor(_productCatalogService); + + protected override Task GetEntityByIdAsync(Guid Id) + { + return _productCatalogService.GetUoMScheduleById(Id); + } + + protected override Task GetEntityByNameAsync(string Name) + { + return _productCatalogService.GetUoMScheduleByName(Name); + } +} diff --git a/src/Dataverse.ConfigurationMigrationTool/Console.Tests/Services/Dataverse/DataverseProductCatalogServiceTests.cs b/src/Dataverse.ConfigurationMigrationTool/Console.Tests/Services/Dataverse/DataverseProductCatalogServiceTests.cs new file mode 100644 index 0000000..f32356d --- /dev/null +++ b/src/Dataverse.ConfigurationMigrationTool/Console.Tests/Services/Dataverse/DataverseProductCatalogServiceTests.cs @@ -0,0 +1,180 @@ +using Dataverse.ConfigurationMigrationTool.Console.Services.Dataverse; +using FakeXrmEasy.Abstractions; +using FakeXrmEasy.Abstractions.Enums; +using FakeXrmEasy.Middleware; +using FakeXrmEasy.Middleware.Crud; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Xrm.Sdk; +using NSubstitute; +using Shouldly; + +namespace Dataverse.ConfigurationMigrationTool.Console.Tests.Services.Dataverse; +public class DataverseProductCatalogServiceTests +{ + private readonly IXrmFakedContext _fakedContext; + private readonly IMemoryCache _memoryCache = Substitute.For(); + private readonly DataverseProductCatalogService _repository; + public DataverseProductCatalogServiceTests() + { + // Arrange + _fakedContext = MiddlewareBuilder.New() + .SetLicense(FakeXrmEasyLicense.NonCommercial) + .AddCrud() + .UseCrud() + .Build(); + _repository = new DataverseProductCatalogService(_fakedContext.GetAsyncOrganizationService2(), _memoryCache); + } + [Fact] + public async Task GivenAProductCatalogServicey_WhenItFetchesTransactionCurrencyById_ThenItShouldCacheItsResult() + { + //Arrange + var recordname = "Test Team"; + + var record = new Entity("transactioncurrency") + { + Id = Guid.NewGuid(), + ["currencyname"] = recordname + }; + var cacheKey = $"product.GetTransacationCurrencyById.{record.Id}"; + _fakedContext.Initialize(record); + var CacheEntry = Substitute.For(); + _memoryCache.TryGetValue(cacheKey, out Arg.Any()).Returns(false); + _memoryCache.CreateEntry(cacheKey).Returns(CacheEntry); + + //Act + + var result = await _repository.GetTransacationCurrencyById(record.Id); + + //Assert + result.Id.ShouldBe(record.Id); + CacheEntry.Received().SetValue(Arg.Is(e => e.Id == record.Id)); + + } + [Fact] + public async Task GivenAProductCatalogServicey_WhenItFetchesTransactionCurrencyByName_ThenItShouldCacheItsResult() + { + //Arrange + var recordname = "Test Team"; + var cacheKey = $"product.GetTransacationCurrencyByName.{recordname}"; + var record = new Entity("transactioncurrency") + { + Id = Guid.NewGuid(), + ["currencyname"] = recordname + }; + _fakedContext.Initialize(record); + var CacheEntry = Substitute.For(); + _memoryCache.TryGetValue(cacheKey, out Arg.Any()).Returns(false); + _memoryCache.CreateEntry(cacheKey).Returns(CacheEntry); + + //Act + + var result = await _repository.GetTransacationCurrencyByName(recordname); + + //Assert + result.Id.ShouldBe(record.Id); + CacheEntry.Received().SetValue(Arg.Is(e => e.Id == record.Id)); + + } + [Fact] + public async Task GivenAProductCatalogServicey_WhenItFetchesUoMById_ThenItShouldCacheItsResult() + { + //Arrange + var recordname = "Test Team"; + + var record = new Entity("uom") + { + Id = Guid.NewGuid(), + ["name"] = recordname + }; + var cacheKey = $"product.GetUoMById.{record.Id}"; + _fakedContext.Initialize(record); + var CacheEntry = Substitute.For(); + _memoryCache.TryGetValue(cacheKey, out Arg.Any()).Returns(false); + _memoryCache.CreateEntry(cacheKey).Returns(CacheEntry); + + //Act + + var result = await _repository.GetUoMById(record.Id); + + //Assert + result.Id.ShouldBe(record.Id); + CacheEntry.Received().SetValue(Arg.Is(e => e.Id == record.Id)); + + } + [Fact] + public async Task GivenAProductCatalogServicey_WhenItFetchesUoMByName_ThenItShouldCacheItsResult() + { + //Arrange + var recordname = "Test Team"; + var cacheKey = $"product.GetUoMByName.{recordname}"; + var record = new Entity("uom") + { + Id = Guid.NewGuid(), + ["name"] = recordname + }; + _fakedContext.Initialize(record); + var CacheEntry = Substitute.For(); + _memoryCache.TryGetValue(cacheKey, out Arg.Any()).Returns(false); + _memoryCache.CreateEntry(cacheKey).Returns(CacheEntry); + + //Act + + var result = await _repository.GetUoMByName(recordname); + + //Assert + result.Id.ShouldBe(record.Id); + CacheEntry.Received().SetValue(Arg.Is(e => e.Id == record.Id)); + + } + [Fact] + public async Task GivenAProductCatalogServicey_WhenItFetchesUoMScheduleById_ThenItShouldCacheItsResult() + { + //Arrange + var recordname = "Test Team"; + + var record = new Entity("uomschedule") + { + Id = Guid.NewGuid(), + ["name"] = recordname + }; + var cacheKey = $"product.GetUoMScheduleById.{record.Id}"; + _fakedContext.Initialize(record); + var CacheEntry = Substitute.For(); + _memoryCache.TryGetValue(cacheKey, out Arg.Any()).Returns(false); + _memoryCache.CreateEntry(cacheKey).Returns(CacheEntry); + + //Act + + var result = await _repository.GetUoMScheduleById(record.Id); + + //Assert + result.Id.ShouldBe(record.Id); + CacheEntry.Received().SetValue(Arg.Is(e => e.Id == record.Id)); + + } + [Fact] + public async Task GivenAProductCatalogServicey_WhenItFetchesUoMScheduleByName_ThenItShouldCacheItsResult() + { + //Arrange + var recordname = "Test Team"; + var cacheKey = $"product.GetUoMScheduleByName.{recordname}"; + var record = new Entity("uomschedule") + { + Id = Guid.NewGuid(), + ["name"] = recordname + }; + _fakedContext.Initialize(record); + var CacheEntry = Substitute.For(); + _memoryCache.TryGetValue(cacheKey, out Arg.Any()).Returns(false); + _memoryCache.CreateEntry(cacheKey).Returns(CacheEntry); + + //Act + + var result = await _repository.GetUoMScheduleByName(recordname); + + //Assert + result.Id.ShouldBe(record.Id); + CacheEntry.Received().SetValue(Arg.Is(e => e.Id == record.Id)); + + } +} diff --git a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Export/Mappers/EntityFieldValueToFieldMapper.cs b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Export/Mappers/EntityFieldValueToFieldMapper.cs index 5960477..dc08e32 100644 --- a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Export/Mappers/EntityFieldValueToFieldMapper.cs +++ b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Export/Mappers/EntityFieldValueToFieldMapper.cs @@ -1,6 +1,7 @@ using Dataverse.ConfigurationMigrationTool.Console.Features.Shared; using Dataverse.ConfigurationMigrationTool.Console.Features.Shared.Domain; using Microsoft.Xrm.Sdk; +using System.Globalization; using System.Web; namespace Dataverse.ConfigurationMigrationTool.Console.Features.Export.Mappers; @@ -46,17 +47,17 @@ public Field Map((FieldSchema, object) source) } if (value is Money moneyValue) { - fieldResult.Value = moneyValue.Value.ToString(); + fieldResult.Value = moneyValue.Value.ToString(CultureInfo.InvariantCulture); return fieldResult; } if (value is decimal decimalValue) { - fieldResult.Value = decimalValue.ToString(); + fieldResult.Value = decimalValue.ToString(CultureInfo.InvariantCulture); return fieldResult; } if (value is double doubleValue) { - fieldResult.Value = doubleValue.ToString(); + fieldResult.Value = doubleValue.ToString(CultureInfo.InvariantCulture); return fieldResult; } var str = value?.ToString() ?? string.Empty; diff --git a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/IServiceCollectionExtensions.cs b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/IServiceCollectionExtensions.cs index 7abd7fc..59dfc9c 100644 --- a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/IServiceCollectionExtensions.cs +++ b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/IServiceCollectionExtensions.cs @@ -25,8 +25,8 @@ public static IServiceCollection AddSharedServices(this IServiceCollection servi { return services.RegisterFromReflection() .RegisterFromReflection() - .AddTransient, SchemaValidator>() - .AddTransient, EntitySchemaValidator>(); + .AddScoped, SchemaValidator>() + .AddScoped, EntitySchemaValidator>(); } public static IServiceCollection UseCommands(this IServiceCollection services, params string[] args) { diff --git a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Import/IServiceCollectionExtensions.cs b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Import/IServiceCollectionExtensions.cs index 2303253..8cb518e 100644 --- a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Import/IServiceCollectionExtensions.cs +++ b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Import/IServiceCollectionExtensions.cs @@ -13,23 +13,26 @@ public static IServiceCollection AddImportFeature(this IServiceCollection servic { return services - .AddSingleton(_ => + .AddScoped(_ => { var valueConverterTypes = Assembly.GetExecutingAssembly().GetTypes().Where(t => !t.IsAbstract && !t.IsInterface && t.BaseType != null && t.BaseType.IsConstructedGenericType && t.BaseType.GetGenericTypeDefinition() == typeof(BaseValueConverter<>)).ToList(); return new ReflectionMainConverter(valueConverterTypes); }) - .AddSingleton() + .AddScoped() - .AddSingleton() - .AddSingleton((sp) => + .AddScoped() + .AddScoped((sp) => { - var BusinessUnitInterceptor = sp.BuildService(); - var UserInterceptor = sp.BuildService(); - var TeamInterceptor = sp.BuildService(); - BusinessUnitInterceptor.SetSuccessor(UserInterceptor) - .SetSuccessor(TeamInterceptor); - return BusinessUnitInterceptor; + + return new EntityInterceptorChainBuilder(sp) + .StartsWith() + .ThenWith() + .ThenWith() + .ThenWith() + .ThenWith() + .ThenWith() + .BuildChain(); }) .Configure(Configuration); diff --git a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Import/Interceptors/BaseEntityReferenceFieldInterceptor.cs b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Import/Interceptors/BaseEntityReferenceFieldInterceptor.cs new file mode 100644 index 0000000..3a590e1 --- /dev/null +++ b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Import/Interceptors/BaseEntityReferenceFieldInterceptor.cs @@ -0,0 +1,40 @@ +using Microsoft.Xrm.Sdk; + +namespace Dataverse.ConfigurationMigrationTool.Console.Features.Import.Interceptors; +public abstract class BaseEntityReferenceFieldInterceptor : BaseEntityInterceptor +{ + protected string EntityName { get; } + protected BaseEntityReferenceFieldInterceptor(string entityName) + { + EntityName = entityName; + } + + protected abstract Task GetEntityByIdAsync(Guid Id); + protected abstract Task GetEntityByNameAsync(string Name); + public override async Task InterceptAsync(Entity entity) + { + var attributes = entity.Attributes.Where(kv => kv.Value is EntityReference ef && ef.LogicalName == EntityName).ToList(); + foreach (var attribute in attributes) + { + var reference = attribute.Value as EntityReference; + var resolvedTeam = await GetEntityByIdAsync(reference.Id); + if (resolvedTeam != null) + { + continue; + + } + resolvedTeam = await GetEntityByNameAsync(reference.Name); + if (resolvedTeam != null) + { + entity[attribute.Key] = new EntityReference(EntityName, resolvedTeam.Id); + + } + else + { + entity.Attributes.Remove(attribute.Key); + } + } + return await base.InterceptAsync(entity); + } + +} diff --git a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Import/Interceptors/EntityInterceptorChainBuilder.cs b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Import/Interceptors/EntityInterceptorChainBuilder.cs new file mode 100644 index 0000000..93950f2 --- /dev/null +++ b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Import/Interceptors/EntityInterceptorChainBuilder.cs @@ -0,0 +1,26 @@ +namespace Dataverse.ConfigurationMigrationTool.Console.Features.Import.Interceptors; +public class EntityInterceptorChainBuilder +{ + private readonly IServiceProvider _serviceProvider; + private IEntityInterceptor Root { get; set; } + private IEntityInterceptor CurrentSuccessor { get; set; } + public EntityInterceptorChainBuilder(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + public EntityInterceptorChainBuilder StartsWith() where TInterceptor : class, IEntityInterceptor + { + var interceptor = _serviceProvider.BuildService(); + + Root = interceptor; + CurrentSuccessor = interceptor; + return this; + } + public EntityInterceptorChainBuilder ThenWith() where TInterceptor : class, IEntityInterceptor + { + CurrentSuccessor = CurrentSuccessor.SetSuccessor(_serviceProvider.BuildService()); + return this; + } + public IEntityInterceptor BuildChain() => Root; + +} diff --git a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Import/Interceptors/TargetTransactionCurrencyInterceptor.cs b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Import/Interceptors/TargetTransactionCurrencyInterceptor.cs new file mode 100644 index 0000000..3b50726 --- /dev/null +++ b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Import/Interceptors/TargetTransactionCurrencyInterceptor.cs @@ -0,0 +1,22 @@ +using Dataverse.ConfigurationMigrationTool.Console.Services.Dataverse; +using Microsoft.Xrm.Sdk; + +namespace Dataverse.ConfigurationMigrationTool.Console.Features.Import.Interceptors; +public class TargetTransactionCurrencyInterceptor : BaseEntityReferenceFieldInterceptor +{ + private readonly IProductCatalogService _productCatalogService; + public TargetTransactionCurrencyInterceptor(IProductCatalogService productCatalogService) : base("transactioncurrency") + { + _productCatalogService = productCatalogService; + } + + protected override async Task GetEntityByIdAsync(Guid Id) + { + return await _productCatalogService.GetTransacationCurrencyById(Id); + } + + protected override async Task GetEntityByNameAsync(string Name) + { + return await _productCatalogService.GetTransacationCurrencyByName(Name); + } +} diff --git a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Import/Interceptors/TargetUoMInterceptor.cs b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Import/Interceptors/TargetUoMInterceptor.cs new file mode 100644 index 0000000..ea35715 --- /dev/null +++ b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Import/Interceptors/TargetUoMInterceptor.cs @@ -0,0 +1,22 @@ +using Dataverse.ConfigurationMigrationTool.Console.Services.Dataverse; +using Microsoft.Xrm.Sdk; + +namespace Dataverse.ConfigurationMigrationTool.Console.Features.Import.Interceptors; +public class TargetUoMInterceptor : BaseEntityReferenceFieldInterceptor +{ + private readonly IProductCatalogService _productCatalogService; + public TargetUoMInterceptor(IProductCatalogService productCatalogService) : base("uom") + { + _productCatalogService = productCatalogService; + } + + protected override async Task GetEntityByIdAsync(Guid Id) + { + return await _productCatalogService.GetUoMById(Id); + } + + protected override async Task GetEntityByNameAsync(string Name) + { + return await _productCatalogService.GetUoMByName(Name); + } +} diff --git a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Import/Interceptors/TargetUoMScheduleInterceptor.cs b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Import/Interceptors/TargetUoMScheduleInterceptor.cs new file mode 100644 index 0000000..492410d --- /dev/null +++ b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Features/Import/Interceptors/TargetUoMScheduleInterceptor.cs @@ -0,0 +1,22 @@ +using Dataverse.ConfigurationMigrationTool.Console.Services.Dataverse; +using Microsoft.Xrm.Sdk; + +namespace Dataverse.ConfigurationMigrationTool.Console.Features.Import.Interceptors; +public class TargetUoMScheduleInterceptor : BaseEntityReferenceFieldInterceptor +{ + private readonly IProductCatalogService _productCatalogService; + public TargetUoMScheduleInterceptor(IProductCatalogService productCatalogService) : base("uomschedule") + { + _productCatalogService = productCatalogService; + } + + protected override async Task GetEntityByIdAsync(Guid Id) + { + return await _productCatalogService.GetUoMScheduleById(Id); + } + + protected override async Task GetEntityByNameAsync(string Name) + { + return await _productCatalogService.GetUoMScheduleByName(Name); + } +} diff --git a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Program.cs b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Program.cs index 8518e13..2b29b9f 100644 --- a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Program.cs +++ b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Program.cs @@ -24,7 +24,7 @@ { config .AddEnvironmentVariables() - .AddCommandLine(args) + .SetBasePath(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!) .AddJsonFile("appsettings.json", false, false) .AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", false, false); if (!context.HostingEnvironment.IsProduction()) @@ -35,6 +35,7 @@ //or you can configure another Configuration Provider to provide the secrets like AzureKeyvault or Hashicorp Vault. config.AddUserSecrets(Assembly.GetExecutingAssembly()); } + config.AddCommandLine(args); Console.WriteLine($"Using configuration file: appsettings.{context.HostingEnvironment.EnvironmentName}.json"); }); builder.ConfigureServices((context, services) => @@ -45,18 +46,19 @@ .AddScoped() .Configure(context.Configuration.GetSection("Dataverse")) .Configure(context.Configuration.GetSection("Dataverse")) - .AddTransient() + .AddScoped() .AddSingleton() - .AddTransient() - .AddTransient((sp) => (ServiceClient)sp.GetRequiredService().Create()) - .AddSingleton() - .AddDataverseClient() + .AddScoped() + .AddScoped((sp) => (ServiceClient)sp.GetRequiredService().Create()) + .AddScoped() + .AddDataverseClient(ServiceLifetime.Scoped) .AddConfigurationMigrationTool(context.Configuration) .UseCommands(args) .AddMemoryCache() - .AddTransient() - .AddTransient() - .AddTransient(); + .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped(); // Configure other services. }); diff --git a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Services/Dataverse/Connection/SdkDataverseServiceFactory.cs b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Services/Dataverse/Connection/SdkDataverseServiceFactory.cs index 042b79e..86c39eb 100644 --- a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Services/Dataverse/Connection/SdkDataverseServiceFactory.cs +++ b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Services/Dataverse/Connection/SdkDataverseServiceFactory.cs @@ -18,6 +18,7 @@ public SdkDataverseServiceFactory(IOptions op } public IOrganizationServiceAsync2 Create() { + _logger.LogWarning("Creating a new ServiceClient with Url: {url}", _options.Url); var serviceClient = new ServiceClient( new Uri(_options.Url), _options.ClientId.ToString(), diff --git a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Services/Dataverse/DataverseProductCatalogService.cs b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Services/Dataverse/DataverseProductCatalogService.cs new file mode 100644 index 0000000..07737e5 --- /dev/null +++ b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Services/Dataverse/DataverseProductCatalogService.cs @@ -0,0 +1,136 @@ +using Microsoft.Extensions.Caching.Memory; +using Microsoft.PowerPlatform.Dataverse.Client; +using Microsoft.Xrm.Sdk; +using Microsoft.Xrm.Sdk.Query; + +namespace Dataverse.ConfigurationMigrationTool.Console.Services.Dataverse; +public class DataverseProductCatalogService : IProductCatalogService +{ + private readonly IOrganizationServiceAsync2 _orgService; + private readonly IMemoryCache _memoryCache; + + public DataverseProductCatalogService(IOrganizationServiceAsync2 orgService, IMemoryCache memoryCache) + { + _orgService = orgService; + _memoryCache = memoryCache; + } + public async Task GetTransacationCurrencyById(Guid Id) + { + return await _memoryCache.GetOrCreateAsync($"product.GetTransacationCurrencyById.{Id}", async (_) => + { + var query = new QueryExpression("transactioncurrency") + { + ColumnSet = new ColumnSet("transactioncurrencyid", "currencyname"), + Criteria = new FilterExpression + { + Conditions = + { + new ConditionExpression("transactioncurrencyid", ConditionOperator.Equal, Id) + } + } + }; + var result = await _orgService.RetrieveMultipleAsync(query); + return result.Entities.FirstOrDefault(); + }); + } + + public async Task GetTransacationCurrencyByName(string name) + { + return await _memoryCache.GetOrCreateAsync($"product.GetTransacationCurrencyByName.{name}", async (_) => + { + var query = new QueryExpression("transactioncurrency") + { + ColumnSet = new ColumnSet("transactioncurrencyid", "currencyname"), + Criteria = new FilterExpression + { + Conditions = + { + new ConditionExpression("currencyname", ConditionOperator.Equal, name) + } + } + }; + var result = await _orgService.RetrieveMultipleAsync(query); + return result.Entities.FirstOrDefault(); + }); + } + + public async Task GetUoMById(Guid Id) + { + return await _memoryCache.GetOrCreateAsync($"product.GetUoMById.{Id}", async (_) => + { + var query = new QueryExpression("uom") + { + ColumnSet = new ColumnSet("uomid", "name"), + Criteria = new FilterExpression + { + Conditions = + { + new ConditionExpression("uomid", ConditionOperator.Equal, Id) + } + } + }; + var result = await _orgService.RetrieveMultipleAsync(query); + return result.Entities.FirstOrDefault(); + }); + } + + public async Task GetUoMByName(string name) + { + return await _memoryCache.GetOrCreateAsync($"product.GetUoMByName.{name}", async (_) => + { + var query = new QueryExpression("uom") + { + ColumnSet = new ColumnSet("uomid", "name"), + Criteria = new FilterExpression + { + Conditions = + { + new ConditionExpression("name", ConditionOperator.Equal, name) + } + } + }; + var result = await _orgService.RetrieveMultipleAsync(query); + return result.Entities.FirstOrDefault(); + }); + } + + public async Task GetUoMScheduleById(Guid Id) + { + return await _memoryCache.GetOrCreateAsync($"product.GetUoMScheduleById.{Id}", async (_) => + { + var query = new QueryExpression("uomschedule") + { + ColumnSet = new ColumnSet("uomscheduleid", "name"), + Criteria = new FilterExpression + { + Conditions = + { + new ConditionExpression("uomscheduleid", ConditionOperator.Equal, Id) + } + } + }; + var result = await _orgService.RetrieveMultipleAsync(query); + return result.Entities.FirstOrDefault(); + }); + } + + public async Task GetUoMScheduleByName(string name) + { + return await _memoryCache.GetOrCreateAsync($"product.GetUoMScheduleByName.{name}", async (_) => + { + var query = new QueryExpression("uomschedule") + { + ColumnSet = new ColumnSet("uomscheduleid", "name"), + Criteria = new FilterExpression + { + Conditions = + { + new ConditionExpression("name", ConditionOperator.Equal, name) + } + } + }; + var result = await _orgService.RetrieveMultipleAsync(query); + return result.Entities.FirstOrDefault(); + }); + } +} diff --git a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Services/Dataverse/IProductCatalogService.cs b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Services/Dataverse/IProductCatalogService.cs new file mode 100644 index 0000000..41142a8 --- /dev/null +++ b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/Services/Dataverse/IProductCatalogService.cs @@ -0,0 +1,12 @@ +using Microsoft.Xrm.Sdk; + +namespace Dataverse.ConfigurationMigrationTool.Console.Services.Dataverse; +public interface IProductCatalogService +{ + Task GetTransacationCurrencyById(Guid Id); + Task GetTransacationCurrencyByName(string name); + Task GetUoMById(Guid Id); + Task GetUoMByName(string name); + Task GetUoMScheduleById(Guid Id); + Task GetUoMScheduleByName(string name); +} diff --git a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/appsettings.json b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/appsettings.json index 537be9f..0fa6b5f 100644 --- a/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/appsettings.json +++ b/src/Dataverse.ConfigurationMigrationTool/Dataverse.ConfigurationMigrationTool.Console/appsettings.json @@ -2,7 +2,7 @@ "Logging": { "LogLevel": { "Default": "Information", - "Dataverse.ConfigurationMigrationTool.Console.Services.Dataverse.SdkDataverseServiceFactory": "Warning" + "Dataverse.ConfigurationMigrationTool.Console.Services.Dataverse.Connection.SdkDataverseServiceFactory": "Warning" } }, "Dataverse": {