/// <summary>
 /// Initialize Organization client for D365 integration
 /// </summary>
 protected void InitializeOrganizationServiceManager(Instance instance = null)
 {
     if (JobSettings.ConnectionStringDefined)
     {
         ProxiesPool = new ProxiesPool(JobSettings.CrmConnectionString, this.Logger);
         Logger.LogInformation($"Organization service initialized to {ProxiesPool.InstanceUri} with user ID : {ProxiesPool.MainProxy.CallerId} !");
     }
     else
     {
         if (instance != null)
         {
             JobSettings.SelectedInstanceName = instance.UniqueName;
             if (!string.IsNullOrWhiteSpace(instance.ConnectionString))
             {
                 ProxiesPool = new ProxiesPool(instance.ConnectionString, this.Logger);
                 Logger.LogInformation($"Organization service initialized to {instance.DisplayName} with user {JobSettings.CrmUserName} [ID : {ProxiesPool.MainProxy.CallerId} - Url : {ProxiesPool.MainProxy.EndpointUrl}]!");
             }
             else
             {
                 throw new Exception("ConnectionString attribute is not defined in instances.xml!");
             }
         }
     }
 }
        /// <summary>
        /// Run the job
        /// </summary>
        /// <param name="job"></param>
        /// <returns></returns>
        public override bool Run()
        {
            var progressWriter = new MultiThreadFileWriter(ProgressFilePath);

            Logger.LogInformation($"Checking {ProgressFilePath} existence...");
            // Load already processed items from tracking file if exists
            var processedItems = new List <string>();

            if (File.Exists(ProgressFilePath))
            {
                Logger.LogInformation($"File {ProgressFilePath} detected! Continue process at it last state");

                var lines = File.ReadAllLines(ProgressFilePath);
                processedItems = lines.ToList();
            }
            else
            {
                Logger.LogInformation($"File {ProgressFilePath} not detected! Start process from 0");
            }

            var jobName = GetName();
            var query   = GetQuery(CallerId);

            query.PageInfo.Count = JobSettings.QueryRecordLimit;
            query.NoLock         = true;

            var results = ProxiesPool.MainProxy.RetrieveAll(query);

            Logger.LogInformation($"Retrieved {results.Entities.Count} records from CRM");
            var processedItemCount = 0;
            var stopwatch          = Stopwatch.StartNew();
            var data      = PrepareData(results.Entities);
            var dataCount = data.Count();

            Logger.LogInformation($"{dataCount} records to process");

            var threads             = (this.OverrideThreadNumber.HasValue) ? this.OverrideThreadNumber : JobSettings.ThreadNumber;
            var progressDisplayStep = (this.OverrideProgressDisplayStep.HasValue) ? this.OverrideProgressDisplayStep.Value : DefaultProgressDisplayStep;

            Parallel.ForEach(data,
                             new ParallelOptions()
            {
                MaxDegreeOfParallelism = threads.Value
            },
                             () =>
            {
                var proxy = ProxiesPool.GetProxy();
                return(new
                {
                    Proxy = proxy
                });
            },
                             (item, loopState, context) =>
            {
                var jobExecutionContext = new JobExecutionContext(context.Proxy, item);
                jobExecutionContext.PushMetrics(base.ContextProperties);

                // Increment progress index
                Interlocked.Increment(ref processedItemCount);

                // Increment progress bar every x records
                if (processedItemCount % progressDisplayStep == 0)
                {
                    Logger.LogInformation($"Processing record {processedItemCount} / {dataCount}");
                }

                // Exit if record has already been processed
                if (processedItems.Contains(item.Id.ToString()))
                {
                    return(context);
                }

                try
                {
                    ProcessRecord(jobExecutionContext);
                    Logger.LogSuccess("Record processed with success!", jobExecutionContext.DumpMetrics());

                    // Track job progress
                    progressWriter.Write(item.Id.ToString());
                }
                catch (FaultException <OrganizationServiceFault> faultException)
                {
                    var properties = jobExecutionContext.DumpMetrics().MergeWith(faultException.ExportProperties());
                    Logger.LogFailure(faultException, properties);
                }
                catch (Exception ex)
                {
                    Logger.LogFailure(ex, jobExecutionContext.DumpMetrics());
                }

                return(context);
            },
                             (context) =>
            {
                context.Proxy.Dispose();
            });

            stopwatch.Stop();
            var speed = Utilities.GetSpeed(stopwatch.Elapsed.TotalMilliseconds, results.Entities.Count);

            Logger.LogInformation($"{dataCount} records processed in {stopwatch.Elapsed.TotalSeconds} => {stopwatch.Elapsed:g} [Speed = {speed}]!");

            if (File.Exists(ProgressFilePath))
            {
                File.Delete(ProgressFilePath);
                Logger.LogInformation($"Progress file {ProgressFilePath} removed!");
            }

            return(true);
        }
Beispiel #3
0
        /// <summary>
        /// Run the job
        /// </summary>
        /// <param name="job"></param>
        /// <returns></returns>
        public override bool Run()
        {
            var jobName = GetName();
            var query   = GetQuery(CallerId);

            query.TopCount = JobSettings.QueryRecordLimit;
            query.NoLock   = true;
            query.PageInfo = null;

            var records    = ProxiesPool.MainProxy.RetrieveMultiple(query).Entities;
            var entityName = query.EntityName;
            var startTime  = DateTime.Now;

            // Initialize last result count to prevent infinite loop
            int lastRunCount = JobSettings.QueryRecordLimit;
            var threads      = (this.OverrideThreadNumber.HasValue) ? this.OverrideThreadNumber : JobSettings.ThreadNumber;

            int totalProcessed = 0;
            int totalSuccess   = 0;
            int totalFailures  = 0;

            while (records.Count > 0)
            {
                var stopwatch = Stopwatch.StartNew();
                Logger.LogInformation($"Retrieved {records.Count} records from CRM (Entity : {entityName})");

                int currentProcessed = 0;
                int currentSuccess   = 0;
                int currentFailures  = 0;

                Parallel.ForEach(
                    records,
                    new ParallelOptions()
                {
                    MaxDegreeOfParallelism = threads.Value
                },
                    () =>
                {
                    var proxy = ProxiesPool.GetProxy();
                    return(new
                    {
                        Proxy = proxy
                    });
                },
                    (item, loopState, context) =>
                {
                    var jobExecutionContext = new JobExecutionContext(context.Proxy, item);
                    jobExecutionContext.PushMetrics(base.ContextProperties);

                    try
                    {
                        Interlocked.Increment(ref totalProcessed);
                        Interlocked.Increment(ref currentProcessed);

                        ProcessRecord(jobExecutionContext);

                        Interlocked.Increment(ref totalSuccess);
                        Interlocked.Increment(ref currentSuccess);
                        Logger.LogSuccess($"Record processed with success! (Entity : {entityName})", jobExecutionContext.DumpMetrics());
                    }
                    catch (FaultException <OrganizationServiceFault> faultException)
                    {
                        var properties = jobExecutionContext.DumpMetrics().MergeWith(faultException.ExportProperties());
                        Interlocked.Increment(ref totalFailures);
                        Interlocked.Increment(ref currentFailures);
                        Logger.LogFailure(faultException, properties);
                    }
                    catch (Exception ex)
                    {
                        Interlocked.Increment(ref totalFailures);
                        Interlocked.Increment(ref currentFailures);
                        Logger.LogFailure(ex, jobExecutionContext.DumpMetrics());
                    }
                    return(context);
                },
                    (context) =>
                {
                    context.Proxy.Dispose();
                }
                    );

                stopwatch.Stop();
                var speed = Utilities.GetSpeed(stopwatch.Elapsed.TotalMilliseconds, records.Count);
                Logger.LogInformation($"{currentProcessed} records (Entity : {entityName}) processed in {stopwatch.Elapsed.TotalSeconds} => {stopwatch.Elapsed:g} [Speed = {speed} | Success = {currentSuccess} | Failures = {currentFailures}]!");

                var duration    = (DateTime.Now - startTime);
                var globalSpeed = Utilities.GetSpeed(duration.TotalMilliseconds, totalProcessed);
                Logger.LogInformation($"Total = {totalProcessed} records processed (Entity : {entityName}) in {duration:g}! [Speed = {globalSpeed} | Success = {totalSuccess} | Failures = {totalFailures}]");

                // If we have the same number of record processed in this round than the previous one,
                // that mean that we don't need to continue
                if (lastRunCount < JobSettings.QueryRecordLimit && lastRunCount == records.Count)
                {
                    Logger.LogInformation($"Operation completed! (Entity : {entityName} | Reason: Infinite loop detected)");
                    return(false);
                }

                // If job duration is greater or equal to execution limit, we can stop the process
                if (duration.TotalHours >= JobSettings.MaxRunDurationInHour)
                {
                    Logger.LogInformation($"Operation completed! (Entity : {entityName} | Reason: Max duration reached)");
                    return(false);
                }

                // If we have only errors, we must stop
                if (currentFailures == records.Count)
                {
                    Logger.LogInformation($"Operation failed! (Entity : {entityName} | Reason: Too many errors detected)");
                    return(false);
                }

                lastRunCount = records.Count;

                // Retrieve records for next round
                records = ProxiesPool.MainProxy.RetrieveMultiple(query).Entities;
            }

            // If the query return nothing, we have finished!
            if (records.Count == 0)
            {
                Logger.LogInformation($"Operation completed! (Entity : {entityName} | Reason: No more data to process)");
                return(true);
            }

            return(false);
        }
Beispiel #4
0
        /// <summary>
        /// Run the job
        /// </summary>
        /// <param name="job"></param>
        /// <returns></returns>
        public override bool Run()
        {
            var jobName = GetName();
            var defaultFileSeparator = GetInputFileSeparator().First();

            List <string> lines = new List <string>();

            // Check if pivot file exists
            if (File.Exists(GetPivotFilePath()))
            {
                var fileLines = File.ReadAllLines(GetPivotFilePath());
                lines = fileLines.ToList();

                Logger.LogInformation($"Retrieved {lines.Count} from file {GetPivotFilePath()}");
            }
            else
            {
                // Load file content
                var fileLines = File.ReadAllLines(GetInputFilePath());
                lines = fileLines.ToList();

                Logger.LogInformation($"Retrieved {lines.Count} from file {GetInputFilePath()}");

                // Create pivot file that track progress and outcome
                var header     = lines.First();
                var pivotLines = new List <string>()
                {
                    string.Concat(header, defaultFileSeparator, PivotUniqueMarker, defaultFileSeparator, "RecordId", defaultFileSeparator, "Outcome", defaultFileSeparator, "Details")
                };
                File.WriteAllLines(GetPivotFilePath(), pivotLines, Encoding.UTF8);
            }
            var pivotFileWriter = new MultiThreadFileWriter(GetPivotFilePath());

            var processedItemCount = 0;
            var stopwatch          = Stopwatch.StartNew();

            var threads = (this.OverrideThreadNumber.HasValue) ? this.OverrideThreadNumber : JobSettings.ThreadNumber;

            var linesToProcess = lines.Skip(1);

            Parallel.ForEach(
                linesToProcess,
                new ParallelOptions()
            {
                MaxDegreeOfParallelism = threads.Value
            },
                () =>
            {
                var proxy = ProxiesPool.GetProxy();
                return(new
                {
                    Proxy = proxy
                });
            },
                (line, loopState, context) =>
            {
                var jobExecutionContext = new JobExecutionContext(context.Proxy);
                jobExecutionContext.PushMetrics(base.ContextProperties);

                // Increment progress index
                Interlocked.Increment(ref processedItemCount);

                var isPivotLine = line.Contains(PivotUniqueMarker);
                if (isPivotLine)
                {
                    // TODO : Handle already processed pivot lines to replay errors
                    return(context);
                }
                Entity record = null;
                try
                {
                    var lineData = line.Split(GetInputFileSeparator(), StringSplitOptions.RemoveEmptyEntries);

                    // Retrieve CRM record based on current line
                    record = SearchRecord(context.Proxy, lineData);
                    jobExecutionContext.PushRecordToMetrics(record);
                    ProcessRecord(jobExecutionContext, lineData);
                    Logger.LogSuccess("Record processed with success!", jobExecutionContext.DumpMetrics());

                    // Track progress and outcome
                    var pivotLine = string.Concat(line,
                                                  defaultFileSeparator, PivotUniqueMarker,
                                                  defaultFileSeparator, record.Id.ToString() /* RecordId */,
                                                  defaultFileSeparator, "OK" /* Outcome */,
                                                  defaultFileSeparator, "Success" /*Details */);
                    pivotFileWriter.Write(pivotLine);
                }
                catch (FaultException <OrganizationServiceFault> faultException)
                {
                    var properties = jobExecutionContext.DumpMetrics().MergeWith(faultException.ExportProperties());
                    Logger.LogFailure(faultException, properties);

                    // Track progress and outcome
                    var pivotLine = string.Concat(line,
                                                  defaultFileSeparator, PivotUniqueMarker,
                                                  defaultFileSeparator, record?.Id.ToString() /* RecordId */,
                                                  defaultFileSeparator, "KO" /* Outcome */,
                                                  defaultFileSeparator, faultException.Message /*Details */);
                    pivotFileWriter.Write(pivotLine);
                }
                catch (Exception ex)
                {
                    Logger.LogFailure(ex, jobExecutionContext.DumpMetrics());

                    // Track progress and outcome
                    var pivotLine = string.Concat(line,
                                                  defaultFileSeparator, PivotUniqueMarker,
                                                  defaultFileSeparator, record?.Id.ToString() /* RecordId */,
                                                  defaultFileSeparator, "KO" /* Outcome */,
                                                  defaultFileSeparator, ex.Message /*Details */);
                    pivotFileWriter.Write(pivotLine);
                }

                return(context);
            },
                (context) =>
            {
                context.Proxy.Dispose();
            });
            stopwatch.Stop();
            var speed = Utilities.GetSpeed(stopwatch.Elapsed.TotalMilliseconds, lines.Count);

            Logger.LogInformation($"{lines.Count} records processed in {stopwatch.Elapsed.TotalSeconds} => {stopwatch.Elapsed.ToString("g")} [Speed = {speed}]!");

            return(true);
        }