ABP vNext 框架功能模块

news/2025/10/24 20:17:46/文章来源:https://www.cnblogs.com/tangge/p/19164164

以下是ABP框架中各核心功能的辅助类及示例说明,涵盖模块化、依赖注入、ORM集成等关键特性:

一. 模块化(Modularity)

核心辅助类

  • AbpModule:所有模块的基类,定义模块生命周期方法。
  • DependsOnAttribute:声明模块依赖关系。
  • ModuleInitializer:模块初始化器(自动生成)。
  • IModuleContainer:模块容器,用于运行时获取所有加载的模块信息。
  • PreConfigureServicesAttribute:在模块ConfigureServices之前执行配置(用于提前注册服务)。

1. AbpModule

示例

[DependsOn(typeof(AbpAspNetCoreModule), typeof(SqlSugarModule))]
// 定义应用模块,继承AbpModule
public class MyApplicationModule : AbpModule
{// 配置服务(注册依赖、设置选项等)public override void ConfigureServices(ServiceConfigurationContext context){// 注册应用服务context.Services.AddScoped<IBookAppService, BookAppService>();// 配置对象映射Configure<AbpAutoMapperOptions>(options =>{options.AddMaps<MyApplicationModule>();});}// 应用初始化(配置中间件、数据库迁移等)public override void OnApplicationInitialization(ApplicationInitializationContext context){var app = context.GetApplicationBuilder();var env = context.GetEnvironment();// 开发环境启用异常页面if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}// 配置路由app.UseRouting();app.UseEndpoints(endpoints =>{endpoints.MapControllers();});}
}

2. DependsOnAttribute

声明当前模块依赖的其他模块,确保依赖模块先于当前模块加载。

示例

// 声明当前模块依赖于EfCore模块和Identity模块
[DependsOn(typeof(AbpEfCoreModule),        // EF Core集成模块typeof(AbpIdentityAspNetCoreModule), // 身份认证模块typeof(MyDomainModule)          // 自定义领域模块
)]
public class MyApplicationModule : AbpModule
{// 模块逻辑...
}

说明:框架会先加载DependsOn中声明的模块,再加载当前模块,避免依赖顺序问题。

3. ModuleInitializer

模块初始化器(自动生成,无需手动编写),用于在应用启动时自动加载所有模块。

背景

ABP 在编译时会扫描所有AbpModule子类,自动生成ModuleInitializer类,其内部会创建模块容器并按依赖顺序初始化模块。

使用场景

开发者无需手动实例化模块,只需在Program.cs中通过AddApplicationAsync触发自动初始化:

var builder = WebApplication.CreateBuilder(args);
// 自动加载模块(内部使用ModuleInitializer)
await builder.AddApplicationAsync<MyWebModule>(); var app = builder.Build();
await app.InitializeApplicationAsync(); // 初始化所有模块
await app.RunAsync();

4. IModuleContainer

模块容器,用于在运行时获取已加载的所有模块信息(如模块类型、依赖关系等)。

示例

public class ModuleInfoService : ITransientDependency
{private readonly IModuleContainer _moduleContainer;public ModuleInfoService(IModuleContainer moduleContainer){_moduleContainer = moduleContainer;}public List<string> GetLoadedModuleNames(){// 获取所有已加载模块的类型名称return _moduleContainer.Modules.Select(m => m.Type.FullName).ToList();}public List<string> GetModuleDependencies(string moduleName){// 获取指定模块的依赖模块名称var module = _moduleContainer.Modules.FirstOrDefault(m => m.Type.Name == moduleName);return module?.Dependencies.Select(d => d.Type.Name).ToList() ?? new List<string>();}
}

用途:可用于调试(查看模块加载情况)或动态根据模块配置功能。

5. PreConfigureServicesAttribute

标记方法在模块ConfigureServices之前执行,用于提前注册服务或修改配置(如覆盖框架默认配置)。

示例

public class MyApplicationModule : AbpModule
{// 标记此方法在ConfigureServices之前执行[PreConfigureServices]public void PreConfigureServices(ServiceConfigurationContext context){// 提前配置JSON序列化(覆盖默认设置)context.Services.Configure<JsonOptions>(options =>{options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;options.JsonSerializerOptions.WriteIndented = true;});// 提前注册一个必须在其他服务之前初始化的组件context.Services.AddSingleton<IMyPreService, MyPreService>();}public override void ConfigureServices(ServiceConfigurationContext context){// 此处的配置会在PreConfigureServices之后执行context.Services.AddScoped<IBookAppService, BookAppService>();}
}

说明:适用于需要在模块正式配置服务前调整基础设置的场景(如序列化、跨域等)。

这些类共同构成了 ABP 的模块化系统,通过明确的依赖管理和生命周期控制,实现了应用的模块化拆分与灵活扩展。

二. 动态API(Dynamic API)

核心辅助类

  • DynamicApiControllerBuilder:动态生成API控制器。
  • RemoteServiceAttribute:标记类/方法为远程服务(自动暴露API)。

在ABP框架中,DynamicApiControllerBuilderRemoteServiceAttribute是实现动态API的核心工具,它们能自动将应用服务(Application Service)暴露为HTTP API接口,无需手动编写控制器代码,极大简化了API开发流程。以下是具体示例和讲解:

1. RemoteServiceAttribute:标记服务为远程服务(自动暴露API)

RemoteServiceAttribute用于标记类或方法为“远程服务”,ABP框架会自动识别这些标记,将其作为API接口暴露给客户端。它可以控制是否暴露服务、定义API前缀等。

示例:标记应用服务为远程服务

using Volo.Abp.Application.Services;
using Volo.Abp.RemoteServices;// 标记整个服务为远程服务(默认暴露所有公共方法)
[RemoteService(Name = "BookStore")] // 可选:指定API名称(影响路由)
public class BookAppService : ApplicationService, IBookAppService
{// 方法会自动暴露为API:GET /api/book-store/book/{id}public async Task<BookDto> GetAsync(Guid id){// 业务逻辑:查询书籍}// 标记此方法不暴露为API[RemoteService(IsEnabled = false)]public async Task InternalMethodAsync(){// 内部方法,不对外提供API}// 自定义HTTP方法和路由(默认按方法名推断,如Create->POST)[HttpPost("custom-create")] // 覆盖默认路由:POST /api/book-store/book/custom-createpublic async Task<BookDto> CreateWithSpecialLogicAsync(CreateBookDto input){// 业务逻辑:创建书籍}
}

讲解:

  • 类级别标记[RemoteService] 加在服务类上,表明该服务的公共方法默认会被暴露为API。Name属性可指定API的前缀(如示例中的BookStore,默认路由会包含该名称)。
  • 方法级别标记[RemoteService(IsEnabled = false)] 可单独禁用某个方法的API暴露,适用于内部调用的方法。
  • 与HTTP特性结合:可通过[HttpGet][HttpPost]等特性自定义HTTP方法和路由,覆盖ABP的默认推断(默认规则:Get->GETCreate->POSTUpdate->PUTDelete->DELETE)。

2. DynamicApiControllerBuilder:手动配置动态API生成

DynamicApiControllerBuilder用于手动配置动态API的生成规则,通常在模块的ConfigureServices方法中使用。它可以批量处理程序集内的服务,自定义API路由、命名空间等。

示例:批量配置程序集中的服务为动态API

using Volo.Abp.Application.Services;
using Volo.Abp.Http.Configuration;public class MyAppModule : AbpModule
{public override void ConfigureServices(ServiceConfigurationContext context){// 批量处理指定程序集中的所有应用服务DynamicApiControllerBuilder.ForAssembly(typeof(MyAppModule).Assembly) // 扫描当前模块所在程序集.AddControllers(options =>{// 自定义API前缀(默认是"api")options.RootPath = "api/v2"; // 过滤需要暴露的服务(仅包含实现了IApplicationService的类)options.TypePredicate = type => typeof(IApplicationService).IsAssignableFrom(type);});// 单独配置某个服务(更精细的控制)DynamicApiControllerBuilder.For<IBookAppService>("book-store/custom-books") // 指定API路由前缀:/api/book-store/custom-books.Build(); // 生成控制器}
}

讲解:

  • 批量配置ForAssembly方法扫描整个程序集,自动将实现了IApplicationService(应用服务接口)的类暴露为API。AddControllers中的选项可全局设置路由前缀(如api/v2)、过滤服务类型等。
  • 单独配置For<TService>方法针对单个服务接口进行配置,通过参数指定路由前缀(如book-store/custom-books),生成的API路由会变为/api/book-store/custom-books/[方法名]
  • 默认路由规则:若不手动指定,ABP会按“服务名称+方法名”生成路由。例如BookAppServiceGetAsync方法,默认路由为/api/app/book/getapp是默认前缀,book是服务名缩写)。

3. 动态API的核心优势

  1. 零控制器代码:无需手动编写Controller类,框架自动生成,减少重复工作。
  2. 自动路由生成:按服务名和方法名自动推断路由,也支持自定义。
  3. 与依赖注入集成:动态生成的控制器会自动注入应用服务,无需手动处理依赖。
  4. Swagger自动集成:生成的API会自动显示在Swagger文档中,便于调试。

4. 总结

  • RemoteServiceAttribute是“标记型”配置,通过特性快速指定服务或方法是否暴露为API,适合简单场景。
  • DynamicApiControllerBuilder是“编程式”配置,适合批量处理或需要自定义路由、前缀的复杂场景。

两者结合使用,可高效实现API的自动生成,让开发者专注于业务逻辑而非API配置。

三. 依赖注入(Dependency Injection)

核心辅助类

  • IServiceCollection:扩展方法(如AddTransientAddScoped)。

  • DependencyAttribute:标记注入生命周期(Transient/Scoped/Singleton)。

  • IIocResolver:手动解析服务。

  • ITransientDependency/IScopedDependency/ISingletonDependency:标记接口,自动注册对应生命周期的服务(无需手动在模块中配置)。

在ABP框架的依赖注入(DI)系统中,这些类和接口用于管理服务的注册与解析,核心目标是简化依赖注入的配置,确保服务按预期的生命周期(瞬时、作用域、单例)工作。以下是具体示例和讲解:

1. IServiceCollection:服务注册的核心接口(配合扩展方法)

IServiceCollection是.NET依赖注入的标准接口,ABP通过扩展方法(如AddTransientAddScopedAddSingleton)简化服务注册,定义服务的类型和生命周期。

示例:手动注册服务

public class MyModule : AbpModule
{public override void ConfigureServices(ServiceConfigurationContext context){// 获取IServiceCollection实例var services = context.Services;// 1. 注册瞬时服务(每次请求创建新实例)services.AddTransient<IBookService, BookService>();// 2. 注册作用域服务(同一请求内共享实例)services.AddScoped<IAuthorRepository, AuthorRepository>();// 3. 注册单例服务(整个应用生命周期共享一个实例)services.AddSingleton<ILoggerProvider, CustomLoggerProvider>();// 4. 注册泛型服务(如仓储)services.AddScoped(typeof(IRepository<,>), typeof(EfCoreRepository<,>));}
}

讲解:

  • IServiceCollection是服务注册的“容器”,所有服务需通过它注册后才能被依赖注入系统识别。
  • 生命周期说明:
    • Transient:适合轻量级、无状态服务(如工具类),每次注入或解析时创建新实例。
    • Scoped:适合数据库上下文、仓储等,在一次HTTP请求内复用实例(避免频繁创建连接)。
    • Singleton:适合全局配置、缓存等,整个应用生命周期只创建一次实例。
  • ABP在模块的ConfigureServices方法中提供ServiceConfigurationContext,通过它可直接访问IServiceCollection

2. DependencyAttribute:通过特性标记服务生命周期

DependencyAttribute是ABP提供的特性,可直接标记在类上,指定服务的生命周期,无需手动在IServiceCollection中注册(框架会自动扫描并注册)。

示例:用特性标记服务生命周期

using Volo.Abp.DependencyInjection;// 标记为瞬时服务(等价于AddTransient)
[Dependency(ServiceLifetime.Transient)]
public class BookService : IBookService
{// 实现...
}// 标记为作用域服务,且允许被替换(便于测试时Mock)
[Dependency(ServiceLifetime.Scoped, ReplaceServices = true)]
public class AuthorRepository : IAuthorRepository
{// 实现...
}// 标记为单例服务,且自动注册其接口
[Dependency(ServiceLifetime.Singleton, ImplementInterfaces = true)]
public class GlobalConfigService : IGlobalConfigService
{// 实现...
}

讲解:

  • 自动注册:标记[Dependency]后,ABP会在模块初始化时自动扫描并注册该类,无需在IServiceCollection中手动调用AddXXX
  • 参数说明
    • ServiceLifetime:指定生命周期(Transient/Scoped/Singleton)。
    • ReplaceServices:是否替换已注册的同类型服务(默认false,设为true可覆盖框架默认实现)。
    • ImplementInterfaces:是否自动注册类实现的所有接口(默认false,设为true后,注入接口时会返回该类实例)。

3. IIocResolver:手动解析服务(非构造函数注入场景)

IIocResolver是ABP提供的服务解析器,用于在无法通过构造函数注入的场景(如静态方法、动态代码)中手动获取服务实例,同时确保服务正确释放。

示例:手动解析服务

using Volo.Abp.DependencyInjection;public class BookManager : ITransientDependency
{private readonly IIocResolver _iocResolver;// 构造函数注入IIocResolverpublic BookManager(IIocResolver iocResolver){_iocResolver = iocResolver;}public void ProcessBook(Guid bookId){// 1. 解析瞬时服务(使用后需手动释放)using (var bookService = _iocResolver.ResolveAsDisposable<IBookService>()){var book = bookService.Object.GetById(bookId);// 处理书籍...}// 2. 解析作用域服务(在当前作用域内复用)var authorRepo = _iocResolver.Resolve<IAuthorRepository>();var author = authorRepo.GetByBookId(bookId);// 3. 解析单例服务(全局唯一实例,无需释放)var configService = _iocResolver.Resolve<IGlobalConfigService>();var maxBookCount = configService.GetMaxBookCount();}
}

讲解:

  • ResolveAsDisposable:用于解析TransientScoped服务,返回Disposable对象,通过using语句确保服务使用后自动释放(避免内存泄漏)。
  • Resolve:直接解析服务,适用于单例服务(无需释放)或明确知道服务生命周期的场景(需手动管理释放)。
  • 注意:尽量优先使用构造函数注入,仅在必要时使用IIocResolver(如动态生成的代码、静态方法)。

4. ITransientDependency/IScopedDependency/ISingletonDependency:标记接口自动注册

这三个接口是ABP提供的“标记接口”,无需指定特性或手动注册,只需让服务类实现对应接口,框架会自动按接口类型注册服务生命周期。

示例:通过接口标记生命周期

using Volo.Abp.DependencyInjection;// 实现ITransientDependency:自动注册为瞬时服务
public class BookService : IBookService, ITransientDependency
{// 实现...
}// 实现IScopedDependency:自动注册为作用域服务
public class AuthorRepository : IAuthorRepository, IScopedDependency
{// 实现...
}// 实现ISingletonDependency:自动注册为单例服务
public class GlobalConfigService : IGlobalConfigService, ISingletonDependency
{// 实现...
}

讲解:

  • 零配置注册:只需实现接口,无需其他代码,ABP会在模块扫描时自动注册,大幅简化配置。
  • 接口作用:这三个接口本身无任何方法,仅作为“标记”告知框架服务的生命周期,等价于:
    • ITransientDependency[Dependency(ServiceLifetime.Transient)]
    • IScopedDependency[Dependency(ServiceLifetime.Scoped)]
    • ISingletonDependency[Dependency(ServiceLifetime.Singleton)]
  • 适用场景:适合大多数常规服务,推荐优先使用(比DependencyAttribute更简洁)。

5. 总结:服务注册方式的选择

  1. 简单场景:优先使用ITransientDependency等标记接口(最简洁,零配置)。
  2. 需要自定义:使用DependencyAttribute(支持替换服务、注册接口等高级配置)。
  3. 批量或泛型服务:使用IServiceCollection的扩展方法(如泛型仓储、第三方服务)。
  4. 手动解析:通过IIocResolver(仅在构造函数注入不可行时使用)。

这些工具共同构成了ABP灵活的依赖注入系统,既简化了常规配置,又支持复杂场景的自定义需求。

四. 属性注入(Property Injection)

核心辅助类

  • [Inject]:标记属性为注入点(需配合IIocResolver或框架自动注入)。

在ABP框架中,[Inject]特性用于标记类的属性作为属性注入点,允许依赖注入系统自动为该属性赋值,而无需通过构造函数传递依赖。这在某些场景(如基类、第三方库类)中非常实用,因为这些类可能无法通过构造函数注入依赖。

[Inject]特性的使用示例

示例1:基础属性注入(框架自动注入)

using Volo.Abp.DependencyInjection;// 标记类为依赖注入服务(需配合生命周期接口或特性)
public class BookService : ITransientDependency
{// 用[Inject]标记属性,框架会自动注入ILogger实例[Inject]public ILogger<BookService> Logger { get; set; }public void DoWork(){// 使用注入的LoggerLogger.LogInformation("BookService is working...");}
}

示例2:在基类中使用属性注入

// 基类(无法通过构造函数注入,因为子类可能有不同构造函数)
public abstract class BaseService
{// 基类需要的依赖通过属性注入[Inject]protected IStringLocalizer<MyAppResource> Localizer { get; set; }public string GetLocalizedMessage(string key){return Localizer[key];}
}// 子类继承基类,自动获得Localizer的注入
public class BookService : BaseService, ITransientDependency
{public void ShowMessage(){// 直接使用基类中注入的Localizervar message = GetLocalizedMessage("BookCreated");Console.WriteLine(message);}
}

示例3:配合IIocResolver手动触发注入

如果类未被依赖注入系统管理(如手动new创建的实例),可通过IIocResolver手动触发属性注入:

public class MyNonManagedClass
{[Inject]public IBookService BookService { get; set; }public void DoSomething(){// 如果未触发注入,BookService可能为nullBookService?.CreateBook(new BookDto());}
}// 在其他服务中手动创建实例并触发注入
public class TestService : ITransientDependency
{private readonly IIocResolver _iocResolver;public TestService(IIocResolver iocResolver){_iocResolver = iocResolver;}public void Test(){// 手动创建未被DI管理的实例var myClass = new MyNonManagedClass();// 通过IIocResolver触发属性注入_iocResolver.InjectProperties(myClass);// 此时BookService已被注入,可安全使用myClass.DoSomething();}
}

核心讲解

  1. 适用场景

    • 基类共享依赖:当多个子类继承同一个基类,且基类需要依赖时,属性注入可避免每个子类在构造函数中重复传递依赖。
    • 无法修改构造函数的类:如第三方库中的类、自动生成的代码,无法添加构造函数参数,此时属性注入是唯一选择。
    • 可选依赖:某些依赖不是必须的(允许为null),属性注入比构造函数注入更灵活(构造函数参数通常是必须的)。
  2. 与构造函数注入的区别

    • 构造函数注入:依赖在对象创建时必须提供,确保对象实例化后即可正常使用(无null风险),是ABP推荐的主要注入方式。
    • 属性注入:依赖在对象创建后由框架自动赋值(或手动触发),存在未注入时为null的风险,适合非核心依赖。
  3. 注意事项

    • 必须配合依赖注入容器:只有被ABP依赖注入系统管理的类(如实现了ITransientDependency的类),框架才会自动执行属性注入。手动new的实例需通过IIocResolver.InjectProperties()手动触发。
    • 避免过度使用:属性注入可能导致依赖关系不明显(不如构造函数参数直观),建议优先使用构造函数注入,仅在必要时使用属性注入。
    • 可空检查:使用属性注入的依赖可能为null,需在代码中添加空值检查(如BookService?.CreateBook(...)),或通过[Required]特性要求必须注入(框架会在注入失败时抛异常)。

总结

[Inject]特性是ABP依赖注入系统的补充,用于解决构造函数注入不适用的场景。它通过标记属性为注入点,让框架或开发者手动为其赋值,平衡了灵活性和易用性。但需注意其潜在的null风险,合理选择注入方式。

五. 当前用户(Current User)

核心辅助类

  • ICurrentUser:获取当前登录用户信息(ID、用户名、角色等)。
  • CurrentUser:静态快捷访问(需在请求上下文内)。

在ABP框架中,ICurrentUserCurrentUser用于获取当前登录用户的信息(如ID、用户名、角色、权限等),是处理用户上下文的核心工具。它们在业务逻辑中频繁用于权限验证、数据隔离(如多租户场景)、操作审计等场景。

1. ICurrentUser:接口形式的当前用户访问器

ICurrentUser是一个接口,定义了获取当前用户信息的方法和属性,需通过依赖注入使用。它提供了灵活的用户信息访问能力,支持在各种服务中获取当前登录用户的上下文。

示例:通过ICurrentUser获取用户信息

using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Identity;public class BookAppService : ApplicationService
{private readonly ICurrentUser _currentUser;private readonly IRepository<Book, Guid> _bookRepository;// 构造函数注入ICurrentUserpublic BookAppService(ICurrentUser currentUser, IRepository<Book, Guid> bookRepository){_currentUser = currentUser;_bookRepository = bookRepository;}public async Task<List<BookDto>> GetMyBooksAsync(){// 检查用户是否已登录if (!_currentUser.IsAuthenticated){throw new UserFriendlyException("请先登录!");}// 获取当前用户ID(Guid类型,多租户场景下自动关联租户)var userId = _currentUser.Id;// 获取当前用户名var userName = _currentUser.UserName;// 检查用户是否属于某个角色(如"Admin")var isAdmin = _currentUser.IsInRole("Admin");// 查询当前用户创建的书籍(数据隔离)var myBooks = await _bookRepository.GetListAsync(book => book.CreatorId == userId);return ObjectMapper.Map<List<Book>, List<BookDto>>(myBooks);}
}

讲解:

  • 核心属性
    • IsAuthenticated:是否已登录(true表示用户已认证)。
    • Id:当前用户的唯一标识(Guid?类型,未登录时为null)。
    • UserName:用户名(未登录时为null)。
    • Email/PhoneNumber:用户的邮箱、手机号等基础信息。
    • Roles:用户所属的角色集合(IReadOnlyList<string>)。
    • TenantId:多租户场景下的当前租户ID(与用户关联)。
  • 核心方法
    • IsInRole(string roleName):检查用户是否属于指定角色。
    • FindClaim(string claimType):获取用户的指定声明(如自定义权限声明)。
  • 依赖注入ICurrentUser需通过构造函数注入使用,框架会自动提供当前请求的用户上下文(HTTP请求、后台任务等场景均支持)。

2. CurrentUser:静态快捷访问器

CurrentUserICurrentUser的静态封装,提供更简洁的语法(无需注入),但仅能在请求上下文内使用(如HTTP请求处理过程中)。

示例:通过CurrentUser静态类获取用户信息

public class BookController : AbpController
{private readonly IRepository<Book, Guid> _bookRepository;public BookController(IRepository<Book, Guid> bookRepository){_bookRepository = bookRepository;}[HttpPost]public async Task<IActionResult> Create(CreateBookDto input){// 静态访问当前用户ID(等价于注入ICurrentUser后访问其Id)var creatorId = CurrentUser.Id;// 静态检查是否为管理员if (!CurrentUser.IsInRole("Admin") && input.Price > 1000){return BadRequest("普通用户无法创建价格超过1000的书籍!");}var book = new Book{Name = input.Name,Price = input.Price,CreatorId = creatorId // 记录创建人};await _bookRepository.InsertAsync(book);return Ok(book.Id);}
}

讲解:

  • 语法简化CurrentUser是静态类,无需在构造函数中注入,直接通过CurrentUser.IdCurrentUser.UserName等访问,代码更简洁。
  • 使用限制
    • 仅能在有用户上下文的场景中使用(如HTTP请求、已认证的后台作业)。
    • 非请求上下文(如应用启动时、无用户的后台任务)中使用会抛异常(CurrentUser内部会检查上下文是否存在)。
  • 本质CurrentUser内部通过IHttpContextAccessorICurrentUser获取用户信息,是对ICurrentUser的静态代理,功能完全一致。

关键区别与使用建议

特性 ICurrentUser(接口) CurrentUser(静态类)
使用方式 需通过依赖注入(构造函数参数) 直接静态访问(CurrentUser.XXX
适用场景 所有场景(包括无HTTP上下文的后台任务) 仅请求上下文内(如Controller、ApplicationService)
灵活性 高(支持单元测试时Mock) 低(静态方法难以Mock,测试复杂度高)
代码可读性 依赖关系明确(构造函数可见) 依赖关系隐藏(静态调用不直观)

最佳实践

  1. 优先使用ICurrentUser:尤其是在服务层(ApplicationService)、领域层,通过依赖注入使用,便于单元测试(可通过Mock<ICurrentUser>模拟用户上下文)。
  2. 有限使用CurrentUser:仅在Controller等请求上下文明确的场景中使用,简化代码(如快速获取用户ID用于日志记录)。
  3. 多租户场景注意ICurrentUser.TenantId可直接获取当前租户ID,结合多租户过滤器实现数据隔离(如查询时自动过滤当前租户的数据)。
  4. 未登录处理:使用前需检查IsAuthenticated,避免未登录时访问Id等属性导致NullReferenceException

通过ICurrentUserCurrentUser,ABP框架为开发者提供了统一的用户上下文访问方式,无需关心底层认证机制(如JWT、Cookie),大幅简化了与用户相关的业务逻辑实现。

六. SqlSugar ORM 集成

核心辅助类

  • SqlSugarModule:SqlSugar集成模块(需手动引入社区包)。
  • ISqlSugarClient:SqlSugar核心客户端。
  • SqlSugarRepository<T>:基于SqlSugar的仓储实现。

你关注到了ABP与SqlSugar ORM的集成细节,这个组合能兼顾ABP的模块化架构和SqlSugar的高效查询能力,非常实用。下面通过具体示例和讲解,带你理解这三个核心组件的用法:

1. SqlSugarModule:SqlSugar的集成入口(模块配置)

SqlSugarModule是ABP框架中集成SqlSugar的核心模块,负责初始化SqlSugar客户端、注册相关服务。使用前需先引入社区包(如Abp.SqlSugarSqlSugarCore),再在自定义模块中配置依赖和连接信息。

示例:配置SqlSugarModule

// 1. 先引入必要包(NuGet安装)
// - SqlSugarCore(SqlSugar核心包)
// - Abp.SqlSugar(ABP与SqlSugar的集成包,社区维护)using Volo.Abp.Modularity;
using SqlSugar;[DependsOn(typeof(AbpSqlSugarModule) // 依赖SqlSugar集成模块
)]
public class MyAppModule : AbpModule
{public override void ConfigureServices(ServiceConfigurationContext context){// 2. 配置SqlSugar客户端context.Services.AddSqlSugarClient(config =>{// 配置数据库连接字符串config.ConnectionString = "Server=.;Database=MyAppDb;Uid=sa;Pwd=123456;";// 配置数据库类型(SqlServer/MySql/PostgreSQL等)config.DbType = DbType.SqlServer;// 可选:配置全局查询过滤(如多租户隔离、软删除)config.GlobalFilter.Add("SoftDelete", it => it.GetType().GetProperty("IsDeleted") != null, it => (bool)it.GetValue("IsDeleted") == false);// 可选:开启日志(便于调试SQL)config.Aop.OnLogExecuting = (sql, pars) =>{Console.WriteLine($"SQL: {sql}");};});}
}

讲解:

  • 依赖声明:必须在自定义模块上添加[DependsOn(typeof(AbpSqlSugarModule))],确保SqlSugar的基础服务被注册。
  • 核心配置:通过AddSqlSugarClient配置数据库连接、数据库类型,这是SqlSugar工作的基础。
  • 高级配置:可通过GlobalFilter添加全局过滤(如软删除逻辑)、通过Aop配置SQL日志或事务拦截,减少重复代码。

2. ISqlSugarClient:SqlSugar的核心操作客户端

ISqlSugarClient是SqlSugar的核心接口,封装了所有数据库操作(查询、插入、更新、删除、事务等),通过ABP的依赖注入可直接在服务中使用,无需手动创建实例。

示例:使用ISqlSugarClient操作数据库

using Volo.Abp.DependencyInjection;
using SqlSugar;
using System.Linq.Expressions;public class BookService : ITransientDependency
{private readonly ISqlSugarClient _db;// 构造函数注入ISqlSugarClient(由SqlSugarModule自动注册)public BookService(ISqlSugarClient db){_db = db;}// 1. 查询(单条数据)public async Task<Book> GetBookByIdAsync(Guid id){// 链式查询:Where条件 -> 取单条return await _db.Queryable<Book>().Where(book => book.Id == id).FirstAsync();}// 2. 分页查询public async Task<PageResult<BookDto>> GetBooksByPageAsync(int pageIndex, int pageSize){// 分页查询:Skip(跳过条数)-> Take(取条数)-> 总计数var totalCount = await _db.Queryable<Book>().CountAsync();var books = await _db.Queryable<Book>().OrderBy(book => book.CreationTime, OrderByType.Desc).Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync();// 转换为DTO并返回分页结果return new PageResult<BookDto>{TotalCount = totalCount,Items = ObjectMapper.Map<List<Book>, List<BookDto>>(books)};}// 3. 事务操作(插入+更新)public async Task CreateBookWithAuthorAsync(Book book, Author author){// 开启事务using (var tran = _db.Ado.BeginTran()){try{// 插入作者await _db.Insertable(author).ExecuteReturnSnowflakeIdAsync();// 关联作者ID并插入书籍book.AuthorId = author.Id;await _db.Insertable(book).ExecuteAsync();// 提交事务tran.Commit();}catch (Exception){// 回滚事务tran.Rollback();throw;}}}
}

讲解:

  • 核心能力ISqlSugarClient提供链式查询(Queryable)、插入(Insertable)、更新(Updateable)、删除(Deleteable)等API,语法简洁且支持Lambda表达式。
  • 事务支持:通过_db.Ado.BeginTran()开启事务,配合Commit()/Rollback()实现事务一致性,也可结合ABP的[UnitOfWork]特性自动管理事务。
  • 灵活性:支持原生SQL(_db.Ado.SqlQuery)、存储过程调用,适合复杂查询场景,同时保留ORM的类型安全特性。

3. SqlSugarRepository<T>:基于SqlSugar的仓储实现

SqlSugarRepository<T>是ABP风格的仓储类,封装了ISqlSugarClient的基础CRUD操作,提供更符合ABP开发习惯的仓储接口(如GetAsyncInsertAsync),同时支持扩展自定义查询。

示例:自定义仓储继承SqlSugarRepository<T>

using Volo.Abp.Domain.Repositories;
using SqlSugar;// 1. 定义实体(Book)
public class Book : Entity<Guid>
{public string Name { get; set; }public decimal Price { get; set; }public Guid? AuthorId { get; set; }public DateTime CreationTime { get; set; } = DateTime.Now;
}// 2. 定义仓储接口(继承IRepository,符合ABP规范)
public interface IBookRepository : IRepository<Book, Guid>
{// 扩展自定义方法:查询作者的所有书籍Task<List<Book>> GetBooksByAuthorIdAsync(Guid authorId);
}// 3. 实现仓储(继承SqlSugarRepository,复用基础CRUD)
public class BookRepository : SqlSugarRepository<Book, Guid>, IBookRepository
{// 构造函数注入ISqlSugarClient(父类已封装,可直接使用)public BookRepository(ISqlSugarClient sqlSugarClient) : base(sqlSugarClient){}// 实现自定义查询方法public async Task<List<Book>> GetBooksByAuthorIdAsync(Guid authorId){// 可直接使用父类的DbClient(即ISqlSugarClient)进行查询return await DbClient.Queryable<Book>().Where(book => book.AuthorId == authorId).OrderBy(book => book.CreationTime).ToListAsync();}
}// 4. 在服务中使用自定义仓储
public class BookAppService : ApplicationService
{private readonly IBookRepository _bookRepository;public BookAppService(IBookRepository bookRepository){_bookRepository = bookRepository;}public async Task<List<BookDto>> GetByAuthorAsync(Guid authorId){// 调用仓储的自定义方法var books = await _bookRepository.GetBooksByAuthorIdAsync(authorId);return ObjectMapper.Map<List<Book>, List<BookDto>>(books);}public async Task DeleteAsync(Guid id){// 调用父类封装的基础方法(DeleteAsync)await _bookRepository.DeleteAsync(id);}
}

讲解:

  • ABP规范兼容SqlSugarRepository<T>实现了ABP的IRepository接口,可无缝替换ABP默认的EF Core仓储,无需修改服务层代码。
  • 基础CRUD复用:父类已封装GetAsyncInsertAsyncUpdateAsyncDeleteAsync等基础方法,无需重复编写。
  • 自定义扩展:通过继承SqlSugarRepository<T>,可在子类中扩展复杂查询(如多表关联、聚合查询),同时保留SqlSugar的高效语法。

核心总结与优势

  1. 模块化集成SqlSugarModule让SqlSugar融入ABP的模块生命周期,配置集中且可复用。
  2. 高效操作ISqlSugarClient提供比EF Core更简洁的查询语法,性能更优(尤其复杂查询场景)。
  3. 仓储适配SqlSugarRepository<T>桥接SqlSugar与ABP的仓储模式,兼顾ORM灵活性和架构规范性。

要不要我帮你整理一份ABP+SqlSugar的基础项目模板代码?包含模块配置、仓储实现、服务调用的完整流程,你可以直接复用。

七. 仓储(Repository)

核心辅助类

  • IRepository<TEntity, TKey>:通用仓储接口(CRUD操作)。
  • EfCoreRepository<TEntity, TKey>:EF Core实现(默认)。
  • SqlSugarRepository<TEntity, TKey>:SqlSugar实现。
  • IReadOnlyRepository<TEntity, TKey>:只读仓储接口(仅包含查询方法,适用于不需要修改数据的场景)。
  • EfCoreRepositoryBase<TDbContext, TEntity, TKey>:EF Core 仓储基类,可自定义数据库上下文。

在ABP框架中,仓储(Repository)是数据访问层的核心抽象,用于隔离业务逻辑与数据访问细节。以下是仓储相关接口和实现类的具体示例及讲解,涵盖EF Core和SqlSugar两种ORM场景:

1. IRepository<TEntity, TKey>:通用仓储接口(核心CRUD)

IRepository<TEntity, TKey>是ABP定义的通用仓储接口,封装了实体的完整CRUD操作(查询、新增、更新、删除),是所有仓储实现的基础接口,不依赖具体ORM。

示例:使用IRepository接口

using Volo.Abp.Domain.Repositories;
using Volo.Abp.Application.Services;// 实体(领域层)
public class Product : Entity<Guid>
{public string Name { get; set; }public decimal Price { get; set; }
}// 应用服务(依赖IRepository)
public class ProductAppService : ApplicationService
{// 注入通用仓储接口(不关心底层ORM)private readonly IRepository<Product, Guid> _productRepository;public ProductAppService(IRepository<Product, Guid> productRepository){_productRepository = productRepository;}// 新增public async Task<Product> CreateAsync(Product input){return await _productRepository.InsertAsync(input);}// 按条件查询public async Task<List<Product>> GetCheaperProductsAsync(decimal maxPrice){return await _productRepository.GetListAsync(p => p.Price <= maxPrice);}// 分页查询public async Task<long> GetTotalCountAsync(){return await _productRepository.CountAsync();}
}

讲解:

  • 核心方法:包含InsertAsync(新增)、GetAsync(单查)、GetListAsync(列表查询)、UpdateAsync(更新)、DeleteAsync(删除)、CountAsync(计数)等,覆盖所有基础数据操作。
  • ORM无关性:接口定义与具体ORM(EF Core/SqlSugar)无关,业务层无需修改代码即可切换ORM。
  • 依赖注入:ABP会自动根据项目配置的ORM注册对应的实现类(如EF Core项目注册EfCoreRepository),业务层直接注入IRepository即可使用。

2. EfCoreRepository<TEntity, TKey>:EF Core默认实现

EfCoreRepository<TEntity, TKey>是ABP为EF Core提供的IRepository默认实现,内部封装了DbContextDbSet操作,适用于EF Core作为ORM的项目。

示例:使用EF Core仓储

using Volo.Abp.EntityFrameworkCore.Repositories;
using Microsoft.EntityFrameworkCore;// 1. 定义EF Core数据库上下文
public class MyAppDbContext : AbpDbContext<MyAppDbContext>
{// 注册实体DbSetpublic DbSet<Product> Products { get; set; }public MyAppDbContext(DbContextOptions<MyAppDbContext> options) : base(options){}
}// 2. 自定义EF Core仓储(继承EfCoreRepository)
public class ProductEfCoreRepository : EfCoreRepository<MyAppDbContext, Product, Guid>, IProductRepository
{// 注入数据库上下文(父类已处理,可直接使用)public ProductEfCoreRepository(IDbContextProvider<MyAppDbContext> dbContextProvider) : base(dbContextProvider){}// 扩展自定义查询(使用EF Core的Include关联查询)public async Task<Product> GetWithCategoryAsync(Guid id){// 访问EF Core的DbSet,使用Include关联查询return await DbSet.Include(p => p.Category) // 关联查询分类.FirstOrDefaultAsync(p => p.Id == id);}
}// 3. 仓储接口(继承IRepository)
public interface IProductRepository : IRepository<Product, Guid>
{Task<Product> GetWithCategoryAsync(Guid id); // 自定义方法
}

讲解:

  • 基础实现:继承EfCoreRepository后,自动获得IRepository的所有方法实现,无需重复编写。
  • EF Core特性:可直接访问DbSet(仓储内的DbSet属性)和DbContextawait GetDbContextAsync()),使用EF Core的关联查询(Include)、跟踪机制等特性。
  • 数据库上下文绑定:通过泛型参数MyAppDbContext指定当前仓储使用的数据库上下文,支持多上下文场景。

3. SqlSugarRepository<TEntity, TKey>:SqlSugar实现

SqlSugarRepository<TEntity, TKey>是社区为SqlSugar ORM提供的仓储实现,适配ABP的IRepository接口,保留SqlSugar的链式查询优势。

示例:使用SqlSugar仓储

using SqlSugar;
using Volo.Abp.Domain.Repositories;// 1. 自定义SqlSugar仓储(继承SqlSugarRepository)
public class ProductSqlSugarRepository : SqlSugarRepository<Product, Guid>, IProductRepository
{// 注入SqlSugar客户端(父类已封装)public ProductSqlSugarRepository(ISqlSugarClient sqlSugarClient) : base(sqlSugarClient){}// 扩展自定义查询(使用SqlSugar的链式查询)public async Task<Product> GetWithCategoryAsync(Guid id){// 使用SqlSugar的Queryable和Include语法return await DbClient.Queryable<Product>().Include(p => p.Category) // 关联查询.Where(p => p.Id == id).FirstAsync();}
}// 2. 复用IProductRepository接口(与EF Core版本一致)
public interface IProductRepository : IRepository<Product, Guid>
{Task<Product> GetWithCategoryAsync(Guid id);
}

讲解:

  • SqlSugar特性:内部通过ISqlSugarClient实现操作,支持SqlSugar的链式查询(Queryable)、Lambda条件、高性能批量操作等。
  • 接口兼容:与EF Core仓储实现同一接口(IProductRepository),业务层切换ORM时无需修改服务代码,只需替换仓储实现。

4. IReadOnlyRepository<TEntity, TKey>:只读仓储接口

IReadOnlyRepository<TEntity, TKey>IRepository的子集,仅包含查询相关方法(无Insert/Update/Delete),适用于只需要读取数据的场景(如报表服务、查询服务)。

示例:使用只读仓储

using Volo.Abp.Domain.Repositories;// 只读服务(仅需查询数据)
public class ProductReportAppService : ApplicationService
{// 注入只读仓储(限制写入操作)private readonly IReadOnlyRepository<Product, Guid> _productRepository;public ProductReportAppService(IReadOnlyRepository<Product, Guid> productRepository){_productRepository = productRepository;}// 生成价格统计报表(仅查询)public async Task<PriceReportDto> GeneratePriceReportAsync(){var totalCount = await _productRepository.CountAsync();var avgPrice = await _productRepository.AverageAsync(p => p.Price);var maxPrice = await _productRepository.MaxAsync(p => p.Price);return new PriceReportDto{TotalCount = totalCount,AveragePrice = avgPrice,MaxPrice = maxPrice};}
}

讲解:

  • 方法限制:仅包含GetAsyncGetListAsyncCountAsyncAverageAsync等查询方法,无任何写入操作,从接口层面避免误修改数据。
  • 适用场景:报表服务、数据导出、查询接口等纯读取场景,增强代码安全性和可读性。

5. EfCoreRepositoryBase<TDbContext, TEntity, TKey>:EF Core自定义上下文基类

EfCoreRepositoryBase是EF Core仓储的底层基类,允许直接指定数据库上下文类型,适用于多数据库上下文场景(如一个项目连接多个数据库)。

示例:多上下文场景下使用EfCoreRepositoryBase

using Volo.Abp.EntityFrameworkCore.Repositories;
using Microsoft.EntityFrameworkCore;// 1. 定义两个数据库上下文(如主库和日志库)
public class MainDbContext : AbpDbContext<MainDbContext>
{public DbSet<Product> Products { get; set; } // 主库实体// 构造函数...
}public class LogDbContext : AbpDbContext<LogDbContext>
{public DbSet<OperationLog> OperationLogs { get; set; } // 日志实体// 构造函数...
}// 2. 主库仓储(绑定MainDbContext)
public class ProductRepository : EfCoreRepositoryBase<MainDbContext, Product, Guid>, IProductRepository
{public ProductRepository(IDbContextProvider<MainDbContext> dbContextProvider) : base(dbContextProvider){}
}// 3. 日志仓储(绑定LogDbContext)
public class OperationLogRepository : EfCoreRepositoryBase<LogDbContext, OperationLog, Guid>, IOperationLogRepository
{public OperationLogRepository(IDbContextProvider<LogDbContext> dbContextProvider) : base(dbContextProvider){}
}

讲解:

  • 多上下文支持:通过泛型参数TDbContext明确指定仓储绑定的数据库上下文,解决多库场景下的实体隔离问题。
  • 底层控制:继承自EfCoreRepositoryBase可直接访问EF Core的DbContextDbSet,适合需要深度定制EF Core操作的场景。

总结:仓储组件的选择与设计

组件 核心作用 适用场景
IRepository<TEntity, TKey> 定义通用CRUD接口 业务层依赖注入,不关心ORM实现
EfCoreRepository<TEntity, TKey> EF Core的默认实现 EF Core项目,单数据库上下文
SqlSugarRepository<TEntity, TKey> SqlSugar的实现 SqlSugar项目,需要高效链式查询
IReadOnlyRepository<TEntity, TKey> 只读查询接口 纯查询场景(报表、导出),避免数据修改
EfCoreRepositoryBase<...> EF Core多上下文基类 多数据库上下文场景,需明确绑定上下文

ABP的仓储设计遵循“接口抽象+ORM实现”的模式,既保证了业务层与数据访问层的解耦,又保留了不同ORM的特性优势,是构建分层架构的核心工具。

八. CRUD 增删改查

核心辅助类

  • 基于IRepository的方法:InsertAsyncUpdateAsyncDeleteAsyncGetAsync等。
  • AbstractCrudAppService<TEntity, TDto, TKey>:基于主键的 CRUD 抽象服务,自动实现GetGetListCreateUpdateDelete等基础方法。
  • AbstractKeyCrudAppService<TEntity, TDto, TKey, TGetListInput, TCreateInput, TUpdateInput>:更灵活的泛型 CRUD 基类,支持自定义输入 DTO(适用于复杂查询 / 创建 / 更新场景)。
  • PagedAndSortedResultRequestDto:分页和排序的基础输入 DTO(配合 CRUD 服务实现分页查询)。

你关注的这几个类是ABP框架实现快速CRUD开发的核心工具,从仓储层的基础操作到服务层的自动化接口,能大幅减少重复代码。下面通过完整示例,带你理解它们的联动用法和设计逻辑:

1. 基于IRepository的基础CRUD方法

IRepository是ABP仓储层的通用接口,封装了实体的基础CRUD操作(InsertAsync/UpdateAsync等),无需手动编写SQL或ORM语句,框架会自动适配底层ORM(如EF Core、SqlSugar)。

示例:在服务中使用IRepository

using Volo.Abp.Domain.Repositories;
using Volo.Abp.Application.Services;// 1. 定义实体(Book)
public class Book : Entity<Guid>
{public string Name { get; set; }public decimal Price { get; set; }public DateTime CreationTime { get; set; } = DateTime.Now;
}// 2. 定义DTO(数据传输对象,避免直接暴露实体)
public class BookDto : EntityDto<Guid>
{public string Name { get; set; }public decimal Price { get; set; }
}// 3. 应用服务(手动实现CRUD,依赖IRepository)
public class BookAppService : ApplicationService
{private readonly IRepository<Book, Guid> _bookRepository;// 注入IRepository<Book, Guid>public BookAppService(IRepository<Book, Guid> bookRepository){_bookRepository = bookRepository;}// 新增:使用InsertAsyncpublic async Task<BookDto> CreateAsync(BookDto input){// 实体映射(DTO -> 实体)var book = ObjectMapper.Map<BookDto, Book>(input);// 仓储新增:返回新增后的实体(含ID)var newBook = await _bookRepository.InsertAsync(book);// 实体 -> DTO 返回return ObjectMapper.Map<Book, BookDto>(newBook);}// 单查:使用GetAsyncpublic async Task<BookDto> GetAsync(Guid id){var book = await _bookRepository.GetAsync(id);return ObjectMapper.Map<Book, BookDto>(book);}// 列表查询:使用GetListAsync(带条件)public async Task<List<BookDto>> GetListAsync(string bookName){// 带过滤条件的查询var books = await _bookRepository.GetListAsync(book => book.Name.Contains(bookName) // Lambda条件);return ObjectMapper.Map<List<Book>, List<BookDto>>(books);}// 更新:使用UpdateAsyncpublic async Task<BookDto> UpdateAsync(Guid id, BookDto input){// 先查询实体var book = await _bookRepository.GetAsync(id);// 赋值更新ObjectMapper.Map(input, book);// 仓储更新var updatedBook = await _bookRepository.UpdateAsync(book);return ObjectMapper.Map<Book, BookDto>(updatedBook);}// 删除:使用DeleteAsyncpublic async Task DeleteAsync(Guid id){await _bookRepository.DeleteAsync(id);}
}

讲解:

  • 核心方法作用
    • InsertAsync:新增实体,自动赋值主键(如Guid自增、雪花ID),返回新增后的实体。
    • GetAsync:根据主键查询单个实体,不存在时抛EntityNotFoundException
    • GetListAsync:支持Lambda条件查询,返回符合条件的实体列表。
    • UpdateAsync:更新实体(需先查询出实体再赋值),返回更新后的实体。
    • DeleteAsync:根据主键删除实体,支持批量删除(DeleteAsync(book => book.Price < 0))。
  • 依赖ORMIRepository的实现由底层ORM决定(如EF Core用EfCoreRepository,SqlSugar用SqlSugarRepository),开发者无需关心ORM细节。

2. AbstractCrudAppService:简化CRUD服务(基础版)

AbstractCrudAppService是ABP服务层的抽象基类,自动实现Get/GetList/Create/Update/Delete等基础方法,无需手动编写上述BookAppService中的重复逻辑。

示例:继承AbstractCrudAppService

using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;// 1. 复用上面的Book实体和BookDto// 2. 继承AbstractCrudAppService,自动获得基础CRUD方法
public class BookCrudAppService : AbstractCrudAppService<Book,               // 实体类型BookDto,            // 返回DTO类型Guid,               // 实体主键类型PagedAndSortedResultRequestDto, // 列表查询输入DTO(分页排序)BookDto,            // 创建输入DTO(直接复用返回DTO)BookDto             // 更新输入DTO(直接复用返回DTO)
>
{// 构造函数:注入仓储,调用父类构造public BookCrudAppService(IRepository<Book, Guid> repository) : base(repository){// 可选:配置权限(如只有Admin能删除)GetPolicyName = "BookManagement.Read";DeletePolicyName = "BookManagement.Delete";}// 可选:重写方法自定义逻辑(如新增时补充创建人)protected override async Task<Book> MapToEntityAsync(BookDto createInput, Book entity){var book = await base.MapToEntityAsync(createInput, entity);// 补充当前用户ID(需注入ICurrentUser)book.CreatorId = CurrentUser.Id; return book;}
}

讲解:

  • 自动生成的方法:继承后无需编写代码,框架自动暴露以下API(配合动态API):
    • GET /api/app/book/{id}:单查(对应GetAsync)。
    • GET /api/app/book:分页列表(对应GetListAsync,支持Skip/Take/Sorting)。
    • POST /api/app/book:新增(对应CreateAsync)。
    • PUT /api/app/book/{id}:更新(对应UpdateAsync)。
    • DELETE /api/app/book/{id}:删除(对应DeleteAsync)。
  • 自定义扩展:通过重写父类方法(如MapToEntityAsyncMapToDtoAsync)添加业务逻辑,不破坏自动实现的基础功能。
  • 权限集成:通过GetPolicyName/CreatePolicyName等属性绑定权限,实现接口授权(需配合ABP权限系统)。

3. AbstractKeyCrudAppService:灵活CRUD服务(复杂版)

AbstractKeyCrudAppServiceAbstractCrudAppService的泛型扩展,支持自定义输入DTO,适用于查询/创建/更新逻辑复杂的场景(如查询需多条件过滤、创建需特殊字段验证)。

示例:继承AbstractKeyCrudAppService

using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;// 1. 定义自定义输入DTO(复杂查询/创建/更新)
// 列表查询输入:支持书名模糊查询+分页+排序
public class GetBookListInput : PagedAndSortedResultRequestDto
{public string BookName { get; set; } // 新增自定义查询条件public decimal? MinPrice { get; set; } // 新增价格下限过滤
}// 创建输入:仅允许传递Name和Price,且Name必填
public class CreateBookInput
{[Required] // 验证特性public string Name { get; set; }public decimal Price { get; set; }
}// 更新输入:允许部分字段更新(如只更新Price)
public class UpdateBookInput
{public decimal? Price { get; set; } // 可选字段,支持部分更新
}// 2. 继承AbstractKeyCrudAppService,使用自定义DTO
public class BookAdvancedCrudAppService : AbstractKeyCrudAppService<Book,               // 实体类型BookDto,            // 返回DTO类型Guid,               // 主键类型GetBookListInput,   // 自定义列表查询输入CreateBookInput,    // 自定义创建输入UpdateBookInput     // 自定义更新输入
>
{public BookAdvancedCrudAppService(IRepository<Book, Guid> repository) : base(repository){}// 重写列表查询:添加自定义过滤条件(BookName+MinPrice)protected override async Task<IQueryable<Book>> CreateFilteredQueryAsync(GetBookListInput input){var query = await base.CreateFilteredQueryAsync(input);// 过滤1:书名模糊查询(输入不为空时)if (!string.IsNullOrWhiteSpace(input.BookName)){query = query.Where(book => book.Name.Contains(input.BookName));}// 过滤2:价格下限(输入不为空时)if (input.MinPrice.HasValue){query = query.Where(book => book.Price >= input.MinPrice.Value);}return query;}// 重写更新:支持部分字段更新(只更新有值的字段)protected override async Task MapToEntityAsync(UpdateBookInput updateInput, Book entity){// 仅更新Price(如果输入有值)if (updateInput.Price.HasValue){entity.Price = updateInput.Price.Value;}// 其他字段不更新(如Name保持不变)}
}

讲解:

  • 自定义输入DTO优势
    • 查询输入(GetBookListInput):在分页排序基础上添加业务字段(如BookName),满足复杂查询需求。
    • 创建输入(CreateBookInput):仅暴露必要字段,配合[Required]等验证特性,确保输入合法性。
    • 更新输入(UpdateBookInput):支持部分字段更新(如只改价格),避免全量赋值。
  • 核心重写方法
    • CreateFilteredQueryAsync:重写列表查询的过滤逻辑,添加自定义条件。
    • MapToEntityAsync:重写DTO到实体的映射,支持部分更新或特殊字段处理。
  • 自动化仍保留:即使自定义输入,Get/Create/Update等方法的核心逻辑(如仓储调用、事务管理)仍由框架自动实现。

4. PagedAndSortedResultRequestDto:分页排序基础DTO

PagedAndSortedResultRequestDto是ABP提供的基础输入DTO,封装了分页Skip/Take)和排序Sorting)参数,直接用于CRUD服务的列表查询。

示例:使用PagedAndSortedResultRequestDto

// 1. 基础用法(直接作为输入DTO)
public class SimpleBookAppService : AbstractCrudAppService<Book, BookDto, Guid, PagedAndSortedResultRequestDto // 直接使用基础分页DTO
>
{public SimpleBookAppService(IRepository<Book, Guid> repository) : base(repository){}
}// 2. 前端调用示例(传递分页排序参数)
// GET请求URL:/api/app/book?Skip=0&Take=10&Sorting=CreationTime%20desc
// - Skip=0:跳过0条(第一页)
// - Take=10:取10条(每页10条)
// - Sorting=CreationTime desc:按CreationTime降序排序// 3. 自定义分页DTO(继承扩展)
public class GetBookWithAuthorInput : PagedAndSortedResultRequestDto
{public Guid? AuthorId { get; set; } // 扩展:按作者ID过滤
}

讲解:

  • 核心属性
    • Skip:跳过的记录数(计算页码:Skip = (页码-1)*Take)。
    • Take:获取的记录数(每页条数)。
    • Sorting:排序规则(格式:字段名 [asc|desc],如Name ascPrice desc)。
  • 扩展性:通过继承PagedAndSortedResultRequestDto添加自定义字段(如AuthorId),实现“分页+排序+业务过滤”的组合查询。
  • 自动支持:ABP的AbstractCrudAppService会自动解析PagedAndSortedResultRequestDto的参数,无需手动处理分页和排序逻辑。

核心总结:CRUD工具的选择逻辑

工具类 适用场景 优势
IRepository 自定义CRUD逻辑、复杂业务 灵活性高,支持全自定义
AbstractCrudAppService 简单CRUD场景(输入输出DTO一致) 零代码实现基础CRUD,开发效率高
AbstractKeyCrudAppService 复杂场景(自定义查询/创建/更新输入) 兼顾自动化和灵活性,支持部分更新
PagedAndSortedResultRequestDto 所有列表查询场景 标准化分页排序参数,减少重复DTO定义

要不要我帮你整理一份ABP CRUD服务的完整代码模板?包含实体、DTO、仓储、服务的联动代码,你可以直接替换业务字段复用。

九. 审计日志(Auditing)

核心辅助类

  • AuditedAttribute:标记类/方法记录审计日志。
  • IAuditingManager:手动管理审计日志。
  • EntityAuditingHelper:实体审计辅助(自动填充创建/修改时间)。

示例

[Audited] // 自动记录方法调用日志
public class BookAppService : ApplicationService
{public async Task UpdateAsync(Guid id, UpdateBookDto input){var book = await _repository.GetAsync(id);book.Name = input.Name;await _repository.UpdateAsync(book);}
}

十. 工作单元(Unit of Work)

核心辅助类

  • IUnitOfWorkManager:管理工作单元。

  • UnitOfWorkAttribute:标记方法为工作单元(自动事务)。

  • UnitOfWorkOptions:工作单元配置选项(如事务隔离级别、超时时间)。

    [UnitOfWork(IsolationLevel.ReadCommitted, Timeout = 30)]
    public async Task DoWorkAsync()
    {// 业务逻辑
    }
    

示例

[UnitOfWork] // 自动开启事务,异常时回滚
public async Task CreateBookAndAuthorAsync(CreateBookWithAuthorDto input)
{var author = new Author { Name = input.AuthorName };await _authorRepository.InsertAsync(author);var book = new Book { Name = input.BookName, AuthorId = author.Id };await _bookRepository.InsertAsync(book);
}

十一. 种子数据(Seed Data)

核心辅助类

  • IDataSeeder:数据种子接口。
  • DataSeedContext:种子数据上下文。

示例

public class BookDataSeeder : IDataSeeder, ITransientDependency
{private readonly IRepository<Book, Guid> _repository;public BookDataSeeder(IRepository<Book, Guid> repository){_repository = repository;}public async Task SeedAsync(DataSeedContext context){if (await _repository.GetCountAsync() == 0){await _repository.InsertAsync(new Book { Name = "Initial Book" });}}
}

十二. JWT 鉴权(JWT Authentication)

核心辅助类

  • AbpJwtBearerModule:JWT集成模块。
  • JwtBearerOptions:JWT配置选项。

示例

// 模块中配置JWT
public override void ConfigureServices(ServiceConfigurationContext context)
{context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>{options.TokenValidationParameters = new TokenValidationParameters{ValidateIssuer = true,ValidIssuer = "MyIssuer",ValidateAudience = true,ValidAudience = "MyAudience",ValidateLifetime = true,IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("MySecretKey"))};});
}

十三. 接口授权(Authorization)

核心辅助类

  • [Authorize]:标记需要授权的接口。
  • [AllowAnonymous]:允许匿名访问。
  • IPermissionChecker:手动检查权限。

示例

[Authorize("BookManagement.Create")] // 需"BookManagement.Create"权限
public async Task CreateAsync(CreateBookDto input)
{// 业务逻辑
}// 手动检查权限
public async Task DeleteAsync(Guid id)
{if (!await PermissionChecker.IsGrantedAsync("BookManagement.Delete")){throw new AbpAuthorizationException("No permission to delete books.");}await _repository.DeleteAsync(id);
}

十四. 异常处理(Exception Handling)

核心辅助类

  • AbpExceptionFilter:自动捕获并处理异常。
  • UserFriendlyException:用户友好异常(直接返回给前端)。
  • IExceptionSubscriber:自定义异常订阅。

示例

public async Task UpdateAsync(Guid id, UpdateBookDto input)
{var book = await _repository.GetAsync(id);if (book == null){throw new UserFriendlyException("Book not found!"); // 直接返回给前端}// 业务逻辑
}

十五. 缓存(Caching)

核心辅助类

  • IDistributedCache:分布式缓存(基于Redis等)。
  • ICacheManager:缓存管理器(支持多级缓存)。
  • [Cache]:方法缓存特性。
  • ICacheKeyNormalizer:缓存键标准化器,自动添加租户前缀(多租户场景)或应用前缀。
  • [CacheInvalidate]:缓存失效特性(修改数据时自动清除对应缓存)。

示例

public class BookCacheService : ITransientDependency
{private readonly IDistributedCache<BookDto> _cache;public BookCacheService(IDistributedCache<BookDto> cache){_cache = cache;}public async Task<BookDto> GetAsync(Guid id){var cacheKey = $"book:{id}";var book = await _cache.GetOrAddAsync(cacheKey,async () => await GetBookFromDatabaseAsync(id),() => new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1) });return book;}
}

十六. SaaS 多租户(Multi-Tenancy)

核心辅助类

  • ITenantRepository:租户仓储。
  • ICurrentTenant:当前租户信息。
  • MultiTenantAttribute:标记多租户支持。

示例

public class BookAppService : ApplicationService
{public async Task<List<BookDto>> GetTenantBooksAsync(){var tenantId = CurrentTenant.Id; // 获取当前租户IDvar books = await _repository.GetListAsync(b => b.TenantId == tenantId);return ObjectMapper.Map<List<Book>, List<BookDto>>(books);}
}

十七. 事件总线(Event Bus)

核心辅助类

  • IEventBus:发布/订阅事件。
  • LocalEventBus:本地事件总线(进程内)。
  • IntegrationEvent:跨服务集成事件。

示例

// 定义事件
public class BookCreatedEvent : EventData
{public Guid BookId { get; set; }public string BookName { get; set; }
}// 发布事件
public class BookAppService : ApplicationService
{public async Task CreateAsync(CreateBookDto input){var book = new Book { Name = input.Name };await _repository.InsertAsync(book);// 发布事件await EventBus.PublishAsync(new BookCreatedEvent{BookId = book.Id,BookName = book.Name});}
}// 订阅事件
public class BookCreatedEventHandler : IEventHandler<BookCreatedEvent>, ITransientDependency
{public Task HandleEventAsync(BookCreatedEvent eventData){// 处理事件(如发送通知)Logger.LogInformation($"Book created: {eventData.BookName}");return Task.CompletedTask;}
}

以上是ABP框架核心功能的关键辅助类及典型用法,实际开发中可结合官方文档扩展细节。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/945638.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

题解:P14299 [JOI2023 预选赛 R2] 填充 / Painting

\(\displaystyle \large {题目传送门}\) 题面 给定一个一个 H*W 的矩形 , 每个坐标上有一个颜色 , 上下左右相邻的同颜色节点可以形成连通块 。 你可以对任意一个连通块 , 进行一次并仅有一次的染色 , 求新形成的连…

Devolutions Server权限提升漏洞分析与修复指南

本文详细分析了CVE-2025-11957漏洞,该漏洞存在于Devolutions Server 2025.2.12.0及更早版本中,由于临时访问工作流程的授权机制存在缺陷,允许经过身份验证的基本用户通过精心构造的API请求自我批准或批准其他用户的…

AI股票预测分析报告 - 2025年10月24日 - 20:08:50

AI股票预测分析报告 - 2025年10月24日body { font-family: "Microsoft YaHei", "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; line-height: 1.6; color: rgba(51, 51, 51, 1); max-width: 1…

在 Astro 博客中优雅使用 51.la 统计数据

在 Astro 博客中使用 51.la 免费流量统计,通过解析 widget JS 自行渲染访问数据,既保留统计功能,又可自定义展示,让你直观了解博客访客情况作为老牌网站流量统计服务商,51.la 提供每月高达 1000 万次的免费统计额…

申威服务器安装Java11(swjdk-11u-9.ky10.sw_64.rpm)详细操作步骤(附安装包)

申威服务器安装Java11(swjdk-11u-9.ky10.sw_64.rpm)详细操作步骤(附安装包)​这是申威架构(国产芯片,常见于Kylin V10等国产系统)专用的 ​Java 11 版本(RPM安装包)​,包名为 java-11.0.7-swjdk-11u-9.ky10.…

str.endswith() 类似的方法

在Python中,与str.endswith()类似的方法(主要涉及字符串的开头/结尾检查、子串搜索等)有很多,以下是核心方法及其功能对比: 1. 开头检查:str.startswith()功能:检查字符串是否以指定前缀开头,返回True/False。…

深度剖析OpenHarmony AI Engine:开发板端侧大模型推理插件机制全链路拆解 - 实践

深度剖析OpenHarmony AI Engine:开发板端侧大模型推理插件机制全链路拆解 - 实践pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font…

Linux下的拼音输入法 (3)

此贴简介libgooglepinyin-0.1.2,刚看了几分钟(持续更新中): data/下: 730 2012年 2月 3日 CMakeLists.txt227 2012年 2月 3日 googlepinyin.pc.in3.5M 2012年 2月 3日 rawdict_utf16_65105_freq.be.txt3.5M 2012年…

P2606 [ZJOI2010] 排列计数 分析

题目概述 题目链接:https://www.luogu.com.cn/problem/P2606。 称一个 \(1 \sim n\) 的排列 \(p_1,p_2, \dots ,p_n\) 是 Magic 的,当且仅当 \[\forall i \in [2,n],p_i > p_{\lfloor i/2 \rfloor} \]计算 \(1 \s…

实用指南:MacOS - Clang使用bits/stdc++.h - 非官方(竞赛用) - 通用方法

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

cgroup

cgroupcgroup v1使用流程: mount -t tmpfs cgroup_root /sys/fs/cgroup mkdir -p /sys/fs/cgroup/cpu mkdir -p /sys/fs/cgroup/memory mount -t cgroup -o cpu none /sys/fs/cgroup/cpu mount -t cgroup -o memory n…

设计模式:代码界的 “光之巨人” 养成指南(附 C++ 实战)

参考 https://bbs.huaweicloud.com/blogs/397606 https://refactoring.guru/design-patterns/catalog 一、概述 1.1、什么是设计模式 官方定义说得有点绕:“一套被反复使用、多数人知晓、分类编目的代码设计经验总结”…

详细介绍:17-Language Modeling with Gated Convolutional Networks

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

数字人:数字人公司排行榜及技术深度剖析

数字人企业:探索虚拟与现实的融合新纪元 数字人企业的创新之路与市场机遇解析 数字人企业排行榜及技术深度剖析 正文: 在当今科技飞速发展的时代,数字人企业正成为推动数字化转型的重要力量。它们不仅重塑了人机交互…

【同余最短路】学习笔记

例题 \(1\):P3403 跳楼机:给定正整数 \(h,x,y,z\),求有多少 \(d\in[1,h]\) 满足 \(ax+by+cz=d-1\),其中 \(a,b,c\) 为非负整数。这道题第一眼给我的印象是一道数论题,但仔细想了想发现做不了。注意到 \(x,y,z\) 的…

ESP32-S3入门第七天:UART串口通信与设备交互 - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

数字人:数字人公司深度解析与未来展望

数字人企业 数字人企业新趋势:虚拟与现实的融合探索 数字人企业深度解析与未来展望 在当今科技飞速发展的时代,数字人企业正成为推动创新与变革的重要力量。数字人,作为虚拟与现实交互的桥梁,不仅改变了人机交互的…

CSP/NOIP 复习:单调栈

最近模拟赛打的都不是太好,先随便复习复习吧,马上就要 CSPS 了,我可以考好的。 这里放一些单调栈的题目,笛卡尔树先不说,这个我已经忘了,后天复习一下。 本体 栈中维护有单调性的数据,入栈时维护这个单调性,这…

数字人企业:数字人公司排行榜深度解析

数字人企业新纪元:创新科技重塑未来生态 探秘数字人企业的核心驱动力与市场前景 数字人企业排行榜深度解析与趋势洞察 正文: 在数字化浪潮席卷全球的今天,数字人企业正成为科技领域的一颗新星,推动着虚拟与现实的深…