A:增加数据库配置和使用
This commit is contained in:
1 parent
53d1dd2dbf
commit
f7660c49e8
86 files changed
+7401
-271
No files matched your search
@@ -0,0 +1,382 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Sharp.Data;
|
||||
using Sharp.Data.Context;
|
||||
|
||||
namespace Data.Test;
|
||||
|
||||
/// <summary>
|
||||
/// 演示 04: 软删除功能
|
||||
///
|
||||
/// 本测试类演示:
|
||||
/// 1. 什么是软删除
|
||||
/// 2. 如何启用软删除
|
||||
/// 3. 软删除 vs 物理删除
|
||||
/// 4. 查询时自动过滤已删除数据
|
||||
/// 5. 如何查询已删除的数据
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class _04_SoftDeleteTests
|
||||
{
|
||||
/// <summary>
|
||||
/// 演示: 软删除基础概念
|
||||
///
|
||||
/// 软删除(Soft Delete)不会真正从数据库删除记录,
|
||||
/// 而是将 IsDeleted 标记设置为 true
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public async Task Demo_SoftDeleteBasics()
|
||||
{
|
||||
var builder = new DataContextBuilder<TestDbContext>();
|
||||
builder.UseInMemoryDatabase($"SoftDeleteDemo_{Guid.NewGuid()}")
|
||||
.UseAudit(true)
|
||||
.UseSoftDelete(true) // 启用软删除
|
||||
.UseCurrentUser("admin");
|
||||
|
||||
using var db = builder.Build();
|
||||
var repo = db.Repository<Product>();
|
||||
|
||||
// 创建产品
|
||||
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<Product>()
|
||||
.IgnoreQueryFilters() // 忽略软删除过滤器
|
||||
.Where(p => p.Id == productId)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
Assert.IsNotNull(allIncludingDeleted);
|
||||
Console.WriteLine($"✓ 使用 IgnoreQueryFilters() 仍可查询到: {allIncludingDeleted.Name}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 演示: 软删除 vs 物理删除
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public async Task Demo_SoftDeleteVsPhysicalDelete()
|
||||
{
|
||||
var builder = new DataContextBuilder<TestDbContext>();
|
||||
builder.UseInMemoryDatabase($"DeleteCompareDemo_{Guid.NewGuid()}")
|
||||
.UseAudit(true)
|
||||
.UseSoftDelete(true)
|
||||
.UseCurrentUser("admin");
|
||||
|
||||
using var db = builder.Build();
|
||||
|
||||
// 1. 软删除:Product 实现了 ISoftDeletable
|
||||
var productRepo = db.Repository<Product>();
|
||||
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<Product>()
|
||||
.IgnoreQueryFilters()
|
||||
.AnyAsync(p => p.Id == product.Id);
|
||||
Assert.IsTrue(productStillExists);
|
||||
|
||||
// 2. 物理删除:Category 不实现 ISoftDeletable
|
||||
var categoryRepo = db.Repository<Category>();
|
||||
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<Category>()
|
||||
.AnyAsync(c => c.Id == category.Id);
|
||||
Assert.IsFalse(categoryDeleted);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 演示: 查询时自动过滤已删除数据
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public async Task Demo_AutoFilterDeletedData()
|
||||
{
|
||||
var builder = new DataContextBuilder<TestDbContext>();
|
||||
builder.UseInMemoryDatabase($"FilterDemo_{Guid.NewGuid()}")
|
||||
.UseSoftDelete(true)
|
||||
.UseCurrentUser("admin");
|
||||
|
||||
using var db = builder.Build();
|
||||
var repo = db.Repository<Product>();
|
||||
|
||||
// 添加 5 个产品
|
||||
for (int 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<Product>()
|
||||
.IgnoreQueryFilters()
|
||||
.ToListAsync();
|
||||
Console.WriteLine($"✓ IgnoreQueryFilters 返回: {allIncludingDeleted.Count} 个产品(包含已删除)");
|
||||
Assert.AreEqual(5, allIncludingDeleted.Count);
|
||||
|
||||
// 只查询已删除的
|
||||
var deletedOnly = await db.Context.Set<Product>()
|
||||
.IgnoreQueryFilters()
|
||||
.Where(p => p.IsDeleted)
|
||||
.ToListAsync();
|
||||
Console.WriteLine($"✓ 只查询已删除: {deletedOnly.Count} 个产品");
|
||||
Assert.AreEqual(2, deletedOnly.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 演示: 软删除的实际应用场景
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public async Task Demo_SoftDeleteRealWorldScenario()
|
||||
{
|
||||
var builder = new DataContextBuilder<TestDbContext>();
|
||||
builder.UseInMemoryDatabase($"RealWorldSoftDelete_{Guid.NewGuid()}")
|
||||
.UseAudit(true)
|
||||
.UseSoftDelete(true)
|
||||
.UseCurrentUser("admin");
|
||||
|
||||
using var db = builder.Build();
|
||||
var repo = db.Repository<Product>();
|
||||
|
||||
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<Product>()
|
||||
.IgnoreQueryFilters()
|
||||
.ToListAsync();
|
||||
Console.WriteLine($"\n4. 管理员视角");
|
||||
Console.WriteLine($" 所有产品(含下架): {allProducts.Count}");
|
||||
Console.WriteLine($" 可恢复下架产品的历史数据");
|
||||
|
||||
Console.WriteLine($"\n✓ 软删除的优势:");
|
||||
Console.WriteLine($" - 保留历史数据");
|
||||
Console.WriteLine($" - 可以恢复");
|
||||
Console.WriteLine($" - 可以审计追踪");
|
||||
Console.WriteLine($" - 满足合规要求");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 演示: 批量软删除
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public async Task Demo_BulkSoftDelete()
|
||||
{
|
||||
var builder = new DataContextBuilder<TestDbContext>();
|
||||
builder.UseInMemoryDatabase($"BulkSoftDelete_{Guid.NewGuid()}")
|
||||
.UseSoftDelete(true)
|
||||
.UseCurrentUser("admin");
|
||||
|
||||
using var db = builder.Build();
|
||||
var repo = db.Repository<Product>();
|
||||
|
||||
// 添加多个产品
|
||||
var products = new List<Product>();
|
||||
for (int 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 演示: 只实现软删除,不要审计
|
||||
///
|
||||
/// Notification 只实现了 ISoftDeletable
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public async Task Demo_SoftDeleteWithoutAudit()
|
||||
{
|
||||
var builder = new DataContextBuilder<TestDbContext>();
|
||||
builder.UseInMemoryDatabase($"SoftDeleteOnly_{Guid.NewGuid()}")
|
||||
.UseSoftDelete(true)
|
||||
.UseCurrentUser("admin");
|
||||
|
||||
using var db = builder.Build();
|
||||
var repo = db.Repository<Notification>();
|
||||
|
||||
// 创建通知
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 演示: 禁用软删除时的行为
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public async Task Demo_DisableSoftDelete()
|
||||
{
|
||||
var builder = new DataContextBuilder<TestDbContext>();
|
||||
builder.UseInMemoryDatabase($"DisableSoftDelete_{Guid.NewGuid()}")
|
||||
.UseSoftDelete(false) // 禁用软删除
|
||||
.UseCurrentUser("admin");
|
||||
|
||||
using var db = builder.Build();
|
||||
var repo = db.Repository<Product>();
|
||||
|
||||
// 添加产品
|
||||
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<Product>()
|
||||
.IgnoreQueryFilters()
|
||||
.FirstOrDefaultAsync(p => p.Id == productId);
|
||||
|
||||
if (found != null)
|
||||
{
|
||||
Console.WriteLine($" 结果:软删除 (IsDeleted={found.IsDeleted})");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($" 结果:物理删除");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user