//---------------------------------------------- //create methods /// <summary> /// This creates a Sale entry, and also update the ShopStock number in stock /// </summary> /// <param name="numBought"></param> /// <param name="shopStockId"></param> /// <param name="context"></param> /// <returns></returns> public static IStatusGeneric <ShopSale> CreateSellAndUpdateStock(int numBought, int shopStockId, DbContext context) { if (numBought < 0) { throw new ArgumentException("must be positive", nameof(numBought)); } var status = new StatusGenericHandler <ShopSale>(); var stock = context.Find <ShopStock>(shopStockId); if (stock == null) { status.AddError("Could not find the stock item you requested."); return(status); } stock.NumInStock = stock.NumInStock - numBought; if (stock.NumInStock < 0) { status.AddError("There are not enough items of that product to sell."); return(status); } var sale = new ShopSale(numBought, null, shopStockId); return(status.SetResult(sale)); }
public IStatusGeneric DeleteRole(string roleName, bool removeFromUsers, ExtraAuthorizeDbContext context) { var status = new StatusGenericHandler { Message = "Deleted role successfully." }; var roleToUpdate = context.Find <RoleToPermissions>(roleName); if (roleToUpdate == null) { return(status.AddError("That role doesn't exists")); } var usersWithRoles = context.UserToRoles.Where(x => x.RoleName == roleName).ToList(); if (usersWithRoles.Any()) { if (!removeFromUsers) { return(status.AddError($"That role is used by {usersWithRoles.Count} and you didn't ask for them to be updated.")); } context.RemoveRange(usersWithRoles); status.Message = $"Removed role from {usersWithRoles.Count} user and then deleted role successfully."; } context.Remove(roleToUpdate); return(status); }
public IStatusGeneric UpdateApprove(string checkBy, string approveBy, DateTime?checkDate) { var status = new StatusGenericHandler(); if (string.IsNullOrWhiteSpace(checkBy)) { status.AddError("check by value is null!!", "punch"); } if (string.IsNullOrWhiteSpace(approveBy)) { status.AddError("approve by value is null!!", "punch"); } if (!checkDate.HasValue) { status.AddError("check Date is invalied", "punch"); } if (!status.HasErrors) { this.CheckBy = checkBy; this.ApproveBy = approveBy; this.CheckDate = checkDate; } return(status); }
public IStatusGeneric UpdatePerson(string firstName, string lastName , string nationalId, string mobileNumber) { var status = new StatusGenericHandler(); if (string.IsNullOrWhiteSpace(firstName)) { status.AddError("I'm sorry, but firstName is empty."); return(status); } if (string.IsNullOrWhiteSpace(lastName)) { status.AddError("I'm sorry, but lastName is empty."); return(status); } if (string.IsNullOrWhiteSpace(nationalId)) { status.AddError("I'm sorry, but nationalId is empty."); return(status); } //All Ok this.FirstName = firstName; this.LastName = lastName; this.NationalId = nationalId; this.MobileNumber = mobileNumber; return(status); }
public async Task <IStatusGeneric <Order> > //#A CreateOrderAndSaveAsync(PlaceOrderInDto dto) //#B { var status = new StatusGenericHandler <Order>(); //#C if (!dto.AcceptTAndCs) //#D { return(status.AddError("You must accept the T&Cs to place an order.")); } if (!dto.LineItems.Any()) //#D { return(status.AddError("No items in your basket.")); } var booksDict = await _dbAccess //#E .FindBooksByIdsAsync //#E (dto.LineItems.Select(x => x.BookId)); //#E var linesStatus = FormLineItemsWithErrorChecking //#F (dto.LineItems, booksDict); //#F if (status.CombineStatuses(linesStatus).HasErrors) //#G { return(status); //#G } var orderStatus = Order.CreateOrder( //#H dto.UserId, linesStatus.Result); //#H if (status.CombineStatuses(orderStatus).HasErrors) //#I { return(status); //#I } await _dbAccess.AddAndSave(orderStatus.Result); //#J return(status.SetResult(orderStatus.Result)); //#K }
//---------------------------------------------------- //private private static IStatusGeneric CompareRouteValues(RouteValueDictionary foundValueRouteValues, object expectedRouteValues) { var expectedRouteValueDict = expectedRouteValues.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance) .Select(x => new { key = x.Name, value = x.GetValue(expectedRouteValues) }) .ToDictionary(x => x.key, y => y.value); var status = new StatusGenericHandler(); if (string.Join(", ", expectedRouteValueDict.Keys) != string.Join(", ", foundValueRouteValues.Keys)) { return(status.AddError( $"RouteValues: Different named properties: expected = {string.Join(",", expectedRouteValueDict.Keys)}, found = {string.Join(", ", foundValueRouteValues.Keys)}")); } foreach (var propName in expectedRouteValueDict.Keys) { var expectedValue = expectedRouteValueDict[propName]; var foundValue = foundValueRouteValues[propName]; if (expectedValue.GetType() != foundValue.GetType()) { status.AddError( $"RouteValues->{propName}, different type: expected = {expectedValue.GetType().Name}, found = {foundValue.GetType().Name}"); } else if (!Equals(expectedValue, foundValue)) { status.AddError( $"RouteValues->{propName}, different values: expected = {expectedValue}, found = {foundValue}"); } } return(status); }
public IStatusGeneric ChangeDeliveryDate(string userId, DateTime newDeliveryDate) { var status = new StatusGenericHandler(); if (CustomerName != userId) { status.AddError("I'm sorry, but that order does not belong to you"); //Log a security issue return(status); } if (HasBeenDelivered) { status.AddError("I'm sorry, but that order has been delivered."); return(status); } if (newDeliveryDate < DateTime.Today.AddDays(1)) { status.AddError("I'm sorry, we cannot get the order to you that quickly. Please choose a new date.", "NewDeliveryDate"); return(status); } if (newDeliveryDate.DayOfWeek == DayOfWeek.Sunday) { status.AddError("I'm sorry, we don't deliver on Sunday. Please choose a new date.", "NewDeliveryDate"); return(status); } //All Ok ExpectedDeliveryDate = newDeliveryDate; return(status); }
/// <summary> /// This will result the cascade soft delete flag on this entity and any dependent entities with the correct delete behaviour and cascade level /// </summary> /// <typeparam name="TEntity"></typeparam> /// <param name="resetSoftDeleteThisEntity">entity class with cascade soft delete interface. Mustn't be null</param> /// <param name="callSaveChanges">Defaults to calling SaveChanges. If set to false, then you must call SaveChanges</param> /// <returns>Returns a status. If no errors then Result contains the number of entities that had been reset, plus summary string in Message part</returns> public async Task <IStatusGeneric <int> > ResetCascadeSoftDeleteAsync <TEntity>(TEntity resetSoftDeleteThisEntity, bool callSaveChanges = true) where TEntity : class, TInterface { if (resetSoftDeleteThisEntity == null) { throw new ArgumentNullException(nameof(resetSoftDeleteThisEntity)); } var status = new StatusGenericHandler <int>(); var currentDeleteLevel = _config.GetSoftDeleteValue.Compile().Invoke(resetSoftDeleteThisEntity); if (currentDeleteLevel == 0) { return(status.AddError($"This entry isn't {_config.TextSoftDeletedPastTense}.")); } if (currentDeleteLevel > 1) { return(status.AddError($"This entry was soft deleted {currentDeleteLevel - 1} " + $"level{(currentDeleteLevel > 2 ? "s" : "")} above here")); } //For reset you need to read every time because some of the collection might be soft deleted already var walker = new CascadeWalker <TInterface>(_context, _config, true, CascadeSoftDelWhatDoing.ResetSoftDelete, true); await walker.WalkEntitiesSoftDelete(resetSoftDeleteThisEntity, 1); if (callSaveChanges) { await _context.SaveChangesAsync(); } return(ReturnSuccessFullResult(CascadeSoftDelWhatDoing.ResetSoftDelete, walker.NumFound)); }
private StatusGenericHandler AddQuestionGroup(string name, IEnumerable <Question> questions, SurveyDbContext context = null) { var status = new StatusGenericHandler(); if (string.IsNullOrWhiteSpace(name)) { status.AddError("A name is needed when creating a new question group.", nameof(name)); return(status); } if (_questionGroups != null) { var questionGroup = new QuestionGroup(name, questions, this); _questionGroups.Add(questionGroup); } else if (context == null) { status.AddError("You must provide a context if the QuestionGroups collection isn't valid.", nameof(context)); return(status); } else if (context.Entry(this).IsKeySet) { context.Add(new QuestionGroup(name, questions, this)); } else { status.AddError("Could not add a new QuestionGroup."); return(status); } return(status); }
private StatusGenericHandler AddQuestion(string text, QuestionType questionType, QuestionGroup questionGroup, SurveyDbContext context = null) { var status = new StatusGenericHandler(); if (string.IsNullOrWhiteSpace(text)) { status.AddError("Question text is needed when creating a new question.", nameof(text)); return(status); } if (questionType == null) { status.AddError("A QuestionType has not been specified.", nameof(questionType)); return(status); } if (questionGroup == null) { status.AddError("A QuestionGroup which to add this question to has not been specified.", nameof(questionGroup)); return(status); } if (context != null) { status = questionGroup.AddQuestion(text, questionType, context); } else { status.AddError("Could not add a new Question."); return(status); } return(status); }
public StatusGenericHandler RenameSurvey(string newName, SurveyDbContext context = null) { var status = new StatusGenericHandler(); if (string.IsNullOrWhiteSpace(newName)) { status.AddError("A new name has not been provided"); return(status); } if (context == null) { status.AddError("You must provide a context if you want to remove this Survey.", nameof(context)); return(status); } var nameAlreadyTaken = context.Surveys.Any(m => m.Name == newName); if (nameAlreadyTaken) { status.AddError("A survey with the name you have provided already exists.", nameof(context)); return(status); } Name = newName; return(status); }
/********************************************************* #A This method forms a Review to be filled in by the user #B I read the book title to show to the user when they are filling in their review #C This simply creates a Review with the BookId foreign key filled in #D Ths method updates the book with the new review #E This loads the correct book using the value in the review's foreign key, and includes any existing reviews (or empty collection if no reviews yet) #F I now add the new review to the Reviews collection #G The SaveChanges uses its DetectChanges method, which sees that the Book Review property has changed. It then creates a new row in the Review table #H The method returns the updated book * ******************************************************/ public IStatusGeneric AddReviewWithChecks(Review review) //#A { var status = new StatusGenericHandler(); //#B if (review.NumStars < 0 || review.NumStars > 5) //#C { status.AddError("This must be between 0 and 5.", //#C nameof(Review.NumStars)); //#C } if (string.IsNullOrWhiteSpace(review.Comment)) //#D { status.AddError("Please provide a comment with your review.", //#D nameof(Review.Comment)); //#D } if (!status.IsValid) //#E { return(status); //#E } var book = _context.Books //#F .Include(r => r.Reviews) //#F .Single(k => k.BookId //#F == review.BookId); //#F book.Reviews.Add(review); //#F _context.SaveChanges(); //#F return(status); //#G }
public StatusGenericHandler AddQuestion(CompletedQuestion question, SurveyDbContext context = null) { var status = new StatusGenericHandler(); if (string.IsNullOrWhiteSpace(question.Answer)) { status.AddError("An answer is needed when submitting a question.", nameof(question.Answer)); return(status); } if (_completedQuestions != null) { var completedQuestion = new CompletedQuestion(question.Question, question.Answer, this); _completedQuestions.Add(completedQuestion); } else if (context == null) { status.AddError("You must provide a context if the CompletedQuestions collection isn't valid.", nameof(context)); return(status); } else if (context.Entry(this).IsKeySet) { context.Add(new CompletedQuestion(question.Question, question.Answer, this)); } else { status.AddError("Could not add a new CompletedQuestion."); return(status); } return(status); }
public static IStatusGeneric <UserToRole> AddRoleToUser(string userId, string roleName, ExtraAuthorizeDbContext context) { if (userId == null) { throw new ArgumentNullException(nameof(userId)); } if (roleName == null) { throw new ArgumentNullException(nameof(roleName)); } var status = new StatusGenericHandler <UserToRole>(); if (context.Find <UserToRole>(userId, roleName) != null) { status.AddError($"The user already has the Role '{roleName}'."); return(status); } var roleToAdd = context.Find <RoleToPermissions>(roleName); if (roleToAdd == null) { status.AddError($"I could not find the Role '{roleName}'."); return(status); } return(status.SetResult(new UserToRole(userId, roleToAdd))); }
public StatusGenericHandler AddQuestion(string text, QuestionType type, SurveyDbContext context = null) { var status = new StatusGenericHandler(); var question = new Question(text, type, this); if (_questions != null) { _questions.Add(question); } else if (context == null) { status.AddError("You must provide a context if you want to remove this Survey.", nameof(context)); return(status); } else if (context.Entry(this).IsKeySet) { context.Add(new Question(text, type, this)); } else { status.AddError("Could not add a new survey.", nameof(context)); return(status); } return(status); }
} //Needed by EF Core public static IStatusGeneric <Book> CreateBook( string title, DateTime publishedOn, bool estimatedDate, string publisher, decimal price, string imageUrl, ICollection <Author> authors, ICollection <Tag> tags = null) { var status = new StatusGenericHandler <Book>(); if (string.IsNullOrWhiteSpace(title)) { status.AddError( "The book title cannot be empty."); } var book = new Book { Title = title, PublishedOn = publishedOn, EstimatedDate = estimatedDate, Publisher = publisher, OrgPrice = price, ActualPrice = price, ImageUrl = imageUrl, //We need to initialise the AuthorsOrdered string when the entry is created //NOTE: We must NOT initialise the ReviewsCount and the ReviewsAverageVotes as they default to zero AuthorsOrdered = string.Join(", ", authors.Select(x => x.Name)), _tags = tags != null ? new HashSet <Tag>(tags) : new HashSet <Tag>(), _reviews = new HashSet <Review>() //We add an empty list on create. I allows reviews to be added when building test data }; if (authors == null) { throw new ArgumentNullException(nameof(authors)); } byte order = 0; book._authorsLink = new HashSet <BookAuthor>( authors.Select(a => new BookAuthor(book, a, order++))); if (!book._authorsLink.Any()) { status.AddError( "You must have at least one Author for a book."); } if (status.IsValid) { book.AddEvent(new BookChangedEvent(BookChangeTypes.Added), EventToSend.DuringSave); } return(status.SetResult(book)); }
public static async Task <IStatusGeneric> CheckSingleBookAsync( this BookDbContext context, int bookId, bool fixBadCacheValues, CancellationToken cancellationToken) { var status = new StatusGenericHandler(); var dto = await context.Books .IgnoreQueryFilters() .MapBookToDto() .SingleOrDefaultAsync(x => x.BookId == bookId, cancellationToken); if (dto == null) { status.AddError("SQL: No book found."); } Book loadedBook = null; var fixedThem = fixBadCacheValues ? "and fixed it" : "(not fixed)"; if (dto.RecalcReviewsCount != dto.ReviewsCount || Math.Abs((dto.RecalcReviewsAverageVotes ?? 0) - dto.ReviewsAverageVotes) > 0.0001) { status.AddError($"SQL: Review cached values incorrect {fixedThem}. " + $"Actual Reviews.Count = {dto.RecalcReviewsCount}, Cached ReviewsCount = {dto.ReviewsCount}. " + $"Actual Reviews average = {dto.RecalcReviewsAverageVotes:F5}, Cached ReviewsAverageVotes = {dto.ReviewsAverageVotes:F5}. " + $"Last updated {dto.LastUpdatedUtc:G}"); if (fixBadCacheValues) { loadedBook = await context.Books. SingleOrDefaultAsync(x => x.BookId == bookId); loadedBook?.UpdateReviewCachedValues(dto.RecalcReviewsCount, dto.RecalcReviewsAverageVotes ?? 0); } } if (dto.RecalcAuthorsOrdered != dto.AuthorsOrdered) { status.AddError($"SQL: AuthorsOrdered cached value incorrect {fixedThem}. " + $"Actual authors string = {dto.RecalcAuthorsOrdered}, Cached AuthorsOrdered = {dto.AuthorsOrdered}. " + $"Last updated {dto.LastUpdatedUtc:G}"); if (fixBadCacheValues) { loadedBook ??= await context.Books.SingleOrDefaultAsync(x => x.BookId == bookId); loadedBook.ResetAuthorsOrdered(dto.RecalcAuthorsOrdered); } } return(status); }
public void TestStatusHasTwoErrorOnSamePropOk() { //SETUP var status = new StatusGenericHandler(); status.AddError("An Error", "MyProp"); status.AddError("Another Error", "MyProp"); //ATTEMPT var actionResult = status.Response(); //VERIFY actionResult.CheckResponse(status); }
public void TestStatusHasTwoErrors() { //SETUP var status = new StatusGenericHandler(); //ATTEMPT status.AddError("This is an error"); status.AddError("This is another error"); //VERIFY status.HasErrors.ShouldBeTrue(); status.Message.ShouldEqual("Failed with 2 errors"); status.GetAllErrors().ShouldEqual("This is an error\nThis is another error"); }
public IStatusGeneric NonStatusGenericNum(int i) { var status = new StatusGenericHandler(); //add error and return immediately if (i <= 0) { return(status.AddError("input must be positive", nameof(i))); } //combine error from another non-StatusGeneric method and return if has errors status.CombineStatuses(NonStatusGenericString(i == 10 ? null : "good")); if (status.HasErrors) { return(status); } //combine errors from another generic status, keeping the status to extract later var stringStatus = StatusGenericNumReturnString(i); if (status.CombineStatuses(stringStatus).HasErrors) { return(status); } var stringResult = stringStatus.Result; //Other methods here return(status); }
public static IStatusGeneric <Order> CreateOrderFactory( string customerName, DateTime expectedDeliveryDate, IEnumerable <OrderBooksDto> bookOrders) { var status = new StatusGenericHandler <Order>(); var order = new Order { CustomerName = customerName, ExpectedDeliveryDate = expectedDeliveryDate, DateOrderedUtc = DateTime.UtcNow, HasBeenDelivered = expectedDeliveryDate < DateTime.Today }; byte lineNum = 1; order._lineItems = new HashSet <LineItem>(bookOrders .Select(x => new LineItem(x.numBooks, x.ChosenBook, lineNum++))); if (!order._lineItems.Any()) { status.AddError("No items in your basket."); } status.Result = order; return(status); }
/// <summary> /// This hard deletes this entity and any dependent entities that are already been cascade soft deleted /// </summary> /// <typeparam name="TEntity"></typeparam> /// <param name="hardDeleteThisEntity">entity class with cascade soft delete interface. Mustn't be null</param> /// <param name="callSaveChanges">Defaults to calling SaveChanges. If set to false, then you must call SaveChanges</param> /// <returns>Returns a status. If no errors then Result contains the number of entities which were hard deleted, plus summary string in Message part</returns> public IStatusGeneric <int> HardDeleteSoftDeletedEntries <TEntity>(TEntity hardDeleteThisEntity, bool callSaveChanges = true) where TEntity : class, TInterface { if (hardDeleteThisEntity == null) { throw new ArgumentNullException(nameof(hardDeleteThisEntity)); } var status = new StatusGenericHandler <int>(); if (_config.GetSoftDeleteValue.Compile().Invoke(hardDeleteThisEntity) == 0) { return(status.AddError($"This entry isn't {_config.TextSoftDeletedPastTense}.")); } //For reset you need to read every time because some of the collection might be soft deleted already var walker = new CascadeWalker <TInterface>(_context, _config, false, CascadeSoftDelWhatDoing.HardDeleteSoftDeleted, false); walker.WalkEntitiesSoftDelete(hardDeleteThisEntity, 1).CheckSyncValueTaskWorked(); if (callSaveChanges) { _context.SaveChanges(); } return(ReturnSuccessFullResult(CascadeSoftDelWhatDoing.HardDeleteSoftDeleted, walker.NumFound)); }
/// <summary> /// This with cascade soft delete this entity and any dependent entities with the correct delete behaviour /// </summary> /// <typeparam name="TEntity"></typeparam> /// <param name="softDeleteThisEntity">entity class with cascade soft delete interface. Mustn't be null</param> /// <param name="callSaveChanges">Defaults to calling SaveChanges. If set to false, then you must call SaveChanges</param> /// <returns>Returns a status. If no errors then Result contains the number of entities that had been cascaded deleted, plus summary string in Message part</returns> public IStatusGeneric <int> SetCascadeSoftDelete <TEntity>(TEntity softDeleteThisEntity, bool callSaveChanges = true) where TEntity : class, TInterface { if (softDeleteThisEntity == null) { throw new ArgumentNullException(nameof(softDeleteThisEntity)); } //If is a one-to-one entity we return an error var keys = _context.Entry(softDeleteThisEntity).Metadata.GetForeignKeys(); if (!keys.All(x => x.DependentToPrincipal?.IsCollection == true || x.PrincipalToDependent?.IsCollection == true)) { //This it is a one-to-one entity throw new InvalidOperationException("You cannot soft delete a one-to-one relationship. " + "It causes problems if you try to create a new version."); } var status = new StatusGenericHandler <int>(); if (_config.GetSoftDeleteValue.Compile().Invoke(softDeleteThisEntity) != 0) { return(status.AddError($"This entry is already {_config.TextSoftDeletedPastTense}.")); } var walker = new CascadeWalker <TInterface>(_context, _config, CascadeSoftDelWhatDoing.SoftDelete, _config.ReadEveryTime); walker.WalkEntitiesSoftDelete(softDeleteThisEntity, 1); if (callSaveChanges) { _context.SaveChanges(); } return(ReturnSuccessFullResult(CascadeSoftDelWhatDoing.SoftDelete, walker.NumFound)); }
/// <summary> /// This with cascade soft delete this entity and any dependent entities with the correct delete behaviour /// </summary> /// <typeparam name="TEntity"></typeparam> /// <param name="softDeleteThisEntity">entity class with cascade soft delete interface. Mustn't be null</param> /// <param name="callSaveChanges">Defaults to calling SaveChanges. If set to false, then you must call SaveChanges</param> /// <returns>Returns a status. If no errors then Result contains the number of entities that had been cascaded deleted, plus summary string in Message part</returns> public async Task <IStatusGeneric <int> > SetCascadeSoftDeleteAsync <TEntity>(TEntity softDeleteThisEntity, bool callSaveChanges = true) where TEntity : class, TInterface { if (softDeleteThisEntity == null) { throw new ArgumentNullException(nameof(softDeleteThisEntity)); } _context.ThrowExceptionIfPrincipalOneToOne(softDeleteThisEntity); var status = new StatusGenericHandler <int>(); if (_config.GetSoftDeleteValue.Compile().Invoke(softDeleteThisEntity) != 0) { return(status.AddError($"This entry is already {_config.TextSoftDeletedPastTense}.")); } var walker = new CascadeWalker <TInterface>(_context, _config, true, CascadeSoftDelWhatDoing.SoftDelete, _config.ReadEveryTime); await walker.WalkEntitiesSoftDelete(softDeleteThisEntity, 1); if (callSaveChanges) { await _context.SaveChangesAsync(); } return(ReturnSuccessFullResult(CascadeSoftDelWhatDoing.SoftDelete, walker.NumFound)); }
/// <summary> /// This will soft delete the single entity. This may delete other dependent /// </summary> /// <param name="softDeleteThisEntity">Mustn't be null</param> /// <param name="callSaveChanges">Defaults to calling SaveChanges. If set to false, then you must call SaveChanges</param> /// <returns>Returns status. If not errors then Result return 1 to say it worked. Zero if error</returns> public async Task <IStatusGeneric <int> > SetSoftDeleteAsync(TInterface softDeleteThisEntity, bool callSaveChanges = true) { if (softDeleteThisEntity == null) { throw new ArgumentNullException(nameof(softDeleteThisEntity)); } var keys = _context.Entry(softDeleteThisEntity).Metadata.GetForeignKeys(); if (!keys.All(x => x.DependentToPrincipal?.IsCollection == true || x.PrincipalToDependent?.IsCollection == true)) { //This it is a one-to-one entity - setting a one-to-one as soft deleted causes problems when you try to create a replacement throw new InvalidOperationException("You cannot soft delete a one-to-one relationship. " + "It causes problems if you try to create a new version."); } var status = new StatusGenericHandler <int>(); if (_config.GetSoftDeleteValue.Compile().Invoke(softDeleteThisEntity)) { return(status.AddError($"This entry is already {_config.TextSoftDeletedPastTense}.")); } _config.SetSoftDeleteValue(softDeleteThisEntity, true); if (callSaveChanges) { await _context.SaveChangesAsync(); } status.Message = $"Successfully {_config.TextSoftDeletedPastTense} this entry"; status.SetResult(1); //one changed return(status); }
/// <summary> /// This hard deletes (i.e. calls EF Core's Remove method) for this entity ONLY if it first been soft deleted. /// This will delete the entity and possibly delete other dependent entities. /// </summary> /// <typeparam name="TEntity"></typeparam> /// <param name="hardDeleteThisEntity">The entity to delete</param> /// <param name="callSaveChanges">Defaults to calling SaveChanges. If set to false, then you must call SaveChanges yourself</param> /// <returns>The number of entities that were deleted. This will include any dependent entities that that had a cascade delete behaviour</returns> public async Task <IStatusGeneric <int> > HardDeleteSoftDeletedEntryAsync <TEntity>(TEntity hardDeleteThisEntity, bool callSaveChanges = true) where TEntity : class, TInterface { if (hardDeleteThisEntity == null) { throw new ArgumentNullException(nameof(hardDeleteThisEntity)); } var status = new StatusGenericHandler <int>(); if (!_config.GetSoftDeleteValue.Compile().Invoke(hardDeleteThisEntity)) { return(status.AddError($"This entry isn't {_config.TextSoftDeletedPastTense}.")); } _context.Remove(hardDeleteThisEntity); var numDeleted = 1; if (callSaveChanges) { numDeleted = await _context.SaveChangesAsync(); } status.Message = $"Successfully {_config.TextHardDeletedPastTense} this entry"; status.SetResult(numDeleted); return(status); }
//--------------------------------------------- // private methods private IStatusGeneric CatchAndFixConcurrencyException(Exception ex, DbContext context) { var dbUpdateEx = ex as DbUpdateConcurrencyException; if (dbUpdateEx == null || dbUpdateEx.Entries.Count != 1) { return(null); //can't handle this error } var entry = dbUpdateEx.Entries.Single(); if (!(entry.Entity is ProductStock failedUpdate)) { return(null); } var status = new StatusGenericHandler(); //This entity MUST be read as NoTracking otherwise it will interfere with the same entity we are trying to write var overwroteData = context.Set <ProductStock>().AsNoTracking().SingleOrDefault(p => p.ProductName == failedUpdate.ProductName); if (overwroteData == null) { //The ProductStock was deleted return(status.AddError("The product you were interested in has been removed from our stock.")); } var addedChange = failedUpdate.NumAllocated - (int)entry.Property(nameof(ProductStock.NumAllocated)).OriginalValue; var combinedAlloc = overwroteData.NumAllocated + addedChange; entry.Property(nameof(ProductStock.NumAllocated)).CurrentValue = combinedAlloc; entry.Property(nameof(ProductStock.NumAllocated)).OriginalValue = overwroteData.NumAllocated; return(status); }
/// <summary> /// This looks for this entity and any dependent entities that are already been cascade soft deleted and are valid to be hard deleted. /// </summary> /// <typeparam name="TEntity"></typeparam> /// <param name="checkHardDeleteThisEntity">entity class with cascade soft delete interface. Mustn't be null</param> /// <returns>Returns a status. If no errors then Result contains the number of entities which are eligible for hard delete, plus summary string in Message part</returns> public IStatusGeneric <int> CheckCascadeSoftDelete <TEntity>(TEntity checkHardDeleteThisEntity) where TEntity : class, TInterface { if (checkHardDeleteThisEntity == null) { throw new ArgumentNullException(nameof(checkHardDeleteThisEntity)); } var status = new StatusGenericHandler <int>(); if (_config.GetSoftDeleteValue.Compile().Invoke(checkHardDeleteThisEntity) == 0) { return(status.AddError($"This entry isn't {_config.TextSoftDeletedPastTense}.")); } //For reset you need to read every time because some of the collection might be soft deleted already var walker = new CascadeWalker <TInterface>(_context, _config, false, CascadeSoftDelWhatDoing.CheckWhatWillDelete, _config.ReadEveryTime); var valueTask = walker.WalkEntitiesSoftDelete(checkHardDeleteThisEntity, 1); if (!valueTask.IsCompleted) { throw new InvalidOperationException("Can only run sync tasks"); } return(ReturnSuccessFullResult(CascadeSoftDelWhatDoing.CheckWhatWillDelete, walker.NumFound)); }
/******************************************************************* #A This method returns a status with the created Order, which is null if there are no errors #B The PlaceOrderInDto contains a TandC bool and a collection of BookIds and number of books #C This status is used to gather and errors and, if no errors, return an Order #D These validate the user's input #E The _dbAccess contains the code to find each book - see listing 4.3 #F This method creates list of bookId and number of books - see end of listing 4.2 #G If any errors were found while checking each order line, then it returns the error status #H This calls the Order static factory. It is the Order's job to form the Order with LineItems #I Again, any errors will abort the Order and the errors returned #J The _dbAccess contains the code add the Order and call SaveChangesAsync #K Finally it returns a successful status with the created Order entity ****************************************************************/ private IStatusGeneric <List <OrderBookDto> > FormLineItemsWithErrorChecking (IEnumerable <OrderLineItem> lineItems, IDictionary <int, BookView> booksDict) { var status = new StatusGenericHandler <List <OrderBookDto> >(); var result = new List <OrderBookDto>(); foreach (var lineItem in lineItems) { if (!booksDict.ContainsKey(lineItem.BookId)) { throw new InvalidOperationException( $"An order failed because book, id = {lineItem.BookId} was missing."); } var bookView = booksDict[lineItem.BookId]; if (bookView.ActualPrice <= 0) { status.AddError($"Sorry, the book '{bookView.Title}' is not for sale."); } else { //Valid, so add to the order result.Add(new OrderBookDto(bookView, lineItem.NumBooks)); } } return(status.SetResult(result)); }
/// <summary> /// This will soft delete the single entity. This may delete other dependent /// </summary> /// <param name="softDeleteThisEntity">Mustn't be null</param> /// <param name="callSaveChanges">Defaults to calling SaveChanges. If set to false, then you must call SaveChanges</param> /// <returns>Returns status. If not errors then Result return 1 to say it worked. Zero if error</returns> public async Task <IStatusGeneric <int> > SetSoftDeleteAsync(TInterface softDeleteThisEntity, bool callSaveChanges = true) { if (softDeleteThisEntity == null) { throw new ArgumentNullException(nameof(softDeleteThisEntity)); } _context.ThrowExceptionIfPrincipalOneToOne(softDeleteThisEntity); var status = new StatusGenericHandler <int>(); if (_config.GetSoftDeleteValue.Compile().Invoke(softDeleteThisEntity)) { return(status.AddError($"This entry is already {_config.TextSoftDeletedPastTense}.")); } _config.SetSoftDeleteValue(softDeleteThisEntity, true); if (callSaveChanges) { await _context.SaveChangesAsync(); } status.Message = $"Successfully {_config.TextSoftDeletedPastTense} this entry"; status.SetResult(1); //one changed return(status); }