380 lines
13 KiB
C#
380 lines
13 KiB
C#
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(" 灵活性和便利性可以兼得!");
|
||
}
|
||
} |