以下是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框架中,DynamicApiControllerBuilder和RemoteServiceAttribute是实现动态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->GET、Create->POST、Update->PUT、Delete->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会按“服务名称+方法名”生成路由。例如
BookAppService的GetAsync方法,默认路由为/api/app/book/get(app是默认前缀,book是服务名缩写)。
3. 动态API的核心优势
- 零控制器代码:无需手动编写
Controller类,框架自动生成,减少重复工作。 - 自动路由生成:按服务名和方法名自动推断路由,也支持自定义。
- 与依赖注入集成:动态生成的控制器会自动注入应用服务,无需手动处理依赖。
- Swagger自动集成:生成的API会自动显示在Swagger文档中,便于调试。
4. 总结
RemoteServiceAttribute是“标记型”配置,通过特性快速指定服务或方法是否暴露为API,适合简单场景。DynamicApiControllerBuilder是“编程式”配置,适合批量处理或需要自定义路由、前缀的复杂场景。
两者结合使用,可高效实现API的自动生成,让开发者专注于业务逻辑而非API配置。
三. 依赖注入(Dependency Injection)
核心辅助类:
-
IServiceCollection:扩展方法(如AddTransient、AddScoped)。 -
DependencyAttribute:标记注入生命周期(Transient/Scoped/Singleton)。 -
IIocResolver:手动解析服务。 -
ITransientDependency/IScopedDependency/ISingletonDependency:标记接口,自动注册对应生命周期的服务(无需手动在模块中配置)。
在ABP框架的依赖注入(DI)系统中,这些类和接口用于管理服务的注册与解析,核心目标是简化依赖注入的配置,确保服务按预期的生命周期(瞬时、作用域、单例)工作。以下是具体示例和讲解:
1. IServiceCollection:服务注册的核心接口(配合扩展方法)
IServiceCollection是.NET依赖注入的标准接口,ABP通过扩展方法(如AddTransient、AddScoped、AddSingleton)简化服务注册,定义服务的类型和生命周期。
示例:手动注册服务
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:用于解析Transient或Scoped服务,返回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. 总结:服务注册方式的选择
- 简单场景:优先使用
ITransientDependency等标记接口(最简洁,零配置)。 - 需要自定义:使用
DependencyAttribute(支持替换服务、注册接口等高级配置)。 - 批量或泛型服务:使用
IServiceCollection的扩展方法(如泛型仓储、第三方服务)。 - 手动解析:通过
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();}
}
核心讲解
-
适用场景:
- 基类共享依赖:当多个子类继承同一个基类,且基类需要依赖时,属性注入可避免每个子类在构造函数中重复传递依赖。
- 无法修改构造函数的类:如第三方库中的类、自动生成的代码,无法添加构造函数参数,此时属性注入是唯一选择。
- 可选依赖:某些依赖不是必须的(允许为
null),属性注入比构造函数注入更灵活(构造函数参数通常是必须的)。
-
与构造函数注入的区别:
- 构造函数注入:依赖在对象创建时必须提供,确保对象实例化后即可正常使用(无
null风险),是ABP推荐的主要注入方式。 - 属性注入:依赖在对象创建后由框架自动赋值(或手动触发),存在未注入时为
null的风险,适合非核心依赖。
- 构造函数注入:依赖在对象创建时必须提供,确保对象实例化后即可正常使用(无
-
注意事项:
- 必须配合依赖注入容器:只有被ABP依赖注入系统管理的类(如实现了
ITransientDependency的类),框架才会自动执行属性注入。手动new的实例需通过IIocResolver.InjectProperties()手动触发。 - 避免过度使用:属性注入可能导致依赖关系不明显(不如构造函数参数直观),建议优先使用构造函数注入,仅在必要时使用属性注入。
- 可空检查:使用属性注入的依赖可能为
null,需在代码中添加空值检查(如BookService?.CreateBook(...)),或通过[Required]特性要求必须注入(框架会在注入失败时抛异常)。
- 必须配合依赖注入容器:只有被ABP依赖注入系统管理的类(如实现了
总结
[Inject]特性是ABP依赖注入系统的补充,用于解决构造函数注入不适用的场景。它通过标记属性为注入点,让框架或开发者手动为其赋值,平衡了灵活性和易用性。但需注意其潜在的null风险,合理选择注入方式。
五. 当前用户(Current User)
核心辅助类:
ICurrentUser:获取当前登录用户信息(ID、用户名、角色等)。CurrentUser:静态快捷访问(需在请求上下文内)。
在ABP框架中,ICurrentUser和CurrentUser用于获取当前登录用户的信息(如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:静态快捷访问器
CurrentUser是ICurrentUser的静态封装,提供更简洁的语法(无需注入),但仅能在请求上下文内使用(如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.Id、CurrentUser.UserName等访问,代码更简洁。 - 使用限制:
- 仅能在有用户上下文的场景中使用(如HTTP请求、已认证的后台作业)。
- 非请求上下文(如应用启动时、无用户的后台任务)中使用会抛异常(
CurrentUser内部会检查上下文是否存在)。
- 本质:
CurrentUser内部通过IHttpContextAccessor或ICurrentUser获取用户信息,是对ICurrentUser的静态代理,功能完全一致。
关键区别与使用建议
| 特性 | ICurrentUser(接口) |
CurrentUser(静态类) |
|---|---|---|
| 使用方式 | 需通过依赖注入(构造函数参数) | 直接静态访问(CurrentUser.XXX) |
| 适用场景 | 所有场景(包括无HTTP上下文的后台任务) | 仅请求上下文内(如Controller、ApplicationService) |
| 灵活性 | 高(支持单元测试时Mock) | 低(静态方法难以Mock,测试复杂度高) |
| 代码可读性 | 依赖关系明确(构造函数可见) | 依赖关系隐藏(静态调用不直观) |
最佳实践
- 优先使用
ICurrentUser:尤其是在服务层(ApplicationService)、领域层,通过依赖注入使用,便于单元测试(可通过Mock<ICurrentUser>模拟用户上下文)。 - 有限使用
CurrentUser:仅在Controller等请求上下文明确的场景中使用,简化代码(如快速获取用户ID用于日志记录)。 - 多租户场景注意:
ICurrentUser.TenantId可直接获取当前租户ID,结合多租户过滤器实现数据隔离(如查询时自动过滤当前租户的数据)。 - 未登录处理:使用前需检查
IsAuthenticated,避免未登录时访问Id等属性导致NullReferenceException。
通过ICurrentUser和CurrentUser,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.SqlSugar或SqlSugarCore),再在自定义模块中配置依赖和连接信息。
示例:配置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开发习惯的仓储接口(如GetAsync、InsertAsync),同时支持扩展自定义查询。
示例:自定义仓储继承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复用:父类已封装
GetAsync、InsertAsync、UpdateAsync、DeleteAsync等基础方法,无需重复编写。 - 自定义扩展:通过继承
SqlSugarRepository<T>,可在子类中扩展复杂查询(如多表关联、聚合查询),同时保留SqlSugar的高效语法。
核心总结与优势
- 模块化集成:
SqlSugarModule让SqlSugar融入ABP的模块生命周期,配置集中且可复用。 - 高效操作:
ISqlSugarClient提供比EF Core更简洁的查询语法,性能更优(尤其复杂查询场景)。 - 仓储适配:
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默认实现,内部封装了DbContext和DbSet操作,适用于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属性)和DbContext(await 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};}
}
讲解:
- 方法限制:仅包含
GetAsync、GetListAsync、CountAsync、AverageAsync等查询方法,无任何写入操作,从接口层面避免误修改数据。 - 适用场景:报表服务、数据导出、查询接口等纯读取场景,增强代码安全性和可读性。
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的DbContext和DbSet,适合需要深度定制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的方法:InsertAsync、UpdateAsync、DeleteAsync、GetAsync等。 AbstractCrudAppService<TEntity, TDto, TKey>:基于主键的 CRUD 抽象服务,自动实现Get、GetList、Create、Update、Delete等基础方法。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))。
- 依赖ORM:
IRepository的实现由底层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)。
- 自定义扩展:通过重写父类方法(如
MapToEntityAsync、MapToDtoAsync)添加业务逻辑,不破坏自动实现的基础功能。 - 权限集成:通过
GetPolicyName/CreatePolicyName等属性绑定权限,实现接口授权(需配合ABP权限系统)。
3. AbstractKeyCrudAppService:灵活CRUD服务(复杂版)
AbstractKeyCrudAppService是AbstractCrudAppService的泛型扩展,支持自定义输入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 asc、Price 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框架核心功能的关键辅助类及典型用法,实际开发中可结合官方文档扩展细节。