using Microsoft.EntityFrameworkCore;
using Sharp.Data;
using Sharp.Data.Context;
namespace Data.Test;
///
/// 演示 04: 软删除功能
///
/// 本测试类演示:
/// 1. 什么是软删除
/// 2. 如何启用软删除
/// 3. 软删除 vs 物理删除
/// 4. 查询时自动过滤已删除数据
/// 5. 如何查询已删除的数据
///
[TestClass]
public class _04_SoftDeleteTests
{
///
/// 演示: 软删除基础概念
///
/// 软删除(Soft Delete)不会真正从数据库删除记录,
/// 而是将 IsDeleted 标记设置为 true
///
[TestMethod]
public async Task Demo_SoftDeleteBasics()
{
var builder = new DataContextBuilder();
builder.UseInMemoryDatabase($"SoftDeleteDemo_{Guid.NewGuid()}")
.UseAudit(true)
.UseSoftDelete(true) // 启用软删除
.UseCurrentUser("admin");
using var db = builder.Build();
var repo = db.Repository();
// 创建产品
var product = new Product
{
Name = "要删除的产品",
Price = 100m,
Stock = 10
};
await repo.AddAsync(product);
await db.SaveChangesAsync();
Console.WriteLine($"✓ 创建产品: {product.Name}");
Console.WriteLine($" IsDeleted: {product.IsDeleted}");
var productId = product.Id;
// 软删除产品
await repo.RemoveAsync(productId);
await db.SaveChangesAsync();
Console.WriteLine($"\n✓ 软删除后:");
Console.WriteLine($" IsDeleted: {product.IsDeleted}");
Console.WriteLine($" DeletedAt: {product.DeletedAt:yyyy-MM-dd HH:mm:ss}");
Console.WriteLine($" DeletedBy: {product.DeletedBy}");
// 验证
Assert.IsTrue(product.IsDeleted);
Assert.IsNotNull(product.DeletedAt);
Assert.AreEqual("admin", product.DeletedBy);
// 正常查询看不到已删除的数据
var found = await repo.GetByIdAsync(productId);
Assert.IsNull(found);
Console.WriteLine($"\n✓ 正常查询返回 null (数据被过滤)");
// 但数据仍在数据库中
var allIncludingDeleted = await db.Context.Set()
.IgnoreQueryFilters() // 忽略软删除过滤器
.Where(p => p.Id == productId)
.FirstOrDefaultAsync();
Assert.IsNotNull(allIncludingDeleted);
Console.WriteLine($"✓ 使用 IgnoreQueryFilters() 仍可查询到: {allIncludingDeleted.Name}");
}
///
/// 演示: 软删除 vs 物理删除
///
[TestMethod]
public async Task Demo_SoftDeleteVsPhysicalDelete()
{
var builder = new DataContextBuilder();
builder.UseInMemoryDatabase($"DeleteCompareDemo_{Guid.NewGuid()}")
.UseAudit(true)
.UseSoftDelete(true)
.UseCurrentUser("admin");
using var db = builder.Build();
// 1. 软删除:Product 实现了 ISoftDeletable
var productRepo = db.Repository();
var product = new Product { Name = "软删除产品", Price = 100m, Stock = 10 };
await productRepo.AddAsync(product);
await db.SaveChangesAsync();
await productRepo.RemoveAsync(product.Id);
await db.SaveChangesAsync();
Console.WriteLine("1. 软删除 (Product):");
Console.WriteLine($" ✓ IsDeleted 被设置为 true");
Console.WriteLine($" ✓ 数据仍在数据库中");
Console.WriteLine($" ✓ 正常查询看不到");
// 验证数据仍在
var productStillExists = await db.Context.Set()
.IgnoreQueryFilters()
.AnyAsync(p => p.Id == product.Id);
Assert.IsTrue(productStillExists);
// 2. 物理删除:Category 不实现 ISoftDeletable
var categoryRepo = db.Repository();
var category = new Category { Name = "物理删除分类" };
await categoryRepo.AddAsync(category);
await db.SaveChangesAsync();
categoryRepo.Remove(category);
await db.SaveChangesAsync();
Console.WriteLine("\n2. 物理删除 (Category):");
Console.WriteLine($" ✓ 记录被真正删除");
Console.WriteLine($" ✓ 数据库中不再存在");
// 验证数据不在
var categoryDeleted = await db.Context.Set()
.AnyAsync(c => c.Id == category.Id);
Assert.IsFalse(categoryDeleted);
}
///
/// 演示: 查询时自动过滤已删除数据
///
[TestMethod]
public async Task Demo_AutoFilterDeletedData()
{
var builder = new DataContextBuilder();
builder.UseInMemoryDatabase($"FilterDemo_{Guid.NewGuid()}")
.UseSoftDelete(true)
.UseCurrentUser("admin");
using var db = builder.Build();
var repo = db.Repository();
// 添加 5 个产品
for (var i = 1; i <= 5; i++)
await repo.AddAsync(new Product
{
Name = $"产品{i}",
Price = i * 100m,
Stock = i * 10
});
await db.SaveChangesAsync();
Console.WriteLine("✓ 创建了 5 个产品");
// 软删除其中 2 个
var allProducts = await repo.GetAllAsync();
await repo.RemoveAsync(allProducts[0].Id);
await repo.RemoveAsync(allProducts[1].Id);
await db.SaveChangesAsync();
Console.WriteLine("✓ 软删除了 2 个产品");
// 正常查询只返回未删除的
var activeProducts = await repo.GetAllAsync();
Console.WriteLine($"\n✓ 正常查询返回: {activeProducts.Count} 个产品(自动过滤已删除)");
Assert.AreEqual(3, activeProducts.Count);
// 使用 IgnoreQueryFilters 查询所有
var allIncludingDeleted = await db.Context.Set()
.IgnoreQueryFilters()
.ToListAsync();
Console.WriteLine($"✓ IgnoreQueryFilters 返回: {allIncludingDeleted.Count} 个产品(包含已删除)");
Assert.AreEqual(5, allIncludingDeleted.Count);
// 只查询已删除的
var deletedOnly = await db.Context.Set()
.IgnoreQueryFilters()
.Where(p => p.IsDeleted)
.ToListAsync();
Console.WriteLine($"✓ 只查询已删除: {deletedOnly.Count} 个产品");
Assert.AreEqual(2, deletedOnly.Count);
}
///
/// 演示: 软删除的实际应用场景
///
[TestMethod]
public async Task Demo_SoftDeleteRealWorldScenario()
{
var builder = new DataContextBuilder();
builder.UseInMemoryDatabase($"RealWorldSoftDelete_{Guid.NewGuid()}")
.UseAudit(true)
.UseSoftDelete(true)
.UseCurrentUser("admin");
using var db = builder.Build();
var repo = db.Repository();
Console.WriteLine("=== 产品下架场景 ===\n");
// 1. 创建产品
var product = new Product
{
Name = "iPhone 14",
Price = 6999m,
Stock = 100
};
await repo.AddAsync(product);
await db.SaveChangesAsync();
Console.WriteLine($"1. 产品上架");
Console.WriteLine($" {product.Name} - {product.Price:C}");
// 2. 产品下架(软删除)
// (db as Sharp.Data.Context.DataContext)?.SetCurrentUser("product.manager");
await repo.RemoveAsync(product.Id);
await db.SaveChangesAsync();
Console.WriteLine($"\n2. 产品下架");
Console.WriteLine($" 操作人: {product.DeletedBy}");
Console.WriteLine($" 下架时间: {product.DeletedAt:yyyy-MM-dd HH:mm:ss}");
// 3. 用户无法看到下架产品
var userVisibleProducts = await repo.GetAllAsync();
Console.WriteLine($"\n3. 用户视角");
Console.WriteLine($" 可见产品数: {userVisibleProducts.Count} (下架产品不可见)");
// 4. 管理员可以查看下架产品历史
var allProducts = await db.Context.Set()
.IgnoreQueryFilters()
.ToListAsync();
Console.WriteLine($"\n4. 管理员视角");
Console.WriteLine($" 所有产品(含下架): {allProducts.Count}");
Console.WriteLine($" 可恢复下架产品的历史数据");
Console.WriteLine($"\n✓ 软删除的优势:");
Console.WriteLine($" - 保留历史数据");
Console.WriteLine($" - 可以恢复");
Console.WriteLine($" - 可以审计追踪");
Console.WriteLine($" - 满足合规要求");
}
///
/// 演示: 批量软删除
///
[TestMethod]
public async Task Demo_BulkSoftDelete()
{
var builder = new DataContextBuilder();
builder.UseInMemoryDatabase($"BulkSoftDelete_{Guid.NewGuid()}")
.UseSoftDelete(true)
.UseCurrentUser("admin");
using var db = builder.Build();
var repo = db.Repository();
// 添加多个产品
var products = new List();
for (var i = 1; i <= 10; i++)
products.Add(new Product
{
Name = $"产品{i}",
Price = i * 100m,
Stock = i * 10
});
await repo.AddRangeAsync(products);
await db.SaveChangesAsync();
Console.WriteLine($"✓ 创建了 {products.Count} 个产品");
// 批量软删除价格 > 500 的产品
var expensiveProducts = await repo.FindAsync(p => p.Price > 500);
repo.RemoveRange(expensiveProducts);
await db.SaveChangesAsync();
Console.WriteLine($"✓ 批量软删除了 {expensiveProducts.Count} 个产品(价格>500)");
// 验证
var remaining = await repo.GetAllAsync();
Console.WriteLine($"✓ 剩余可见产品: {remaining.Count} 个");
Assert.AreEqual(5, remaining.Count);
foreach (var p in expensiveProducts) Assert.IsTrue(p.IsDeleted);
}
///
/// 演示: 只实现软删除,不要审计
///
/// Notification 只实现了 ISoftDeletable
///
[TestMethod]
public async Task Demo_SoftDeleteWithoutAudit()
{
var builder = new DataContextBuilder();
builder.UseInMemoryDatabase($"SoftDeleteOnly_{Guid.NewGuid()}")
.UseSoftDelete(true)
.UseCurrentUser("admin");
using var db = builder.Build();
var repo = db.Repository();
// 创建通知
var notification = new Notification
{
Title = "系统通知",
Content = "这是一条测试通知",
CreatedAt = DateTime.UtcNow
};
await repo.AddAsync(notification);
await db.SaveChangesAsync();
// 软删除
await repo.RemoveAsync(notification.Id);
await db.SaveChangesAsync();
Console.WriteLine("✓ Notification 只实现软删除,不要审计:");
Console.WriteLine($" IsDeleted: {notification.IsDeleted}");
Console.WriteLine($" DeletedAt: {notification.DeletedAt:yyyy-MM-dd HH:mm:ss}");
Console.WriteLine($" DeletedBy: {notification.DeletedBy}");
Console.WriteLine($" 没有 CreatedBy/UpdatedBy 字段");
Assert.IsTrue(notification.IsDeleted);
}
///
/// 演示: 禁用软删除时的行为
///
[TestMethod]
public async Task Demo_DisableSoftDelete()
{
var builder = new DataContextBuilder();
builder.UseInMemoryDatabase($"DisableSoftDelete_{Guid.NewGuid()}")
.UseSoftDelete(false) // 禁用软删除
.UseCurrentUser("admin");
using var db = builder.Build();
var repo = db.Repository();
// 添加产品
var product = new Product { Name = "测试产品", Price = 100m, Stock = 10 };
await repo.AddAsync(product);
await db.SaveChangesAsync();
var productId = product.Id;
// 删除产品(禁用软删除后,RemoveAsync 会尝试物理删除)
// 但 Product 实体仍有 IsDeleted 字段,所以可能变成软删除
await repo.RemoveAsync(productId);
await db.SaveChangesAsync();
Console.WriteLine("✓ 禁用软删除功能后:");
Console.WriteLine($" RemoveAsync 的行为取决于实现");
Console.WriteLine($" 建议:如果不需要软删除,不要让实体实现 ISoftDeletable");
// 查询
var found = await db.Context.Set()
.IgnoreQueryFilters()
.FirstOrDefaultAsync(p => p.Id == productId);
if (found != null)
Console.WriteLine($" 结果:软删除 (IsDeleted={found.IsDeleted})");
else
Console.WriteLine($" 结果:物理删除");
}
}