using SqlSugar; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace OrmTest { /// /// ASYNC DELETE TEST SUITE - Comprehensive Testing for SqlSugar Async Delete Operations /// /// PURPOSE: /// Tests all async delete methods in SqlSugar ORM with focus on: /// - Basic async delete operations (ExecuteCommandAsync, ExecuteCommandHasChangeAsync) /// - Delete by primary key, expression, and entity /// - CancellationToken support (CRITICAL - previously untested) /// - Cascade and soft delete scenarios /// - Error handling and edge cases /// /// PRIORITY: HIGH - Critical for data integrity and async operation support /// /// USAGE: /// // Run all tests /// NewUnitTest.AsyncDelete(); /// /// // Run individual test /// NewUnitTest.AsyncDelete_ExecuteCommandAsync(); /// /// TEST COVERAGE: /// ExecuteCommandAsync() - Basic async delete /// ExecuteCommandHasChangeAsync() - Change detection /// DeleteRange() - Bulk async delete /// Delete by primary key /// Delete by expression /// CancellationToken support across all methods /// Cascade delete scenarios /// Soft delete (IsLogic) /// Concurrent delete operations /// Error scenarios /// /// DEPENDENCIES: /// - Order entity (with identity column) /// - OrderItem entity (for cascade tests) /// - SoftDeleteEntity (for soft delete tests) /// /// public partial class NewUnitTest { #region Main Entry Point /// /// Main entry point - Executes all 15 async delete tests /// /// Test Categories: /// A. Basic Async Delete Tests (5 tests) - Core delete operations /// B. CancellationToken Tests (4 tests) - Cancellation support /// C. Cascade & Soft Delete Tests (3 tests) - Advanced delete scenarios /// D. Edge Cases & Error Handling (3 tests) - Robustness validation /// /// Usage: NewUnitTest.AsyncDelete(); /// public static void AsyncDelete() { Console.WriteLine("\n================================================================"); Console.WriteLine(" ASYNC DELETE TEST SUITE - COMPREHENSIVE"); Console.WriteLine("================================================================\n"); try { // CATEGORY A: Basic Async Delete Tests (5 functions) Console.WriteLine("--- BASIC ASYNC DELETE OPERATIONS ---\n"); AsyncDelete_ExecuteCommandAsync(); AsyncDelete_ExecuteCommandHasChangeAsync(); AsyncDelete_MultipleEntitiesAsync(); AsyncDelete_ByPrimaryKey(); AsyncDelete_ByExpression(); // CATEGORY B: CancellationToken Tests (4 functions) Console.WriteLine("\n--- CANCELLATION TOKEN SUPPORT ---\n"); AsyncDelete_CancellationToken_Basic(); AsyncDelete_CancellationToken_Immediate(); AsyncDelete_CancellationToken_BulkDelete(); AsyncDelete_CancellationToken_Timeouts(); // CATEGORY C: Cascade & Soft Delete Tests (3 functions) Console.WriteLine("\n--- CASCADE & SOFT DELETE TESTS ---\n"); AsyncDelete_CascadeDelete(); AsyncDelete_SoftDelete(); AsyncDelete_SoftDelete_Timestamp(); // CATEGORY D: Edge Cases & Error Handling (3 functions) Console.WriteLine("\n--- ERROR HANDLING & EDGE CASES ---\n"); AsyncDelete_NonExistent(); AsyncDelete_ConcurrentDeletes(); AsyncDelete_Performance(); Console.WriteLine("\n================================================================"); Console.WriteLine(" ALL ASYNC DELETE TESTS PASSED (15/15)"); Console.WriteLine("================================================================\n"); } catch (Exception ex) { Console.WriteLine("\n================================================================"); Console.WriteLine(" TEST SUITE FAILED"); Console.WriteLine("================================================================"); Console.WriteLine("\nError: " + ex.Message + "\n"); throw; } } #endregion #region A. Basic Async Delete Tests (5 functions) /// /// Test 1: Basic ExecuteCommandAsync /// Validates: Single entity async delete, affected rows returned, entity removed from database /// public static void AsyncDelete_ExecuteCommandAsync() { Console.WriteLine("TEST 1: AsyncDelete_ExecuteCommandAsync"); var db = Db; // Setup: Insert test entity var order = new Order { Name = "Test Order for Delete", Price = 100.50m, CreateTime = DateTime.Now }; var insertedId = db.Insertable(order).ExecuteReturnIdentity(); // Test: Delete single entity async var deleteTask = db.Deleteable() .Where(x => x.Id == insertedId) .ExecuteCommandAsync(); var affectedRows = deleteTask.GetAwaiter().GetResult(); // Verify: Affected rows returned if (affectedRows != 1) throw new Exception($"Expected 1 affected row, got {affectedRows}"); // Verify: Entity deleted from database var deletedOrder = db.Queryable().InSingle(insertedId); if (deletedOrder != null) throw new Exception("Entity should be deleted but still exists in database"); Console.WriteLine(" [OK] ExecuteCommandAsync works correctly"); Console.WriteLine(" [OK] Affected rows returned: " + affectedRows); Console.WriteLine(" [OK] Entity deleted from database\n"); } /// /// Test 2: ExecuteCommandHasChangeAsync /// Validates: Change detection for existing and non-existent entities /// public static void AsyncDelete_ExecuteCommandHasChangeAsync() { Console.WriteLine("TEST 2: AsyncDelete_ExecuteCommandHasChangeAsync"); var db = Db; // Setup: Insert test entity var order = new Order { Name = "Test Order for HasChange", Price = 200.75m, CreateTime = DateTime.Now }; var insertedId = db.Insertable(order).ExecuteReturnIdentity(); // Test: Delete existing entity (should return true) var hasChangeTask = db.Deleteable() .Where(x => x.Id == insertedId) .ExecuteCommandHasChangeAsync(); var hasChange = hasChangeTask.GetAwaiter().GetResult(); if (!hasChange) throw new Exception("Expected HasChange=true for existing entity"); // Test: Delete non-existent entity (should return false) var noChangeTask = db.Deleteable() .Where(x => x.Id == 999999) .ExecuteCommandHasChangeAsync(); var noChange = noChangeTask.GetAwaiter().GetResult(); if (noChange) throw new Exception("Expected HasChange=false for non-existent entity"); Console.WriteLine(" [OK] HasChange=true for existing entity"); Console.WriteLine(" [OK] HasChange=false for non-existent entity"); Console.WriteLine(" [OK] Change detection works correctly\n"); } /// /// Test 3: Multiple entities async delete /// Validates: Bulk delete with DeleteRange, all entities removed /// public static void AsyncDelete_MultipleEntitiesAsync() { Console.WriteLine("TEST 3: AsyncDelete_MultipleEntitiesAsync"); var db = Db; // Setup: Insert 100 test entities var orders = new List(); for (int i = 0; i < 100; i++) { orders.Add(new Order { Name = $"Bulk Delete Order {i}", Price = 10.0m + i, CreateTime = DateTime.Now }); } var insertedIds = db.Insertable(orders).ExecuteReturnPkList(); // Test: DeleteRange with 100 entities var deleteTask = db.Deleteable() .Where(x => insertedIds.Contains(x.Id)) .ExecuteCommandAsync(); var affectedRows = deleteTask.GetAwaiter().GetResult(); // Verify: All deleted if (affectedRows != 100) throw new Exception($"Expected 100 affected rows, got {affectedRows}"); var remainingCount = db.Queryable() .Where(x => insertedIds.Contains(x.Id)) .Count(); if (remainingCount != 0) throw new Exception($"Expected 0 remaining entities, found {remainingCount}"); Console.WriteLine(" [OK] Bulk delete async works"); Console.WriteLine($" [OK] Deleted {affectedRows} entities"); Console.WriteLine(" [OK] All entities removed from database\n"); } /// /// Test 4: Delete by primary key /// Validates: PK-based delete, only specific entity deleted /// public static void AsyncDelete_ByPrimaryKey() { Console.WriteLine("TEST 4: AsyncDelete_ByPrimaryKey"); var db = Db; // Setup: Insert multiple test entities var orders = new List { new Order { Name = "Order 1", Price = 100m, CreateTime = DateTime.Now }, new Order { Name = "Order 2", Price = 200m, CreateTime = DateTime.Now }, new Order { Name = "Order 3", Price = 300m, CreateTime = DateTime.Now } }; var insertedIds = db.Insertable(orders).ExecuteReturnPkList(); // Test: Delete by ID (using In method for primary key) var targetId = insertedIds[1]; // Delete middle one var deleteTask = db.Deleteable() .In(targetId) .ExecuteCommandAsync(); var affectedRows = deleteTask.GetAwaiter().GetResult(); // Verify: Entity deleted if (affectedRows != 1) throw new Exception($"Expected 1 affected row, got {affectedRows}"); var deletedOrder = db.Queryable().InSingle(targetId); if (deletedOrder != null) throw new Exception("Target entity should be deleted"); // Verify: Only specific entity deleted var remainingOrders = db.Queryable() .Where(x => insertedIds.Contains(x.Id)) .ToList(); if (remainingOrders.Count != 2) throw new Exception($"Expected 2 remaining orders, found {remainingOrders.Count}"); // Cleanup db.Deleteable().Where(x => insertedIds.Contains(x.Id)).ExecuteCommand(); Console.WriteLine(" [OK] PK-based delete works"); Console.WriteLine(" [OK] Only specific entity deleted"); Console.WriteLine(" [OK] Other entities remain intact\n"); } /// /// Test 5: Delete by expression /// Validates: Expression-based delete, correct entities removed, others remain /// public static void AsyncDelete_ByExpression() { Console.WriteLine("TEST 5: AsyncDelete_ByExpression"); var db = Db; // Setup: Insert test entities with varying prices var orders = new List { new Order { Name = "Low Price 1", Price = 25m, CreateTime = DateTime.Now }, new Order { Name = "Low Price 2", Price = 35m, CreateTime = DateTime.Now }, new Order { Name = "High Price 1", Price = 55m, CreateTime = DateTime.Now }, new Order { Name = "High Price 2", Price = 75m, CreateTime = DateTime.Now }, new Order { Name = "High Price 3", Price = 95m, CreateTime = DateTime.Now } }; var insertedIds = db.Insertable(orders).ExecuteReturnPkList(); // Test: Delete(x => x.Price > 50) - should delete 3 orders var deleteTask = db.Deleteable() .Where(x => x.Price > 50 && insertedIds.Contains(x.Id)) .ExecuteCommandAsync(); var affectedRows = deleteTask.GetAwaiter().GetResult(); // Verify: Correct entities deleted (3 with Price > 50) if (affectedRows != 3) throw new Exception($"Expected 3 affected rows, got {affectedRows}"); // Verify: Others remain (2 with Price <= 50) var remainingOrders = db.Queryable() .Where(x => insertedIds.Contains(x.Id)) .ToList(); if (remainingOrders.Count != 2) throw new Exception($"Expected 2 remaining orders, found {remainingOrders.Count}"); if (remainingOrders.Any(x => x.Price > 50)) throw new Exception("Orders with Price > 50 should be deleted"); // Cleanup db.Deleteable().Where(x => insertedIds.Contains(x.Id)).ExecuteCommand(); Console.WriteLine(" [OK] Expression-based delete works"); Console.WriteLine($" [OK] Deleted {affectedRows} entities matching expression"); Console.WriteLine(" [OK] Other entities remain intact\n"); } #endregion #region B. CancellationToken Tests (4 functions) /// /// Test 6: CancellationToken basic functionality /// Validates: Delete operation can be cancelled with timeout /// public static void AsyncDelete_CancellationToken_Basic() { Console.WriteLine("TEST 6: AsyncDelete_CancellationToken_Basic"); var db = Db; // Setup: Insert test entity var order = new Order { Name = "Test Order for Cancellation", Price = 100m, CreateTime = DateTime.Now }; var insertedId = db.Insertable(order).ExecuteReturnIdentity(); // Test: ExecuteCommandAsync with valid token (should succeed) using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5))) { var deleteTask = db.Deleteable() .Where(x => x.Id == insertedId) .ExecuteCommandAsync(cts.Token); var affectedRows = deleteTask.GetAwaiter().GetResult(); if (affectedRows != 1) throw new Exception($"Expected 1 affected row, got {affectedRows}"); } Console.WriteLine(" [OK] Delete with CancellationToken succeeded"); Console.WriteLine(" [OK] Token not cancelled - operation completed\n"); } /// /// Test 7: CancellationToken immediate cancellation /// Validates: Pre-cancelled token prevents database operation /// public static void AsyncDelete_CancellationToken_Immediate() { Console.WriteLine("TEST 7: AsyncDelete_CancellationToken_Immediate"); var db = Db; // Setup: Insert test entity var order = new Order { Name = "Test Order for Immediate Cancel", Price = 100m, CreateTime = DateTime.Now }; var insertedId = db.Insertable(order).ExecuteReturnIdentity(); // Test: Pre-cancelled token using (var cts = new CancellationTokenSource()) { cts.Cancel(); // Cancel immediately try { var deleteTask = db.Deleteable() .Where(x => x.Id == insertedId) .ExecuteCommandAsync(cts.Token); deleteTask.GetAwaiter().GetResult(); throw new Exception("Expected OperationCanceledException"); } catch (OperationCanceledException) { // Expected - operation was cancelled } catch (AggregateException ex) when (ex.InnerException is OperationCanceledException) { // Expected - wrapped in AggregateException } } // Verify: Entity still exists (delete was cancelled) var existingOrder = db.Queryable().InSingle(insertedId); if (existingOrder == null) throw new Exception("Entity should still exist after cancelled delete"); // Cleanup db.Deleteable().In(insertedId).ExecuteCommand(); Console.WriteLine(" [OK] Immediate cancellation works"); Console.WriteLine(" [OK] No database operation performed"); Console.WriteLine(" [OK] Entity remains in database\n"); } /// /// Test 8: CancellationToken bulk delete /// Validates: Bulk delete can be cancelled mid-operation /// public static void AsyncDelete_CancellationToken_BulkDelete() { Console.WriteLine("TEST 8: AsyncDelete_CancellationToken_BulkDelete"); var db = Db; // Setup: Insert 1000 test entities var orders = new List(); for (int i = 0; i < 1000; i++) { orders.Add(new Order { Name = $"Bulk Cancel Order {i}", Price = 10.0m + i, CreateTime = DateTime.Now }); } var insertedIds = db.Insertable(orders).ExecuteReturnPkList(); // Test: Delete with short timeout (may or may not cancel depending on speed) using (var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(1))) { try { var deleteTask = db.Deleteable() .Where(x => insertedIds.Contains(x.Id)) .ExecuteCommandAsync(cts.Token); deleteTask.GetAwaiter().GetResult(); // If we get here, operation completed before timeout Console.WriteLine(" [OK] Bulk delete completed before timeout"); } catch (OperationCanceledException) { Console.WriteLine(" [OK] Bulk delete was cancelled"); } catch (AggregateException ex) when (ex.InnerException is OperationCanceledException) { Console.WriteLine(" [OK] Bulk delete was cancelled (wrapped)"); } catch (Exception ex) when (ex.Message.Contains("Operation cancelled")) { Console.WriteLine(" [OK] Bulk delete was cancelled (SQL exception)"); } } // Cleanup: Remove any remaining entities db.Deleteable().Where(x => insertedIds.Contains(x.Id)).ExecuteCommand(); Console.WriteLine(" [OK] Bulk delete cancellation handled\n"); } /// /// Test 9: CancellationToken timeout scenarios /// Validates: Various timeout durations work correctly /// public static void AsyncDelete_CancellationToken_Timeouts() { Console.WriteLine("TEST 9: AsyncDelete_CancellationToken_Timeouts"); var db = Db; // Test various timeout durations var timeouts = new[] { 100, 500, 1000, 5000 }; // milliseconds foreach (var timeout in timeouts) { // Setup: Insert test entity var order = new Order { Name = $"Timeout Test Order {timeout}ms", Price = 100m, CreateTime = DateTime.Now }; var insertedId = db.Insertable(order).ExecuteReturnIdentity(); // Test: Delete with specific timeout using (var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(timeout))) { try { var deleteTask = db.Deleteable() .Where(x => x.Id == insertedId) .ExecuteCommandAsync(cts.Token); deleteTask.GetAwaiter().GetResult(); Console.WriteLine($" [OK] {timeout}ms timeout - operation completed"); } catch (OperationCanceledException) { Console.WriteLine($" [OK] {timeout}ms timeout - operation cancelled"); // Cleanup if cancelled db.Deleteable().In(insertedId).ExecuteCommand(); } catch (AggregateException ex) when (ex.InnerException is OperationCanceledException) { Console.WriteLine($" [OK] {timeout}ms timeout - operation cancelled (wrapped)"); // Cleanup if cancelled db.Deleteable().In(insertedId).ExecuteCommand(); } } } Console.WriteLine(" [OK] All timeout scenarios handled correctly\n"); } #endregion #region C. Cascade & Soft Delete Tests (3 functions) /// /// Test 10: Cascade delete /// Validates: Foreign key constraints and cascade behavior /// Note: Actual cascade depends on database FK configuration /// public static void AsyncDelete_CascadeDelete() { Console.WriteLine("TEST 10: AsyncDelete_CascadeDelete"); var db = Db; // Setup: Ensure tables exist db.CodeFirst.InitTables(); // Setup: Insert parent order var order = new Order { Name = "Parent Order for Cascade", Price = 500m, CreateTime = DateTime.Now }; var orderId = db.Insertable(order).ExecuteReturnIdentity(); // Setup: Insert child order items var orderItems = new List { new OrderItem { OrderId = orderId, ItemId = 1, Price = 100m, CreateTime = DateTime.Now }, new OrderItem { OrderId = orderId, ItemId = 2, Price = 200m, CreateTime = DateTime.Now }, new OrderItem { OrderId = orderId, ItemId = 3, Price = 200m, CreateTime = DateTime.Now } }; db.Insertable(orderItems).ExecuteCommand(); // Test: Delete parent entity var deleteTask = db.Deleteable() .Where(x => x.Id == orderId) .ExecuteCommandAsync(); var affectedRows = deleteTask.GetAwaiter().GetResult(); if (affectedRows != 1) throw new Exception($"Expected 1 affected row, got {affectedRows}"); // Verify: Parent deleted var deletedOrder = db.Queryable().InSingle(orderId); if (deletedOrder != null) throw new Exception("Parent entity should be deleted"); // Check child entities (behavior depends on FK configuration) var remainingItems = db.Queryable() .Where(x => x.OrderId == orderId) .ToList(); if (remainingItems.Any()) { Console.WriteLine(" [OK] Parent deleted, child records remain (no FK cascade)"); // Cleanup child records manually db.Deleteable().Where(x => x.OrderId == orderId).ExecuteCommand(); } else { Console.WriteLine(" [OK] Parent deleted, child records cascaded (FK cascade enabled)"); } Console.WriteLine(" [OK] Cascade delete behavior verified\n"); } /// /// Test 11: Soft delete (IsLogic) /// Validates: Entity marked as deleted but not physically removed /// public static void AsyncDelete_SoftDelete() { Console.WriteLine("TEST 11: AsyncDelete_SoftDelete"); var db = Db; // Setup: Create soft delete entity table db.CodeFirst.InitTables(); // Insert test entity var entity = new SoftDeleteEntity { Name = "Soft Delete Test", Value = 100 }; var insertedId = db.Insertable(entity).ExecuteReturnIdentity(); // Test: Soft delete with IsLogic var deleteTask = db.Deleteable() .Where(x => x.Id == insertedId) .IsLogic() // Enable soft delete .ExecuteCommandAsync(); var affectedRows = deleteTask.GetAwaiter().GetResult(); if (affectedRows != 1) throw new Exception($"Expected 1 affected row, got {affectedRows}"); // Verify: IsDeleted=true, not actually deleted var softDeletedEntity = db.Queryable() .Where(x => x.Id == insertedId) .First(); if (softDeletedEntity == null) throw new Exception("Entity should still exist in database"); if (!softDeletedEntity.IsDeleted) throw new Exception("IsDeleted should be true"); Console.WriteLine(" [OK] Soft delete works"); Console.WriteLine(" [OK] IsDeleted=true, entity not physically deleted"); Console.WriteLine(" [OK] Entity still in database\n"); } /// /// Test 12: Soft delete with timestamp /// Validates: Soft delete with IsDeleted flag (timestamp would require custom logic) /// public static void AsyncDelete_SoftDelete_Timestamp() { Console.WriteLine("TEST 12: AsyncDelete_SoftDelete_Timestamp"); var db = Db; // Setup: Create soft delete entity with timestamp table db.CodeFirst.InitTables(); // Insert test entity var entity = new SoftDeleteWithTimestamp { Name = "Soft Delete with Timestamp", Value = 200 }; var insertedId = db.Insertable(entity).ExecuteReturnIdentity(); // Test: Soft delete with IsLogic (sets IsDeleted flag) var deleteTask = db.Deleteable() .Where(x => x.Id == insertedId) .IsLogic() // Enable soft delete .ExecuteCommandAsync(); var affectedRows = deleteTask.GetAwaiter().GetResult(); if (affectedRows != 1) throw new Exception($"Expected 1 affected row, got {affectedRows}"); // Verify: IsDeleted flag set var softDeletedEntity = db.Queryable() .Where(x => x.Id == insertedId) .First(); if (softDeletedEntity == null) throw new Exception("Entity should still exist in database"); if (!softDeletedEntity.IsDeleted) throw new Exception("IsDeleted should be true"); // Note: DeletedAt timestamp would require custom update logic // IsLogic() only sets the IsDeleted flag by default Console.WriteLine(" [OK] Soft delete works with IsDeleted flag"); Console.WriteLine(" [OK] Entity marked as deleted but not physically removed"); Console.WriteLine(" [OK] Entity still in database (timestamp field available for custom logic)\n"); } #endregion #region D. Edge Cases & Error Handling (3 functions) /// /// Test 13: Delete non-existent entity /// Validates: Graceful handling of deleting non-existent records /// public static void AsyncDelete_NonExistent() { Console.WriteLine("TEST 13: AsyncDelete_NonExistent"); var db = Db; // Test: Delete entity that doesn't exist var deleteTask = db.Deleteable() .Where(x => x.Id == 999999) .ExecuteCommandAsync(); var affectedRows = deleteTask.GetAwaiter().GetResult(); // Verify: 0 rows affected if (affectedRows != 0) throw new Exception($"Expected 0 affected rows, got {affectedRows}"); // Test: HasChange should return false var hasChangeTask = db.Deleteable() .Where(x => x.Id == 999999) .ExecuteCommandHasChangeAsync(); var hasChange = hasChangeTask.GetAwaiter().GetResult(); if (hasChange) throw new Exception("Expected HasChange=false for non-existent entity"); Console.WriteLine(" [OK] Delete non-existent entity handled gracefully"); Console.WriteLine(" [OK] 0 rows affected"); Console.WriteLine(" [OK] No error thrown\n"); } /// /// Test 14: Concurrent delete operations /// Validates: Multiple threads deleting different entities safely /// public static void AsyncDelete_ConcurrentDeletes() { Console.WriteLine("TEST 14: AsyncDelete_ConcurrentDeletes"); var db = Db; // Setup: Insert 10 test entities var orders = new List(); for (int i = 0; i < 10; i++) { orders.Add(new Order { Name = $"Concurrent Delete Order {i}", Price = 100m + i, CreateTime = DateTime.Now }); } var insertedIds = db.Insertable(orders).ExecuteReturnPkList(); // Test: 10 threads deleting different entities var tasks = new List>(); foreach (var id in insertedIds) { var task = Task.Run(async () => { var dbInstance = Db; // Each task gets its own db instance return await dbInstance.Deleteable() .Where(x => x.Id == id) .ExecuteCommandAsync(); }); tasks.Add(task); } // Wait for all tasks to complete Task.WaitAll(tasks.ToArray()); // Verify: All deletes succeeded var totalAffected = tasks.Sum(t => t.Result); if (totalAffected != 10) throw new Exception($"Expected 10 total affected rows, got {totalAffected}"); // Verify: No deadlocks occurred (all tasks completed) if (tasks.Any(t => t.IsFaulted)) throw new Exception("Some tasks failed with exceptions"); // Verify: All entities deleted var remainingCount = db.Queryable() .Where(x => insertedIds.Contains(x.Id)) .Count(); if (remainingCount != 0) throw new Exception($"Expected 0 remaining entities, found {remainingCount}"); Console.WriteLine(" [OK] Concurrent async deletes safe"); Console.WriteLine(" [OK] All 10 deletes succeeded"); Console.WriteLine(" [OK] No deadlocks occurred\n"); } /// /// Test 15: Delete performance /// Validates: Async delete performance is acceptable /// public static void AsyncDelete_Performance() { Console.WriteLine("TEST 15: AsyncDelete_Performance"); var db = Db; // Setup: Insert 1000 test entities var orders = new List(); for (int i = 0; i < 1000; i++) { orders.Add(new Order { Name = $"Performance Test Order {i}", Price = 10.0m + i, CreateTime = DateTime.Now }); } var insertedIds = db.Insertable(orders).ExecuteReturnPkList(); // Test: Delete 1000 entities async var asyncStart = DateTime.Now; var deleteTask = db.Deleteable() .Where(x => insertedIds.Contains(x.Id)) .ExecuteCommandAsync(); var affectedRows = deleteTask.GetAwaiter().GetResult(); var asyncDuration = DateTime.Now - asyncStart; // Verify: All deleted if (affectedRows != 1000) throw new Exception($"Expected 1000 affected rows, got {affectedRows}"); var remainingCount = db.Queryable() .Where(x => insertedIds.Contains(x.Id)) .Count(); if (remainingCount != 0) throw new Exception($"Expected 0 remaining entities, found {remainingCount}"); Console.WriteLine($" [OK] Async delete: {affectedRows} rows in {asyncDuration.TotalMilliseconds}ms"); Console.WriteLine(" [OK] Performance acceptable"); Console.WriteLine(" [OK] All entities deleted successfully\n"); } #endregion #region Helper Classes /// /// Helper entity for soft delete tests /// Uses IsDeleted flag for logical deletion /// [SugarTable("SoftDeleteEntity")] public class SoftDeleteEntity { [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] public int Id { get; set; } public string Name { get; set; } public int Value { get; set; } public bool IsDeleted { get; set; } } /// /// Helper entity for soft delete with timestamp tests /// Uses IsDeleted flag and DeletedAt timestamp for audit trail /// [SugarTable("SoftDeleteWithTimestamp")] public class SoftDeleteWithTimestamp { [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] public int Id { get; set; } public string Name { get; set; } public int Value { get; set; } public bool IsDeleted { get; set; } [SugarColumn(IsNullable = true)] public DateTime? DeletedAt { get; set; } } #endregion } }