private async Task ProcessBundleItems(DbContextScope scope, Product product, Product clone, IEnumerable <Language> languages) { var localizedKeySelectors = new List <Expression <Func <ProductBundleItem, string> > > { x => x.Name, x => x.ShortDescription }; var bundledItems = await _db.ProductBundleItem .AsNoTracking() .Include(x => x.AttributeFilters) .ApplyBundledProductsFilter(new[] { product.Id }, true) .ToListAsync(); if (!bundledItems.Any()) { return; } var itemMap = new Dictionary <int, ProductBundleItem>(); foreach (var bundledItem in bundledItems) { var newBundleItem = bundledItem.Clone(); newBundleItem.BundleProductId = clone.Id; itemMap[bundledItem.Id] = newBundleItem; } _db.ProductBundleItem.AddRange(itemMap.Select(x => x.Value).Reverse()); await scope.CommitAsync(); foreach (var bundledItem in bundledItems) { if (!itemMap.TryGetValue(bundledItem.Id, out var newBundleItem)) { continue; } foreach (var itemFilter in bundledItem.AttributeFilters) { var newItemFilter = itemFilter.Clone(); newItemFilter.BundleItemId = newBundleItem.Id; _db.ProductBundleItemAttributeFilter.Add(newItemFilter); } await ProcessLocalizations(bundledItem, newBundleItem, localizedKeySelectors, languages); } await scope.CommitAsync(); }
private async Task MoveMedia() { if (_config.StoreMediaInDB) { return; } // All pictures have initially been stored in the DB. Move the binaries to disk as configured. var fileSystemStorageProvider = EngineContext.Current.ResolveService <Func <IMediaStorageProvider> >().Invoke(); using (var scope = new DbContextScope(_db, autoDetectChanges: true)) { var mediaFiles = await _db.MediaFiles .Include(x => x.MediaStorage) .Where(x => x.MediaStorageId != null) .ToListAsync(); foreach (var mediaFile in mediaFiles) { if (mediaFile.MediaStorage?.Data?.LongLength > 0) { await fileSystemStorageProvider.SaveAsync(mediaFile, MediaStorageItem.FromStream(mediaFile.MediaStorage.Data.ToStream())); mediaFile.MediaStorageId = null; mediaFile.MediaStorage = null; } } await scope.CommitAsync(); } }
public async Task <MediaFileInfo> ReplaceFileAsync(MediaFile file, Stream inStream, string newFileName) { Guard.NotNull(file, nameof(file)); Guard.NotNull(inStream, nameof(inStream)); Guard.NotEmpty(newFileName, nameof(newFileName)); var fileInfo = ConvertMediaFile(file); var pathData = CreatePathData(fileInfo.Path); pathData.FileName = newFileName; var storageItem = ProcessFile(ref file, pathData, inStream, false, DuplicateFileHandling.Overwrite, MimeValidationType.MediaTypeMustMatch); using (var scope = new DbContextScope(_fileRepo.Context, autoCommit: false)) { try { await _storageProvider.SaveAsync(file, storageItem); await scope.CommitAsync(); } catch (Exception ex) { Logger.Error(ex); } } return(fileInfo); }
protected virtual async Task CheckOrderStatusAsync(Order order) { Guard.NotNull(order, nameof(order)); using var scope = new DbContextScope(_db, deferCommit: true); if (order.PaymentStatus == PaymentStatus.Paid && !order.PaidDateUtc.HasValue) { order.PaidDateUtc = DateTime.UtcNow; } if (order.OrderStatus == OrderStatus.Pending && (order.PaymentStatus == PaymentStatus.Authorized || order.PaymentStatus == PaymentStatus.Paid)) { await SetOrderStatusAsync(order, OrderStatus.Processing, false); } if (order.OrderStatus == OrderStatus.Pending && (order.ShippingStatus == ShippingStatus.PartiallyShipped || order.ShippingStatus == ShippingStatus.Shipped || order.ShippingStatus == ShippingStatus.Delivered)) { await SetOrderStatusAsync(order, OrderStatus.Processing, false); } if (order.OrderStatus != OrderStatus.Cancelled && order.OrderStatus != OrderStatus.Complete && order.PaymentStatus == PaymentStatus.Paid && (order.ShippingStatus == ShippingStatus.ShippingNotRequired || order.ShippingStatus == ShippingStatus.Delivered)) { await SetOrderStatusAsync(order, OrderStatus.Complete, true); } await scope.CommitAsync(); }
protected virtual async Task <int> ProcessCustomerRolesAsync(ImportExecuteContext context, DbContextScope scope, IEnumerable <ImportRow <Customer> > batch) { var cargo = await GetCargoData(context); if (!cargo.AllowManagingCustomerRoles) { return(0); } foreach (var row in batch) { var customer = row.Entity; var importRoleSystemNames = row.GetDataValue <List <string> >("CustomerRoleSystemNames"); var assignedRoles = customer.CustomerRoleMappings .Where(x => !x.IsSystemMapping) .Select(x => x.CustomerRole) .ToDictionarySafe(x => x.SystemName, StringComparer.OrdinalIgnoreCase); // Roles to remove. foreach (var customerRole in assignedRoles) { var systemName = customerRole.Key; if (!systemName.EqualsNoCase(SystemCustomerRoleNames.Administrators) && !systemName.EqualsNoCase(SystemCustomerRoleNames.SuperAdministrators) && !importRoleSystemNames.Contains(systemName)) { var mappings = customer.CustomerRoleMappings.Where(x => !x.IsSystemMapping && x.CustomerRoleId == customerRole.Value.Id); _db.CustomerRoleMappings.RemoveRange(mappings); } } // Roles to add. foreach (var systemName in importRoleSystemNames) { if (systemName.EqualsNoCase(SystemCustomerRoleNames.Administrators) || systemName.EqualsNoCase(SystemCustomerRoleNames.SuperAdministrators)) { context.Result.AddInfo("Security. Ignored administrator role.", row.RowInfo, "CustomerRoleSystemNames"); } else if (!assignedRoles.ContainsKey(systemName)) { // Add role mapping but never insert roles. // Be careful not to insert mappings several times! if (cargo.CustomerRoleIds.TryGetValue(systemName, out var roleId)) { _db.CustomerRoleMappings.Add(new CustomerRoleMapping { CustomerId = customer.Id, CustomerRoleId = roleId }); } } } } var num = await scope.CommitAsync(context.CancelToken); return(num); }
public async Task <MediaFileInfo> SaveFileAsync(string path, Stream stream, bool isTransient = true, DuplicateFileHandling dupeFileHandling = DuplicateFileHandling.ThrowError) { var pathData = CreatePathData(path); var file = await _fileRepo.Table.FirstOrDefaultAsync(x => x.Name == pathData.FileName && x.FolderId == pathData.Folder.Id); var isNewFile = file == null; var storageItem = ProcessFile(ref file, pathData, stream, isTransient, dupeFileHandling); using (var scope = new DbContextScope(_fileRepo.Context, autoCommit: false)) { if (file.Id == 0) { _fileRepo.Insert(file); await scope.CommitAsync(); } try { await _storageProvider.SaveAsync(file, storageItem); await scope.CommitAsync(); } catch (Exception ex) { if (isNewFile) { // New file's metadata should be removed on storage save failure immediately DeleteFile(file, true, true); await scope.CommitAsync(); } Logger.Error(ex); } } return(ConvertMediaFile(file, pathData.Folder)); }
protected virtual async Task <int> ProcessAddressesAsync(ImportExecuteContext context, DbContextScope scope, IEnumerable <ImportRow <Customer> > batch) { var cargo = await GetCargoData(context); foreach (var row in batch) { ImportAddress("BillingAddress.", row, context, cargo); ImportAddress("ShippingAddress.", row, context, cargo); } var num = await scope.CommitAsync(context.CancelToken); return(num); }
private async Task <MediaFolderInfo> InternalCopyFolder( DbContextScope scope, TreeNode <MediaFolderNode> sourceNode, string destPath, DuplicateEntryHandling dupeEntryHandling, IList <DuplicateFileInfo> dupeFiles, CancellationToken cancelToken = default) { // Get dest node var destNode = _folderService.GetNodeByPath(destPath); // Dupe handling if (destNode != null && dupeEntryHandling == DuplicateEntryHandling.ThrowError) { throw _exceptionFactory.DuplicateFolder(sourceNode.Value.Path, destNode.Value); } var doDupeCheck = destNode != null; // Create dest folder if (destNode == null) { destNode = await CreateFolderAsync(destPath); } // INFO: we gonna change file name during the files loop later. var destPathData = new MediaPathData(destNode, "placeholder.txt"); // Get all source files in one go var files = await _searcher.SearchFiles( new MediaSearchQuery { FolderId = sourceNode.Value.Id }, MediaLoadFlags.AsNoTracking | MediaLoadFlags.WithTags).LoadAsync(); IDictionary <string, MediaFile> destFiles = null; HashSet <string> destNames = null; if (doDupeCheck) { // Get all files in destination folder for faster dupe selection destFiles = (await _searcher .SearchFiles(new MediaSearchQuery { FolderId = destNode.Value.Id }, MediaLoadFlags.None).LoadAsync()) .ToDictionarySafe(x => x.Name); // Make a HashSet from all file names in the destination folder for faster unique file name lookups destNames = new HashSet <string>(destFiles.Keys, StringComparer.CurrentCultureIgnoreCase); } // Holds source and copy together, 'cause we perform a two-pass copy (file first, then data) var tuples = new List <(MediaFile, MediaFile)>(500); // Copy files batched foreach (var batch in files.Slice(500)) { if (cancelToken.IsCancellationRequested) { break; } foreach (var file in batch) { if (cancelToken.IsCancellationRequested) { break; } destPathData.FileName = file.Name; // >>> Do copy var copyResult = await InternalCopyFile( file, destPathData, false /* copyData */, dupeEntryHandling, () => Task.FromResult(destFiles?.Get(file.Name)), p => UniqueFileNameChecker(p)); if (copyResult.Copy != null) { if (copyResult.IsDupe) { dupeFiles.Add(new DuplicateFileInfo { SourceFile = ConvertMediaFile(file, sourceNode.Value), DestinationFile = ConvertMediaFile(copyResult.Copy, destNode.Value), UniquePath = destPathData.FullPath }); } if (!copyResult.IsDupe || dupeEntryHandling != DuplicateEntryHandling.Skip) { // When dupe: add to processing queue only if file was NOT skipped tuples.Add((file, copyResult.Copy)); } } } if (!cancelToken.IsCancellationRequested) { // Save batch to DB (1st pass) await scope.CommitAsync(cancelToken); // Now copy file data foreach (var op in tuples) { await InternalCopyFileData(op.Item1, op.Item2); } // Save batch to DB (2nd pass) await scope.CommitAsync(cancelToken); } _db.DetachEntities <MediaFolder>(); _db.DetachEntities <MediaFile>(); tuples.Clear(); } // Copy folders foreach (var node in sourceNode.Children) { if (cancelToken.IsCancellationRequested) { break; } destPath = destNode.Value.Path + "/" + node.Value.Name; await InternalCopyFolder(scope, node, destPath, dupeEntryHandling, dupeFiles, cancelToken); } return(new MediaFolderInfo(destNode)); Task UniqueFileNameChecker(MediaPathData pathData) { if (destNames != null && _helper.CheckUniqueFileName(pathData.FileTitle, pathData.Extension, destNames, out var uniqueName)) { pathData.FileName = uniqueName; } return(Task.CompletedTask); } }
protected virtual async Task <int> ProcessGenericAttributesAsync(ImportExecuteContext context, DbContextScope scope, IEnumerable <ImportRow <Customer> > batch) { // TODO: (mg) (core) (perf) (low) Prefetch all generic attributes for whole batch and work against batch (to be implemented after initial release). var cargo = await GetCargoData(context); foreach (var row in batch) { if (_taxSettings.EuVatEnabled) { SetGenericAttribute <string>(SystemCustomerAttributeNames.VatNumber, row); } if (_customerSettings.StreetAddressEnabled) { SetGenericAttribute <string>(SystemCustomerAttributeNames.StreetAddress, row); } if (_customerSettings.StreetAddress2Enabled) { SetGenericAttribute <string>(SystemCustomerAttributeNames.StreetAddress2, row); } if (_customerSettings.CityEnabled) { SetGenericAttribute <string>(SystemCustomerAttributeNames.City, row); } if (_customerSettings.ZipPostalCodeEnabled) { SetGenericAttribute <string>(SystemCustomerAttributeNames.ZipPostalCode, row); } if (_customerSettings.CountryEnabled) { SetGenericAttribute <int>(SystemCustomerAttributeNames.CountryId, row); } if (_customerSettings.CountryEnabled && _customerSettings.StateProvinceEnabled) { SetGenericAttribute <int>(SystemCustomerAttributeNames.StateProvinceId, row); } if (_customerSettings.PhoneEnabled) { SetGenericAttribute <string>(SystemCustomerAttributeNames.Phone, row); } if (_customerSettings.FaxEnabled) { SetGenericAttribute <string>(SystemCustomerAttributeNames.Fax, row); } // TODO: (mg) (core) ForumSettings required in CustomerImporter. //if (_forumSettings.ForumsEnabled) // SetGenericAttribute<int>(SystemCustomerAttributeNames.ForumPostCount, row); //if (_forumSettings.SignaturesEnabled) // SetGenericAttribute<string>(SystemCustomerAttributeNames.Signature, row); var countryId = CountryCodeToId(row.GetDataValue <string>("CountryCode"), cargo); var stateId = StateAbbreviationToId(countryId, row.GetDataValue <string>("StateAbbreviation"), cargo); if (countryId.HasValue) { SetGenericAttribute(SystemCustomerAttributeNames.CountryId, countryId.Value, row); } if (stateId.HasValue) { SetGenericAttribute(SystemCustomerAttributeNames.StateProvinceId, stateId.Value, row); } } var num = await scope.CommitAsync(context.CancelToken); return(num); }
private async Task InternalDeleteFolder( DbContextScope scope, MediaFolder folder, TreeNode <MediaFolderNode> node, TreeNode <MediaFolderNode> root, FolderDeleteResult result, FileHandling strategy, CancellationToken cancelToken = default) { // (perf) We gonna check file tracks, so we should preload all tracks. await _db.LoadCollectionAsync(folder, (MediaFolder x) => x.Files, false, q => q.Include(f => f.Tracks)); var files = folder.Files.ToList(); var lockedFiles = new List <MediaFile>(files.Count); var trackedFiles = new List <MediaFile>(files.Count); // First delete files if (folder.Files.Any()) { var albumId = strategy == FileHandling.MoveToRoot ? _folderService.FindAlbum(folder.Id).Value.Id : (int?)null; foreach (var batch in files.Slice(500)) { if (cancelToken.IsCancellationRequested) { break; } foreach (var file in batch) { if (cancelToken.IsCancellationRequested) { break; } if (strategy == FileHandling.Delete && file.Tracks.Any()) { // Don't delete tracked files trackedFiles.Add(file); continue; } if (strategy == FileHandling.Delete) { try { result.DeletedFileNames.Add(file.Name); await DeleteFileAsync(file, true); } catch (DeleteTrackedFileException) { trackedFiles.Add(file); } catch (IOException) { lockedFiles.Add(file); } } else if (strategy == FileHandling.SoftDelete) { await DeleteFileAsync(file, false); file.FolderId = null; result.DeletedFileNames.Add(file.Name); } else if (strategy == FileHandling.MoveToRoot) { file.FolderId = albumId; result.DeletedFileNames.Add(file.Name); } } await scope.CommitAsync(cancelToken); } if (lockedFiles.Any()) { // Retry deletion of failed files due to locking. // INFO: By default "LocalFileSystem" waits for 500ms until the lock is revoked or it throws. foreach (var lockedFile in lockedFiles.ToArray()) { if (cancelToken.IsCancellationRequested) { break; } try { await DeleteFileAsync(lockedFile, true); lockedFiles.Remove(lockedFile); } catch { } } await scope.CommitAsync(cancelToken); } } if (!cancelToken.IsCancellationRequested && lockedFiles.Count > 0) { var fullPath = CombinePaths(root.Value.Path, lockedFiles[0].Name); throw new IOException(T("Admin.Media.Exception.InUse", fullPath)); } if (!cancelToken.IsCancellationRequested && lockedFiles.Count == 0 && trackedFiles.Count == 0 && node.Children.All(x => result.DeletedFolderIds.Contains(x.Value.Id))) { // Don't delete folder if a containing file could not be deleted, // any tracked file was found or any of its child folders could not be deleted.. _db.MediaFolders.Remove(folder); await scope.CommitAsync(cancelToken); result.DeletedFolderIds.Add(folder.Id); } result.LockedFileNames = lockedFiles.Select(x => x.Name).ToList(); result.TrackedFileNames = trackedFiles.Select(x => x.Name).ToList(); }
protected virtual async Task <int> ProcessCustomersAsync(ImportExecuteContext context, DbContextScope scope, IEnumerable <ImportRow <Customer> > batch) { var cargo = await GetCargoData(context); var currentCustomer = _services.WorkContext.CurrentCustomer; var customerQuery = _db.Customers .Include(x => x.Addresses) .Include(x => x.CustomerRoleMappings) .ThenInclude(x => x.CustomerRole); foreach (var row in batch) { Customer customer = null; var id = row.GetDataValue <int>("Id"); var email = row.GetDataValue <string>("Email"); var userName = row.GetDataValue <string>("Username"); foreach (var keyName in context.KeyFieldNames) { switch (keyName) { case "Id": customer = await _db.Customers.FindByIdAsync(id, true, context.CancelToken); break; case "CustomerGuid": var customerGuid = row.GetDataValue <string>("CustomerGuid"); if (customerGuid.HasValue()) { var guid = new Guid(customerGuid); customer = await customerQuery.FirstOrDefaultAsync(x => x.CustomerGuid == guid, context.CancelToken); } break; case "Email": if (email.HasValue()) { customer = await customerQuery.FirstOrDefaultAsync(x => x.Email == email, context.CancelToken); } break; case "Username": if (userName.HasValue()) { customer = await customerQuery.FirstOrDefaultAsync(x => x.Username == userName, context.CancelToken); } break; } if (customer != null) { break; } } if (customer == null) { if (context.UpdateOnly) { ++context.Result.SkippedRecords; continue; } customer = new Customer { CustomerGuid = new Guid(), AffiliateId = 0, Active = true }; } else { await _db.LoadCollectionAsync(customer, x => x.CustomerRoleMappings, false, q => q.Include(x => x.CustomerRole), context.CancelToken); } var affiliateId = row.GetDataValue <int>("AffiliateId"); row.Initialize(customer, email ?? id.ToString()); row.SetProperty(context.Result, (x) => x.CustomerGuid); row.SetProperty(context.Result, (x) => x.Username); row.SetProperty(context.Result, (x) => x.Email); row.SetProperty(context.Result, (x) => x.Salutation); row.SetProperty(context.Result, (x) => x.FullName); row.SetProperty(context.Result, (x) => x.FirstName); row.SetProperty(context.Result, (x) => x.LastName); if (_customerSettings.TitleEnabled) { row.SetProperty(context.Result, (x) => x.Title); } if (_customerSettings.CompanyEnabled) { row.SetProperty(context.Result, (x) => x.Company); } if (_customerSettings.DateOfBirthEnabled) { row.SetProperty(context.Result, (x) => x.BirthDate); } if (_privacySettings.StoreLastIpAddress) { row.SetProperty(context.Result, (x) => x.LastIpAddress); } if (email.HasValue() && currentCustomer.Email.EqualsNoCase(email)) { context.Result.AddInfo("Security. Ignored password of current customer (who started this import).", row.RowInfo, "Password"); } else { row.SetProperty(context.Result, (x) => x.Password); row.SetProperty(context.Result, (x) => x.PasswordFormatId); row.SetProperty(context.Result, (x) => x.PasswordSalt); } row.SetProperty(context.Result, (x) => x.AdminComment); row.SetProperty(context.Result, (x) => x.IsTaxExempt); row.SetProperty(context.Result, (x) => x.Active); row.SetProperty(context.Result, (x) => x.CreatedOnUtc, context.UtcNow); row.SetProperty(context.Result, (x) => x.LastActivityDateUtc, context.UtcNow); if (_taxSettings.EuVatEnabled) { row.SetProperty(context.Result, (x) => x.VatNumberStatusId); } if (_dateTimeSettings.AllowCustomersToSetTimeZone) { row.SetProperty(context.Result, (x) => x.TimeZoneId); } if (_customerSettings.GenderEnabled) { row.SetProperty(context.Result, (x) => x.Gender); } if (affiliateId > 0 && cargo.AffiliateIds.Contains(affiliateId)) { customer.AffiliateId = affiliateId; } string customerNumber = null; if (_customerSettings.CustomerNumberMethod == CustomerNumberMethod.AutomaticallySet && row.IsTransient) { customerNumber = row.Entity.Id.ToString(); } else if (_customerSettings.CustomerNumberMethod == CustomerNumberMethod.Enabled && !row.IsTransient && row.HasDataValue("CustomerNumber")) { customerNumber = row.GetDataValue <string>("CustomerNumber"); } if (customerNumber.HasValue() || !cargo.CustomerNumbers.Contains(customerNumber)) { row.Entity.CustomerNumber = customerNumber; if (!customerNumber.IsEmpty()) { cargo.CustomerNumbers.Add(customerNumber); } } if (row.IsTransient) { _db.Customers.Add(customer); } } // Commit whole batch at once. var num = await scope.CommitAsync(context.CancelToken); return(num); }
public async Task Run(TaskExecutionContext ctx, CancellationToken cancelToken = default) { var count = 0; var numDeleted = 0; var numAdded = 0; var numCategories = 0; var pageSize = 500; var pageIndex = -1; var categoryIds = ctx.Parameters.ContainsKey("CategoryIds") ? ctx.Parameters["CategoryIds"].ToIntArray() : null; // Hooks are enabled because search index needs to be updated. using (var scope = new DbContextScope(_db, autoDetectChanges: false, hooksEnabled: true, deferCommit: true)) { // Delete existing system mappings. var deleteQuery = _db.ProductCategories.Where(x => x.IsSystemMapping); if (categoryIds != null) { deleteQuery = deleteQuery.Where(x => categoryIds.Contains(x.CategoryId)); } numDeleted = await deleteQuery.BatchDeleteAsync(cancelToken); // Insert new product category mappings. var categoryQuery = _db.Categories .Include(x => x.RuleSets) .AsNoTracking(); if (categoryIds != null) { categoryQuery = categoryQuery.Where(x => categoryIds.Contains(x.Id)); } var categories = await categoryQuery .Where(x => x.Published && x.RuleSets.Any(y => y.IsActive)) .ToListAsync(cancelToken); numCategories = categories.Count; foreach (var category in categories) { var ruleSetProductIds = new HashSet <int>(); await ctx.SetProgressAsync(++count, categories.Count, $"Add product mappings for category \"{category.Name.NaIfEmpty()}\"."); // Execute active rule sets and collect product ids. foreach (var ruleSet in category.RuleSets.Where(x => x.IsActive)) { if (cancelToken.IsCancellationRequested) { return; } var expressionGroup = await _ruleService.CreateExpressionGroupAsync(ruleSet, (IRuleVisitor)_productRuleProvider); if (expressionGroup is SearchFilterExpression expression) { pageIndex = -1; while (true) { // Do not touch searchResult.Hits. We only need the product identifiers. var searchResult = await _productRuleProvider.SearchAsync(new[] { expression }, ++pageIndex, pageSize); ruleSetProductIds.AddRange(searchResult.HitsEntityIds); if (pageIndex >= (searchResult.TotalHitsCount / pageSize)) { break; } } } } // Add mappings. if (ruleSetProductIds.Any()) { foreach (var chunk in ruleSetProductIds.Slice(500)) { if (cancelToken.IsCancellationRequested) { return; } foreach (var productId in chunk) { _db.ProductCategories.Add(new ProductCategory { ProductId = productId, CategoryId = category.Id, IsSystemMapping = true }); ++numAdded; } await scope.CommitAsync(cancelToken); } try { scope.DbContext.DetachEntities <ProductCategory>(); } catch { } } } } Debug.WriteLineIf(numDeleted > 0 || numAdded > 0, $"Deleted {numDeleted} and added {numAdded} product mappings for {numCategories} categories."); }
public async Task Run(TaskExecutionContext ctx, CancellationToken cancelToken = default) { //var count = 0; var numDeleted = 0; var numAdded = 0; var rolesCount = 0; using (var scope = new DbContextScope(_db, autoDetectChanges: false, hooksEnabled: false, deferCommit: true)) { // Delete existing system mappings. var deleteQuery = _db.CustomerRoleMappings.Where(x => x.IsSystemMapping); if (ctx.Parameters.ContainsKey("CustomerRoleIds")) { var roleIds = ctx.Parameters["CustomerRoleIds"].ToIntArray(); deleteQuery = deleteQuery.Where(x => roleIds.Contains(x.CustomerRoleId)); } numDeleted = await deleteQuery.BatchDeleteAsync(cancelToken); // Insert new customer role mappings. var roles = await _db.CustomerRoles .Include(x => x.RuleSets) .AsNoTracking() .Where(x => x.Active && x.RuleSets.Any(y => y.IsActive)) .ToListAsync(cancelToken); rolesCount = roles.Count; foreach (var role in roles) { var ruleSetCustomerIds = new HashSet <int>(); // TODO: (mg) (core) Complete TargetGroupEvaluatorTask (TaskExecutionContext required). //ctx.SetProgress(++count, roles.Count, $"Add customer assignments for role \"{role.SystemName.NaIfEmpty()}\"."); // Execute active rule sets and collect customer ids. foreach (var ruleSet in role.RuleSets.Where(x => x.IsActive)) { if (cancelToken.IsCancellationRequested) { return; } var expressionGroup = await _ruleService.CreateExpressionGroupAsync(ruleSet, _targetGroupService); if (expressionGroup is FilterExpression expression) { var filterResult = _targetGroupService.ProcessFilter(expression, 0, 500); var resultPager = new FastPager <Customer>(filterResult.SourceQuery, 500); while ((await resultPager.ReadNextPageAsync(x => x.Id, x => x)).Out(out var customerIds)) { ruleSetCustomerIds.AddRange(customerIds); } } } // Add mappings. if (ruleSetCustomerIds.Any()) { foreach (var chunk in ruleSetCustomerIds.Slice(500)) { if (cancelToken.IsCancellationRequested) { return; } foreach (var customerId in chunk) { _db.CustomerRoleMappings.Add(new CustomerRoleMapping { CustomerId = customerId, CustomerRoleId = role.Id, IsSystemMapping = true }); ++numAdded; } await scope.CommitAsync(cancelToken); } try { scope.DbContext.DetachEntities <CustomerRoleMapping>(); } catch { } } } } if (numAdded > 0 || numDeleted > 0) { await _cache.RemoveByPatternAsync(AclService.ACL_SEGMENT_PATTERN); } Debug.WriteLineIf(numDeleted > 0 || numAdded > 0, $"Deleted {numDeleted} and added {numAdded} customer assignments for {rolesCount} roles."); }
public virtual async Task <ProductSummaryModel> MapProductSummaryModelAsync(IPagedList <Product> products, ProductSummaryMappingSettings settings) { Guard.NotNull(products, nameof(products)); if (settings == null) { settings = new ProductSummaryMappingSettings(); } using (_services.Chronometer.Step("MapProductSummaryModel")) { var model = new ProductSummaryModel(products) { ViewMode = settings.ViewMode, GridColumnSpan = _catalogSettings.GridStyleListColumnSpan, ShowSku = _catalogSettings.ShowProductSku, ShowWeight = _catalogSettings.ShowWeight, ShowDimensions = settings.MapDimensions, ShowLegalInfo = settings.MapLegalInfo, ShowDescription = settings.MapShortDescription, ShowFullDescription = settings.MapFullDescription, ShowRatings = settings.MapReviews, ShowPrice = settings.MapPrices, ShowBasePrice = settings.MapPrices && _catalogSettings.ShowBasePriceInProductLists && settings.ViewMode != ProductSummaryViewMode.Mini, ShowShippingSurcharge = settings.MapPrices && settings.ViewMode != ProductSummaryViewMode.Mini, ShowButtons = settings.ViewMode != ProductSummaryViewMode.Mini, ShowBrand = settings.MapManufacturers, ForceRedirectionAfterAddingToCart = settings.ForceRedirectionAfterAddingToCart, CompareEnabled = _catalogSettings.CompareProductsEnabled, WishlistEnabled = _services.Permissions.Authorize(Permissions.Cart.AccessWishlist), BuyEnabled = !_catalogSettings.HideBuyButtonInLists, ThumbSize = settings.ThumbnailSize, ShowDiscountBadge = _catalogSettings.ShowDiscountSign, ShowNewBadge = _catalogSettings.LabelAsNewForMaxDays.HasValue, DeliveryTimesPresentation = settings.DeliveryTimesPresentation, }; if (products.Count == 0) { // No products, stop here. return(model); } using var scope = new DbContextScope(_db, retainConnection: true, deferCommit: true); // PERF!! var store = _services.StoreContext.CurrentStore; var customer = _services.WorkContext.CurrentCustomer; var currency = _services.WorkContext.WorkingCurrency; var language = _services.WorkContext.WorkingLanguage; var allowPrices = await _services.Permissions.AuthorizeAsync(Permissions.Catalog.DisplayPrice); var allowShoppingCart = await _services.Permissions.AuthorizeAsync(Permissions.Cart.AccessShoppingCart); var allowWishlist = await _services.Permissions.AuthorizeAsync(Permissions.Cart.AccessWishlist); var taxDisplayType = _workContext.GetTaxDisplayTypeFor(customer, store.Id); var cachedBrandModels = new Dictionary <int, BrandOverviewModel>(); var prefetchTranslations = settings.PrefetchTranslations == true || (settings.PrefetchTranslations == null && _performanceSettings.AlwaysPrefetchTranslations); var prefetchSlugs = settings.PrefetchUrlSlugs == true || (settings.PrefetchUrlSlugs == null && _performanceSettings.AlwaysPrefetchUrlSlugs); var allProductIds = prefetchSlugs || prefetchTranslations?products.Select(x => x.Id).ToArray() : Array.Empty <int>(); //var productIds = products.Select(x => x.Id).ToArray(); string taxInfo = T(taxDisplayType == TaxDisplayType.IncludingTax ? "Tax.InclVAT" : "Tax.ExclVAT"); var legalInfo = string.Empty; var res = new Dictionary <string, LocalizedString>(StringComparer.OrdinalIgnoreCase) { { "Products.CallForPrice", T("Products.CallForPrice") }, { "Products.PriceRangeFrom", T("Products.PriceRangeFrom") }, { "Media.Product.ImageLinkTitleFormat", T("Media.Product.ImageLinkTitleFormat") }, { "Media.Product.ImageAlternateTextFormat", T("Media.Product.ImageAlternateTextFormat") }, { "Products.DimensionsValue", T("Products.DimensionsValue") }, { "Common.AdditionalShippingSurcharge", T("Common.AdditionalShippingSurcharge") } }; if (settings.MapLegalInfo) { var shippingInfoUrl = (await _urlHelper.TopicAsync("shippinginfo")); legalInfo = shippingInfoUrl.HasValue() ? T("Tax.LegalInfoShort", taxInfo, shippingInfoUrl) : T("Tax.LegalInfoShort2", taxInfo); } if (prefetchSlugs) { await _urlService.PrefetchUrlRecordsAsync(nameof(Product), new[] { language.Id, 0 }, allProductIds); } if (prefetchTranslations) { // Prefetch all delivery time translations await _localizedEntityService.PrefetchLocalizedPropertiesAsync(nameof(DeliveryTime), language.Id, null); } // Run in uncommitting scope, because pictures could be updated (IsNew property) var batchContext = _dataExporter.Value.CreateProductExportContext(products, customer, null, 1, false); if (settings.MapPrices) { await batchContext.AppliedDiscounts.LoadAllAsync(); await batchContext.TierPrices.LoadAllAsync(); } if (settings.MapAttributes || settings.MapColorAttributes) { await batchContext.Attributes.LoadAllAsync(); if (prefetchTranslations) { // Prefetch all product attribute translations await PrefetchTranslations( nameof(ProductAttribute), language.Id, batchContext.Attributes.SelectMany(x => x.Value).Select(x => x.ProductAttribute)); // Prefetch all variant attribute value translations await PrefetchTranslations( nameof(ProductVariantAttributeValue), language.Id, batchContext.Attributes.SelectMany(x => x.Value).SelectMany(x => x.ProductVariantAttributeValues)); } } if (settings.MapManufacturers) { await batchContext.ProductManufacturers.LoadAllAsync(); } if (settings.MapSpecificationAttributes) { await batchContext.SpecificationAttributes.LoadAllAsync(); if (prefetchTranslations) { // Prefetch all spec attribute option translations await PrefetchTranslations( nameof(SpecificationAttributeOption), language.Id, batchContext.SpecificationAttributes.SelectMany(x => x.Value).Select(x => x.SpecificationAttributeOption)); // Prefetch all spec attribute translations await PrefetchTranslations( nameof(SpecificationAttribute), language.Id, batchContext.SpecificationAttributes.SelectMany(x => x.Value).Select(x => x.SpecificationAttributeOption.SpecificationAttribute)); } } // If a size has been set in the view, we use it in priority int thumbSize = model.ThumbSize ?? _mediaSettings.ProductThumbPictureSize; var mapItemContext = new MapProductSummaryItemContext { BatchContext = batchContext, CachedBrandModels = cachedBrandModels, PrimaryCurrency = store.PrimaryStoreCurrency, StoreCurrency = currency, LegalInfo = legalInfo, Model = model, Resources = res, Settings = settings, Customer = customer, Store = store, AllowPrices = allowPrices, AllowShoppingCart = allowShoppingCart, AllowWishlist = allowWishlist, TaxDisplayType = taxDisplayType }; if (settings.MapPictures) { var fileIds = products .Select(x => x.MainPictureId ?? 0) .Where(x => x != 0) .Distinct() .ToArray(); mapItemContext.MediaFiles = (await _mediaService.GetFilesByIdsAsync(fileIds)).ToDictionarySafe(x => x.Id); } foreach (var product in products) { await MapProductSummaryItem(product, mapItemContext); } _services.DisplayControl.AnnounceRange(products); await scope.CommitAsync(); batchContext.Clear(); // don't show stuff without data at all model.ShowDescription = model.ShowDescription && model.Items.Any(x => x.ShortDescription?.Value?.HasValue() == true); model.ShowBrand = model.ShowBrand && model.Items.Any(x => x.Brand != null); return(model); } }
public override async Task ExecuteAsync(TaskExecutionContext ctx) { var count = 0; var clearCache = false; var roleQuery = _customerRoleRepository.TableUntracked.Expand(x => x.RuleSets); if (ctx.Parameters.ContainsKey("CustomerRoleIds")) { var rolesIds = ctx.Parameters["CustomerRoleIds"].ToIntArray(); roleQuery = roleQuery.Where(x => rolesIds.Contains(x.Id)); } var roles = await roleQuery.ToListAsync(); if (!roles.Any()) { return; } using (var scope = new DbContextScope(ctx: _customerRoleMappingRepository.Context, autoDetectChanges: false, validateOnSave: false, hooksEnabled: false, autoCommit: false)) foreach (var role in roles) { try { ctx.SetProgress(++count, roles.Count, $"Sync customer assignments for role {role.SystemName.NaIfEmpty()}."); _customerRoleMappingRepository.Context.DetachEntities(x => x is CustomerRoleMapping); } catch { } var ruleSetCustomerIds = new HashSet <int>(); var existingCustomerIds = new HashSet <int>(); var numDeleted = 0; var numAdded = 0; // Execute active rule sets and collect customer ids. // Delete old mappings if the role is inactive or has no assigned rule sets. if (role.Active) { foreach (var ruleSet in role.RuleSets.Where(x => x.IsActive)) { if (ctx.CancellationToken.IsCancellationRequested) { return; } var expression = _ruleFactory.CreateExpressionGroup(ruleSet, _targetGroupService) as FilterExpression; if (expression != null) { var filterResult = _targetGroupService.ProcessFilter(expression, 0, 500); var resultPager = new FastPager <Customer>(filterResult.SourceQuery, 500); for (var i = 0; i < 9999999; ++i) { var customerIds = await resultPager.ReadNextPageAsync(x => x.Id, x => x); if (!(customerIds?.Any() ?? false)) { break; } ruleSetCustomerIds.AddRange(customerIds); } } } } // Sync mappings. var query = _customerRoleMappingRepository.Table.Where(x => x.CustomerRoleId == role.Id && x.IsSystemMapping); var pager = new FastPager <CustomerRoleMapping>(query, 500); // Mappings to delete. for (var i = 0; i < 9999999; ++i) { if (ctx.CancellationToken.IsCancellationRequested) { return; } var mappings = await pager.ReadNextPageAsync <CustomerRoleMapping>(); if (!(mappings?.Any() ?? false)) { break; } foreach (var mapping in mappings) { if (!role.Active || !ruleSetCustomerIds.Contains(mapping.CustomerId)) { _customerRoleMappingRepository.Delete(mapping); ++numDeleted; clearCache = true; } else { existingCustomerIds.Add(mapping.CustomerId); } } await scope.CommitAsync(); } // Mappings to add. if (role.Active) { var toAdd = ruleSetCustomerIds.Except(existingCustomerIds).ToList(); if (toAdd.Any()) { foreach (var chunk in toAdd.Slice(500)) { if (ctx.CancellationToken.IsCancellationRequested) { return; } foreach (var customerId in chunk) { _customerRoleMappingRepository.Insert(new CustomerRoleMapping { CustomerId = customerId, CustomerRoleId = role.Id, IsSystemMapping = true }); ++numAdded; clearCache = true; } await scope.CommitAsync(); } } } Debug.WriteLineIf(numDeleted > 0 || numAdded > 0, $"Customer assignments for {role.SystemName.NaIfEmpty()}: deleted {numDeleted}, added {numAdded}."); } if (clearCache) { _cacheManager.RemoveByPattern(AclService.ACL_SEGMENT_PATTERN); } }
public override async Task ExecuteAsync(TaskExecutionContext ctx) { var count = 0; var numDeleted = 0; var numAdded = 0; var pageSize = 500; var categoryQuery = _categoryRepository.TableUntracked.Expand(x => x.RuleSets); if (ctx.Parameters.ContainsKey("CategoryIds")) { var categoryIds = ctx.Parameters["CategoryIds"].ToIntArray(); categoryQuery = categoryQuery.Where(x => categoryIds.Contains(x.Id)); numDeleted = _productCategoryRepository.Context.ExecuteSqlCommand( "Delete From [dbo].[Product_Category_Mapping] Where [CategoryId] In ({0}) And [IsSystemMapping] = 1", false, null, string.Join(",", categoryIds)); } else { numDeleted = _productCategoryRepository.Context.ExecuteSqlCommand("Delete From [dbo].[Product_Category_Mapping] Where [IsSystemMapping] = 1"); } var categories = await categoryQuery .Where(x => x.Published && !x.Deleted && x.RuleSets.Any(y => y.IsActive)) .ToListAsync(); using (var scope = new DbContextScope(ctx: _productCategoryRepository.Context, autoDetectChanges: false, validateOnSave: false, hooksEnabled: false, autoCommit: false)) { foreach (var category in categories) { var ruleSetProductIds = new HashSet <int>(); ctx.SetProgress(++count, categories.Count, $"Add product mappings for category \"{category.Name.NaIfEmpty()}\"."); // Execute active rule sets and collect product ids. foreach (var ruleSet in category.RuleSets.Where(x => x.IsActive)) { if (ctx.CancellationToken.IsCancellationRequested) { return; } if (_ruleFactory.CreateExpressionGroup(ruleSet, _productRuleProvider) is SearchFilterExpression expression) { var pageIndex = -1; while (true) { // Do not touch searchResult.Hits. We only need the product identifiers. var searchResult = _productRuleProvider.Search(new SearchFilterExpression[] { expression }, ++pageIndex, pageSize); ruleSetProductIds.AddRange(searchResult.HitsEntityIds); if (pageIndex >= (searchResult.TotalHitsCount / pageSize)) { break; } } } } // Add mappings. if (ruleSetProductIds.Any()) { foreach (var chunk in ruleSetProductIds.Slice(500)) { if (ctx.CancellationToken.IsCancellationRequested) { return; } foreach (var productId in chunk) { _productCategoryRepository.Insert(new ProductCategory { ProductId = productId, CategoryId = category.Id, IsSystemMapping = true }); ++numAdded; } await scope.CommitAsync(); } try { _productCategoryRepository.Context.DetachEntities <ProductCategory>(); } catch { } } } } Debug.WriteLineIf(numDeleted > 0 || numAdded > 0, $"Deleted {numDeleted} and added {numAdded} product mappings for {categories.Count} categories."); }
public virtual async Task <(int AffectedCategories, int AffectedProducts)> InheritStoresIntoChildrenAsync( int categoryId, bool touchProductsWithMultipleCategories = false, bool touchExistingAcls = false, bool categoriesOnly = false) { var categoryEntityName = nameof(Category); var productEntityName = nameof(Product); var affectedCategories = 0; var affectedProducts = 0; var allStoreIds = await _db.Stores .AsQueryable() .Select(x => x.Id) .ToListAsync(); var referenceCategory = await _db.Categories.FindByIdAsync(categoryId); var referenceStoreMappingIds = await _storeMappingService.GetAuthorizedStoreIdsAsync(referenceCategory); using (var scope = new DbContextScope(_db, autoDetectChanges: false)) { await ProcessCategory(scope, referenceCategory); } _cache.RemoveByPattern(StoreMappingService.STOREMAPPING_SEGMENT_PATTERN); return(affectedCategories, affectedProducts); async Task ProcessCategory(DbContextScope scope, Category c) { // Process sub-categories. var subCategories = await _db.Categories .AsQueryable() .Where(x => x.ParentCategoryId == c.Id) .OrderBy(x => x.DisplayOrder) .ToListAsync(); foreach (var subCategory in subCategories) { if (subCategory.LimitedToStores != referenceCategory.LimitedToStores) { subCategory.LimitedToStores = referenceCategory.LimitedToStores; } var storeMappings = await _db.StoreMappings .ApplyEntityFilter(subCategory) .ToListAsync(); var storeMappingsDic = storeMappings.ToDictionarySafe(x => x.StoreId); foreach (var storeId in allStoreIds) { if (referenceStoreMappingIds.Contains(storeId)) { if (!storeMappingsDic.ContainsKey(storeId)) { _db.StoreMappings.Add(new StoreMapping { StoreId = storeId, EntityId = subCategory.Id, EntityName = categoryEntityName }); } } else if (storeMappingsDic.TryGetValue(storeId, out var storeMappingToDelete)) { _db.StoreMappings.Remove(storeMappingToDelete); } } } await scope.CommitAsync(); affectedCategories += subCategories.Count; // Process products. var categoryIds = new HashSet <int>(subCategories.Select(x => x.Id)) { c.Id }; var searchQuery = new CatalogSearchQuery().WithCategoryIds(null, categoryIds.ToArray()); var productsQuery = _catalogSearchService.PrepareQuery(searchQuery); var productsPager = new FastPager <Product>(productsQuery, 500); while ((await productsPager.ReadNextPageAsync <Product>()).Out(out var products)) { foreach (var product in products) { if (product.LimitedToStores != referenceCategory.LimitedToStores) { product.LimitedToStores = referenceCategory.LimitedToStores; } var storeMappings = await _db.StoreMappings .ApplyEntityFilter(product) .ToListAsync(); var storeMappingsDic = storeMappings.ToDictionarySafe(x => x.StoreId); foreach (var storeId in allStoreIds) { if (referenceStoreMappingIds.Contains(storeId)) { if (!storeMappingsDic.ContainsKey(storeId)) { _db.StoreMappings.Add(new StoreMapping { StoreId = storeId, EntityId = product.Id, EntityName = productEntityName }); } } else if (storeMappingsDic.TryGetValue(storeId, out var storeMappingToDelete)) { _db.StoreMappings.Remove(storeMappingToDelete); } } } await scope.CommitAsync(); affectedProducts += products.Count; } await scope.CommitAsync(); try { scope.DbContext.DetachEntities(x => x is Product || x is Category || x is StoreMapping, false); } catch { } foreach (var subCategory in subCategories) { await ProcessCategory(scope, subCategory); } } }
public virtual async Task <(int AffectedCategories, int AffectedProducts)> InheritAclIntoChildrenAsync( int categoryId, bool touchProductsWithMultipleCategories = false, bool touchExistingAcls = false, bool categoriesOnly = false) { var categoryEntityName = nameof(Category); //var productEntityName = nameof(Product); var affectedCategories = 0; var affectedProducts = 0; var allCustomerRolesIds = await _db.CustomerRoles .AsQueryable() .Select(x => x.Id) .ToListAsync(); var referenceCategory = await _db.Categories.FindByIdAsync(categoryId); // TODO: (mg) (core) Complete ICategoryService.InheritAclIntoChildrenAsync (IAclService required). var referenceRoleIds = Array.Empty <int>(); using (var scope = new DbContextScope(_db, autoDetectChanges: false)) { await ProcessCategory(scope, referenceCategory); } // TODO: (mg) (core) Complete ICategoryService.InheritAclIntoChildrenAsync (IAclService required). //_cache.RemoveByPattern(AclService.ACL_SEGMENT_PATTERN); return(affectedCategories, affectedProducts); async Task ProcessCategory(DbContextScope scope, Category c) { // Process sub-categories. var subCategories = await _db.Categories .AsQueryable() .Where(x => x.ParentCategoryId == c.Id) .OrderBy(x => x.DisplayOrder) .ToListAsync(); foreach (var subCategory in subCategories) { if (subCategory.SubjectToAcl != referenceCategory.SubjectToAcl) { subCategory.SubjectToAcl = referenceCategory.SubjectToAcl; } var aclRecords = await _db.AclRecords .ApplyEntityFilter(subCategory) .ToListAsync(); var aclRecordsDic = aclRecords.ToDictionarySafe(x => x.CustomerRoleId); foreach (var roleId in allCustomerRolesIds) { if (referenceRoleIds.Contains(roleId)) { if (!aclRecordsDic.ContainsKey(roleId)) { await _db.AclRecords.AddAsync(new AclRecord { CustomerRoleId = roleId, EntityId = subCategory.Id, EntityName = categoryEntityName }); } } else if (aclRecordsDic.TryGetValue(roleId, out var aclRecordToDelete)) { _db.AclRecords.Remove(aclRecordToDelete); } } } await scope.CommitAsync(); affectedCategories += subCategories.Count; // Process products. var categoryIds = new HashSet <int>(subCategories.Select(x => x.Id)); categoryIds.Add(c.Id); // TODO: (mg) (core) Complete ICategoryService.InheritAclIntoChildrenAsync (ICatalogSearchService required). //var searchQuery = new CatalogSearchQuery().WithCategoryIds(null, categoryIds.ToArray()); //var productsQuery = _catalogSearchService.PrepareQuery(searchQuery); //var productsPager = new FastPager<Product>(productsQuery, 500); //while ((await productsPager.ReadNextPageAsync<Product>()).Out(out var products)) //{ // foreach (var product in products) // { // if (product.SubjectToAcl != referenceCategory.SubjectToAcl) // { // product.SubjectToAcl = referenceCategory.SubjectToAcl; // } // var aclRecords = await _db.AclRecords // .ApplyEntityFilter(product) // .ToListAsync(); // var aclRecordsDic = aclRecords.ToDictionarySafe(x => x.CustomerRoleId); // foreach (var roleId in allCustomerRolesIds) // { // if (referenceRoleIds.Contains(roleId)) // { // if (!aclRecordsDic.ContainsKey(roleId)) // { // await _db.AclRecords.AddAsync(new AclRecord { CustomerRoleId = roleId, EntityId = product.Id, EntityName = productEntityName }); // } // } // else if (aclRecordsDic.TryGetValue(roleId, out var aclRecordToDelete)) // { // _db.AclRecords.Remove(aclRecordToDelete); // } // } // } // await scope.CommitAsync(); // affectedProducts += products.Count; //} //await scope.CommitAsync(); try { scope.DbContext.DetachEntities(x => x is Product || x is Category || x is AclRecord, false); } catch { } foreach (var subCategory in subCategories) { await ProcessCategory(scope, subCategory); } } }
public override async Task ExecuteAsync(TaskExecutionContext ctx) { var count = 0; var numDeleted = 0; var numAdded = 0; var roleQuery = _customerRoleRepository.TableUntracked.Expand(x => x.RuleSets); if (ctx.Parameters.ContainsKey("CustomerRoleIds")) { var roleIds = ctx.Parameters["CustomerRoleIds"].ToIntArray(); roleQuery = roleQuery.Where(x => roleIds.Contains(x.Id)); numDeleted = _customerRoleMappingRepository.Context.ExecuteSqlCommand( "Delete From [dbo].[CustomerRoleMapping] Where [CustomerRoleId] In ({0}) And [IsSystemMapping] = 1", false, null, string.Join(",", roleIds)); } else { numDeleted = _customerRoleMappingRepository.Context.ExecuteSqlCommand("Delete From [dbo].[CustomerRoleMapping] Where [IsSystemMapping] = 1"); } var roles = await roleQuery .Where(x => x.Active && x.RuleSets.Any(y => y.IsActive)) .ToListAsync(); using (var scope = new DbContextScope(ctx: _customerRoleMappingRepository.Context, autoDetectChanges: false, validateOnSave: false, hooksEnabled: false, autoCommit: false)) { foreach (var role in roles) { var ruleSetCustomerIds = new HashSet <int>(); ctx.SetProgress(++count, roles.Count, $"Add customer assignments for role \"{role.SystemName.NaIfEmpty()}\"."); // Execute active rule sets and collect customer ids. foreach (var ruleSet in role.RuleSets.Where(x => x.IsActive)) { if (ctx.CancellationToken.IsCancellationRequested) { return; } if (_ruleFactory.CreateExpressionGroup(ruleSet, _targetGroupService) is FilterExpression expression) { var filterResult = _targetGroupService.ProcessFilter(expression, 0, 500); var resultPager = new FastPager <Customer>(filterResult.SourceQuery, 500); while (true) { var customerIds = await resultPager.ReadNextPageAsync(x => x.Id, x => x); if (!(customerIds?.Any() ?? false)) { break; } ruleSetCustomerIds.AddRange(customerIds); } } } // Add mappings. if (ruleSetCustomerIds.Any()) { foreach (var chunk in ruleSetCustomerIds.Slice(500)) { if (ctx.CancellationToken.IsCancellationRequested) { return; } foreach (var customerId in chunk) { _customerRoleMappingRepository.Insert(new CustomerRoleMapping { CustomerId = customerId, CustomerRoleId = role.Id, IsSystemMapping = true }); ++numAdded; } await scope.CommitAsync(); } try { _customerRoleMappingRepository.Context.DetachEntities <CustomerRoleMapping>(); } catch { } } } } if (numAdded > 0 || numDeleted > 0) { _cacheManager.RemoveByPattern(AclService.ACL_SEGMENT_PATTERN); } Debug.WriteLineIf(numDeleted > 0 || numAdded > 0, $"Deleted {numDeleted} and added {numAdded} customer assignments for {roles.Count} roles."); }
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); }
protected virtual async Task <int> ProcessAvatarsAsync(ImportExecuteContext context, DbContextScope scope, IEnumerable <ImportRow <Customer> > batch) { foreach (var row in batch) { var urlOrPath = row.GetDataValue <string>("AvatarPictureUrl"); if (urlOrPath.IsEmpty()) { continue; } var image = CreateDownloadItem(context, urlOrPath, 1); if (image == null) { continue; } // Download avatar. if (image.Url.HasValue() && !image.Success) { await context.DownloadManager.DownloadFilesAsync(new[] { image }, context.CancelToken); } if (FileDownloadSucceeded(image, context)) { using var stream = File.OpenRead(image.Path); if (stream?.Length > 0) { var file = await _services.MediaService.GetFileByIdAsync(row.Entity.GenericAttributes.AvatarPictureId ?? 0, MediaLoadFlags.AsNoTracking); if (file != null) { var isEqualData = await _services.MediaService.FindEqualFileAsync(stream, new[] { file.File }, true); if (isEqualData.Success) { context.Result.AddInfo($"Found equal file for avatar '{image.FileName}'. Skipping file.", row.RowInfo, "AvatarPictureUrl"); continue; } } // An avatar may not be assigned to several customers. A customer could otherwise delete the avatar of another. // Overwriting is probably too dangerous here, because we could overwrite the avatar of another customer, so better rename. var path = _services.MediaService.CombinePaths(SystemAlbumProvider.Customers, image.FileName); var saveFileResult = await _services.MediaService.SaveFileAsync(path, stream, false, DuplicateFileHandling.Rename); if (saveFileResult.File.Id > 0) { SetGenericAttribute(SystemCustomerAttributeNames.AvatarPictureId, saveFileResult.File.Id, row); } } } else { context.Result.AddInfo($"Download failed for avatar {image.Url}.", row.RowInfo, "AvatarPictureUrl"); } } var num = await scope.CommitAsync(context.CancelToken); return(num); }
public async Task ExecuteAsync(ImportExecuteContext context, CancellationToken cancelToken) { var currentStoreId = _services.StoreContext.CurrentStore.Id; var segmenter = context.DataSegmenter; var batch = segmenter.GetCurrentBatch <NewsletterSubscription>(); using (var scope = new DbContextScope(_services.DbContext, autoDetectChanges: false, minHookImportance: HookImportance.Important, deferCommit: true)) { await context.SetProgressAsync(segmenter.CurrentSegmentFirstRowIndex - 1, segmenter.TotalRows); foreach (var row in batch) { try { NewsletterSubscription subscription = null; var email = row.GetDataValue <string>("Email"); var storeId = row.GetDataValue <int>("StoreId"); if (storeId == 0) { storeId = currentStoreId; } if (row.HasDataValue("Active") && row.TryGetDataValue("Active", out bool active)) { } else { active = true; // Default. } if (email.IsEmpty()) { context.Result.AddWarning("Skipped empty email address.", row.RowInfo, "Email"); continue; } if (email.Length > 255) { context.Result.AddWarning($"Skipped email address '{email}'. It exceeds the maximum allowed length of 255.", row.RowInfo, "Email"); continue; } if (!email.IsEmail()) { context.Result.AddWarning($"Skipped invalid email address '{email}'.", row.RowInfo, "Email"); continue; } foreach (var keyName in context.KeyFieldNames) { switch (keyName) { case "Email": subscription = await _services.DbContext.NewsletterSubscriptions .OrderBy(x => x.Id) .FirstOrDefaultAsync(x => x.Email == email && x.StoreId == storeId, cancelToken); break; } if (subscription != null) { break; } } if (subscription == null) { if (context.UpdateOnly) { ++context.Result.SkippedRecords; continue; } subscription = new NewsletterSubscription { Active = active, CreatedOnUtc = context.UtcNow, Email = email, NewsletterSubscriptionGuid = Guid.NewGuid(), StoreId = storeId }; _services.DbContext.NewsletterSubscriptions.Add(subscription); context.Result.NewRecords++; } else { subscription.Active = active; context.Result.ModifiedRecords++; } } catch (Exception ex) { context.Result.AddError(ex.ToAllMessages(), row.RowInfo); } } await scope.CommitAsync(cancelToken); } await _services.EventPublisher.PublishAsync(new ImportBatchExecutedEvent <NewsletterSubscription>(context, batch), cancelToken); }
public override async Task ExecuteAsync(TaskExecutionContext ctx) { var count = 0; var numDeleted = 0; var numAdded = 0; var numCategories = 0; var pageSize = 500; var pageIndex = -1; var categoryIds = ctx.Parameters.ContainsKey("CategoryIds") ? ctx.Parameters["CategoryIds"].ToIntArray() : null; // Hooks are enabled because search index needs to be updated. using (var scope = new DbContextScope(ctx: _productCategoryRepository.Context, autoDetectChanges: false, validateOnSave: false, hooksEnabled: true, autoCommit: false)) { // Delete existing system mappings. var deleteQuery = _productCategoryRepository.Table.Where(x => x.IsSystemMapping); if (categoryIds != null) { deleteQuery = deleteQuery.Where(x => categoryIds.Contains(x.CategoryId)); } var pager = new FastPager <ProductCategory>(deleteQuery, pageSize); while (pager.ReadNextPage(out var mappings)) { if (ctx.CancellationToken.IsCancellationRequested) { return; } if (mappings.Any()) { _productCategoryRepository.DeleteRange(mappings); numDeleted += await scope.CommitAsync(); } } try { _productCategoryRepository.Context.DetachEntities <ProductCategory>(); } catch { } // Insert new product category mappings. var categoryQuery = _categoryRepository.TableUntracked.Expand(x => x.RuleSets); if (categoryIds != null) { categoryQuery = categoryQuery.Where(x => categoryIds.Contains(x.Id)); } var categories = await categoryQuery .Where(x => x.Published && !x.Deleted && x.RuleSets.Any(y => y.IsActive)) .ToListAsync(); numCategories = categories.Count; foreach (var category in categories) { var ruleSetProductIds = new HashSet <int>(); ctx.SetProgress(++count, categories.Count, $"Add product mappings for category \"{category.Name.NaIfEmpty()}\"."); // Execute active rule sets and collect product ids. foreach (var ruleSet in category.RuleSets.Where(x => x.IsActive)) { if (ctx.CancellationToken.IsCancellationRequested) { return; } if (_ruleFactory.CreateExpressionGroup(ruleSet, _productRuleProvider) is SearchFilterExpression expression) { pageIndex = -1; while (true) { // Do not touch searchResult.Hits. We only need the product identifiers. var searchResult = _productRuleProvider.Search(new SearchFilterExpression[] { expression }, ++pageIndex, pageSize); ruleSetProductIds.AddRange(searchResult.HitsEntityIds); if (pageIndex >= (searchResult.TotalHitsCount / pageSize)) { break; } } } } // Add mappings. if (ruleSetProductIds.Any()) { foreach (var chunk in ruleSetProductIds.Slice(500)) { if (ctx.CancellationToken.IsCancellationRequested) { return; } foreach (var productId in chunk) { _productCategoryRepository.Insert(new ProductCategory { ProductId = productId, CategoryId = category.Id, IsSystemMapping = true }); ++numAdded; } await scope.CommitAsync(); } try { _productCategoryRepository.Context.DetachEntities <ProductCategory>(); } catch { } } } } Debug.WriteLineIf(numDeleted > 0 || numAdded > 0, $"Deleted {numDeleted} and added {numAdded} product mappings for {numCategories} categories."); }
private async Task ProcessAttributes(DbContextScope scope, Product product, Product clone, IEnumerable <Language> languages) { var localizedKeySelectors = new List <Expression <Func <ProductVariantAttributeValue, string> > > { x => x.Name, x => x.Alias }; await _db.LoadCollectionAsync(product, x => x.ProductVariantAttributes); await _db.LoadCollectionAsync(product, x => x.ProductVariantAttributeCombinations); // Former attribute id > clone. var attributeMap = new Dictionary <int, ProductVariantAttribute>(); // Former attribute value id > clone. var valueMap = new Dictionary <int, ProductVariantAttributeValue>(); var newCombinations = new List <ProductVariantAttributeCombination>(); // Product attributes. foreach (var attribute in product.ProductVariantAttributes) { // Save associated value (used for combinations copying). attributeMap[attribute.Id] = new ProductVariantAttribute { ProductId = clone.Id, ProductAttributeId = attribute.ProductAttributeId, TextPrompt = attribute.TextPrompt, IsRequired = attribute.IsRequired, AttributeControlTypeId = attribute.AttributeControlTypeId, DisplayOrder = attribute.DisplayOrder }; } // Reverse tracking order to have the clones in the same order in the database as the originals. _db.ProductVariantAttributes.AddRange(attributeMap.Select(x => x.Value).Reverse()); // >>>>>> Commit attributes. await scope.CommitAsync(); // Product variant attribute values. foreach (var attribute in product.ProductVariantAttributes) { var attributeClone = attributeMap[attribute.Id]; foreach (var value in attribute.ProductVariantAttributeValues) { // Save associated value (used for combinations copying). valueMap.Add(value.Id, new ProductVariantAttributeValue { ProductVariantAttributeId = attributeClone.Id, Name = value.Name, Color = value.Color, PriceAdjustment = value.PriceAdjustment, WeightAdjustment = value.WeightAdjustment, IsPreSelected = value.IsPreSelected, DisplayOrder = value.DisplayOrder, ValueTypeId = value.ValueTypeId, LinkedProductId = value.LinkedProductId, Quantity = value.Quantity, MediaFileId = value.MediaFileId }); } } // Reverse tracking order to have the clones in the same order in the database as the originals. _db.ProductVariantAttributeValues.AddRange(valueMap.Select(x => x.Value).Reverse()); // >>>>>> Commit attribute values. await scope.CommitAsync(); // Attribute value localization. var allValues = product.ProductVariantAttributes .Reverse() .SelectMany(x => x.ProductVariantAttributeValues.Reverse()) .ToArray(); foreach (var value in allValues) { if (valueMap.TryGetValue(value.Id, out var newValue)) { await ProcessLocalizations(value, newValue, localizedKeySelectors, languages); } } // >>>>>> Commit localized values. await scope.CommitAsync(); // Attribute combinations. foreach (var combination in product.ProductVariantAttributeCombinations) { var oldAttributesMap = combination.AttributeSelection.AttributesMap; var oldAttributes = await _productAttributeMaterializer.MaterializeProductVariantAttributesAsync(combination.AttributeSelection); var newSelection = new ProductVariantAttributeSelection(null); foreach (var oldAttribute in oldAttributes) { if (attributeMap.TryGetValue(oldAttribute.Id, out var newAttribute)) { var item = oldAttributesMap.FirstOrDefault(x => x.Key == oldAttribute.Id); if (item.Key != 0) { foreach (var value in item.Value) { if (newAttribute.IsListTypeAttribute()) { var oldValueId = value.ToString().EmptyNull().ToInt(); if (valueMap.TryGetValue(oldValueId, out var newValue)) { newSelection.AddAttributeValue(newAttribute.Id, newValue.Id); } } else { newSelection.AddAttributeValue(newAttribute.Id, value); } } } } } newCombinations.Add(new ProductVariantAttributeCombination { ProductId = clone.Id, RawAttributes = newSelection.AsJson(), StockQuantity = combination.StockQuantity, AllowOutOfStockOrders = combination.AllowOutOfStockOrders, Sku = combination.Sku, Gtin = combination.Gtin, ManufacturerPartNumber = combination.ManufacturerPartNumber, Price = combination.Price, AssignedMediaFileIds = combination.AssignedMediaFileIds, Length = combination.Length, Width = combination.Width, Height = combination.Height, BasePriceAmount = combination.BasePriceAmount, BasePriceBaseAmount = combination.BasePriceBaseAmount, DeliveryTimeId = combination.DeliveryTimeId, QuantityUnitId = combination.QuantityUnitId, IsActive = combination.IsActive //IsDefaultCombination = combination.IsDefaultCombination }); } // Reverse tracking order to have the clones in the same order in the database as the originals. _db.ProductVariantAttributeCombinations.AddRange(newCombinations.AsEnumerable().Reverse()); // >>>>>> Commit combinations. await scope.CommitAsync(); }