public virtual async Task <Product> CloneProductAsync( Product product, string cloneName, bool isPublished, bool copyAssociatedProducts = true) { Guard.NotNull(product, nameof(product)); Guard.NotEmpty(cloneName, nameof(cloneName)); var localizedKeySelectors = new List <Expression <Func <Product, string> > > { x => x.Name, x => x.ShortDescription, x => x.FullDescription, x => x.MetaKeywords, x => x.MetaDescription, x => x.MetaTitle, x => x.BundleTitleText }; var clone = new Product(); var utcNow = DateTime.UtcNow; var languages = await _languageService.GetAllLanguagesAsync(true); int?sampleDownloadId = null; // Enable hooks for slugs cache invalidation. using (_chronometer.Step("Clone product " + product.Id)) using (var scope = new DbContextScope(_db, autoDetectChanges: false, hooksEnabled: true, deferCommit: true, forceNoTracking: true)) { if (product.HasSampleDownload && product.SampleDownload != null) { var sampleDownloadClone = product.SampleDownload.Clone(); _db.Downloads.Add(sampleDownloadClone); await scope.CommitAsync(); sampleDownloadId = sampleDownloadClone.Id; } var props = FastProperty.GetProperties(typeof(Product), PropertyCachingStrategy.EagerCached); foreach (var prop in props.Values) { if (prop.IsComplexType) { continue; } if (!prop.IsPublicSettable) { continue; } prop.SetValue(clone, prop.GetValue(product)); } clone.Id = 0; clone.Name = cloneName; clone.SampleDownloadId = sampleDownloadId; clone.Published = isPublished; clone.CreatedOnUtc = utcNow; clone.UpdatedOnUtc = utcNow; // Category mappings. clone.ProductCategories.AddRange(product.ProductCategories.Select(x => new ProductCategory { CategoryId = x.CategoryId, IsFeaturedProduct = x.IsFeaturedProduct, DisplayOrder = x.DisplayOrder })); // Manufacturer mappings. clone.ProductManufacturers.AddRange(product.ProductManufacturers.Select(x => new ProductManufacturer { ManufacturerId = x.ManufacturerId, IsFeaturedProduct = x.IsFeaturedProduct, DisplayOrder = x.DisplayOrder })); // Media file mappings. clone.ProductPictures.AddRange(product.ProductPictures.Select(x => new ProductMediaFile { MediaFileId = x.MediaFileId, DisplayOrder = x.DisplayOrder })); if (clone.MainPictureId == null) { clone.MainPictureId = product.ProductPictures.FirstOrDefault()?.MediaFileId; } // Product specification attributes. clone.ProductSpecificationAttributes.AddRange(product.ProductSpecificationAttributes.Select(x => new ProductSpecificationAttribute { SpecificationAttributeOptionId = x.SpecificationAttributeOptionId, AllowFiltering = x.AllowFiltering, ShowOnProductPage = x.ShowOnProductPage, DisplayOrder = x.DisplayOrder })); // Tier prices. clone.TierPrices.AddRange(product.TierPrices.Select(x => new TierPrice { StoreId = x.StoreId, CustomerRoleId = x.CustomerRoleId, Quantity = x.Quantity, Price = x.Price, CalculationMethod = x.CalculationMethod })); clone.HasTierPrices = clone.TierPrices.Any(); // Discount mappings. foreach (var discount in product.AppliedDiscounts) { clone.AppliedDiscounts.Add(discount); clone.HasDiscountsApplied = true; } // Tags. foreach (var tag in product.ProductTags) { clone.ProductTags.Add(tag); } // >>>>>>> Put clone to db (from here on we need the product clone's ID). _db.Products.Add(clone); await scope.CommitAsync(); // Store mappings. var selectedStoreIds = await _storeMappingService.GetAuthorizedStoreIdsAsync(product); selectedStoreIds.Each(id => _storeMappingService.AddStoreMapping(clone, id)); await ProcessPromotions(product, clone); await ProcessSlugs(product, clone, languages); await ProcessLocalizations(product, clone, localizedKeySelectors, languages); await ProcessDownloads(product, clone); // >>>>>>> Put to db. await scope.CommitAsync(); await ProcessBundleItems(scope, product, clone, languages); // Attributes and attribute combinations. await ProcessAttributes(scope, product, clone, languages); // Update computed properties. clone.LowestAttributeCombinationPrice = await _db.ProductVariantAttributeCombinations .ApplyLowestPriceFilter(clone.Id) .Select(x => x.Price) .FirstOrDefaultAsync(); // Associated products. if (copyAssociatedProducts && product.ProductType != ProductType.BundledProduct) { await ProcessAssociatedProducts(product, clone, isPublished); } // >>>>>>> Our final commit. await scope.CommitAsync(); } return(clone); }