Files
sharp-basic/test/Data.Test/02_RepositoryCRUDTests.cs
T
2025-12-30 09:17:25 +08:00

524 lines
16 KiB
C#

using Microsoft.EntityFrameworkCore;
using Sharp.Data;
using Sharp.Data.Context;
namespace Data.Test;
/// <summary>
/// 演示 02: 仓储 CRUD 操作
///
/// 本测试类演示:
/// 1. 添加数据 (Create)
/// 2. 查询数据 (Read)
/// 3. 更新数据 (Update)
/// 4. 删除数据 (Delete)
/// 5. 批量操作
/// 6. 条件查询
/// </summary>
[TestClass]
public class _02_RepositoryCRUDTests
{
private IDataContext _db = null!;
[TestInitialize]
public void Setup()
{
var builder = new DataContextBuilder<TestDbContext>();
builder.UseInMemoryDatabase($"CRUDDemo_{Guid.NewGuid()}");
_db = builder.Build();
}
[TestCleanup]
public void Cleanup()
{
_db?.Dispose();
}
#region Create - 添加数据
/// <summary>
/// 演示: 添加单个实体
/// </summary>
[TestMethod]
public async Task Demo_AddSingleEntity()
{
var repo = _db.Repository<Product>();
// 创建新产品
var product = new Product
{
Name = "iPhone 15",
Price = 7999.00m,
Description = "最新款 iPhone",
Stock = 100
};
// 添加到数据库
await repo.AddAsync(product);
await _db.SaveChangesAsync();
Console.WriteLine("✓ 成功添加产品");
Console.WriteLine($" - ID: {product.Id}");
Console.WriteLine($" - 名称: {product.Name}");
Console.WriteLine($" - 价格: {product.Price:C}");
// 验证
var savedProduct = await repo.GetByIdAsync(product.Id);
Assert.IsNotNull(savedProduct);
Assert.AreEqual("iPhone 15", savedProduct.Name);
}
/// <summary>
/// 演示: 批量添加实体
/// </summary>
[TestMethod]
public async Task Demo_AddMultipleEntities()
{
var repo = _db.Repository<Product>();
// 创建多个产品
var products = new List<Product>
{
new() { Name = "MacBook Pro", Price = 12999.00m, Stock = 50 },
new() { Name = "iPad Air", Price = 4799.00m, Stock = 80 },
new() { Name = "AirPods Pro", Price = 1899.00m, Stock = 200 }
};
// 批量添加
await repo.AddRangeAsync(products);
await _db.SaveChangesAsync();
Console.WriteLine($"✓ 成功添加 {products.Count} 个产品");
foreach (var product in products) Console.WriteLine($" - {product.Name}: {product.Price:C}");
// 验证
var allProducts = await repo.GetAllAsync();
Assert.AreEqual(3, allProducts.Count);
}
#endregion
#region Read - 查询数据
/// <summary>
/// 演示: 根据 ID 获取实体
/// </summary>
[TestMethod]
public async Task Demo_GetById()
{
var repo = _db.Repository<Product>();
// 先添加数据
var product = new Product { Name = "测试产品", Price = 99.00m, Stock = 10 };
await repo.AddAsync(product);
await _db.SaveChangesAsync();
// 根据 ID 查询
var found = await repo.GetByIdAsync(product.Id);
Assert.IsNotNull(found);
Assert.AreEqual(product.Id, found.Id);
Console.WriteLine($"✓ 通过 ID 查询成功: {found.Name}");
}
/// <summary>
/// 演示: 获取所有数据
/// </summary>
[TestMethod]
public async Task Demo_GetAll()
{
var repo = _db.Repository<Product>();
// 添加测试数据
await repo.AddRangeAsync(new[]
{
new Product { Name = "产品1", Price = 100m, Stock = 10 },
new Product { Name = "产品2", Price = 200m, Stock = 20 },
new Product { Name = "产品3", Price = 300m, Stock = 30 }
});
await _db.SaveChangesAsync();
// 获取所有数据
var allProducts = await repo.GetAllAsync();
Assert.AreEqual(3, allProducts.Count);
Console.WriteLine($"✓ 获取所有数据成功,共 {allProducts.Count} 条");
foreach (var p in allProducts) Console.WriteLine($" - {p.Name}: {p.Price:C}");
}
/// <summary>
/// 演示: 条件查询
/// </summary>
[TestMethod]
public async Task Demo_FindWithCondition()
{
var repo = _db.Repository<Product>();
// 添加测试数据
await repo.AddRangeAsync(new[]
{
new Product { Name = "苹果手机", Price = 7999m, Stock = 50 },
new Product { Name = "苹果平板", Price = 4799m, Stock = 30 },
new Product { Name = "华为手机", Price = 6999m, Stock = 40 }
});
await _db.SaveChangesAsync();
// 查找名称包含"苹果"的产品
var appleProducts = await repo.FindAsync(p => p.Name.Contains("苹果"));
Assert.AreEqual(2, appleProducts.Count);
Console.WriteLine("✓ 条件查询成功,找到包含'苹果'的产品:");
foreach (var p in appleProducts) Console.WriteLine($" - {p.Name}: {p.Price:C}");
}
/// <summary>
/// 演示: 获取第一个符合条件的实体
/// </summary>
[TestMethod]
public async Task Demo_FirstOrDefault()
{
var repo = _db.Repository<Product>();
// 添加测试数据
await repo.AddRangeAsync(new[]
{
new Product { Name = "产品A", Price = 100m, Stock = 10 },
new Product { Name = "产品B", Price = 200m, Stock = 20 }
});
await _db.SaveChangesAsync();
// 查找第一个价格大于150的产品
var expensiveProduct = await repo.FirstOrDefaultAsync(p => p.Price > 150);
Assert.IsNotNull(expensiveProduct);
Assert.AreEqual("产品B", expensiveProduct.Name);
Console.WriteLine($"✓ 找到第一个价格>150的产品: {expensiveProduct.Name}");
// 查找不存在的条件
var notFound = await repo.FirstOrDefaultAsync(p => p.Price > 1000);
Assert.IsNull(notFound);
Console.WriteLine("✓ 不存在的条件返回 null");
}
/// <summary>
/// 演示: 统计查询
/// </summary>
[TestMethod]
public async Task Demo_CountAndAny()
{
var repo = _db.Repository<Product>();
// 添加测试数据
await repo.AddRangeAsync(new[]
{
new Product { Name = "产品1", Price = 100m, Stock = 10 },
new Product { Name = "产品2", Price = 200m, Stock = 20 },
new Product { Name = "产品3", Price = 300m, Stock = 30 }
});
await _db.SaveChangesAsync();
// 统计总数
var totalCount = await repo.CountAsync();
Console.WriteLine($"✓ 总记录数: {totalCount}");
Assert.AreEqual(3, totalCount);
// 统计符合条件的数量
var expensiveCount = await repo.CountAsync(p => p.Price > 150);
Console.WriteLine($"✓ 价格>150的产品数: {expensiveCount}");
Assert.AreEqual(2, expensiveCount);
// 检查是否存在
var hasExpensive = await repo.AnyAsync(p => p.Price > 250);
Console.WriteLine($"✓ 是否存在价格>250的产品: {hasExpensive}");
Assert.IsTrue(hasExpensive);
var hasVeryExpensive = await repo.AnyAsync(p => p.Price > 500);
Console.WriteLine($"✓ 是否存在价格>500的产品: {hasVeryExpensive}");
Assert.IsFalse(hasVeryExpensive);
}
/// <summary>
/// 演示: 使用 Query 方法进行复杂查询
/// </summary>
[TestMethod]
public async Task Demo_ComplexQueryWithLinq()
{
var repo = _db.Repository<Product>();
// 添加测试数据
await repo.AddRangeAsync(new[]
{
new Product { Name = "iPhone", Price = 7999m, Stock = 50 },
new Product { Name = "iPad", Price = 4799m, Stock = 30 },
new Product { Name = "MacBook", Price = 12999m, Stock = 20 },
new Product { Name = "AirPods", Price = 1899m, Stock = 100 }
});
await _db.SaveChangesAsync();
// 获取可查询对象,进行复杂查询
var query = repo.Query(false); // trackChanges: false 提升查询性能
var result = await query
.Where(p => p.Price > 2000) // 价格大于2000
.OrderByDescending(p => p.Price) // 按价格降序
.Select(p => new // 投影到匿名类型
{
p.Name,
p.Price,
p.Stock,
TotalValue = p.Price * p.Stock // 计算总价值
})
.ToListAsync();
Console.WriteLine("✓ 复杂查询成功,价格>2000的产品(按价格降序):");
foreach (var item in result)
Console.WriteLine($" - {item.Name}: {item.Price:C} x {item.Stock} = {item.TotalValue:C}");
Assert.AreEqual(3, result.Count);
}
#endregion
#region Update - 更新数据
/// <summary>
/// 演示: 更新实体
/// </summary>
[TestMethod]
public async Task Demo_UpdateEntity()
{
var repo = _db.Repository<Product>();
// 添加产品
var product = new Product
{
Name = "旧名称",
Price = 100m,
Stock = 50
};
await repo.AddAsync(product);
await _db.SaveChangesAsync();
Console.WriteLine($"✓ 原始数据: {product.Name}, 价格: {product.Price:C}");
// 修改产品
product.Name = "新名称";
product.Price = 200m;
repo.Update(product);
await _db.SaveChangesAsync();
Console.WriteLine($"✓ 更新后: {product.Name}, 价格: {product.Price:C}");
// 验证更新
var updated = await repo.GetByIdAsync(product.Id);
Assert.AreEqual("新名称", updated!.Name);
Assert.AreEqual(200m, updated.Price);
}
/// <summary>
/// 演示: 批量更新
/// </summary>
[TestMethod]
public async Task Demo_UpdateMultipleEntities()
{
var repo = _db.Repository<Product>();
// 添加产品
var products = new List<Product>
{
new() { Name = "产品1", Price = 100m, Stock = 10 },
new() { Name = "产品2", Price = 200m, Stock = 20 },
new() { Name = "产品3", Price = 300m, Stock = 30 }
};
await repo.AddRangeAsync(products);
await _db.SaveChangesAsync();
// 批量涨价 10%
foreach (var product in products) product.Price *= 1.10m;
repo.UpdateRange(products);
await _db.SaveChangesAsync();
Console.WriteLine("✓ 批量涨价 10% 成功:");
var updatedProducts = await repo.GetAllAsync();
foreach (var p in updatedProducts) Console.WriteLine($" - {p.Name}: {p.Price:C}");
// 验证
Assert.AreEqual(110m, updatedProducts[0].Price);
}
#endregion
#region Delete - 删除数据
/// <summary>
/// 演示: 删除实体 (物理删除)
///
/// 注意: 对于实现了 ISoftDeletable 的实体,RemoveAsync 会变成软删除
/// 本例中的 Product 继承自 AuditableEntity,实现了软删除接口
/// 所以这个测试实际演示的是软删除,详见 04_SoftDeleteTests
/// </summary>
[TestMethod]
public async Task Demo_DeleteEntity()
{
var repo = _db.Repository<Category>(); // Category 没有软删除,是真实删除
// 添加分类
var category = new Category { Name = "测试分类" };
await repo.AddAsync(category);
await _db.SaveChangesAsync();
Console.WriteLine($"✓ 添加分类: {category.Name}, ID: {category.Id}");
// 删除分类
repo.Remove(category);
await _db.SaveChangesAsync();
Console.WriteLine("✓ 删除分类成功");
// 验证删除
var deleted = await repo.GetByIdAsync(category.Id);
Assert.IsNull(deleted);
Console.WriteLine("✓ 验证: 分类已被物理删除");
}
/// <summary>
/// 演示: 根据 ID 删除
/// </summary>
[TestMethod]
public async Task Demo_DeleteById()
{
var repo = _db.Repository<Category>();
// 添加分类
var category = new Category { Name = "要删除的分类" };
await repo.AddAsync(category);
await _db.SaveChangesAsync();
var categoryId = category.Id;
Console.WriteLine($"✓ 添加分类: ID={categoryId}");
// 根据 ID 删除
await repo.RemoveAsync(categoryId);
await _db.SaveChangesAsync();
Console.WriteLine("✓ 根据 ID 删除成功");
// 验证删除
var deleted = await repo.GetByIdAsync(categoryId);
Assert.IsNull(deleted);
}
/// <summary>
/// 演示: 批量删除
/// </summary>
[TestMethod]
public async Task Demo_DeleteMultipleEntities()
{
var repo = _db.Repository<Category>();
// 添加多个分类
var categories = new List<Category>
{
new() { Name = "分类1" },
new() { Name = "分类2" },
new() { Name = "分类3" }
};
await repo.AddRangeAsync(categories);
await _db.SaveChangesAsync();
Console.WriteLine($"✓ 添加了 {categories.Count} 个分类");
// 批量删除
repo.RemoveRange(categories);
await _db.SaveChangesAsync();
Console.WriteLine("✓ 批量删除成功");
// 验证
var remaining = await repo.GetAllAsync();
Assert.AreEqual(0, remaining.Count);
}
#endregion
#region 高级查询
/// <summary>
/// 演示: 排序查询
/// </summary>
[TestMethod]
public async Task Demo_OrderedQuery()
{
var repo = _db.Repository<Product>();
// 添加测试数据
await repo.AddRangeAsync(new[]
{
new Product { Name = "C产品", Price = 300m, Stock = 30 },
new Product { Name = "A产品", Price = 100m, Stock = 10 },
new Product { Name = "B产品", Price = 200m, Stock = 20 }
});
await _db.SaveChangesAsync();
// 按价格升序
var ascProducts = await repo.Query()
.OrderBy(p => p.Price)
.ToListAsync();
Console.WriteLine("✓ 按价格升序:");
foreach (var p in ascProducts) Console.WriteLine($" - {p.Name}: {p.Price:C}");
// 按价格降序
var descProducts = await repo.Query()
.OrderByDescending(p => p.Price)
.ToListAsync();
Console.WriteLine("✓ 按价格降序:");
foreach (var p in descProducts) Console.WriteLine($" - {p.Name}: {p.Price:C}");
Assert.AreEqual("A产品", ascProducts[0].Name);
Assert.AreEqual("C产品", descProducts[0].Name);
}
/// <summary>
/// 演示: 分组查询
/// </summary>
[TestMethod]
public async Task Demo_GroupQuery()
{
var repo = _db.Repository<Product>();
// 添加不同价格区间的产品
await repo.AddRangeAsync(new[]
{
new Product { Name = "低价产品1", Price = 50m, Stock = 10 },
new Product { Name = "低价产品2", Price = 80m, Stock = 20 },
new Product { Name = "中价产品1", Price = 150m, Stock = 15 },
new Product { Name = "中价产品2", Price = 180m, Stock = 25 },
new Product { Name = "高价产品1", Price = 500m, Stock = 5 },
new Product { Name = "高价产品2", Price = 600m, Stock = 8 }
});
await _db.SaveChangesAsync();
// 按价格区间分组统计
var priceGroups = await repo.Query()
.GroupBy(p => p.Price < 100 ? "低价" : p.Price < 300 ? "中价" : "高价")
.Select(g => new
{
PriceRange = g.Key,
Count = g.Count(),
AvgPrice = g.Average(p => p.Price),
TotalStock = g.Sum(p => p.Stock)
})
.ToListAsync();
Console.WriteLine("✓ 按价格区间分组统计:");
foreach (var group in priceGroups)
Console.WriteLine(
$" - {group.PriceRange}: {group.Count}个产品, 平均价格 {group.AvgPrice:C}, 总库存 {group.TotalStock}");
Assert.AreEqual(3, priceGroups.Count);
}
#endregion
}