# Sharp.Data 让 EF Core 使用更简单的数据访问模块,提供开箱即用的仓储模式、审计、软删除等基础设施。 > **设计理念**:Data 模块专注于数据访问基础设施,不包含业务逻辑。实体基类是**可选的**,你可以使用自己的实体定义。 ## 特性 - ✅ **极简 API** - 3 行代码启动,类似 NetServer 的设计理念 - ✅ **泛型仓储** - 提供常用 CRUD 操作,无需重复编写 - ✅ **自动审计** - 可选的审计功能,自动记录创建/修改信息 - ✅ **软删除** - 可选的软删除支持,逻辑删除而非物理删除 - ✅ **分页查询** - 复用 Common 模块的分页功能 - ✅ **多数据库** - 支持 SQLite、SQL Server、MySQL、PostgreSQL、InMemory - ✅ **低耦合** - 仅依赖 EF Core 和 Common 模块 - ✅ **灵活扩展** - 所有功能都是可选的,按需启用 ## 快速开始 ### 1. 定义实体(可选使用基类) ```csharp using Sharp.Data.Entities; // 方式1:使用提供的基类(可选) public class User : AuditableEntity // 包含审计和软删除 { public string Name { get; set; } = string.Empty; public string Email { get; set; } = string.Empty; } // 方式2:完全自定义(不使用基类) public class Product { public Guid Id { get; set; } public string Name { get; set; } = string.Empty; public decimal Price { get; set; } } ``` ### 2. 定义 DbContext ```csharp using Microsoft.EntityFrameworkCore; public class AppDbContext : DbContext { public AppDbContext(DbContextOptions options) : base(options) { } public DbSet Users => Set(); public DbSet Products => Set(); } ``` ### 3. 启动和使用(3 行代码) ```csharp using Sharp.Data; // 创建构建器并配置 var builder = new DataContextBuilder(); builder.UseSqlite("Data Source=app.db"); var db = builder.Build(); // 使用仓储 var userRepo = db.Repository(); // 添加数据 await userRepo.AddAsync(new User { Name = "张三", Email = "zhangsan@example.com" }); await db.SaveChangesAsync(); // 如果使用 AuditableEntity,会自动记录 CreatedAt // 查询数据 var users = await userRepo.GetAllAsync(); var user = await userRepo.FirstOrDefaultAsync(u => u.Email == "zhangsan@example.com"); // 分页查询(使用 Common 模块的 PagedResult) var pagedUsers = await userRepo.GetPagedAsync( page: 1, pageSize: 20, predicate: u => u.Name.Contains("张"), orderBy: q => q.OrderByDescending(u => u.Id)); Console.WriteLine($"Total: {pagedUsers.TotalCount}, Pages: {pagedUsers.TotalPages}"); ``` ## 模块依赖 ``` Sharp.Data ├── EF Core 8.0 (数据库访问) └── Sharp.Common (复用基础功能) ├── PagedResult (分页结果) ├── Result (错误处理,可选) └── 各种扩展方法 ``` ## 配置选项 ### 使用不同数据库 ```csharp var builder = new DataContextBuilder(); // SQLite builder.UseSqlite("Data Source=app.db"); // SQL Server builder.UseSqlServer("Server=.;Database=MyApp;Trusted_Connection=True;"); // MySQL builder.UseMySql("server=localhost;database=myapp;user=root;password=123456", "8.0.35"); // PostgreSQL builder.UseNpgsql("Host=localhost;Database=myapp;Username=postgres;Password=123456"); // 内存数据库(用于测试) builder.UseInMemoryDatabase("TestDb"); ``` ### 配置功能(所有功能都是可选的) ```csharp var builder = new DataContextBuilder(); builder.UseSqlite("Data Source=app.db") .UseAudit(true) // 启用审计(仅对实现 IAuditableEntity 的实体生效) .UseSoftDelete(true) // 启用软删除(仅对实现 ISoftDeletable 的实体生效) .UseAutoMigration(true) // 启动时自动迁移 .UseCurrentUser("user123") // 设置当前用户(用于审计) .EnableDetailedErrors() // 启用详细错误信息 .EnableSensitiveDataLogging(); // 启用敏感数据日志(仅开发) var db = builder.Build(); ``` ### 动态获取当前用户 ```csharp builder.UseCurrentUserProvider(() => { // 从你的认证系统获取当前用户 return HttpContext.Current?.User?.Identity?.Name ?? "system"; }); ``` ## 实体基类(可选) Data 模块提供了可选的实体基类,你**不是必须**使用它们: ### Entity - 基础实体(可选) ```csharp // Guid 主键(最常用) public class Product : Entity { } // Int 主键 public class Category : EntityInt { } // Long 主键 public class Log : EntityLong { } // 自定义主键类型 public class CustomEntity : Entity { } // 或者完全自定义 public class MyEntity { public int Id { get; set; } public string Name { get; set; } } ``` ### AuditableEntity - 审计实体(可选) 如果继承此基类,会自动管理以下字段: - `Id` - 主键 - `CreatedAt` - 创建时间 - `CreatedBy` - 创建人 - `UpdatedAt` - 更新时间 - `UpdatedBy` - 更新人 - `IsDeleted` - 是否删除 - `DeletedAt` - 删除时间 - `DeletedBy` - 删除人 ```csharp // 使用审计基类 public class User : AuditableEntity { public string Name { get; set; } public string Email { get; set; } } // 或者自己实现审计接口 public class MyEntity : IAuditableEntity, ISoftDeletable { public Guid Id { get; set; } public DateTime CreatedAt { get; set; } public string? CreatedBy { get; set; } // ... 实现其他接口属性 } // 或者完全自定义(不使用审计) public class SimpleEntity { public Guid Id { get; set; } public string Name { get; set; } } ``` ## 仓储方法 ### 查询方法 ```csharp var repo = db.Repository(); // 获取可查询对象 var query = repo.Query(trackChanges: false); var activeUsers = await query.Where(u => u.Name.Contains("张")).ToListAsync(); // 根据主键获取 var user = await repo.GetByIdAsync(userId); // 获取第一个符合条件的 var user = await repo.FirstOrDefaultAsync(u => u.Email == "test@example.com"); // 获取所有 var allUsers = await repo.GetAllAsync(); // 查找符合条件的 var users = await repo.FindAsync(u => u.Name.Contains("张")); // 分页查询(返回 Common.ExpandFun.PagedResult) var pagedResult = await repo.GetPagedAsync(1, 20); Console.WriteLine($"Total: {pagedResult.TotalCount}, Pages: {pagedResult.TotalPages}"); // 统计 var count = await repo.CountAsync(u => u.Name.Contains("张")); var exists = await repo.AnyAsync(u => u.Email == "test@example.com"); ``` ### 修改方法 ```csharp // 添加 var user = new User { Name = "张三", Email = "zhang@example.com" }; await repo.AddAsync(user); await db.SaveChangesAsync(); // 批量添加 var users = new List { user1, user2, user3 }; await repo.AddRangeAsync(users); await db.SaveChangesAsync(); // 更新 user.Name = "李四"; repo.Update(user); await db.SaveChangesAsync(); // 删除(如果实体实现 ISoftDeletable,会软删除) await repo.RemoveAsync(userId); await db.SaveChangesAsync(); // 物理删除(对于未实现 ISoftDeletable 的实体) repo.Remove(user); await db.SaveChangesAsync(); ``` ## 查询扩展(复用 Common 模块) ```csharp using Sharp.Data.Extensions; using Common.ExpandFun; // PagedResult // 分页扩展 var query = db.Context.Set().Where(u => u.Name.Contains("张")); var pagedResult = await query.ToPagedResultAsync(page: 1, pageSize: 20); // 条件查询(来自 Common 模块的扩展) var query = db.Context.Set() .WhereIf(!string.IsNullOrEmpty(searchTerm), u => u.Name.Contains(searchTerm)); // 动态排序 var query = db.Context.Set() .OrderByProperty("CreatedAt", descending: true); ``` ## 最佳实践 ### 1. 控制台应用 ```csharp using Sharp.Data; var builder = new DataContextBuilder(); builder.UseSqlite("Data Source=app.db"); using var db = builder.Build(); // 使用 db 进行数据操作 var repo = db.Repository(); await repo.AddAsync(new User { Name = "Test" }); await db.SaveChangesAsync(); // 自动释放资源 ``` ### 2. WPF 应用 ```csharp // App.xaml.cs public partial class App : Application { public static IDataContext Database { get; private set; } = null!; protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); var builder = new DataContextBuilder(); builder.UseSqlite("Data Source=app.db") .UseCurrentUserProvider(() => GetCurrentUser()); Database = builder.Build(); } protected override void OnExit(ExitEventArgs e) { Database?.Dispose(); base.OnExit(e); } } ``` ### 3. ASP.NET Core 应用 ```csharp // Program.cs builder.Services.AddScoped(sp => { var dataBuilder = new DataContextBuilder(); dataBuilder.UseSqlServer(builder.Configuration.GetConnectionString("Default")) .UseCurrentUserProvider(() => { var httpContext = sp.GetService()?.HttpContext; return httpContext?.User?.Identity?.Name ?? "anonymous"; }); return dataBuilder.Build(); }); builder.Services.AddHttpContextAccessor(); ``` ## 与其他 Sharp 模块集成 ### 与 Sharp.Common 集成(已内置) Data 模块已经引用了 Common 模块,可以直接使用: - `PagedResult` - 分页结果 - `Result` - 错误处理(可选) - 各种扩展方法 ### 与 Sharp.Log 集成 ```csharp using Sharp.Log; var loggerFactory = SharpLogDefaults.CreateLoggerFactory(); builder.ConfigureDbContext(options => { options.UseLoggerFactory(loggerFactory); }); ``` ## 高级功能 ### 访问底层 DbContext ```csharp var db = builder.Build(); // 执行原生 SQL await db.Context.Database.ExecuteSqlRawAsync( "DELETE FROM Logs WHERE CreatedAt < @p0", DateTime.Now.AddDays(-30)); // 开始事务 using var transaction = await db.Context.Database.BeginTransactionAsync(); try { await db.SaveChangesAsync(); await transaction.CommitAsync(); } catch { await transaction.RollbackAsync(); throw; } ``` ## 常见问题 ### Q: 我必须使用 Entity/AuditableEntity 基类吗? **不是!** 基类是**完全可选**的。你可以: - 使用自己的实体定义 - 实现 `IAuditableEntity` 或 `ISoftDeletable` 接口 - 完全不使用审计和软删除功能 ### Q: 如何使用自定义实体? ```csharp // 完全自定义,不使用任何基类 public class MyEntity { public int Id { get; set; } public string Name { get; set; } public DateTime CreatedAt { get; set; } } // 正常使用仓储 var repo = db.Repository(); await repo.AddAsync(new MyEntity { Name = "Test" }); ``` ### Q: PagedResult 在哪里定义的? `PagedResult` 来自 `Sharp.Common` 模块(`Common.ExpandFun` 命名空间),Data 模块复用它以避免重复。 ### Q: 如何禁用审计或软删除? ```csharp builder.UseAudit(false) // 禁用审计 .UseSoftDelete(false); // 禁用软删除 ``` ## 性能建议 1. 查询时使用 `Query(trackChanges: false)` 提升性能 2. 批量操作使用 `AddRangeAsync`/`UpdateRange` 3. 生产环境关闭 `EnableSensitiveDataLogging` 4. 使用分页查询避免一次加载大量数据 ## 许可证 MIT License