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