Files
sharp-basic/src/Sharp/Data/README.md
T
2025-12-30 09:17:25 +08:00

11 KiB
Raw Blame History

Sharp.Data

让 EF Core 使用更简单的数据访问模块,提供开箱即用的仓储模式、审计、软删除等基础设施。

设计理念:Data 模块专注于数据访问基础设施,不包含业务逻辑。实体基类是可选的,你可以使用自己的实体定义。

特性

  • ✅ 极简 API - 3 行代码启动,类似 NetServer 的设计理念
  • ✅ 泛型仓储 - 提供常用 CRUD 操作,无需重复编写
  • ✅ 自动审计 - 可选的审计功能,自动记录创建/修改信息
  • ✅ 软删除 - 可选的软删除支持,逻辑删除而非物理删除
  • ✅ 分页查询 - 复用 Common 模块的分页功能
  • ✅ 多数据库 - 支持 SQLite、SQL Server、MySQL、PostgreSQL、InMemory
  • ✅ 低耦合 - 仅依赖 EF Core 和 Common 模块
  • ✅ 灵活扩展 - 所有功能都是可选的,按需启用

快速开始

1. 定义实体(可选使用基类)

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

using Microsoft.EntityFrameworkCore;

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }

    public DbSet<User> Users => Set<User>();
    public DbSet<Product> Products => Set<Product>();
}

3. 启动和使用(3 行代码)

using Sharp.Data;

// 创建构建器并配置
var builder = new DataContextBuilder<AppDbContext>();
builder.UseSqlite("Data Source=app.db");
var db = builder.Build();

// 使用仓储
var userRepo = db.Repository<User>();

// 添加数据
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<T> (分页结果)
    ├── Result<T> (错误处理,可选)
    └── 各种扩展方法

配置选项

使用不同数据库

var builder = new DataContextBuilder<AppDbContext>();

// 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");

配置功能(所有功能都是可选的)

var builder = new DataContextBuilder<AppDbContext>();

builder.UseSqlite("Data Source=app.db")
    .UseAudit(true)              // 启用审计(仅对实现 IAuditableEntity 的实体生效)
    .UseSoftDelete(true)         // 启用软删除(仅对实现 ISoftDeletable 的实体生效)
    .UseAutoMigration(true)      // 启动时自动迁移
    .UseCurrentUser("user123")   // 设置当前用户(用于审计)
    .EnableDetailedErrors()      // 启用详细错误信息
    .EnableSensitiveDataLogging(); // 启用敏感数据日志(仅开发)

var db = builder.Build();

动态获取当前用户

builder.UseCurrentUserProvider(() =>
{
    // 从你的认证系统获取当前用户
    return HttpContext.Current?.User?.Identity?.Name ?? "system";
});

实体基类(可选)

Data 模块提供了可选的实体基类,你不是必须使用它们:

Entity - 基础实体(可选)

// Guid 主键(最常用)
public class Product : Entity { }

// Int 主键
public class Category : EntityInt { }

// Long 主键
public class Log : EntityLong { }

// 自定义主键类型
public class CustomEntity : Entity<string> { }

// 或者完全自定义
public class MyEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
}

AuditableEntity - 审计实体(可选)

如果继承此基类,会自动管理以下字段:

  • Id - 主键
  • CreatedAt - 创建时间
  • CreatedBy - 创建人
  • UpdatedAt - 更新时间
  • UpdatedBy - 更新人
  • IsDeleted - 是否删除
  • DeletedAt - 删除时间
  • DeletedBy - 删除人
// 使用审计基类
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; }
}

仓储方法

查询方法

var repo = db.Repository<User>();

// 获取可查询对象
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<T>)
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");

修改方法

// 添加
var user = new User { Name = "张三", Email = "zhang@example.com" };
await repo.AddAsync(user);
await db.SaveChangesAsync();

// 批量添加
var users = new List<User> { 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 模块)

using Sharp.Data.Extensions;
using Common.ExpandFun; // PagedResult

// 分页扩展
var query = db.Context.Set<User>().Where(u => u.Name.Contains("张"));
var pagedResult = await query.ToPagedResultAsync(page: 1, pageSize: 20);

// 条件查询(来自 Common 模块的扩展)
var query = db.Context.Set<User>()
    .WhereIf(!string.IsNullOrEmpty(searchTerm), u => u.Name.Contains(searchTerm));

// 动态排序
var query = db.Context.Set<User>()
    .OrderByProperty("CreatedAt", descending: true);

最佳实践

1. 控制台应用

using Sharp.Data;

var builder = new DataContextBuilder<AppDbContext>();
builder.UseSqlite("Data Source=app.db");
using var db = builder.Build();

// 使用 db 进行数据操作
var repo = db.Repository<User>();
await repo.AddAsync(new User { Name = "Test" });
await db.SaveChangesAsync();

// 自动释放资源

2. WPF 应用

// 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<AppDbContext>();
        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 应用

// Program.cs
builder.Services.AddScoped<IDataContext>(sp =>
{
    var dataBuilder = new DataContextBuilder<AppDbContext>();

    dataBuilder.UseSqlServer(builder.Configuration.GetConnectionString("Default"))
        .UseCurrentUserProvider(() =>
        {
            var httpContext = sp.GetService<IHttpContextAccessor>()?.HttpContext;
            return httpContext?.User?.Identity?.Name ?? "anonymous";
        });

    return dataBuilder.Build();
});

builder.Services.AddHttpContextAccessor();

与其他 Sharp 模块集成

与 Sharp.Common 集成(已内置)

Data 模块已经引用了 Common 模块,可以直接使用:

  • PagedResult<T> - 分页结果
  • Result<T> - 错误处理(可选)
  • 各种扩展方法

与 Sharp.Log 集成

using Sharp.Log;

var loggerFactory = SharpLogDefaults.CreateLoggerFactory();

builder.ConfigureDbContext(options =>
{
    options.UseLoggerFactory(loggerFactory);
});

高级功能

访问底层 DbContext

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: 如何使用自定义实体?

// 完全自定义,不使用任何基类
public class MyEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
    public DateTime CreatedAt { get; set; }
}

// 正常使用仓储
var repo = db.Repository<MyEntity>();
await repo.AddAsync(new MyEntity { Name = "Test" });

Q: PagedResult 在哪里定义的?

PagedResult<T> 来自 Sharp.Common 模块(Common.ExpandFun 命名空间),Data 模块复用它以避免重复。

Q: 如何禁用审计或软删除?

builder.UseAudit(false)       // 禁用审计
       .UseSoftDelete(false);  // 禁用软删除

性能建议

  1. 查询时使用 Query(trackChanges: false) 提升性能
  2. 批量操作使用 AddRangeAsync/UpdateRange
  3. 生产环境关闭 EnableSensitiveDataLogging
  4. 使用分页查询避免一次加载大量数据

许可证

MIT License