using Microsoft.EntityFrameworkCore;
using Sharp.Data;
using Sharp.Data.Context;
namespace Data.Test;
///
/// 演示 06: 自定义实体(不使用基类)
///
/// 本测试类演示:
/// 1. 完全自定义的实体定义
/// 2. 不使用任何基类的实体
/// 3. 使用特殊主键类型(字符串、复合主键等)
/// 4. 仓储对自定义实体的支持
/// 5. 灵活性和自主控制
///
[TestClass]
public class _06_CustomEntityTests
{
///
/// 演示: 完全自定义的实体
///
/// CustomProduct 不使用任何基类,完全自定义
///
[TestMethod]
public async Task Demo_FullyCustomEntity()
{
var builder = new DataContextBuilder();
builder.UseInMemoryDatabase($"CustomEntityDemo_{Guid.NewGuid()}");
using var db = builder.Build();
var repo = db.Repository();
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✓ 查询成功,完全自定义实体照样工作!");
}
///
/// 演示: 字符串主键实体
///
/// Region 使用字符串作为主键
///
[TestMethod]
public async Task Demo_StringPrimaryKey()
{
var builder = new DataContextBuilder();
builder.UseInMemoryDatabase($"StringPKDemo_{Guid.NewGuid()}");
using var db = builder.Build();
// Region 不能使用泛型仓储,因为主键不是 Guid
// 但可以直接使用 DbContext
var regions = db.Context.Set();
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}");
}
///
/// 演示: 自增 Int 主键实体
///
/// Tag 使用 EntityInt 基类,主键是自增 Int
///
[TestMethod]
public async Task Demo_AutoIncrementIntPrimaryKey()
{
var builder = new DataContextBuilder();
builder.UseInMemoryDatabase($"IntPKDemo_{Guid.NewGuid()}");
using var db = builder.Build();
var repo = db.Repository();
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}");
}
///
/// 演示: 包含值对象的实体
///
/// Customer 包含 Address 值对象
///
[TestMethod]
public async Task Demo_EntityWithValueObject()
{
var builder = new DataContextBuilder();
builder.UseInMemoryDatabase($"ValueObjectDemo_{Guid.NewGuid()}")
.UseAudit(true)
.UseCurrentUser("admin");
using var db = builder.Build();
var repo = db.Repository();
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✓ 值对象被正确保存和查询");
}
///
/// 演示: 混合使用不同类型的实体
///
/// 在同一个应用中可以混合使用各种实体定义方式
///
[TestMethod]
public async Task Demo_MixedEntityTypes()
{
var builder = new DataContextBuilder();
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();
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();
var category = new Category { Name = "分类A" };
await categoryRepo.AddAsync(category);
Console.WriteLine($"\n2. Category (Entity):");
Console.WriteLine($" 支持: 仅基础 CRUD");
// 3. 完全自定义
var customRepo = db.Repository();
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();
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($"✓ 根据需求选择合适的实体定义方式");
}
///
/// 演示: 自定义实体的查询
///
[TestMethod]
public async Task Demo_QueryCustomEntities()
{
var builder = new DataContextBuilder();
builder.UseInMemoryDatabase($"QueryCustomDemo_{Guid.NewGuid()}");
using var db = builder.Build();
var repo = db.Repository();
// 添加测试数据
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✓ 自定义实体支持所有标准查询操作");
}
///
/// 演示: 为什么要使用自定义实体?
///
[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(" 灵活性和便利性可以兼得!");
}
}