EasyCore.UnitOfWork 8.3.4

dotnet add package EasyCore.UnitOfWork --version 8.3.4
                    
NuGet\Install-Package EasyCore.UnitOfWork -Version 8.3.4
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="EasyCore.UnitOfWork" Version="8.3.4" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="EasyCore.UnitOfWork" Version="8.3.4" />
                    
Directory.Packages.props
<PackageReference Include="EasyCore.UnitOfWork" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add EasyCore.UnitOfWork --version 8.3.4
                    
#r "nuget: EasyCore.UnitOfWork, 8.3.4"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package EasyCore.UnitOfWork@8.3.4
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=EasyCore.UnitOfWork&version=8.3.4
                    
Install as a Cake Addin
#tool nuget:?package=EasyCore.UnitOfWork&version=8.3.4
                    
Install as a Cake Tool

🏗️ EasyCore.EFCoreRepository

English README | MongoDb English README

📚 概述 Repository 是软件开发中的一个重要概念,尤其在 领域驱动设计(DDD) 🎯 和 数据访问层 中广泛使用。Repository 是一种 抽象数据访问层的设计模式 🏛️,它封装了数据访问逻辑,使上层业务逻辑与底层数据存储解耦。Repository 就像是数据仓库的接口 📦,用于管理实体对象的持久化(增删改查)操作。

🚀 快速开始

1. 📝 Program 注册

public class Program
{
    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);

        builder.Services.AddControllers();
        builder.Services.AddEndpointsApiExplorer();
        builder.Services.AddSwaggerGen();
        builder.Services.EasyCoreDependencie();

        builder.Services.AddDbContext<TestDbContext>();

        // ✨ 使用 EasyCore EFCore Repository
        builder.Services.AddEasyCoreEFCoreRepository();

        var app = builder.Build();

        if (app.Environment.IsDevelopment())
        {
            app.UseSwagger();
            app.UseSwaggerUI();
        }

        app.UseAuthorization();
        app.MapControllers();
        app.Run();
    }
}

2. 🏷️ 实体继承

EasyCore.EFCoreRepository 提供了一个功能丰富的实体基类 EasyCoreEntity,包含:

🔄 并发标记

🗑️ 软删除

🏢 多租户 ID

// 💡 注意:EasyCoreEntity<TKey> 中的泛型类型为表的主键类型
public class TestEntity : EasyCoreEntity<Guid>
{
    public string Name { get; set; }
    public int Age { get; set; }
}

3. 🔧 仓储类继承

EasyCore.EFCoreRepository 提供了完整的仓储抽象和实现:

仓储接口 📜
public interface ITestEntityRepository : IRepository<TestDbContext, TestEntity>, ITransientDependencie
{
    // 💡 ITransientDependencie 为 EasyCore.Dependencie 中的自动注入标记
}
仓储实现 ⚙️
public class TestEntityRepository : EfCoreRepository<TestDbContext, TestEntity>, ITestEntityRepository
{
    public TestEntityRepository(TestDbContext dbContext, IServiceProvider serviceProvider) 
        : base(dbContext, serviceProvider)
    {
    }
}

4.💡 使用仓储

4.1 🎯 基础 CRUD 操作
[Route("api/[controller]")]
[ApiController]
public class RepositoryController : ControllerBase
{
    private readonly ITestEntityRepository _repository;

    public RepositoryController(ITestEntityRepository repository) => _repository = repository;

    // 🔍 查询
    [HttpGet]
    public async Task<TestEntity> Get()
    {
        return await _repository.GetFirstAsync(e => e.Name == "Test");
    }

    // ➕ 新增
    [HttpPost]
    public async Task Post()
    {
        await _repository.InsertAsync(new TestEntity { 
            Name = "Test", 
            Age = 10, 
            Id = Guid.NewGuid() 
        }, true);
    }

    // ✏️ 更新
    [HttpPut]
    public async Task Put()
    {
        var entity = await _repository.GetFirstAsync(e => e.Name == "Test");
        entity.Age = 20;
        await _repository.UpdateAsync(entity, true);
    }

    // 🗑️ 删除(软删除)
    [HttpDelete]
    public async Task Delete()
    {
        var entity = await _repository.GetFirstAsync(e => e.Age == 20);
        entity.IsDeleted = true;
        await _repository.UpdateAsync(entity, true);
    }
}
4.2 📋 完整的 API 列表

EasyCore.EFCoreRepository 提供了丰富的 API 方法:

🔧 过滤器管理
EfCoreRepository<TDbContext, TEntity> AddFilter(Type filterType);

EfCoreRepository<TDbContext, TEntity> RemoveFilter(Type filterType);
➕ 插入操作
Task<TEntity> InsertAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default);

TEntity Insert(TEntity entity, bool autoSave = false);

Task InsertManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default);

void InsertMany(IEnumerable<TEntity> entities, bool autoSave = false);
✏️ 更新操作
Task<TEntity> UpdateAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default);

TEntity Update(TEntity entity, bool autoSave = false);

Task UpdateManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default);

void UpdateMany(IEnumerable<TEntity> entities, bool autoSave = false);
🗑️ 删除操作
Task DeleteAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default);

void Delete(TEntity entity, bool autoSave = false);

Task DeleteManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default);

void DeleteMany(IEnumerable<TEntity> entities, bool autoSave = false);
💾 保存操作
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);

int SaveChanges();
🔍 查询操作
// 获取列表
Task<List<TEntity>> GetListAsync(CancellationToken cancellationToken = default);
List<TEntity> GetList();
Task<List<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default);
List<TEntity> GetList(Expression<Func<TEntity, bool>> predicate);

// 数量统计
Task<long> GetCountAsync(CancellationToken cancellationToken = default);
long GetCount();
Task<long> GetCountAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default);
long GetCount(Expression<Func<TEntity, bool>> predicate);

// 分页查询(可选排序)
Task<List<TEntity>> GetPagedListAsync(int skipCount, int maxResultCount, CancellationToken cancellationToken = default);
List<TEntity> GetPagedList(int skipCount, int maxResultCount);
Task<List<TEntity>> GetPagedListAsync(int skipCount, int maxResultCount, Expression<Func<TEntity, object>> orderBy, bool ascending = true, CancellationToken cancellationToken = default);
List<TEntity> GetPagedList(int skipCount, int maxResultCount, Expression<Func<TEntity, object>> orderBy, bool ascending = true);

// 单实体查询(GetFirstAsync 未找到时抛出异常;GetFirst / GetFirstOrDefaultAsync 返回 null)
Task<TEntity> GetFirstAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default);
Task<TEntity?> GetFirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default);
TEntity? GetFirst(Expression<Func<TEntity, bool>> predicate);
⚡ 直接删除操作
// DeleteDirect* 绕过软删除/租户过滤器,物理删除
Task DeleteDirectAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default);

void DeleteDirect(Expression<Func<TEntity, bool>> predicate);

void DeleteManyDirect(IEnumerable<TEntity> entities, bool autoSave = false);

Task DeleteManyDirectAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default);

5.🎛️ 高级功能

🎯 WhereIf 支持

EasyCore.EFCoreRepository 提供了智能的条件查询支持:

IQueryable<T>.WhereIf(xxx != null, x => x.xxx == xxx)

✨ 特性:只有当 xxx != null 条件满足时,才会执行后面的过滤条件,否则继续执行后续代码。

6. 🔍 数据过滤器

EasyCore.EFCoreRepository 内置了两个实用的数据过滤器:

🗑️ ISoftDeleteFilter - 软删除过滤器

🏢 ITenantFilter - 租户过滤器(有租户值时按 TenantId 过滤;无租户时只查 TenantId 为 null 的数据)

可通过 ITenantProvider 自定义租户来源(默认与 AspNetCore.Mvc ICurrentTenant 对齐:先 HttpContext.Items["TenantId"],再请求头 X-Tenant-Id)。

自定义过滤器示例 🎨:
public class CustomDataFilter : IDataFilter, ITransientDependencie
{
    public IQueryable<TEntity> Apply<TEntity>(IQueryable<TEntity> query) where TEntity : class, IEntity
    {
        query = query.Where(e => ((e as TestEntity)!.Name == "Test"));
        return query;
    }
}
动态过滤器管理 ⚡:
_repository
    .RemoveFilter(typeof(ITenantFilter))      // 🗑️ 移除租户过滤器
    .RemoveFilter(typeof(ISoftDeleteFilter))  // 🗑️ 移除软删除过滤器  
    .AddFilter(typeof(CustomDataFilter))      // ➕ 添加自定义过滤器
    .Delete(e => e.Name == "Test1", true);    // 🎯 执行操作

🔄 EasyCore.UnitOfWork

🎯 工作单元模式

EasyCore.UnitOfWork 提供了 [SaveChanges] 特性,支持 接口 / 类 / 方法 / 动态 API / Controller / 事件处理器

  • 服务 / 事件处理器:Castle DynamicProxy(经 DI 接口代理调用生效)
  • API 层IFilterFactory + 接口特性 Convention(EasyCoreAppService / Controller);Controller 不走 Castle 代理,避免双写

非 MVC 场景请把特性标在实现类/方法上,并通过接口解析服务。本包使用 Castle.Core.AsyncInterceptor。

ControllerBase(含 Dynamic API AppService)走 MVC Filter 路径。

1. 📝 Program 注册

public class Program
{
    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);

        builder.Services.AddControllers();
        builder.Services.AddEndpointsApiExplorer();
        builder.Services.AddSwaggerGen();
        builder.Services.EasyCoreDependencie();
        builder.Services.AddDbContext<TestDbContext>();

        // ✨ 使用 EasyCore EFCore Repository
        builder.Services.AddEasyCoreEFCoreRepository();

        // 🔄 UnitOfWork:Castle DynamicProxy + MVC Filter
        builder.Services.AddEasyCoreUnitOfWork();

        // RegisterSaveChangesFor / 程序集扫描已废弃(织入在编译期完成)

        var app = builder.Build();

        if (app.Environment.IsDevelopment())
        {
            app.UseSwagger();
            app.UseSwaggerUI();
        }

        app.UseAuthorization();
        app.MapControllers();
        app.Run();
    }
}

2. 📜 抽象接口定义

public interface IUnitOfWorkTest : ITransientDependencie
{
    /// <summary>
    /// 🎯 测试实体工作单元
    /// </summary>
    Task<TestEntity> EntityUnitOfWork();

    /// <summary>
    /// 💰 测试事务工作单元  
    /// </summary>
    Task<TestEntity> Transaction();
}

public interface IUnitOfWorkTest2 : ITransientDependencie
{
    /// <summary>
    /// 🎯 测试实体工作单元
    /// </summary>
    Task<TestEntity> EntityUnitOfWork();
}

3. 🏷️ SaveChangesAttribute 特性使用

方法级别使用 🎯:
public class UnitOfWorkTest : IUnitOfWorkTest
{
    private readonly ITestEntityRepository _repository;

    public UnitOfWorkTest(ITestEntityRepository repository) => _repository = repository;

    [SaveChanges(typeof(TestDbContext))]
    public Task<TestEntity> EntityUnitOfWork() 
        => _repository.InsertAsync(new TestEntity { Name = "Test", Age = 10, Id = Guid.NewGuid() });

    [SaveChanges(true, typeof(TestDbContext))]  // 💰 启用事务
    public Task<TestEntity> Transaction() 
        => _repository.InsertAsync(new TestEntity { Name = "Test", Age = 10, Id = Guid.NewGuid() });
}
类级别使用 🏛️:
[SaveChanges(typeof(TestDbContext))]
public class UnitOfWorkTest2 : IUnitOfWorkTest2
{
    private readonly ITestEntityRepository _repository;

    public UnitOfWorkTest2(ITestEntityRepository repository) => _repository = repository;

    public Task<TestEntity> EntityUnitOfWork() 
        => _repository.InsertAsync(new TestEntity { Name = "Test", Age = 10, Id = Guid.NewGuid() });
}
💡 特性参数说明:

第一个参数:是否为数据库事务,为 true 时执行事务保存 💰

第二个参数:指定要保存的数据库 DbContext 对象

🔍 EasyCore.EntityChange

📊 实体变更追踪

EasyCore.EntityChange 提供了强大的实体变更追踪能力!🕵️

1. 📝 Program 注册

// 1) 注册 EntityChange(默认扫描全部 Handler)
builder.Services.AddEasyCoreEntityChange();

// 可选:关掉扫描,只精准注册
// builder.Services.AddEasyCoreEntityChange(enableAssemblyScanning: false)
//     .AddHandler<EntityChange>();

// 2) 在每个需要追踪的 AddDbContext 里显式挂拦截器(多 DbContext 就分别写)
builder.Services.AddDbContext<TestDbContext>((sp, options) =>
{
    options.UseEasyCoreEntityChange(sp);
});
// builder.Services.AddDbContext<OtherDbContext>((sp, options) =>
// {
//     options.UseSqlServer("...");
//     options.UseEasyCoreEntityChange(sp); // 需要才挂
// });

builder.Services.AddEasyCoreEFCoreRepository();

说明:推荐在 AddDbContext((sp, options) => …) 里调用 UseEasyCoreEntityChange(sp),多个 DbContext 可按需分别启用。若漏写,与仓储联用时 AddEasyCoreEFCoreRepository / AddEasyCoreMongoDbRepository 仍会尝试自动挂上。软删除(IsDeleted 从 false→true)按 Deleted 派发。Handler 异常默认向上抛出(可用 Configure(o => o.SuppressHandlerExceptions = true) 改为吞掉)。

2. 🎯 使用实体变更追踪

public class EntityChange : 
    IEntityUpdatedChangeHandler<TestEntity>, 
    IEntityDeletedChangeHandler<TestEntity>, 
    IEntityAddedChangeHandler<TestEntity>
{
    private readonly ILogger<EntityChange> _logger;

    public EntityChange(ILogger<EntityChange> logger) => _logger = logger;

    // ➕ 实体新增处理
    public async Task OnAddedAsync(TestEntity entity)
    {
        _logger.LogInformation($"🆕 实体新增: Id:{entity.Id}; Name:{entity.Name}; Age:{entity.Age};");
        await Task.CompletedTask;
    }

    // 🗑️ 实体删除处理  
    public async Task OnDeletedAsync(TestEntity entity)
    {
        _logger.LogInformation($"🗑️ 实体删除: Id:{entity.Id}; Name:{entity.Name}; Age:{entity.Age};");
        await Task.CompletedTask;
    }

    // ✏️ 实体更新处理
    public Task OnUpdatedAsync(TestEntity oldEntity, TestEntity currentEntity)
    {
        _logger.LogInformation($"✏️ 实体更新: " +
            $"Id:{oldEntity.Id} → {currentEntity.Id}; " +
            $"Name:{oldEntity.Name} → {currentEntity.Name}; " +
            $"Age:{oldEntity.Age} → {currentEntity.Age};");
        return Task.CompletedTask;
    }
}
🎯 支持的变更接口:
IEntityAddedChangeHandler<TEntity> - 实体新增处理器 ➕

IEntityDeletedChangeHandler<TEntity> - 实体删除处理器 🗑️

IEntityUpdatedChangeHandler<TEntity> - 实体更新处理器 ✏️

💎 Custom Entity 和 Custom Data Filter

1. 用户自定义数据库实体🦄

用户可根据自身项目进行自定义用户实体配置

    public class CustomEntity : EasyCoreEntity<Guid>
    {
        public string CreateId{ get; set; }
    }

    public class TestCustomEntity : CustomEntity
    {

    }

CustomEntity为用户自定义实体对象,自定义实体中存在CreateId字段。保存时可用IEntityAddedChangeHandler<TEntity> 自动保存当前用户id。

    public class TestCustomEntityRepository : EfCoreRepository<TestDbContext, TestCustomEntity>,ITestCustomEntityRepository,IEntityAddedChangeHandler<TestCustomEntity>
    {
        public TestCustomEntityRepository(TestDbContext dbContext, IServiceProvider serviceProvider) : base(dbContext, serviceProvider)
        {

        }

        public async Task OnAddedAsync(TestCustomEntity entity)
        {
            if (entity is CustomEntity customEntity)
            {
                customEntity.CreateId = "Test";
            }

            await Task.CompletedTask;
        }
    }

2. 用户自定义数据过滤器🎁

用户可根据自身项目进行自定义数据过滤器配置

    public class TestEntityRepository :
        EfCoreRepository<TestDbContext, TestEntity>,
        ITestEntityRepository
    {
        public TestEntityRepository(TestDbContext dbContext, IServiceProvider serviceProvider) : base(dbContext, serviceProvider)
        {

        }

        /// <summary>
        /// Applies permanent data filters before persisting entities (Insert/Update/Delete).
        /// This method is called during the persistence pipeline to enforce global filters.
        /// </summary>
        /// <param name="dataFilters">The list of data filters that are currently scheduled for execution.</param>
        /// <returns>The updated list of data filters after applying permanent filter rules.</returns>
        public override List<IDataFilter> OnApplyPersistingFilters(List<IDataFilter> dataFilters)
        {
            AddOnce(dataFilters, typeof(CustomDataFilter));

            RemoveIfExistsFilter(dataFilters, typeof(CustomDataFilter));

            return dataFilters;
        }
    }

重写OnApplyPersistingFilters方法,添加或修改永久过滤器设置。

✨ 特性:当实体完成增删改操作时,系统会自动调用对应的接口方法,实现无缝的变更追踪!

🏷️CustomRepository

用户自定义仓储,由于每一个系统的实体字段都不相同。EasyCore不会对用户的实体字段和字段类型进行限制,比如添加人id等信息。类型可以是Guid也可以是long。

CustomEntityRepository 提供了三个重写方法:OnBeforeAdd (在添加前);OnBeforeUpdate (在修改前);OnBeforeDelete (在删除前);

这样我们在增删改的时候不需要手动的添加字段值。CustomRepository自动添加。

  public class CustomEntityRepository<TDbContext, TEntity> : EfCoreRepository<TDbContext, TEntity>
      where TDbContext : DbContext
      where TEntity : class, IEntity
  {
      public CustomEntityRepository(TDbContext dbContext, IServiceProvider serviceProvider) : base(dbContext, serviceProvider)
      {

      }

      /// <summary>
      /// Custom method to set the CreateId property of the entity before adding it to the database.
      /// </summary>
      /// <param name="entity"></param>
      public override void OnBeforeAdd(TEntity entity)
      {
          if (entity is CustomEntity customEntity)
          {
              customEntity.CreateId = "Test";
          }
      }

      /// <summary>
      /// Custom method to set the UpdateId property of the entity before updating it in the database.
      /// </summary>
      /// <param name="entity"></param>
      public override void OnBeforeUpdate(TEntity entity)
      {
          // Do something entity before update
      }

      /// <summary>
      /// Custom method to set the DeleteId property of the entity before deleting it from the database.
      /// </summary>
      /// <param name="entity"></param>
      public override void OnBeforeDelete(TEntity entity)
      {
          // Do something entity before delete
      }
  public class TestCustomEntityRepository : CustomEntityRepository<TestDbContext, TestCustomEntity>, ITestCustomEntityRepository
  {
      public TestCustomEntityRepository(TestDbContext dbContext, IServiceProvider serviceProvider) : base(dbContext, serviceProvider)
      {

      }
  }

把选择权完全交由用户处理。

🎉 总结

EasyCore.EFCoreRepository 系列组件提供了:

🏗️ 完整的仓储模式实现

🔄 智能的工作单元管理

🔍 强大的实体变更追踪

🎯 丰富的查询和过滤功能

⚡ 高性能的数据访问

让您的数据访问层更加 优雅、强大、易维护!✨

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.