public virtual async Task <int> SendNotificationsToSubscribersAsync(Product product) { Guard.NotNull(product, nameof(product)); var numberOfMessages = 0; var subscriptionQuery = _db.BackInStockSubscriptions.ApplyStandardFilter(product.Id, null, null); var pager = new FastPager <BackInStockSubscription>(subscriptionQuery); while ((await pager.ReadNextPageAsync <BackInStockSubscription>()).Out(out var subscriptions)) { foreach (var subscription in subscriptions) { // Ensure that the customer is registered (simple and fast way). if (subscription?.Customer?.Email?.IsEmail() ?? false) { await _messageFactory.SendBackInStockNotificationAsync(subscription); ++numberOfMessages; } } _db.BackInStockSubscriptions.RemoveRange(subscriptions); await _db.SaveChangesAsync(); } return(numberOfMessages); }
/// <summary> /// Prevents saving of delivery time if it's referenced in products or attribute combinations /// and removes associations to deleted products and attribute combinations. /// </summary> protected override async Task <HookResult> OnDeletingAsync(DeliveryTime entity, IHookedEntity entry, CancellationToken cancelToken) { // Remove associations to deleted products. var productsQuery = _db.Products.Where(x => x.Deleted && x.DeliveryTimeId == entity.Id); var productsPager = new FastPager <Product>(productsQuery, 500); while ((await productsPager.ReadNextPageAsync <Product>()).Out(out var products)) { if (products.Any()) { products.Each(x => x.DeliveryTimeId = null); } } var attributeCombinationQuery = from ac in _db.ProductVariantAttributeCombinations join p in _db.Products.AsNoTracking() on ac.ProductId equals p.Id where p.Deleted && ac.DeliveryTimeId == entity.Id select ac; var attributeCombinationPager = new FastPager <ProductVariantAttributeCombination>(attributeCombinationQuery, 1000); while ((await attributeCombinationPager.ReadNextPageAsync <ProductVariantAttributeCombination>()).Out(out var attributeCombinations)) { if (attributeCombinations.Any()) { attributeCombinations.Each(x => x.DeliveryTimeId = null); } } // Warn and throw if there are associations to active products or attribute combinations. var query = from x in _db.Products where x.DeliveryTimeId == entity.Id || x.ProductVariantAttributeCombinations.Any(c => c.DeliveryTimeId == entity.Id) select x.Id; if (await query.AnyAsync(cancellationToken: cancelToken)) { // Prohibit saving of associated entities. entry.State = Smartstore.Data.EntityState.Detached; throw new SmartException("The delivery time cannot be deleted. It has associated products or product variants."); } return(HookResult.Ok); }
/// <summary> /// Gets a list of all available customer roles. /// </summary> /// <param name="label">Text for optional entry. If not null an entry with the specified label text and the Id 0 will be added to the list.</param> /// <param name="selectedIds">Ids of selected entities.</param> /// <param name="includeSystemRoles">Specifies whether to include system roles.</param> /// <returns>List of all customer roles as JSON.</returns> public async Task <IActionResult> AllCustomerRoles(string label, string selectedIds, bool?includeSystemRoles) { var rolesQuery = _db.CustomerRoles .AsNoTracking() .ApplyStandardFilter(true).AsQueryable(); if (!(includeSystemRoles ?? true)) { rolesQuery = rolesQuery.Where(x => x.IsSystemRole); } var rolesPager = new FastPager <CustomerRole>(rolesQuery, 500); var customerRoles = new List <CustomerRole>(); var ids = selectedIds.ToIntArray(); while ((await rolesPager.ReadNextPageAsync <CustomerRole>()).Out(out var roles)) { customerRoles.AddRange(roles); } var list = customerRoles .OrderBy(x => x.Name) .Select(x => new ChoiceListItem { Id = x.Id.ToString(), Text = x.Name, Selected = ids.Contains(x.Id) }) .ToList(); if (label.HasValue()) { list.Insert(0, new ChoiceListItem { Id = "0", Text = label, Selected = false }); } return(new JsonResult(list)); }
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 <bool> MoveAsync( Provider <IMediaStorageProvider> sourceProvider, Provider <IMediaStorageProvider> targetProvider, CancellationToken cancelToken = default) { Guard.NotNull(sourceProvider, nameof(sourceProvider)); Guard.NotNull(targetProvider, nameof(targetProvider)); // Source must support sending if (sourceProvider.Value is not IMediaSender sender) { throw new ArgumentException(T("Admin.Media.StorageMovingNotSupported", sourceProvider.Metadata.SystemName)); } // Target must support receiving if (targetProvider.Value is not IMediaReceiver receiver) { throw new ArgumentException(T("Admin.Media.StorageMovingNotSupported", targetProvider.Metadata.SystemName)); } // Source and target provider must not be equal if (sender == receiver) { throw new ArgumentException(T("Admin.Media.CannotMoveToSameProvider")); } var success = false; var utcNow = DateTime.UtcNow; var context = new MediaMoverContext(sender, receiver); // We are about to process data in chunks but want to commit ALL at once after ALL chunks have been processed successfully. // AutoDetectChanges true required for newly inserted binary data. using (var scope = new DbContextScope(ctx: _db, autoDetectChanges: true, retainConnection: true)) { using (var transaction = await _db.Database.BeginTransactionAsync(cancelToken)) { try { var pager = new FastPager <MediaFile>(_db.MediaFiles, PAGE_SIZE); while ((await pager.ReadNextPageAsync <MediaFile>()).Out(out var files)) { if (cancelToken.IsCancellationRequested) { break; } foreach (var file in files) { // Move item from source to target await sender.MoveToAsync(receiver, context, file); file.UpdatedOnUtc = utcNow; ++context.MovedItems; } if (!cancelToken.IsCancellationRequested) { await _db.SaveChangesAsync(cancelToken); // Detach all entities from previous page to save memory scope.DbContext.DetachEntities(files, deep: true); } } if (!cancelToken.IsCancellationRequested) { await transaction.CommitAsync(cancelToken); success = true; } else { success = false; await transaction.RollbackAsync(CancellationToken.None); } } catch (Exception exception) { success = false; await transaction.RollbackAsync(cancelToken); _notifier.Error(exception); Logger.Error(exception); } } } if (success) { await _settingService.ApplySettingAsync("Media.Storage.Provider", targetProvider.Metadata.SystemName); await _db.SaveChangesAsync(cancelToken); } // Inform both provider about ending await sender.OnCompletedAsync(context, success, cancelToken); await receiver.OnCompletedAsync(context, success, cancelToken); return(success); }
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 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 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."); }