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

380 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Microsoft.EntityFrameworkCore;
using Sharp.Data;
using Sharp.Data.Context;
namespace Data.Test;
/// <summary>
/// 演示 06: 自定义实体(不使用基类)
///
/// 本测试类演示:
/// 1. 完全自定义的实体定义
/// 2. 不使用任何基类的实体
/// 3. 使用特殊主键类型(字符串、复合主键等)
/// 4. 仓储对自定义实体的支持
/// 5. 灵活性和自主控制
/// </summary>
[TestClass]
public class _06_CustomEntityTests
{
/// <summary>
/// 演示: 完全自定义的实体
///
/// CustomProduct 不使用任何基类,完全自定义
/// </summary>
[TestMethod]
public async Task Demo_FullyCustomEntity()
{
var builder = new DataContextBuilder<TestDbContext>();
builder.UseInMemoryDatabase($"CustomEntityDemo_{Guid.NewGuid()}");
using var db = builder.Build();
var repo = db.Repository<CustomProduct>();
Console.WriteLine("=== 完全自定义实体 ===\n");
Console.WriteLine("CustomProduct:");
Console.WriteLine(" - 不继承任何基类");
Console.WriteLine(" - 不实现任何接口");
Console.WriteLine(" - 完全自主定义所有字段");
Console.WriteLine(" - 仍然可以使用仓储模式\n");
// 创建自定义产品
var product = new CustomProduct
{
Id = Guid.NewGuid(),
Name = "自定义产品",
Price = 299.99m,
CreatedDate = DateTime.UtcNow
};
// 使用仓储添加
await repo.AddAsync(product);
await db.SaveChangesAsync();
Console.WriteLine($"✓ 添加成功:");
Console.WriteLine($" ID: {product.Id}");
Console.WriteLine($" Name: {product.Name}");
Console.WriteLine($" Price: {product.Price:C}");
Console.WriteLine($" CreatedDate: {product.CreatedDate:yyyy-MM-dd HH:mm:ss}");
Console.WriteLine($" IsExpensive: {product.IsExpensive}");
// 查询
var found = await repo.GetByIdAsync(product.Id);
Assert.IsNotNull(found);
Assert.AreEqual(product.Name, found.Name);
Console.WriteLine($"\n✓ 查询成功,完全自定义实体照样工作!");
}
/// <summary>
/// 演示: 字符串主键实体
///
/// Region 使用字符串作为主键
/// </summary>
[TestMethod]
public async Task Demo_StringPrimaryKey()
{
var builder = new DataContextBuilder<TestDbContext>();
builder.UseInMemoryDatabase($"StringPKDemo_{Guid.NewGuid()}");
using var db = builder.Build();
// Region 不能使用泛型仓储,因为主键不是 Guid
// 但可以直接使用 DbContext
var regions = db.Context.Set<Region>();
Console.WriteLine("=== 字符串主键实体 ===\n");
// 添加地区
var china = new Region
{
Code = "CN",
Name = "中国",
Description = "中华人民共和国"
};
var usa = new Region
{
Code = "US",
Name = "美国",
Description = "美利坚合众国"
};
regions.Add(china);
regions.Add(usa);
await db.SaveChangesAsync();
Console.WriteLine($"✓ 添加地区:");
Console.WriteLine($" {china.Code}: {china.Name}");
Console.WriteLine($" {usa.Code}: {usa.Name}");
// 根据字符串主键查询
var foundChina = await regions.FindAsync("CN");
Assert.IsNotNull(foundChina);
Assert.AreEqual("中国", foundChina.Name);
Console.WriteLine($"\n✓ 根据字符串主键查询成功: {foundChina.Name}");
// 列出所有地区
var allRegions = await regions.ToListAsync();
Console.WriteLine($"\n所有地区:");
foreach (var region in allRegions) Console.WriteLine($" [{region.Code}] {region.Name}");
}
/// <summary>
/// 演示: 自增 Int 主键实体
///
/// Tag 使用 EntityInt 基类,主键是自增 Int
/// </summary>
[TestMethod]
public async Task Demo_AutoIncrementIntPrimaryKey()
{
var builder = new DataContextBuilder<TestDbContext>();
builder.UseInMemoryDatabase($"IntPKDemo_{Guid.NewGuid()}");
using var db = builder.Build();
var repo = db.Repository<Tag>();
Console.WriteLine("=== 自增 Int 主键实体 ===\n");
// 添加标签(不需要手动设置 Id)
var tags = new[]
{
new Tag { Name = "技术", Color = "#2196F3" },
new Tag { Name = "生活", Color = "#4CAF50" },
new Tag { Name = "娱乐", Color = "#FF9800" }
};
await repo.AddRangeAsync(tags);
await db.SaveChangesAsync();
Console.WriteLine($"✓ 添加标签(Id 自动生成):");
foreach (var tag in tags)
{
Console.WriteLine($" ID: {tag.Id}, Name: {tag.Name}, Color: {tag.Color}");
Assert.IsTrue(tag.Id > 0); // 自增 Id
}
// 查询
var foundTag = await repo.GetByIdAsync(1);
Assert.IsNotNull(foundTag);
Console.WriteLine($"\n✓ 根据 Int 主键查询: {foundTag.Name}");
}
/// <summary>
/// 演示: 包含值对象的实体
///
/// Customer 包含 Address 值对象
/// </summary>
[TestMethod]
public async Task Demo_EntityWithValueObject()
{
var builder = new DataContextBuilder<TestDbContext>();
builder.UseInMemoryDatabase($"ValueObjectDemo_{Guid.NewGuid()}")
.UseAudit(true)
.UseCurrentUser("admin");
using var db = builder.Build();
var repo = db.Repository<Customer>();
Console.WriteLine("=== 包含值对象的实体 ===\n");
// 创建客户(包含地址值对象)
var customer = new Customer
{
Name = "张三",
Email = "zhangsan@example.com",
ShippingAddress = new Address
{
Street = "中关村大街1号",
City = "北京市",
State = "北京",
ZipCode = "100080",
Country = "中国"
},
BillingAddress = new Address
{
Street = "五道口路2号",
City = "北京市",
State = "北京",
ZipCode = "100081",
Country = "中国"
}
};
await repo.AddAsync(customer);
await db.SaveChangesAsync();
Console.WriteLine($"✓ 客户信息:");
Console.WriteLine($" 姓名: {customer.Name}");
Console.WriteLine($" 邮箱: {customer.Email}");
Console.WriteLine($"\n 收货地址:");
Console.WriteLine($" {customer.ShippingAddress.Street}");
Console.WriteLine($" {customer.ShippingAddress.City}, {customer.ShippingAddress.ZipCode}");
Console.WriteLine($"\n 账单地址:");
Console.WriteLine($" {customer.BillingAddress.Street}");
Console.WriteLine($" {customer.BillingAddress.City}, {customer.BillingAddress.ZipCode}");
// 查询
var found = await repo.GetByIdAsync(customer.Id);
Assert.IsNotNull(found);
Assert.IsNotNull(found.ShippingAddress);
Assert.AreEqual("中关村大街1号", found.ShippingAddress.Street);
Console.WriteLine($"\n✓ 值对象被正确保存和查询");
}
/// <summary>
/// 演示: 混合使用不同类型的实体
///
/// 在同一个应用中可以混合使用各种实体定义方式
/// </summary>
[TestMethod]
public async Task Demo_MixedEntityTypes()
{
var builder = new DataContextBuilder<TestDbContext>();
builder.UseInMemoryDatabase($"MixedDemo_{Guid.NewGuid()}")
.UseAudit(true)
.UseSoftDelete(true)
.UseCurrentUser("admin");
using var db = builder.Build();
Console.WriteLine("=== 混合使用不同实体 ===\n");
// 1. 使用 AuditableEntity (带审计和软删除)
var productRepo = db.Repository<Product>();
var product = new Product { Name = "产品A", Price = 100m, Stock = 10 };
await productRepo.AddAsync(product);
Console.WriteLine($"1. Product (AuditableEntity):");
Console.WriteLine($" 支持: 审计 + 软删除");
// 2. 使用 Entity (只有 Id)
var categoryRepo = db.Repository<Category>();
var category = new Category { Name = "分类A" };
await categoryRepo.AddAsync(category);
Console.WriteLine($"\n2. Category (Entity):");
Console.WriteLine($" 支持: 仅基础 CRUD");
// 3. 完全自定义
var customRepo = db.Repository<CustomProduct>();
var custom = new CustomProduct
{
Id = Guid.NewGuid(),
Name = "自定义产品",
Price = 200m,
CreatedDate = DateTime.UtcNow
};
await customRepo.AddAsync(custom);
Console.WriteLine($"\n3. CustomProduct (完全自定义):");
Console.WriteLine($" 支持: 自定义字段和逻辑");
// 4. 实现接口 (自定义+审计)
var orderRepo = db.Repository<Order>();
var order = new Order
{
OrderNumber = "ORD-001",
TotalAmount = 500m
};
await orderRepo.AddAsync(order);
Console.WriteLine($"\n4. Order (实现接口):");
Console.WriteLine($" 支持: 自定义实现 + 审计");
await db.SaveChangesAsync();
Console.WriteLine($"\n✓ 所有实体类型都能正常工作!");
Console.WriteLine($"✓ 根据需求选择合适的实体定义方式");
}
/// <summary>
/// 演示: 自定义实体的查询
/// </summary>
[TestMethod]
public async Task Demo_QueryCustomEntities()
{
var builder = new DataContextBuilder<TestDbContext>();
builder.UseInMemoryDatabase($"QueryCustomDemo_{Guid.NewGuid()}");
using var db = builder.Build();
var repo = db.Repository<CustomProduct>();
// 添加测试数据
for (var i = 1; i <= 20; i++)
await repo.AddAsync(new CustomProduct
{
Id = Guid.NewGuid(),
Name = $"产品{i}",
Price = i * 100m,
CreatedDate = DateTime.UtcNow.AddDays(-i)
});
await db.SaveChangesAsync();
Console.WriteLine("=== 自定义实体的查询 ===\n");
// 1. 条件查询
var expensiveProducts = await repo.FindAsync(p => p.Price > 1000m);
Console.WriteLine($"1. 价格 > 1000 的产品: {expensiveProducts.Count} 个");
// 2. 使用业务属性查询
var veryExpensive = await repo.Query()
.Where(p => p.IsExpensive) // 使用计算属性
.ToListAsync();
Console.WriteLine($"2. 使用 IsExpensive 属性: {veryExpensive.Count} 个");
// 3. 复杂查询
var recentAndCheap = await repo.Query()
.Where(p => p.CreatedDate > DateTime.UtcNow.AddDays(-7) && p.Price < 500m)
.OrderByDescending(p => p.CreatedDate)
.ToListAsync();
Console.WriteLine($"3. 最近7天创建且价格<500: {recentAndCheap.Count} 个");
// 4. 分页查询
var paged = await repo.GetPagedAsync(1, 5);
Console.WriteLine($"4. 分页查询: 第1页 {paged.Items.Count} 条,共 {paged.TotalCount} 条");
Console.WriteLine($"\n✓ 自定义实体支持所有标准查询操作");
}
/// <summary>
/// 演示: 为什么要使用自定义实体?
/// </summary>
[TestMethod]
public void Demo_WhyCustomEntities()
{
Console.WriteLine("=== 为什么使用自定义实体? ===\n");
Console.WriteLine("1. 已有实体模型");
Console.WriteLine(" - 项目已经有定义好的实体");
Console.WriteLine(" - 不想改动现有代码");
Console.WriteLine(" - 快速集成 Sharp.Data\n");
Console.WriteLine("2. 特殊主键类型");
Console.WriteLine(" - 使用字符串作为主键(如: 地区代码)");
Console.WriteLine(" - 使用复合主键");
Console.WriteLine(" - 使用自增 Int 主键\n");
Console.WriteLine("3. 不需要框架功能");
Console.WriteLine(" - 不需要审计");
Console.WriteLine(" - 不需要软删除");
Console.WriteLine(" - 只需要基础 CRUD\n");
Console.WriteLine("4. 完全自主控制");
Console.WriteLine(" - 自定义字段名称");
Console.WriteLine(" - 自定义业务逻辑");
Console.WriteLine(" - 不依赖框架约束\n");
Console.WriteLine("5. 灵活性最高");
Console.WriteLine(" - 可以在同一项目中混用");
Console.WriteLine(" - 根据实体特点选择合适方式");
Console.WriteLine(" - 渐进式采用 Sharp.Data\n");
Console.WriteLine("✓ 总结:");
Console.WriteLine(" Sharp.Data 的基类和接口都是可选的");
Console.WriteLine(" 你可以完全自定义,仍然享受仓储模式的便利");
Console.WriteLine(" 灵活性和便利性可以兼得!");
}
}