/// <summary>
        /// Called when a scheduled job executes
        /// </summary>
        /// <returns>A status message to be stored in the database log and visible from admin mode</returns>
        /// <exception cref="T:System.AggregateException">The exception that contains all the individual exceptions thrown on all threads.</exception>
        public override string Execute()
        {
            this.OnStatusChanged("Starting to update/train the model");

            List <ProductEntry> products    = this.GetProductEntries();
            ITransformer        transformer = this.LoadDataAndTrain(products: products);

            this.predictionEngine = new MlModelEngine <ProductEntry, CoPurchasePrediction>(transformer: transformer);

            this.OnStatusChanged("Prediction model has been updated and trained");

            ConcurrentBag <IProductCoPurchasePrediction> predictions = new ConcurrentBag <IProductCoPurchasePrediction>();

            List <int> productIds = this.GetAllVariantIds().ToList();

            this.OnStatusChanged("Creating predictions");

            foreach (int productId in productIds)
            {
                if (this.stopSignaled)
                {
                    break;
                }

                List <int> coPurchaseProductIds = productIds.Where(id => id != productId).ToList();

                Parallel.ForEach(
                    source: coPurchaseProductIds,
                    coPurchaseProductId =>
                {
                    IProductCoPurchasePrediction productCoPurchasePrediction = this.GetPrediction(
                        productId: productId,
                        coPurchaseProductId: coPurchaseProductId);

                    if (productCoPurchasePrediction != null)
                    {
                        predictions.Add(item: productCoPurchasePrediction);
                    }
                });
            }

            try
            {
                this.recommendationRepository.AddOrUpdate(predictions: predictions);
            }
            catch (Exception exception)
            {
                this.log.Error(
                    $"[Prediction Engine] could not create predictions: {exception.Message}",
                    exception: exception);
                return($"Could not create predictions: {exception.Message}");
            }

            // For long running jobs periodically check if stop is signaled and if so stop execution
            return(this.stopSignaled ? "Stop of job was called" : $"Created {predictions.Count} predictions");
        }
        /// <summary>
        /// Adds or updates <see cref="IProductCoPurchasePrediction" /> in the repository.
        /// </summary>
        /// <param name="productCoPurchasePrediction">The ProductCoPurchasePrediction.</param>
        public virtual void AddOrUpdate(IProductCoPurchasePrediction productCoPurchasePrediction)
        {
            string sqlCommand =
                $@"UPDATE {PredictionsTable} SET [Score] =  @score WHERE [ProductId] = @productId AND [CoPurchaseProductId] = @coPurchaseProductId
                   IF @@ROWCOUNT = 0
                   INSERT INTO {PredictionsTable} ([ProductId], [CoPurchaseProductId], [Score]) VALUES (@productId, @coPurchaseProductId, @score)";

            this.ExecuteNonQuery(
                () => this.CreateCommand(
                    sqlCommand: sqlCommand,
                    this.CreateIntParameter("productId", value: productCoPurchasePrediction.ProductId),
                    this.CreateIntParameter("coPurchaseProductId", value: productCoPurchasePrediction.CoPurchaseProductId),
                    this.CreateFloatParameter("score", value: productCoPurchasePrediction.Score)),
                "An error occurred while updating or creating a prediction.");
        }