private int ProcessProducts(ICollection<ImportRow<Product>> batch, ImportResult result) { _rsProduct.AutoCommitEnabled = true; Product lastInserted = null; Product lastUpdated = null; foreach (var row in batch) { if (row.Count == 0) continue; Product product = null; object key; // try get by int ID if (row.TryGetValue("Id", out key) && key.ToString().ToInt() > 0) { product = _productService.GetProductById(key.ToString().ToInt()); } // try get by SKU if (product == null && row.TryGetValue("SKU", out key)) { product = _productService.GetProductBySku(key.ToString()); } // try get by GTIN if (product == null && row.TryGetValue("Gtin", out key)) { product = _productService.GetProductByGtin(key.ToString()); } if (product == null) { // a Name is required with new products. if (!row.ContainsKey("Name")) { result.AddError("The 'Name' field is required for new products. Skipping row.", row.GetRowInfo(), "Name"); continue; } product = new Product(); } row.Initialize(product, row["Name"].ToString()); if (!row.IsNew) { if (!product.Name.Equals(row["Name"].ToString(), StringComparison.OrdinalIgnoreCase)) { // Perf: use this later for SeName updates. row.NameChanged = true; } } row.SetProperty(result, product, (x) => x.Sku); row.SetProperty(result, product, (x) => x.Gtin); row.SetProperty(result, product, (x) => x.ManufacturerPartNumber); row.SetProperty(result, product, (x) => x.ProductTypeId, (int)ProductType.SimpleProduct); row.SetProperty(result, product, (x) => x.ParentGroupedProductId); row.SetProperty(result, product, (x) => x.VisibleIndividually, true); row.SetProperty(result, product, (x) => x.Name); row.SetProperty(result, product, (x) => x.ShortDescription); row.SetProperty(result, product, (x) => x.FullDescription); row.SetProperty(result, product, (x) => x.ProductTemplateId); row.SetProperty(result, product, (x) => x.ShowOnHomePage); row.SetProperty(result, product, (x) => x.HomePageDisplayOrder); row.SetProperty(result, product, (x) => x.MetaKeywords); row.SetProperty(result, product, (x) => x.MetaDescription); row.SetProperty(result, product, (x) => x.MetaTitle); row.SetProperty(result, product, (x) => x.AllowCustomerReviews, true); row.SetProperty(result, product, (x) => x.Published, true); row.SetProperty(result, product, (x) => x.IsGiftCard); row.SetProperty(result, product, (x) => x.GiftCardTypeId); row.SetProperty(result, product, (x) => x.RequireOtherProducts); row.SetProperty(result, product, (x) => x.RequiredProductIds); row.SetProperty(result, product, (x) => x.AutomaticallyAddRequiredProducts); row.SetProperty(result, product, (x) => x.IsDownload); row.SetProperty(result, product, (x) => x.DownloadId); row.SetProperty(result, product, (x) => x.UnlimitedDownloads, true); row.SetProperty(result, product, (x) => x.MaxNumberOfDownloads, 10); row.SetProperty(result, product, (x) => x.DownloadActivationTypeId, 1); row.SetProperty(result, product, (x) => x.HasSampleDownload); row.SetProperty(result, product, (x) => x.SampleDownloadId, (int?)null, ZeroToNull); row.SetProperty(result, product, (x) => x.HasUserAgreement); row.SetProperty(result, product, (x) => x.UserAgreementText); row.SetProperty(result, product, (x) => x.IsRecurring); row.SetProperty(result, product, (x) => x.RecurringCycleLength, 100); row.SetProperty(result, product, (x) => x.RecurringCyclePeriodId); row.SetProperty(result, product, (x) => x.RecurringTotalCycles, 10); row.SetProperty(result, product, (x) => x.IsShipEnabled, true); row.SetProperty(result, product, (x) => x.IsFreeShipping); row.SetProperty(result, product, (x) => x.AdditionalShippingCharge); row.SetProperty(result, product, (x) => x.IsEsd); row.SetProperty(result, product, (x) => x.IsTaxExempt); row.SetProperty(result, product, (x) => x.TaxCategoryId, 1); row.SetProperty(result, product, (x) => x.ManageInventoryMethodId); row.SetProperty(result, product, (x) => x.StockQuantity, 10000); row.SetProperty(result, product, (x) => x.DisplayStockAvailability); row.SetProperty(result, product, (x) => x.DisplayStockQuantity); row.SetProperty(result, product, (x) => x.MinStockQuantity); row.SetProperty(result, product, (x) => x.LowStockActivityId); row.SetProperty(result, product, (x) => x.NotifyAdminForQuantityBelow, 1); row.SetProperty(result, product, (x) => x.BackorderModeId); row.SetProperty(result, product, (x) => x.AllowBackInStockSubscriptions); row.SetProperty(result, product, (x) => x.OrderMinimumQuantity, 1); row.SetProperty(result, product, (x) => x.OrderMaximumQuantity, 10000); row.SetProperty(result, product, (x) => x.AllowedQuantities); row.SetProperty(result, product, (x) => x.DisableBuyButton); row.SetProperty(result, product, (x) => x.DisableWishlistButton); row.SetProperty(result, product, (x) => x.AvailableForPreOrder); row.SetProperty(result, product, (x) => x.CallForPrice); row.SetProperty(result, product, (x) => x.Price); row.SetProperty(result, product, (x) => x.OldPrice); row.SetProperty(result, product, (x) => x.ProductCost); row.SetProperty(result, product, (x) => x.SpecialPrice); row.SetProperty(result, product, (x) => x.SpecialPriceStartDateTimeUtc, null, OADateToUtcDate); row.SetProperty(result, product, (x) => x.SpecialPriceEndDateTimeUtc, null, OADateToUtcDate); row.SetProperty(result, product, (x) => x.CustomerEntersPrice); row.SetProperty(result, product, (x) => x.MinimumCustomerEnteredPrice); row.SetProperty(result, product, (x) => x.MaximumCustomerEnteredPrice, 1000); row.SetProperty(result, product, (x) => x.Weight); row.SetProperty(result, product, (x) => x.Length); row.SetProperty(result, product, (x) => x.Width); row.SetProperty(result, product, (x) => x.Height); row.SetProperty(result, product, (x) => x.DeliveryTimeId); row.SetProperty(result, product, (x) => x.QuantityUnitId); row.SetProperty(result, product, (x) => x.BasePriceEnabled); row.SetProperty(result, product, (x) => x.BasePriceMeasureUnit); row.SetProperty(result, product, (x) => x.BasePriceAmount); row.SetProperty(result, product, (x) => x.BasePriceBaseAmount); row.SetProperty(result, product, (x) => x.BundlePerItemPricing); row.SetProperty(result, product, (x) => x.BundlePerItemShipping); row.SetProperty(result, product, (x) => x.BundlePerItemShoppingCart); row.SetProperty(result, product, (x) => x.BundleTitleText); row.SetProperty(result, product, (x) => x.AvailableStartDateTimeUtc, null, OADateToUtcDate); row.SetProperty(result, product, (x) => x.AvailableEndDateTimeUtc, null, OADateToUtcDate); row.SetProperty(result, product, (x) => x.LimitedToStores); string storeIds = row.GetValue<string>("StoreIds"); if (storeIds.HasValue()) { _storeMappingService.SaveStoreMappings(product, row["StoreIds"].ToString() .Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries).Select(x => Convert.ToInt32(x.Trim())).ToArray()); } row.SetProperty(result, product, (x) => x.CreatedOnUtc, DateTime.UtcNow, OADateToUtcDate); product.UpdatedOnUtc = DateTime.UtcNow; if (row.IsTransient) { _rsProduct.Insert(product); lastInserted = product; } else { _rsProduct.Update(product); lastUpdated = product; } } // commit whole batch at once var num = _rsProduct.Context.SaveChanges(); // Perf: notify only about LAST insertion and update if (lastInserted != null) _eventPublisher.EntityInserted(lastInserted); if (lastUpdated != null) _eventPublisher.EntityUpdated(lastUpdated); return num; }
/// <summary> /// Import products from XLSX file /// </summary> /// <param name="stream">Stream</param> public virtual ImportResult ImportProductsFromExcel( Stream stream, CancellationToken cancellationToken, IProgress<ImportProgressInfo> progress = null) { Guard.ArgumentNotNull(() => stream); var result = new ImportResult(); int saved = 0; if (progress != null) progress.Report(new ImportProgressInfo { ElapsedTime = TimeSpan.Zero }); using (var scope = new DbContextScope(ctx: _rsProduct.Context, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) { try { using (var segmenter = new DataSegmenter<Product>(stream)) { result.TotalRecords = segmenter.TotalRows; while (segmenter.ReadNextBatch() && !cancellationToken.IsCancellationRequested) { var batch = segmenter.CurrentBatch; // Perf: detach all entities _rsProduct.Context.DetachAll(false); // Update progress for calling thread if (progress != null) { progress.Report(new ImportProgressInfo { TotalRecords = result.TotalRecords, TotalProcessed = segmenter.CurrentSegmentFirstRowIndex - 1, NewRecords = result.NewRecords, ModifiedRecords = result.ModifiedRecords, ElapsedTime = DateTime.UtcNow - result.StartDateUtc, TotalWarnings = result.Messages.Count(x => x.MessageType == ImportMessageType.Warning), TotalErrors = result.Messages.Count(x => x.MessageType == ImportMessageType.Error), }); } // =========================================================================== // 1.) Import products // =========================================================================== try { saved = ProcessProducts(batch, result); } catch (Exception ex) { result.AddError(ex, segmenter.CurrentSegment, "ProcessProducts"); } // reduce batch to saved (valid) products. // No need to perform import operations on errored products. batch = batch.Where(x => x.Entity != null && !x.IsTransient).AsReadOnly(); // update result object result.NewRecords += batch.Count(x => x.IsNew && !x.IsTransient); result.ModifiedRecords += batch.Count(x => !x.IsNew && !x.IsTransient); // =========================================================================== // 2.) Import SEO Slugs // IMPORTANT: Unlike with Products AutoCommitEnabled must be TRUE, // as Slugs are going to be validated against existing ones in DB. // =========================================================================== if (batch.Any(x => x.IsNew || (x.ContainsKey("SeName") || x.NameChanged))) { try { _rsProduct.Context.AutoDetectChangesEnabled = true; ProcessSlugs(batch, result); } catch (Exception ex) { result.AddError(ex, segmenter.CurrentSegment, "ProcessSeoSlugs"); } finally { _rsProduct.Context.AutoDetectChangesEnabled = false; } } // =========================================================================== // 3.) Import Localizations // =========================================================================== try { ProcessLocalizations(batch, result); } catch (Exception ex) { result.AddError(ex, segmenter.CurrentSegment, "ProcessLocalizations"); } // =========================================================================== // 4.) Import product category mappings // =========================================================================== if (batch.Any(x => x.ContainsKey("CategoryIds"))) { try { ProcessProductCategories(batch, result); } catch (Exception ex) { result.AddError(ex, segmenter.CurrentSegment, "ProcessProductCategories"); } } // =========================================================================== // 5.) Import product manufacturer mappings // =========================================================================== if (batch.Any(x => x.ContainsKey("ManufacturerIds"))) { try { ProcessProductManufacturers(batch, result); } catch (Exception ex) { result.AddError(ex, segmenter.CurrentSegment, "ProcessProductManufacturers"); } } // =========================================================================== // 6.) Import product picture mappings // =========================================================================== if (batch.Any(x => x.ContainsKey("Picture1") || x.ContainsKey("Picture2") || x.ContainsKey("Picture3"))) { try { ProcessProductPictures(batch, result); } catch (Exception ex) { result.AddError(ex, segmenter.CurrentSegment, "ProcessProductPictures"); } } } } } catch (Exception ex) { result.AddError(ex, null, "ReadFile"); } } result.EndDateUtc = DateTime.UtcNow; if (cancellationToken.IsCancellationRequested) { result.Cancelled = true; result.AddInfo("Import task was cancelled by user"); } return result; }