524 lines
16 KiB
C#
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
|
|
} |