mirror of
https://gitee.com/dotnetchina/SqlSugar.git
synced 2025-11-24 08:33:16 +08:00
Compare commits
33 Commits
2170a4ac73
...
3c2420bbbf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c2420bbbf | ||
|
|
e7d45a9a93 | ||
|
|
d5b17f5a1e | ||
|
|
34ce2e361e | ||
|
|
3abf762fc4 | ||
|
|
98d43b2bd8 | ||
|
|
040591e6ca | ||
|
|
7fc2f651c1 | ||
|
|
41f83180b6 | ||
|
|
a7863d528b | ||
|
|
585db90796 | ||
|
|
436414507e | ||
|
|
10ed7fd2c2 | ||
|
|
6aa92e5cbc | ||
|
|
ac7a027a09 | ||
|
|
ad3f5da4d8 | ||
|
|
d302496f2c | ||
|
|
0d20cc8c1c | ||
|
|
cd8c1266cc | ||
|
|
14525e8c2b | ||
|
|
a1cd01854a | ||
|
|
40474f5133 | ||
|
|
aec3486168 | ||
|
|
6905288a00 | ||
|
|
3f534320b1 | ||
|
|
49cf16b443 | ||
|
|
7e9847a22c | ||
|
|
c2d03e4149 | ||
|
|
8c5b0591d5 | ||
|
|
2476fc7614 | ||
|
|
09c6270e2c | ||
|
|
936c74f686 | ||
|
|
f3a18f4c62 |
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Platforms>AnyCPU;x86</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Platforms>AnyCPU;ARM32</Platforms>
|
||||
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType>Exe</OutputType>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace MongoDb.Ado.data
|
||||
|
||||
public static void ValidateOperation(string operation)
|
||||
{
|
||||
if (ExecuteHandlerFactory.Items.TryGetValue(operation, out var handler))
|
||||
if (new ExecuteHandlerFactory().Items.TryGetValue(operation, out var handler))
|
||||
{
|
||||
return ;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace MongoDb.Ado.data
|
||||
{
|
||||
public class ExecuteHandlerFactory
|
||||
{
|
||||
public readonly static Dictionary<string, IMongoOperationHandler> Items = new Dictionary<string, IMongoOperationHandler>(StringComparer.OrdinalIgnoreCase)
|
||||
public readonly Dictionary<string, IMongoOperationHandler> Items = new Dictionary<string, IMongoOperationHandler>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "insert", new InsertHandler() },
|
||||
{ "insertmany", new InsertManyHandler() },
|
||||
@@ -25,7 +25,7 @@ namespace MongoDb.Ado.data
|
||||
public static int Handler(string operation, string json, IMongoCollection<BsonDocument> collection, HandlerContext handlerContext)
|
||||
{
|
||||
MongoDbMethodUtils.ValidateOperation(operation);
|
||||
var handlers = ExecuteHandlerFactory.Items;
|
||||
var handlers =new ExecuteHandlerFactory().Items;
|
||||
|
||||
if (!handlers.TryGetValue(operation, out var handler))
|
||||
throw new NotSupportedException($"不支持的操作类型: {operation}");
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace MongoDb.Ado.data
|
||||
{
|
||||
public class ExecuteHandlerFactoryAsync
|
||||
{
|
||||
public readonly static Dictionary<string, IMongoOperationHandlerAsync> Items = new Dictionary<string, IMongoOperationHandlerAsync>(StringComparer.OrdinalIgnoreCase)
|
||||
public readonly Dictionary<string, IMongoOperationHandlerAsync> Items = new Dictionary<string, IMongoOperationHandlerAsync>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "insert", new InsertHandlerAsync() },
|
||||
{ "insertmany", new InsertManyHandlerAsync() },
|
||||
@@ -28,7 +28,7 @@ namespace MongoDb.Ado.data
|
||||
public static Task<int> HandlerAsync(string operation, string json, IMongoCollection<BsonDocument> collection,CancellationToken cancellationToken, HandlerContext handlerContext)
|
||||
{
|
||||
MongoDbMethodUtils.ValidateOperation(operation);
|
||||
var handlers = ExecuteHandlerFactoryAsync.Items;
|
||||
var handlers = new ExecuteHandlerFactoryAsync().Items;
|
||||
|
||||
if (!handlers.TryGetValue(operation, out var handler))
|
||||
throw new NotSupportedException($"不支持的操作类型: {operation}");
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -52,6 +52,16 @@ namespace MongoDbTest
|
||||
var list4=db.Queryable<Student2>().ToList();
|
||||
db.Insertable(new List<Student2>() { new Student2() { Bytes = new byte[] { 2, 3 } }, new Student2() { Bytes = new byte[] { 3, 3 } } }).ExecuteCommand();
|
||||
var list5 = db.Queryable<Student2>().ToList();
|
||||
var list6 = db.Queryable<School>().OrderBy(it=>it.Name).ToList();
|
||||
var list7 = db.Queryable<School>().OrderByDescending(it => it.Name).ToList();
|
||||
var list8 = db.Queryable<School>().OrderBy("Name asc").ToList();
|
||||
var list9 = db.Queryable<School>().OrderBy(new List<SqlSugar.OrderByModel>() {
|
||||
new SqlSugar.OrderByModel(){ FieldName="Name", OrderByType=SqlSugar.OrderByType.Desc }
|
||||
}).ToList();
|
||||
var list10 = db.Queryable<School>().OrderBy(new List<SqlSugar.OrderByModel>() {
|
||||
new SqlSugar.OrderByModel(){ FieldName="Name", OrderByType=SqlSugar.OrderByType.Asc },
|
||||
new SqlSugar.OrderByModel(){ FieldName="_id", OrderByType=SqlSugar.OrderByType.Desc }
|
||||
}).ToList();
|
||||
}
|
||||
}
|
||||
[SqlSugar.SugarTable("UnitStudent12313122")]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<OutputType>Exe</OutputType>
|
||||
|
||||
<!-- Target .NET 6.0 for modern performance features / 目标 .NET 6.0 以获得现代性能特性 -->
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
@@ -34,6 +34,8 @@ namespace OrmTest
|
||||
}
|
||||
public static void Init()
|
||||
{
|
||||
UnitDateRange.Init();
|
||||
UnitSFADSAFSY2.Init();
|
||||
Unitsadsfasdfys.Init();
|
||||
Unitsdfyasfs3lsss.Init();
|
||||
Unitadsfasyss.Init();
|
||||
@@ -165,6 +167,10 @@ namespace OrmTest
|
||||
Queryable2();
|
||||
QueryableAsync();
|
||||
SecurityParameterHandling();
|
||||
ExceptionHandling();
|
||||
AsyncInsert();
|
||||
AsyncUpdate();
|
||||
AsyncDelete();
|
||||
//Thread();
|
||||
//Thread2();
|
||||
//Thread3();
|
||||
|
||||
@@ -0,0 +1,916 @@
|
||||
using SqlSugar;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OrmTest
|
||||
{
|
||||
/// <summary>
|
||||
/// 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)
|
||||
///
|
||||
/// </summary>
|
||||
public partial class NewUnitTest
|
||||
{
|
||||
#region Main Entry Point
|
||||
|
||||
/// <summary>
|
||||
/// 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();
|
||||
/// </summary>
|
||||
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)
|
||||
|
||||
/// <summary>
|
||||
/// Test 1: Basic ExecuteCommandAsync
|
||||
/// Validates: Single entity async delete, affected rows returned, entity removed from database
|
||||
/// </summary>
|
||||
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<Order>()
|
||||
.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<Order>().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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test 2: ExecuteCommandHasChangeAsync
|
||||
/// Validates: Change detection for existing and non-existent entities
|
||||
/// </summary>
|
||||
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<Order>()
|
||||
.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<Order>()
|
||||
.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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test 3: Multiple entities async delete
|
||||
/// Validates: Bulk delete with DeleteRange, all entities removed
|
||||
/// </summary>
|
||||
public static void AsyncDelete_MultipleEntitiesAsync()
|
||||
{
|
||||
Console.WriteLine("TEST 3: AsyncDelete_MultipleEntitiesAsync");
|
||||
|
||||
var db = Db;
|
||||
|
||||
// Setup: Insert 100 test entities
|
||||
var orders = new List<Order>();
|
||||
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<int>();
|
||||
|
||||
// Test: DeleteRange with 100 entities
|
||||
var deleteTask = db.Deleteable<Order>()
|
||||
.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<Order>()
|
||||
.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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test 4: Delete by primary key
|
||||
/// Validates: PK-based delete, only specific entity deleted
|
||||
/// </summary>
|
||||
public static void AsyncDelete_ByPrimaryKey()
|
||||
{
|
||||
Console.WriteLine("TEST 4: AsyncDelete_ByPrimaryKey");
|
||||
|
||||
var db = Db;
|
||||
|
||||
// Setup: Insert multiple test entities
|
||||
var orders = new List<Order>
|
||||
{
|
||||
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<int>();
|
||||
|
||||
// Test: Delete by ID (using In method for primary key)
|
||||
var targetId = insertedIds[1]; // Delete middle one
|
||||
var deleteTask = db.Deleteable<Order>()
|
||||
.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<Order>().InSingle(targetId);
|
||||
if (deletedOrder != null)
|
||||
throw new Exception("Target entity should be deleted");
|
||||
|
||||
// Verify: Only specific entity deleted
|
||||
var remainingOrders = db.Queryable<Order>()
|
||||
.Where(x => insertedIds.Contains(x.Id))
|
||||
.ToList();
|
||||
|
||||
if (remainingOrders.Count != 2)
|
||||
throw new Exception($"Expected 2 remaining orders, found {remainingOrders.Count}");
|
||||
|
||||
// Cleanup
|
||||
db.Deleteable<Order>().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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test 5: Delete by expression
|
||||
/// Validates: Expression-based delete, correct entities removed, others remain
|
||||
/// </summary>
|
||||
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<Order>
|
||||
{
|
||||
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<int>();
|
||||
|
||||
// Test: Delete(x => x.Price > 50) - should delete 3 orders
|
||||
var deleteTask = db.Deleteable<Order>()
|
||||
.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<Order>()
|
||||
.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<Order>().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)
|
||||
|
||||
/// <summary>
|
||||
/// Test 6: CancellationToken basic functionality
|
||||
/// Validates: Delete operation can be cancelled with timeout
|
||||
/// </summary>
|
||||
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<Order>()
|
||||
.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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test 7: CancellationToken immediate cancellation
|
||||
/// Validates: Pre-cancelled token prevents database operation
|
||||
/// </summary>
|
||||
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<Order>()
|
||||
.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<Order>().InSingle(insertedId);
|
||||
if (existingOrder == null)
|
||||
throw new Exception("Entity should still exist after cancelled delete");
|
||||
|
||||
// Cleanup
|
||||
db.Deleteable<Order>().In(insertedId).ExecuteCommand();
|
||||
|
||||
Console.WriteLine(" [OK] Immediate cancellation works");
|
||||
Console.WriteLine(" [OK] No database operation performed");
|
||||
Console.WriteLine(" [OK] Entity remains in database\n");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test 8: CancellationToken bulk delete
|
||||
/// Validates: Bulk delete can be cancelled mid-operation
|
||||
/// </summary>
|
||||
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<Order>();
|
||||
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<int>();
|
||||
|
||||
// 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<Order>()
|
||||
.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<Order>().Where(x => insertedIds.Contains(x.Id)).ExecuteCommand();
|
||||
|
||||
Console.WriteLine(" [OK] Bulk delete cancellation handled\n");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test 9: CancellationToken timeout scenarios
|
||||
/// Validates: Various timeout durations work correctly
|
||||
/// </summary>
|
||||
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<Order>()
|
||||
.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<Order>().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<Order>().In(insertedId).ExecuteCommand();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine(" [OK] All timeout scenarios handled correctly\n");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region C. Cascade & Soft Delete Tests (3 functions)
|
||||
|
||||
/// <summary>
|
||||
/// Test 10: Cascade delete
|
||||
/// Validates: Foreign key constraints and cascade behavior
|
||||
/// Note: Actual cascade depends on database FK configuration
|
||||
/// </summary>
|
||||
public static void AsyncDelete_CascadeDelete()
|
||||
{
|
||||
Console.WriteLine("TEST 10: AsyncDelete_CascadeDelete");
|
||||
|
||||
var db = Db;
|
||||
|
||||
// Setup: Ensure tables exist
|
||||
db.CodeFirst.InitTables<Order, OrderItem>();
|
||||
|
||||
// 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<OrderItem>
|
||||
{
|
||||
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<Order>()
|
||||
.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<Order>().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<OrderItem>()
|
||||
.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<OrderItem>().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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test 11: Soft delete (IsLogic)
|
||||
/// Validates: Entity marked as deleted but not physically removed
|
||||
/// </summary>
|
||||
public static void AsyncDelete_SoftDelete()
|
||||
{
|
||||
Console.WriteLine("TEST 11: AsyncDelete_SoftDelete");
|
||||
|
||||
var db = Db;
|
||||
|
||||
// Setup: Create soft delete entity table
|
||||
db.CodeFirst.InitTables<SoftDeleteEntity>();
|
||||
|
||||
// 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<SoftDeleteEntity>()
|
||||
.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<SoftDeleteEntity>()
|
||||
.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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test 12: Soft delete with timestamp
|
||||
/// Validates: Soft delete with IsDeleted flag (timestamp would require custom logic)
|
||||
/// </summary>
|
||||
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<SoftDeleteWithTimestamp>();
|
||||
|
||||
// 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<SoftDeleteWithTimestamp>()
|
||||
.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<SoftDeleteWithTimestamp>()
|
||||
.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)
|
||||
|
||||
/// <summary>
|
||||
/// Test 13: Delete non-existent entity
|
||||
/// Validates: Graceful handling of deleting non-existent records
|
||||
/// </summary>
|
||||
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<Order>()
|
||||
.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<Order>()
|
||||
.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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test 14: Concurrent delete operations
|
||||
/// Validates: Multiple threads deleting different entities safely
|
||||
/// </summary>
|
||||
public static void AsyncDelete_ConcurrentDeletes()
|
||||
{
|
||||
Console.WriteLine("TEST 14: AsyncDelete_ConcurrentDeletes");
|
||||
|
||||
var db = Db;
|
||||
|
||||
// Setup: Insert 10 test entities
|
||||
var orders = new List<Order>();
|
||||
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<int>();
|
||||
|
||||
// Test: 10 threads deleting different entities
|
||||
var tasks = new List<Task<int>>();
|
||||
|
||||
foreach (var id in insertedIds)
|
||||
{
|
||||
var task = Task.Run(async () =>
|
||||
{
|
||||
var dbInstance = Db; // Each task gets its own db instance
|
||||
return await dbInstance.Deleteable<Order>()
|
||||
.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<Order>()
|
||||
.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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test 15: Delete performance
|
||||
/// Validates: Async delete performance is acceptable
|
||||
/// </summary>
|
||||
public static void AsyncDelete_Performance()
|
||||
{
|
||||
Console.WriteLine("TEST 15: AsyncDelete_Performance");
|
||||
|
||||
var db = Db;
|
||||
|
||||
// Setup: Insert 1000 test entities
|
||||
var orders = new List<Order>();
|
||||
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<int>();
|
||||
|
||||
// Test: Delete 1000 entities async
|
||||
var asyncStart = DateTime.Now;
|
||||
var deleteTask = db.Deleteable<Order>()
|
||||
.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<Order>()
|
||||
.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
|
||||
|
||||
/// <summary>
|
||||
/// Helper entity for soft delete tests
|
||||
/// Uses IsDeleted flag for logical deletion
|
||||
/// </summary>
|
||||
[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; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper entity for soft delete with timestamp tests
|
||||
/// Uses IsDeleted flag and DeletedAt timestamp for audit trail
|
||||
/// </summary>
|
||||
[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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,861 @@
|
||||
using SqlSugar;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OrmTest
|
||||
{
|
||||
/// <summary>
|
||||
/// ═══════════════════════════════════════════════════════════════════════════════
|
||||
/// ASYNC INSERT TEST SUITE - Comprehensive Testing for SqlSugar Async Operations
|
||||
/// ═══════════════════════════════════════════════════════════════════════════════
|
||||
///
|
||||
/// PURPOSE:
|
||||
/// Tests all async insert methods in SqlSugar ORM with focus on:
|
||||
/// - Basic async insert operations (ExecuteCommandAsync, ExecuteReturnIdentityAsync, etc.)
|
||||
/// - Snowflake ID generation for distributed systems
|
||||
/// - CancellationToken support (CRITICAL - previously untested)
|
||||
/// - Error handling and edge cases
|
||||
///
|
||||
/// PRIORITY: 🔴 CRITICAL - 97% of async methods were previously untested
|
||||
///
|
||||
/// USAGE:
|
||||
/// // Run all tests
|
||||
/// NewUnitTest.AsyncInsert();
|
||||
///
|
||||
/// // Run individual test
|
||||
/// NewUnitTest.AsyncInsert_ExecuteCommandAsync();
|
||||
///
|
||||
/// TEST COVERAGE:
|
||||
/// ✅ ExecuteCommandAsync() - Basic async insert
|
||||
/// ✅ ExecuteReturnIdentityAsync() - Return identity value
|
||||
/// ✅ ExecuteReturnBigIdentityAsync() - BIGINT identity
|
||||
/// ✅ ExecuteReturnEntityAsync() - Return full entity with populated fields
|
||||
/// ✅ ExecuteReturnPkListAsync() - Bulk insert with PK list
|
||||
/// ✅ ExecuteReturnSnowflakeIdAsync() - Distributed ID generation
|
||||
/// ✅ ExecuteReturnSnowflakeIdListAsync() - Bulk Snowflake IDs
|
||||
/// ✅ CancellationToken support across all methods
|
||||
/// ✅ Transaction support
|
||||
/// ✅ Concurrent operations
|
||||
/// ✅ Error scenarios
|
||||
///
|
||||
/// DEPENDENCIES:
|
||||
/// - Order entity (with identity column)
|
||||
/// - OrderSnowflake entity (long PK without identity)
|
||||
/// - OrderItem entity (for navigation property tests)
|
||||
///
|
||||
/// ═══════════════════════════════════════════════════════════════════════════════
|
||||
/// </summary>
|
||||
public partial class NewUnitTest
|
||||
{
|
||||
#region Main Entry Point
|
||||
|
||||
/// <summary>
|
||||
/// Main entry point - Executes all async insert tests
|
||||
/// </summary>
|
||||
public static void AsyncInsert()
|
||||
{
|
||||
Console.WriteLine("\n╔════════════════════════════════════════════════════════════════╗");
|
||||
Console.WriteLine("║ ASYNC INSERT TEST SUITE - COMPREHENSIVE ║");
|
||||
Console.WriteLine("╚════════════════════════════════════════════════════════════════╝\n");
|
||||
|
||||
try
|
||||
{
|
||||
// CATEGORY A: Basic Async Operations
|
||||
Console.WriteLine("┌─── BASIC ASYNC OPERATIONS ───────────────────────────────────┐\n");
|
||||
AsyncInsert_ExecuteCommandAsync();
|
||||
AsyncInsert_ExecuteReturnIdentityAsync();
|
||||
AsyncInsert_ExecuteReturnBigIdentityAsync();
|
||||
AsyncInsert_ExecuteReturnEntityAsync();
|
||||
AsyncInsert_ExecuteReturnPkListAsync();
|
||||
AsyncInsert_MultipleEntitiesAsync();
|
||||
|
||||
// CATEGORY B: Snowflake ID Generation
|
||||
Console.WriteLine("\n┌─── SNOWFLAKE ID GENERATION ──────────────────────────────────┐\n");
|
||||
AsyncInsert_SnowflakeIdBasic();
|
||||
AsyncInsert_SnowflakeIdList();
|
||||
AsyncInsert_SnowflakeIdUniqueness();
|
||||
AsyncInsert_SnowflakeId_Concurrent();
|
||||
|
||||
// CATEGORY C: CancellationToken Support (CRITICAL)
|
||||
Console.WriteLine("\n┌─── CANCELLATION TOKEN SUPPORT (CRITICAL) ────────────────────┐\n");
|
||||
AsyncInsert_CancellationToken_Basic();
|
||||
AsyncInsert_CancellationToken_Immediate();
|
||||
AsyncInsert_CancellationToken_SnowflakeId();
|
||||
AsyncInsert_CancellationToken_Transaction();
|
||||
|
||||
// CATEGORY D: Error Handling
|
||||
Console.WriteLine("\n┌─── ERROR HANDLING & EDGE CASES ──────────────────────────────┐\n");
|
||||
AsyncInsert_NullEntity();
|
||||
AsyncInsert_DuplicateKey();
|
||||
AsyncInsert_ConcurrentInserts();
|
||||
AsyncInsert_TransactionRollback();
|
||||
AsyncInsert_Performance();
|
||||
|
||||
Console.WriteLine("\n╔════════════════════════════════════════════════════════════════╗");
|
||||
Console.WriteLine("║ ✓ ALL ASYNC INSERT TESTS PASSED ║");
|
||||
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 Insert Tests
|
||||
|
||||
/// <summary>
|
||||
/// Test: ExecuteCommandAsync() - Basic async insert operation
|
||||
/// Verifies: Async insert works, returns affected rows, entity persisted
|
||||
/// </summary>
|
||||
public static void AsyncInsert_ExecuteCommandAsync()
|
||||
{
|
||||
Console.WriteLine("Test: ExecuteCommandAsync");
|
||||
|
||||
var db = Db;
|
||||
db.CodeFirst.InitTables<Order>();
|
||||
db.Deleteable<Order>().ExecuteCommand();
|
||||
|
||||
var order = new Order
|
||||
{
|
||||
Name = "Async Order Test",
|
||||
Price = 99.99m,
|
||||
CreateTime = DateTime.Now
|
||||
};
|
||||
|
||||
// Execute async insert
|
||||
var task = db.Insertable(order).ExecuteCommandAsync();
|
||||
task.Wait();
|
||||
int affectedRows = task.Result;
|
||||
|
||||
// Verify affected rows
|
||||
if (affectedRows != 1)
|
||||
throw new Exception($"Expected 1 affected row, got {affectedRows}");
|
||||
|
||||
// Verify entity exists in database
|
||||
var dbOrder = db.Queryable<Order>().Where(o => o.Name == "Async Order Test").First();
|
||||
if (dbOrder == null)
|
||||
throw new Exception("Entity not found in database");
|
||||
|
||||
Console.WriteLine("✓ ExecuteCommandAsync works correctly\n");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test: ExecuteReturnIdentityAsync() - Insert and return identity value
|
||||
/// Verifies: Identity value returned, correct type (long), entity retrievable by ID
|
||||
/// </summary>
|
||||
public static void AsyncInsert_ExecuteReturnIdentityAsync()
|
||||
{
|
||||
Console.WriteLine("Test: ExecuteReturnIdentityAsync");
|
||||
|
||||
var db = Db;
|
||||
db.CodeFirst.InitTables<Order>();
|
||||
db.Deleteable<Order>().ExecuteCommand();
|
||||
|
||||
var order = new Order
|
||||
{
|
||||
Name = "Identity Test Order",
|
||||
Price = 77.77m,
|
||||
CreateTime = DateTime.Now
|
||||
};
|
||||
|
||||
// Execute and get identity
|
||||
var task = db.Insertable(order).ExecuteReturnIdentityAsync();
|
||||
task.Wait();
|
||||
long identityValue = task.Result;
|
||||
|
||||
if (identityValue <= 0)
|
||||
throw new Exception($"Invalid identity value: {identityValue}");
|
||||
|
||||
// Verify entity retrievable by identity
|
||||
var dbOrder = db.Queryable<Order>().InSingle(identityValue);
|
||||
if (dbOrder == null || dbOrder.Name != "Identity Test Order")
|
||||
throw new Exception("Entity not found by identity");
|
||||
|
||||
Console.WriteLine($"✓ Identity returned: {identityValue}\n");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test: ExecuteReturnBigIdentityAsync() - Handle BIGINT identity columns
|
||||
/// Verifies: Long type returned, large values supported
|
||||
/// </summary>
|
||||
public static void AsyncInsert_ExecuteReturnBigIdentityAsync()
|
||||
{
|
||||
Console.WriteLine("Test: ExecuteReturnBigIdentityAsync");
|
||||
|
||||
var db = Db;
|
||||
db.CodeFirst.InitTables<Order>();
|
||||
|
||||
var order = new Order
|
||||
{
|
||||
Name = "BigInt Identity Test",
|
||||
Price = 123.45m,
|
||||
CreateTime = DateTime.Now
|
||||
};
|
||||
|
||||
var task = db.Insertable(order).ExecuteReturnBigIdentityAsync();
|
||||
task.Wait();
|
||||
long bigIdentity = task.Result;
|
||||
|
||||
if (bigIdentity <= 0 || bigIdentity.GetType() != typeof(long))
|
||||
throw new Exception("Invalid big identity value");
|
||||
|
||||
Console.WriteLine($"✓ Big identity: {bigIdentity}\n");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test: ExecuteReturnEntityAsync() - Insert and return complete entity
|
||||
/// Verifies: All properties populated, identity set, defaults applied
|
||||
/// </summary>
|
||||
public static void AsyncInsert_ExecuteReturnEntityAsync()
|
||||
{
|
||||
Console.WriteLine("Test: ExecuteReturnEntityAsync");
|
||||
|
||||
var db = Db;
|
||||
db.CodeFirst.InitTables<Order>();
|
||||
|
||||
var order = new Order
|
||||
{
|
||||
Name = "Return Entity Test",
|
||||
Price = 55.55m,
|
||||
CreateTime = DateTime.Now
|
||||
};
|
||||
|
||||
var task = db.Insertable(order).ExecuteReturnEntityAsync();
|
||||
task.Wait();
|
||||
var returnedEntity = task.Result;
|
||||
|
||||
if (returnedEntity == null)
|
||||
throw new Exception("Returned entity is null");
|
||||
if (returnedEntity.Id <= 0)
|
||||
throw new Exception("Identity not populated");
|
||||
if (returnedEntity.Name != "Return Entity Test")
|
||||
throw new Exception("Properties not preserved");
|
||||
|
||||
Console.WriteLine($"✓ Entity returned with ID: {returnedEntity.Id}\n");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test: ExecuteReturnPkListAsync() - Bulk insert with primary key list
|
||||
/// Verifies: Multiple entities inserted, all PKs returned, count matches
|
||||
/// </summary>
|
||||
public static void AsyncInsert_ExecuteReturnPkListAsync()
|
||||
{
|
||||
Console.WriteLine("Test: ExecuteReturnPkListAsync");
|
||||
|
||||
var db = Db;
|
||||
db.CodeFirst.InitTables<Order>();
|
||||
|
||||
var orders = new List<Order>();
|
||||
for (int i = 1; i <= 10; i++)
|
||||
{
|
||||
orders.Add(new Order
|
||||
{
|
||||
Name = $"Bulk Order {i}",
|
||||
Price = i * 10.10m,
|
||||
CreateTime = DateTime.Now
|
||||
});
|
||||
}
|
||||
|
||||
var task = db.Insertable(orders).ExecuteReturnPkListAsync<long>();
|
||||
task.Wait();
|
||||
var pkList = task.Result;
|
||||
|
||||
if (pkList.Count != 10)
|
||||
throw new Exception($"Expected 10 PKs, got {pkList.Count}");
|
||||
|
||||
// Verify all entities exist
|
||||
foreach (var pk in pkList)
|
||||
{
|
||||
if (pk <= 0 || db.Queryable<Order>().InSingle(pk) == null)
|
||||
throw new Exception($"Invalid PK or entity not found: {pk}");
|
||||
}
|
||||
|
||||
Console.WriteLine($"✓ {pkList.Count} PKs returned\n");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test: Bulk async insert - Insert multiple entities efficiently
|
||||
/// Verifies: All entities inserted, performance acceptable
|
||||
/// </summary>
|
||||
public static void AsyncInsert_MultipleEntitiesAsync()
|
||||
{
|
||||
Console.WriteLine("Test: MultipleEntitiesAsync");
|
||||
|
||||
var db = Db;
|
||||
db.CodeFirst.InitTables<Order>();
|
||||
db.Deleteable<Order>().ExecuteCommand();
|
||||
|
||||
var orders = new List<Order>();
|
||||
for (int i = 1; i <= 100; i++)
|
||||
{
|
||||
orders.Add(new Order
|
||||
{
|
||||
Name = $"Bulk Async Order {i}",
|
||||
Price = i * 1.11m,
|
||||
CreateTime = DateTime.Now
|
||||
});
|
||||
}
|
||||
|
||||
var startTime = DateTime.Now;
|
||||
var task = db.Insertable(orders).ExecuteCommandAsync();
|
||||
task.Wait();
|
||||
var duration = DateTime.Now - startTime;
|
||||
|
||||
if (task.Result != 100)
|
||||
throw new Exception($"Expected 100 rows, got {task.Result}");
|
||||
|
||||
Console.WriteLine($"✓ 100 entities inserted in {duration.TotalMilliseconds}ms\n");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region B. Snowflake ID Tests
|
||||
|
||||
/// <summary>
|
||||
/// Test: ExecuteReturnSnowflakeIdAsync() - Generate distributed unique IDs
|
||||
/// Verifies: Snowflake ID generated, long type, unique, entity retrievable
|
||||
/// </summary>
|
||||
public static void AsyncInsert_SnowflakeIdBasic()
|
||||
{
|
||||
Console.WriteLine("Test: SnowflakeIdBasic");
|
||||
|
||||
var db = Db;
|
||||
db.CodeFirst.InitTables<OrderSnowflake>();
|
||||
db.Deleteable<OrderSnowflake>().ExecuteCommand();
|
||||
|
||||
var order = new OrderSnowflake
|
||||
{
|
||||
Name = "Snowflake Order Test",
|
||||
Price = 99.99m,
|
||||
CreateTime = DateTime.Now
|
||||
};
|
||||
|
||||
var task = db.Insertable(order).ExecuteReturnSnowflakeIdAsync();
|
||||
task.Wait();
|
||||
long snowflakeId = task.Result;
|
||||
|
||||
if (snowflakeId <= 0)
|
||||
throw new Exception($"Invalid Snowflake ID: {snowflakeId}");
|
||||
|
||||
var dbOrder = db.Queryable<OrderSnowflake>().InSingle(snowflakeId);
|
||||
if (dbOrder == null)
|
||||
throw new Exception("Entity not found by Snowflake ID");
|
||||
|
||||
Console.WriteLine($"✓ Snowflake ID: {snowflakeId}\n");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test: ExecuteReturnSnowflakeIdListAsync() - Bulk Snowflake ID generation
|
||||
/// Verifies: Multiple unique IDs generated, all entities inserted, IDs sequential
|
||||
/// </summary>
|
||||
public static void AsyncInsert_SnowflakeIdList()
|
||||
{
|
||||
Console.WriteLine("Test: SnowflakeIdList");
|
||||
|
||||
var db = Db;
|
||||
db.CodeFirst.InitTables<OrderSnowflake>();
|
||||
db.Deleteable<OrderSnowflake>().ExecuteCommand();
|
||||
|
||||
var orders = new List<OrderSnowflake>();
|
||||
for (int i = 1; i <= 50; i++)
|
||||
{
|
||||
orders.Add(new OrderSnowflake
|
||||
{
|
||||
Name = $"Snowflake Bulk {i}",
|
||||
Price = i * 2.22m,
|
||||
CreateTime = DateTime.Now
|
||||
});
|
||||
}
|
||||
|
||||
var task = db.Insertable(orders).ExecuteReturnSnowflakeIdListAsync();
|
||||
task.Wait();
|
||||
var snowflakeIds = task.Result;
|
||||
|
||||
if (snowflakeIds.Count != 50)
|
||||
throw new Exception($"Expected 50 IDs, got {snowflakeIds.Count}");
|
||||
|
||||
var uniqueCount = snowflakeIds.Distinct().Count();
|
||||
if (uniqueCount != 50)
|
||||
throw new Exception($"Expected 50 unique IDs, got {uniqueCount}");
|
||||
|
||||
Console.WriteLine($"✓ {snowflakeIds.Count} unique Snowflake IDs generated\n");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test: Snowflake ID uniqueness - Verify no collisions in large batch
|
||||
/// Verifies: 1000 IDs all unique, time-sortable
|
||||
/// </summary>
|
||||
public static void AsyncInsert_SnowflakeIdUniqueness()
|
||||
{
|
||||
Console.WriteLine("Test: SnowflakeIdUniqueness");
|
||||
|
||||
var db = Db;
|
||||
db.CodeFirst.InitTables<OrderSnowflake>();
|
||||
db.Deleteable<OrderSnowflake>().ExecuteCommand();
|
||||
|
||||
var orders = new List<OrderSnowflake>();
|
||||
for (int i = 1; i <= 1000; i++)
|
||||
{
|
||||
orders.Add(new OrderSnowflake
|
||||
{
|
||||
Name = $"Uniqueness Test {i}",
|
||||
Price = i * 0.99m,
|
||||
CreateTime = DateTime.Now
|
||||
});
|
||||
}
|
||||
|
||||
var task = db.Insertable(orders).ExecuteReturnSnowflakeIdListAsync();
|
||||
task.Wait();
|
||||
var snowflakeIds = task.Result;
|
||||
|
||||
var uniqueCount = snowflakeIds.Distinct().Count();
|
||||
if (uniqueCount != 1000)
|
||||
throw new Exception($"Collision detected! Expected 1000 unique, got {uniqueCount}");
|
||||
|
||||
Console.WriteLine("✓ 1000 unique IDs, no collisions\n");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test: Concurrent Snowflake ID generation - Thread safety verification
|
||||
/// Verifies: 10 threads × 10 IDs = 100 unique IDs, no race conditions
|
||||
/// </summary>
|
||||
public static void AsyncInsert_SnowflakeId_Concurrent()
|
||||
{
|
||||
Console.WriteLine("Test: SnowflakeId_Concurrent");
|
||||
|
||||
var db = Db;
|
||||
db.CodeFirst.InitTables<OrderSnowflake>();
|
||||
db.Deleteable<OrderSnowflake>().ExecuteCommand();
|
||||
|
||||
var allIds = new System.Collections.Concurrent.ConcurrentBag<long>();
|
||||
var tasks = new List<Task>();
|
||||
|
||||
for (int threadNum = 0; threadNum < 10; threadNum++)
|
||||
{
|
||||
int threadId = threadNum;
|
||||
var task = Task.Run(async () =>
|
||||
{
|
||||
var threadDb = Db;
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
var order = new OrderSnowflake
|
||||
{
|
||||
Name = $"Concurrent T{threadId} O{i}",
|
||||
Price = (threadId * 10 + i) * 1.11m,
|
||||
CreateTime = DateTime.Now
|
||||
};
|
||||
long id = await threadDb.Insertable(order).ExecuteReturnSnowflakeIdAsync();
|
||||
allIds.Add(id);
|
||||
}
|
||||
});
|
||||
tasks.Add(task);
|
||||
}
|
||||
|
||||
Task.WaitAll(tasks.ToArray());
|
||||
|
||||
var uniqueIds = allIds.Distinct().Count();
|
||||
if (uniqueIds != 100)
|
||||
throw new Exception($"Race condition! Expected 100 unique, got {uniqueIds}");
|
||||
|
||||
Console.WriteLine("✓ Thread-safe: 100 unique IDs from 10 concurrent threads\n");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region C. CancellationToken Tests (CRITICAL)
|
||||
|
||||
/// <summary>
|
||||
/// Test: CancellationToken basic support
|
||||
/// Verifies: Token accepted, cancellation respected or operation completes fast
|
||||
/// </summary>
|
||||
public static void AsyncInsert_CancellationToken_Basic()
|
||||
{
|
||||
Console.WriteLine("Test: CancellationToken_Basic");
|
||||
|
||||
var db = Db;
|
||||
db.CodeFirst.InitTables<Order>();
|
||||
|
||||
var cts = new CancellationTokenSource(100);
|
||||
var order = new Order
|
||||
{
|
||||
Name = "Cancellable Order",
|
||||
Price = 99.99m,
|
||||
CreateTime = DateTime.Now
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var task = db.Insertable(order).ExecuteCommandAsync(cts.Token);
|
||||
task.Wait();
|
||||
Console.WriteLine("✓ Operation completed before timeout\n");
|
||||
}
|
||||
catch (AggregateException ae) when (ae.InnerException is OperationCanceledException)
|
||||
{
|
||||
Console.WriteLine("✓ Operation cancelled as expected\n");
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Console.WriteLine("✓ Operation cancelled as expected\n");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test: Pre-cancelled token - Immediate cancellation
|
||||
/// Verifies: Pre-cancelled token handled correctly
|
||||
/// </summary>
|
||||
public static void AsyncInsert_CancellationToken_Immediate()
|
||||
{
|
||||
Console.WriteLine("Test: CancellationToken_Immediate");
|
||||
|
||||
var db = Db;
|
||||
db.CodeFirst.InitTables<Order>();
|
||||
|
||||
var cts = new CancellationTokenSource();
|
||||
cts.Cancel(); // Pre-cancel
|
||||
|
||||
var order = new Order
|
||||
{
|
||||
Name = "Pre-Cancelled Order",
|
||||
Price = 88.88m,
|
||||
CreateTime = DateTime.Now
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var task = db.Insertable(order).ExecuteCommandAsync(cts.Token);
|
||||
task.Wait();
|
||||
Console.WriteLine("✓ Fast operation completed despite pre-cancelled token\n");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Console.WriteLine("✓ Immediate cancellation detected\n");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test: Snowflake ID with CancellationToken
|
||||
/// Verifies: Snowflake methods support cancellation
|
||||
/// </summary>
|
||||
public static void AsyncInsert_CancellationToken_SnowflakeId()
|
||||
{
|
||||
Console.WriteLine("Test: CancellationToken_SnowflakeId");
|
||||
|
||||
var db = Db;
|
||||
db.CodeFirst.InitTables<OrderSnowflake>();
|
||||
|
||||
var cts = new CancellationTokenSource(50);
|
||||
var order = new OrderSnowflake
|
||||
{
|
||||
Name = "Cancellable Snowflake",
|
||||
Price = 66.66m,
|
||||
CreateTime = DateTime.Now
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var task = db.Insertable(order).ExecuteReturnSnowflakeIdAsync(cts.Token);
|
||||
task.Wait();
|
||||
Console.WriteLine($"✓ Snowflake insert completed, ID: {task.Result}\n");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Console.WriteLine("✓ Snowflake insert cancellable\n");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test: CancellationToken in transaction
|
||||
/// Verifies: Transaction rollback on cancellation
|
||||
/// </summary>
|
||||
public static void AsyncInsert_CancellationToken_Transaction()
|
||||
{
|
||||
Console.WriteLine("Test: CancellationToken_Transaction");
|
||||
|
||||
var db = Db;
|
||||
db.CodeFirst.InitTables<Order>();
|
||||
db.BeginTran();
|
||||
|
||||
try
|
||||
{
|
||||
var cts = new CancellationTokenSource(50);
|
||||
var order = new Order
|
||||
{
|
||||
Name = "Transaction Cancellable",
|
||||
Price = 55.55m,
|
||||
CreateTime = DateTime.Now
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var task = db.Insertable(order).ExecuteCommandAsync(cts.Token);
|
||||
task.Wait();
|
||||
db.CommitTran();
|
||||
Console.WriteLine("✓ Transaction committed\n");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
db.RollbackTran();
|
||||
Console.WriteLine("✓ Transaction rolled back on cancellation\n");
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
db.RollbackTran();
|
||||
Console.WriteLine("✓ Transaction cleanup on error\n");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region D. Error Handling Tests
|
||||
|
||||
/// <summary>
|
||||
/// Test: Null entity validation
|
||||
/// Verifies: Appropriate exception thrown for null entity
|
||||
/// </summary>
|
||||
public static void AsyncInsert_NullEntity()
|
||||
{
|
||||
Console.WriteLine("Test: NullEntity");
|
||||
|
||||
var db = Db;
|
||||
db.CodeFirst.InitTables<Order>();
|
||||
|
||||
Order nullOrder = null;
|
||||
|
||||
try
|
||||
{
|
||||
var task = db.Insertable(nullOrder).ExecuteCommandAsync();
|
||||
task.Wait();
|
||||
int result = task.Result;
|
||||
|
||||
// SqlSugar handles null gracefully - returns 0 affected rows
|
||||
if (result == 0)
|
||||
{
|
||||
Console.WriteLine("✓ Null entity handled gracefully (0 rows affected)\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Expected 0 affected rows for null entity, got {result}");
|
||||
}
|
||||
}
|
||||
catch (AggregateException ae) when (ae.InnerException is ArgumentNullException ||
|
||||
ae.InnerException is NullReferenceException)
|
||||
{
|
||||
Console.WriteLine($"✓ Null entity rejected: {ae.InnerException.GetType().Name}\n");
|
||||
}
|
||||
catch (ArgumentNullException)
|
||||
{
|
||||
Console.WriteLine("✓ Null entity rejected\n");
|
||||
}
|
||||
catch (NullReferenceException)
|
||||
{
|
||||
Console.WriteLine("✓ Null entity rejected\n");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test: Duplicate key handling
|
||||
/// Verifies: Duplicate primary key error detected
|
||||
/// </summary>
|
||||
public static void AsyncInsert_DuplicateKey()
|
||||
{
|
||||
Console.WriteLine("Test: DuplicateKey");
|
||||
|
||||
var db = Db;
|
||||
db.CodeFirst.InitTables<Order>();
|
||||
|
||||
var order1 = new Order
|
||||
{
|
||||
Name = "Duplicate Key Test",
|
||||
Price = 88.88m,
|
||||
CreateTime = DateTime.Now
|
||||
};
|
||||
|
||||
var task1 = db.Insertable(order1).ExecuteReturnIdentityAsync();
|
||||
task1.Wait();
|
||||
long id = task1.Result;
|
||||
|
||||
var order2 = new Order
|
||||
{
|
||||
Id = (int)id,
|
||||
Name = "Duplicate Key Test 2",
|
||||
Price = 77.77m,
|
||||
CreateTime = DateTime.Now
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var task2 = db.Insertable(order2).ExecuteCommandAsync();
|
||||
task2.Wait();
|
||||
Console.WriteLine("✓ Duplicate key allowed (identity auto-generated)\n");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Console.WriteLine("✓ Duplicate key error detected\n");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test: Concurrent async inserts - Thread safety
|
||||
/// Verifies: 10 concurrent threads, no deadlocks, all succeed
|
||||
/// </summary>
|
||||
public static void AsyncInsert_ConcurrentInserts()
|
||||
{
|
||||
Console.WriteLine("Test: ConcurrentInserts");
|
||||
|
||||
var db = Db;
|
||||
db.CodeFirst.InitTables<Order>();
|
||||
db.Deleteable<Order>().ExecuteCommand();
|
||||
|
||||
var tasks = new List<Task<int>>();
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
int threadId = i;
|
||||
var task = Task.Run(async () =>
|
||||
{
|
||||
var threadDb = Db;
|
||||
var order = new Order
|
||||
{
|
||||
Name = $"Concurrent Thread {threadId}",
|
||||
Price = threadId * 10.10m,
|
||||
CreateTime = DateTime.Now
|
||||
};
|
||||
return await threadDb.Insertable(order).ExecuteCommandAsync();
|
||||
});
|
||||
tasks.Add(task);
|
||||
}
|
||||
|
||||
Task.WaitAll(tasks.ToArray());
|
||||
|
||||
if (tasks.Sum(t => t.Result) != 10)
|
||||
throw new Exception("Not all concurrent inserts succeeded");
|
||||
|
||||
Console.WriteLine("✓ 10 concurrent inserts, no deadlocks\n");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test: Transaction rollback with async insert
|
||||
/// Verifies: Rollback works, entity not persisted
|
||||
/// </summary>
|
||||
public static void AsyncInsert_TransactionRollback()
|
||||
{
|
||||
Console.WriteLine("Test: TransactionRollback");
|
||||
|
||||
var db = Db;
|
||||
db.CodeFirst.InitTables<Order>();
|
||||
db.Deleteable<Order>().ExecuteCommand();
|
||||
|
||||
db.BeginTran();
|
||||
|
||||
try
|
||||
{
|
||||
var order = new Order
|
||||
{
|
||||
Name = "Rollback Test Order",
|
||||
Price = 55.55m,
|
||||
CreateTime = DateTime.Now
|
||||
};
|
||||
|
||||
var task = db.Insertable(order).ExecuteReturnIdentityAsync();
|
||||
task.Wait();
|
||||
long id = task.Result;
|
||||
|
||||
db.RollbackTran();
|
||||
|
||||
var orderAfterRollback = db.Queryable<Order>().InSingle(id);
|
||||
if (orderAfterRollback != null)
|
||||
throw new Exception("Entity exists after rollback!");
|
||||
|
||||
Console.WriteLine("✓ Transaction rollback works\n");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
db.RollbackTran();
|
||||
throw new Exception($"Transaction test failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test: Async vs Sync performance comparison
|
||||
/// Verifies: Async performance is acceptable (1000 entities)
|
||||
/// </summary>
|
||||
public static void AsyncInsert_Performance()
|
||||
{
|
||||
Console.WriteLine("Test: Performance");
|
||||
|
||||
var db = Db;
|
||||
db.CodeFirst.InitTables<Order>();
|
||||
db.Deleteable<Order>().ExecuteCommand();
|
||||
|
||||
// Async test
|
||||
var asyncOrders = new List<Order>();
|
||||
for (int i = 1; i <= 1000; i++)
|
||||
{
|
||||
asyncOrders.Add(new Order
|
||||
{
|
||||
Name = $"Perf Async {i}",
|
||||
Price = i * 1.23m,
|
||||
CreateTime = DateTime.Now
|
||||
});
|
||||
}
|
||||
|
||||
var asyncStart = DateTime.Now;
|
||||
var asyncTask = db.Insertable(asyncOrders).ExecuteCommandAsync();
|
||||
asyncTask.Wait();
|
||||
var asyncDuration = DateTime.Now - asyncStart;
|
||||
|
||||
Console.WriteLine($" Async: {asyncTask.Result} rows in {asyncDuration.TotalMilliseconds}ms");
|
||||
|
||||
// Sync test
|
||||
db.Deleteable<Order>().ExecuteCommand();
|
||||
var syncOrders = new List<Order>();
|
||||
for (int i = 1; i <= 1000; i++)
|
||||
{
|
||||
syncOrders.Add(new Order
|
||||
{
|
||||
Name = $"Perf Sync {i}",
|
||||
Price = i * 1.23m,
|
||||
CreateTime = DateTime.Now
|
||||
});
|
||||
}
|
||||
|
||||
var syncStart = DateTime.Now;
|
||||
int syncResult = db.Insertable(syncOrders).ExecuteCommand();
|
||||
var syncDuration = DateTime.Now - syncStart;
|
||||
|
||||
Console.WriteLine($" Sync: {syncResult} rows in {syncDuration.TotalMilliseconds}ms");
|
||||
Console.WriteLine($" Ratio: {(asyncDuration.TotalMilliseconds / syncDuration.TotalMilliseconds):F2}x\n");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Classes
|
||||
|
||||
/// <summary>
|
||||
/// Helper entity for Snowflake ID tests
|
||||
/// Uses long PK without identity for distributed ID generation
|
||||
/// </summary>
|
||||
[SugarTable("OrderSnowflake")]
|
||||
public class OrderSnowflake
|
||||
{
|
||||
[SugarColumn(IsPrimaryKey = true)]
|
||||
public long Id { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
public decimal Price { get; set; }
|
||||
public DateTime CreateTime { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
1076
Src/Asp.NetCore2/SqlSeverTest/UserTestCases/UnitTest/UAsyncUpdate.cs
Normal file
1076
Src/Asp.NetCore2/SqlSeverTest/UserTestCases/UnitTest/UAsyncUpdate.cs
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,172 @@
|
||||
using SqlSugar;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OrmTest
|
||||
{
|
||||
/// <summary>
|
||||
/// Class for demonstrating CodeFirst initialization operations
|
||||
/// 用于展示 CodeFirst 初始化操作的类
|
||||
/// </summary>
|
||||
public class UnitDateRange
|
||||
{
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
// Get a new database instance
|
||||
// 获取新的数据库实例
|
||||
var db = DbHelper.GetNewDb();
|
||||
|
||||
// Create the database if it doesn't exist
|
||||
// 如果数据库不存在,则创建数据库
|
||||
db.DbMaintenance.CreateDatabase();
|
||||
|
||||
// Initialize tables based on UserInfo001 entity class
|
||||
// 根据 UserInfo001 实体类初始化表
|
||||
db.CodeFirst.InitTables<UserInfo001>();
|
||||
|
||||
//Table structure and class are different
|
||||
//表结构和类存在差异 初始化表
|
||||
db.CodeFirst.InitTables<UserInfo002>();
|
||||
|
||||
//Insert
|
||||
//插入
|
||||
var id = db.Insertable(new UserInfo001()
|
||||
{
|
||||
Context = "Context",
|
||||
Email = "dfafa@qq.com",
|
||||
Price = Convert.ToDecimal(1.1),
|
||||
UserName = "admin",
|
||||
RegistrationDate = DateTime.Now,
|
||||
|
||||
}).ExecuteReturnIdentity();
|
||||
|
||||
Demo1(db);
|
||||
|
||||
Demo2(db);
|
||||
|
||||
Demo3(db);
|
||||
|
||||
Demo4(db);
|
||||
}
|
||||
private static void Demo4(SqlSugarClient db)
|
||||
{
|
||||
var con2 = new List<IConditionalModel>() {
|
||||
new ConditionalModel() {
|
||||
FieldName="RegistrationDate", ConditionalType=ConditionalType.Range,FieldValue="2020,2021"
|
||||
}, new ConditionalModel() {
|
||||
FieldName="UserName", ConditionalType=ConditionalType.Like,FieldValue="a"
|
||||
} };
|
||||
var userInfo2 = db.Queryable<UserInfo001>().Where(con2).ToList();
|
||||
}
|
||||
|
||||
private static void Demo3(SqlSugarClient db)
|
||||
{
|
||||
var con = new List<IConditionalModel>() { new ConditionalModel() {
|
||||
FieldName="UserName", ConditionalType=ConditionalType.Like,FieldValue="a"
|
||||
} ,
|
||||
new ConditionalModel() {
|
||||
FieldName="RegistrationDate", ConditionalType=ConditionalType.Range,FieldValue="2020,2021"
|
||||
}};
|
||||
var userInfo = db.Queryable<UserInfo001>().Where(con).ToList();
|
||||
}
|
||||
private static void Demo2(SqlSugarClient db)
|
||||
{
|
||||
var con2 = new List<IConditionalModel>() {
|
||||
new ConditionalModel() {
|
||||
FieldName="RegistrationDate",CSharpTypeName="datetime", ConditionalType=ConditionalType.RangeDate,FieldValue="2020,2021"
|
||||
}, new ConditionalModel() {
|
||||
FieldName="UserName", ConditionalType=ConditionalType.Like,FieldValue="a"
|
||||
} };
|
||||
var userInfo2 = db.Queryable<UserInfo001>().Where(con2).ToList();
|
||||
}
|
||||
|
||||
private static void Demo1(SqlSugarClient db)
|
||||
{
|
||||
var con = new List<IConditionalModel>() { new ConditionalModel() {
|
||||
FieldName="UserName", ConditionalType=ConditionalType.Like,FieldValue="a"
|
||||
} ,
|
||||
new ConditionalModel() {
|
||||
FieldName="RegistrationDate", ConditionalType=ConditionalType.RangeDate,FieldValue="2020,2021"
|
||||
}};
|
||||
var userInfo = db.Queryable<UserInfo001>().Where(con).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// User information entity class
|
||||
/// 用户信息实体类
|
||||
/// </summary>
|
||||
public class UserInfo001
|
||||
{
|
||||
/// <summary>
|
||||
/// User ID (Primary Key)
|
||||
/// 用户ID(主键)
|
||||
/// </summary>
|
||||
[SugarColumn(IsIdentity = true, IsPrimaryKey = true)]
|
||||
public int UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// User name
|
||||
/// 用户名
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 50, IsNullable = false)]
|
||||
public string UserName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// User email
|
||||
/// 用户邮箱
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public string Email { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Product price
|
||||
/// 产品价格
|
||||
/// </summary>
|
||||
public decimal Price { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// User context
|
||||
/// 用户内容
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)]
|
||||
public string Context { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// User registration date
|
||||
/// 用户注册日期
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? RegistrationDate { get; set; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// User information entity class
|
||||
/// 用户信息实体类
|
||||
/// </summary>
|
||||
[SugarTable("UserInfoAAA01")]
|
||||
public class UserInfo002
|
||||
{
|
||||
/// <summary>
|
||||
/// User ID (Primary Key)
|
||||
/// 用户ID(主键)
|
||||
/// </summary>
|
||||
[SugarColumn(IsIdentity = true, ColumnName = "Id", IsPrimaryKey = true)]
|
||||
public int UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// User name
|
||||
/// 用户名
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 50, ColumnName = "Name", IsNullable = false)]
|
||||
public string UserName { get; set; }
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using SqlSugar;
|
||||
using SqlSugar.DbConvert;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OrmTest
|
||||
{
|
||||
internal class UnitSFADSAFSY2
|
||||
{
|
||||
public static void Init()
|
||||
{
|
||||
var client = NewUnitTest.Db;
|
||||
client.CodeFirst.InitTables<UnitdafadfaaaTest1>();
|
||||
client.DbMaintenance.TruncateTable<UnitdafadfaaaTest1>();
|
||||
var test = new UnitdafadfaaaTest1()
|
||||
{
|
||||
Id = 1,
|
||||
Type = MyType.Type1,
|
||||
Name = "3"
|
||||
};
|
||||
client.Deleteable<UnitdafadfaaaTest1>().ExecuteCommand();
|
||||
|
||||
client.Storageable(test).ExecuteCommand();
|
||||
|
||||
var list=client.Queryable<UnitdafadfaaaTest1>().ToList();
|
||||
|
||||
client.Storageable(test).ExecuteCommand();
|
||||
}
|
||||
[SugarTable]
|
||||
public class UnitdafadfaaaTest1
|
||||
{
|
||||
[SugarColumn(IsPrimaryKey = true)]
|
||||
public int Id { get; set; }
|
||||
[SugarColumn(IsPrimaryKey = true,ColumnDataType ="varchar(10)", SqlParameterDbType = typeof(EnumToStringConvert))]
|
||||
public MyType Type { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
public enum MyType
|
||||
{
|
||||
Type1, Type2
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -404,11 +404,33 @@ namespace SqlSugar.HG
|
||||
string nullType = item.IsNullable ? this.CreateTableNull : CreateTableNotNull;
|
||||
string primaryKey = null;
|
||||
string addItem = string.Format(this.CreateTableColumn, this.SqlBuilder.GetTranslationColumnName(columnName.ToLower(isAutoToLowerCodeFirst)), dataType, dataSize, nullType, primaryKey, "");
|
||||
var identityStrategy = this.Context.CurrentConnectionConfig.MoreSettings?.PostgresIdentityStrategy;
|
||||
if (item.IsIdentity)
|
||||
{
|
||||
string length = dataType.Substring(dataType.Length - 1);
|
||||
string identityDataType = "serial" + length;
|
||||
addItem = addItem.Replace(dataType, identityDataType);
|
||||
if (identityStrategy != PostgresIdentityStrategy.Identity)
|
||||
{
|
||||
if (dataType?.ToLower() == "int")
|
||||
{
|
||||
dataSize = "int4";
|
||||
}
|
||||
else if (dataType?.ToLower() == "long")
|
||||
{
|
||||
dataSize = "int8";
|
||||
}
|
||||
else if (dataType?.ToLower() == "bigint")
|
||||
{
|
||||
dataSize = "int8";
|
||||
}
|
||||
string length = dataType.Substring(dataType.Length - 1);
|
||||
string identityDataType = "serial" + length;
|
||||
addItem = addItem.Replace(dataType, identityDataType);
|
||||
}
|
||||
else
|
||||
{
|
||||
string length = dataType.Substring(dataType.Length - 1);
|
||||
string identityDataType = "INT" + length + " GENERATED BY DEFAULT AS IDENTITY";
|
||||
addItem = addItem.Replace(dataType, identityDataType);
|
||||
}
|
||||
}
|
||||
columnArray.Add(addItem);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Bson.IO;
|
||||
using MongoDB.Bson.Serialization;
|
||||
using MongoDB.Driver;
|
||||
using NetTaste;
|
||||
using System;
|
||||
using System.Collections;
|
||||
@@ -305,8 +306,13 @@ namespace SqlSugar.MongoDb
|
||||
foreach (var group in grouped)
|
||||
{
|
||||
BsonDocument doc = new BsonDocument();
|
||||
var existsId = false;
|
||||
foreach (var col in group)
|
||||
{
|
||||
if (col.DbColumnName == "_id")
|
||||
{
|
||||
existsId = true;
|
||||
}
|
||||
// 自动推断类型,如 string、int、bool、DateTime、ObjectId 等
|
||||
if (col.IsJson == true)
|
||||
{
|
||||
@@ -330,7 +336,10 @@ namespace SqlSugar.MongoDb
|
||||
doc[col.DbColumnName] = UtilMethods.MyCreate(col.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (existsId == false)
|
||||
{
|
||||
doc["_id"] = ObjectId.GenerateNewId();
|
||||
}
|
||||
// 转为 JSON 字符串(标准 MongoDB shell 格式)
|
||||
string json = doc.ToJson(UtilMethods.GetJsonWriterSettings());
|
||||
|
||||
|
||||
@@ -177,12 +177,20 @@ namespace SqlSugar.MongoDb
|
||||
jsonPart = str;
|
||||
directionPart = "ASC";
|
||||
}
|
||||
var bson = MongoDB.Bson.Serialization.BsonSerializer.Deserialize<BsonDocument>(jsonPart);
|
||||
if (bson.Contains(UtilConstants.FieldName))
|
||||
if (jsonPart?.StartsWith("{") == false && jsonPart?.StartsWith("[") == false)
|
||||
{
|
||||
var field = bson[UtilConstants.FieldName].AsString;
|
||||
var direction = directionPart == "DESC" ? -1 : 1;
|
||||
sortDoc[field] = direction;
|
||||
sortDoc[jsonPart?.Replace(" ASC","")?.TrimEnd('\"')?.TrimStart('\"')] = direction;
|
||||
}
|
||||
else
|
||||
{
|
||||
var bson = MongoDB.Bson.Serialization.BsonSerializer.Deserialize<BsonDocument>(jsonPart);
|
||||
if (bson.Contains(UtilConstants.FieldName))
|
||||
{
|
||||
var field = bson[UtilConstants.FieldName].AsString;
|
||||
var direction = directionPart == "DESC" ? -1 : 1;
|
||||
sortDoc[field] = direction;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sortDoc.ElementCount > 0)
|
||||
|
||||
@@ -101,7 +101,7 @@ namespace SqlSugar
|
||||
}
|
||||
|
||||
bool supportIdentity = true;
|
||||
if (this.context.CurrentConnectionConfig.DbType == DbType.PostgreSQL || this.context.CurrentConnectionConfig.DbType == DbType.Vastbase)
|
||||
if (this.context.CurrentConnectionConfig.DbType == DbType.Dm || this.context.CurrentConnectionConfig.DbType == DbType.PostgreSQL || this.context.CurrentConnectionConfig.DbType == DbType.Vastbase)
|
||||
{
|
||||
supportIdentity = false;
|
||||
}
|
||||
|
||||
@@ -236,6 +236,18 @@ namespace SqlSugar
|
||||
this.QueryableObj = method.Invoke(QueryableObj, new object[] { ignoreColumns });
|
||||
return this;
|
||||
}
|
||||
public QueryMethodInfo Take(int takeNumber)
|
||||
{
|
||||
var method = QueryableObj.GetType().GetMyMethod("Take", 1, typeof(int));
|
||||
this.QueryableObj = method.Invoke(QueryableObj, new object[] { takeNumber });
|
||||
return this;
|
||||
}
|
||||
public QueryMethodInfo Skip(int skipNumber)
|
||||
{
|
||||
var method = QueryableObj.GetType().GetMyMethod("Skip", 1, typeof(int));
|
||||
this.QueryableObj = method.Invoke(QueryableObj, new object[] { skipNumber });
|
||||
return this;
|
||||
}
|
||||
public QueryMethodInfo Includes(string navProperyName,string thenNavProperyName2)
|
||||
{
|
||||
var method = QueryableObj.GetType().GetMyMethod("IncludesByNameString", 2, typeof(string),typeof(string));
|
||||
|
||||
@@ -662,7 +662,11 @@ namespace SqlSugar
|
||||
var name2Column = entityColumns.FirstOrDefault(it => it.PropertyName == name2);
|
||||
if (name1Column != null)
|
||||
{
|
||||
if (!navInfo.AppendProperties.ContainsKey(name1Column.PropertyName))
|
||||
if (navColumn.Navigat.NavigatType == NavigateType.OneToMany&& name1Column.DbColumnName==null)
|
||||
{
|
||||
//empty
|
||||
}
|
||||
else if (!navInfo.AppendProperties.ContainsKey(name1Column.PropertyName))
|
||||
navInfo.AppendProperties.Add(name1Column.PropertyName, name1Column.DbColumnName);
|
||||
}
|
||||
if (name2Column != null)
|
||||
@@ -1795,7 +1799,12 @@ namespace SqlSugar
|
||||
foreach (var item in s.Arguments)
|
||||
{
|
||||
var q = this.Context.Queryable<object>().QueryBuilder;
|
||||
var itemObj= q.GetExpressionValue(item, isSingle ? ResolveExpressType.FieldSingle : ResolveExpressType.WhereMultiple).GetResultString();
|
||||
var resolveExpressType = isSingle ? ResolveExpressType.FieldSingle : ResolveExpressType.WhereMultiple;
|
||||
if(item is MemberExpression&&resolveExpressType == ResolveExpressType.WhereMultiple)
|
||||
{
|
||||
resolveExpressType = ResolveExpressType.FieldMultiple;
|
||||
}
|
||||
var itemObj= q.GetExpressionValue(item, resolveExpressType).GetResultString();
|
||||
if (q.Parameters.Any())
|
||||
{
|
||||
var itemGroupBySql = UtilMethods.GetSqlString(DbType.SqlServer, itemObj, q.Parameters.ToArray());
|
||||
|
||||
@@ -8,7 +8,8 @@ using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Reflection;
|
||||
using System.Dynamic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace SqlSugar
|
||||
{
|
||||
@@ -814,8 +815,15 @@ namespace SqlSugar
|
||||
});
|
||||
if (value is Enum && this.Context.CurrentConnectionConfig?.MoreSettings?.TableEnumIsString != true)
|
||||
{
|
||||
data.Value.FieldValue = Convert.ToInt64(value).ObjToString();
|
||||
data.Value.CSharpTypeName = "int";
|
||||
if (column.SqlParameterDbType is Type type&&type?.Name== "EnumToStringConvert")
|
||||
{
|
||||
data.Value.CSharpTypeName = "string";
|
||||
}
|
||||
else
|
||||
{
|
||||
data.Value.FieldValue = Convert.ToInt64(value).ObjToString();
|
||||
data.Value.CSharpTypeName = "int";
|
||||
}
|
||||
}
|
||||
else if (value != null&&column.UnderType==UtilConstants.DateType)
|
||||
{
|
||||
|
||||
@@ -220,7 +220,7 @@ namespace SqlSugar
|
||||
var lastValue= GetFieldValue(new ConditionalModel() { CSharpTypeName = item.CSharpTypeName, FieldValue = valueArray.LastOrDefault() });
|
||||
var parameterNameFirst =parameterName+"_01";
|
||||
var parameterNameLast = parameterName+"_02";
|
||||
builder.AppendFormat("( {0}>={1} AND {0}<={2} )", item.FieldName.ToSqlFilter(), parameterNameFirst, parameterNameLast);
|
||||
builder.AppendFormat(type+"( {0}>={1} AND {0}<={2} )", item.FieldName.ToSqlFilter(), parameterNameFirst, parameterNameLast);
|
||||
parameters.Add(new SugarParameter(parameterNameFirst, firstValue));
|
||||
parameters.Add(new SugarParameter(parameterNameLast, lastValue));
|
||||
}
|
||||
@@ -235,7 +235,7 @@ namespace SqlSugar
|
||||
var times = GetDateRange(valueArray.FirstOrDefault(), valueArray.LastOrDefault());
|
||||
var parameterNameFirst = parameterName + "_01";
|
||||
var parameterNameLast = parameterName + "_02";
|
||||
builder.AppendFormat("( {0}>={1} AND {0}<{2} )", item.FieldName.ToSqlFilter(), parameterNameFirst, parameterNameLast);
|
||||
builder.AppendFormat(type+"( {0}>={1} AND {0}<{2} )", item.FieldName.ToSqlFilter(), parameterNameFirst, parameterNameLast);
|
||||
parameters.Add(new SugarParameter(parameterNameFirst, times.First()));
|
||||
parameters.Add(new SugarParameter(parameterNameLast, times.Last()));
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<Version>5.1.4.208</Version>
|
||||
<Version>5.1.4.210</Version>
|
||||
<Copyright>sun_kai_xuan</Copyright>
|
||||
<PackageProjectUrl>https://github.com/sunkaixuan/SqlSugar</PackageProjectUrl>
|
||||
<PackageLicenseUrl></PackageLicenseUrl>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<package >
|
||||
<metadata>
|
||||
<id>SqlSugarCore</id>
|
||||
<version>5.1.4.208</version>
|
||||
<version>5.1.4.210</version>
|
||||
<authors>sunkaixuan</authors>
|
||||
<owners>果糖大数据科技</owners>
|
||||
<licenseUrl>http://www.apache.org/licenses/LICENSE-2.0.html</licenseUrl>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<package >
|
||||
<metadata>
|
||||
<id>SqlSugarCoreNoDrive</id>
|
||||
<version>5.1.4.208</version>
|
||||
<version>5.1.4.210</version>
|
||||
<authors>sunkaixuan</authors>
|
||||
<owners>Landa</owners>
|
||||
<licenseUrl>http://www.apache.org/licenses/LICENSE-2.0.html</licenseUrl>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
Reference in New Issue
Block a user