diff --git a/Src/Asp.NetCore2/SqlSeverTest/UserTestCases/UnitTest/Main.cs b/Src/Asp.NetCore2/SqlSeverTest/UserTestCases/UnitTest/Main.cs
index 2766d6560..c537f620b 100644
--- a/Src/Asp.NetCore2/SqlSeverTest/UserTestCases/UnitTest/Main.cs
+++ b/Src/Asp.NetCore2/SqlSeverTest/UserTestCases/UnitTest/Main.cs
@@ -170,6 +170,7 @@ namespace OrmTest
ExceptionHandling();
AsyncInsert();
AsyncUpdate();
+ AsyncDelete();
//Thread();
//Thread2();
//Thread3();
diff --git a/Src/Asp.NetCore2/SqlSeverTest/UserTestCases/UnitTest/UAsyncDelete.cs b/Src/Asp.NetCore2/SqlSeverTest/UserTestCases/UnitTest/UAsyncDelete.cs
new file mode 100644
index 000000000..2adeddeb4
--- /dev/null
+++ b/Src/Asp.NetCore2/SqlSeverTest/UserTestCases/UnitTest/UAsyncDelete.cs
@@ -0,0 +1,916 @@
+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
+ }
+}