Skip to main content

manage-units-of-work

Control ABP's Unit of Work — transaction and connection scope, explicit UOW scopes, the [UnitOfWork] attribute, and mid-scope SaveChangesAsync. USE FOR: starting explicit scopes with IUnitOfWorkManager.Begin, applying [UnitOfWork], choosing transactional vs non-transactional boundaries, calling SaveChangesAsync mid-scope, working with nested/RequiresNew/reserved scopes, reading IUnitOfWorkManager.Current, using OnCompleted/CompleteAsync/RollbackAsync. DO NOT USE FOR: defining a DbContext or repositories, or EF Core migrations (use ef-core-integration); MongoDB DbContext/repositories (use mongodb-integration); soft-delete/multi-tenant/custom query filters (use apply-data-filters); optimistic concurrency stamps (use handle-optimistic-concurrency); connection-string resolution (use configure-connection-strings).

Ir para a instalação

Informações da origem

Repositório
abpframework/abp-skills
Última atividade na origem
20 de julho de 2026 às 03:24
Idioma detectado do SKILL.md
inglês
Estrelas
18
Forks
0

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
name
manage-units-of-work
description
Control ABP's Unit of Work — transaction and connection scope, explicit UOW scopes, the [UnitOfWork] attribute, and mid-scope SaveChangesAsync. USE FOR: starting explicit scopes with IUnitOfWorkManager.Begin, applying [UnitOfWork], choosing transactional vs non-transactional boundaries, calling SaveChangesAsync mid-scope, working with nested/RequiresNew/reserved scopes, reading IUnitOfWorkManager.Current, using OnCompleted/CompleteAsync/RollbackAsync. DO NOT USE FOR: defining a DbContext or repositories, or EF Core migrations (use ef-core-integration); MongoDB DbContext/repositories (use mongodb-integration); soft-delete/multi-tenant/custom query filters (use apply-data-filters); optimistic concurrency stamps (use handle-optimistic-concurrency); connection-string resolution (use configure-connection-strings).
license
MIT
# Manage ABP Units of Work ABP's Unit of Work (UOW) controls the **database connection and transaction** scope. Once a UOW starts it creates an **ambient scope**: every database operation done inside it participates in one boundary and is committed on success or rolled back on exception, all together. ## When to Use - Starting a UOW outside the conventions (background jobs, console apps, an inner scope) with `IUnitOfWorkManager.Begin`. - Enabling/disabling a UOW or its transaction on a method/class with `[UnitOfWork]`. - Choosing transactional vs non-transactional boundaries, or changing the global default. - Flushing pending changes mid-scope with `SaveChangesAsync` (e.g. to read an auto-increment id). - Running nested/`RequiresNew`/reserved scopes, or hooking `OnCompleted`. ## When Not to Use - **Defining a DbContext, repositories, or EF Core migrations** — use the **ef-core-integration** skill. - **MongoDB DbContext / repositories** — use **mongodb-integration**. - **Soft-delete / multi-tenant / custom query filters** — use **apply-data-filters**. - **Optimistic concurrency stamps** — use **handle-optimistic-concurrency**. - **Connection-string resolution/configuration** — use **configure-connection-strings**. ## How it works ### When a UOW auto-completes (you usually don't touch it) These method types are conventionally wrapped in a UOW and completed automatically when they return without throwing: - ASP.NET Core MVC **controller actions** - Razor **page handlers** - **Application service** methods - **Repository** methods A UOW auto-begins for these **unless there is already a surrounding (ambient) UOW** — in that case the inner call silently participates in the outer one. So a repository called from an app service does not open its own UOW; it joins the app service's UOW. This is why you rarely start a UOW yourself. #### Transactional or not (default behavior) A UOW does not have to be transactional. By default: - **HTTP GET** requests start a UOW but **no database transaction**. - All other HTTP methods (POST/PUT/DELETE/PATCH/QUERY/...) start a UOW **with a transaction**, if the provider supports it — only GET is non-transactional by default. You can mark extra paths non-transactional via `AspNetCoreUnitOfWorkTransactionBehaviourProviderOptions.NonTransactionalUrls`. Change the global default via `AbpUnitOfWorkDefaultOptions` in your module's `ConfigureServices`: ```csharp Configure<AbpUnitOfWorkDefaultOptions>(options => { options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled; // or Enabled / Auto }); ``` `AbpUnitOfWorkDefaultOptions` also exposes `Timeout` (`int?`) and `IsolationLevel` (`IsolationLevel?`). ### `[UnitOfWork]` attribute Put `[UnitOfWork]` on a method or a class to enable/disable a UOW and control its transaction behavior. Interception rules: if you don't inject the service over an interface, the method must be `virtual`; only `async` methods (returning `Task`/`Task<T>`) are intercepted. ```csharp public class MyService : ITransientDependency { [UnitOfWork(IsTransactional = true)] public virtual async Task FooAsync() { /* transactional UOW scope */ } } ``` Attribute properties (from `UnitOfWorkAttribute`): - `IsTransactional` (`bool?`) — null means "decide by convention/config". - `Timeout` (`int?`) — milliseconds. - `IsolationLevel` (`IsolationLevel?`) — only meaningful when transactional. - `IsDisabled` (`bool`) — prevent starting a UOW for this method/class. ```csharp [UnitOfWork(IsDisabled = true)] public virtual async Task NoUowAsync() { /* ... */ } ``` **Important:** if the method is called inside an ambient UOW, the attribute is **ignored** — the method just participates in the surrounding UOW. `[UnitOfWork]` only takes effect when it is the outermost scope. ### Starting a UOW explicitly: `IUnitOfWorkManager.Begin` Inject `IUnitOfWorkManager` when you need a UOW outside the conventions (background jobs, console apps, or an inner scope). The convenient extension overload: ```csharp public class MyService : ITransientDependency { private readonly IUnitOfWorkManager _unitOfWorkManager; public MyService(IUnitOfWorkManager unitOfWorkManager) { _unitOfWorkManager = unitOfWorkManager; } public virtual async Task FooAsync() { using (var uow = _unitOfWorkManager.Begin( requiresNew: true, isTransactional: true)) { // ... database operations ... await uow.CompleteAsync(); // must call, or it rolls back on dispose } } } ``` `Begin(requiresNew = false, isTransactional = false, isolationLevel = null, timeout = null)`: - `requiresNew` — `false` (default): if a surrounding UOW exists, `Begin` does **not** open a new one, it silently joins the existing UOW. `true`: ignore the ambient UOW and start a fresh, independent one (its own transaction). - When you open a UOW manually via `Begin`, you own its completion: call `await uow.CompleteAsync()` before leaving the `using`. If you don't complete it, disposing rolls it back. There is also the lower-level `Begin(AbpUnitOfWorkOptions options, bool requiresNew = false)` on the interface itself. ### The current UOW and `SaveChangesAsync` UOW is ambient — reach it via `IUnitOfWorkManager.Current` (an `IUnitOfWork`), which is **`null`** when there is no surrounding UOW. `IUnitOfWork.SaveChangesAsync()` flushes pending changes to the database mid-scope without ending the UOW. If the UOW is transactional, even those saved changes can still be rolled back on a later error. Typical use — get an auto-increment id right after insert: ```csharp public class CategoryAppService : ApplicationService, ICategoryAppService { private readonly IRepository<Category, int> _categoryRepository; public CategoryAppService(IRepository<Category, int> categoryRepository) => _categoryRepository = categoryRepository; public async Task<int> CreateAsync(string name) { var category = new Category { Name = name }; await _categoryRepository.InsertAsync(category); await CurrentUnitOfWork.SaveChangesAsync(); // or UnitOfWorkManager.Current.SaveChangesAsync() return category.Id; } } ``` `ApplicationService` already exposes `UnitOfWorkManager` and the `CurrentUnitOfWork` shortcut, so you don't inject them there. - **Don't over-call it.** All changes are saved automatically when the UOW ends without error. Only call `SaveChangesAsync()` (or `InsertAsync(..., autoSave: true)`) when you truly need the flush now. - With a **`Guid` primary key** you never need to save just to get the id — it's set in-app immediately. ### Nested and reserved UOW - **Nested / RequiresNew:** call `Begin(requiresNew: true, ...)` inside an existing scope to run an independent inner transaction that commits or rolls back separately from the outer one. Without `requiresNew`, the inner `Begin` just joins the outer UOW. - **Reserved UOW:** `IUnitOfWorkManager.Reserve(reservationName, requiresNew)` reserves a UOW slot up the stack; later `BeginReserved(reservationName, options)` / `TryBeginReserved(...)` starts the actual UOW under that reservation. `IUnitOfWork.IsReserved` and `ReservationName` reflect this. This is an advanced mechanism used internally (e.g. to make the outermost UOW transactional after inner code has already begun). ### Useful `IUnitOfWork` members - `CompleteAsync()` / `RollbackAsync()` — commit or roll back a manually-started UOW. - `OnCompleted(Func<Task>)` — run a callback after the UOW successfully completes (changes are guaranteed saved). - `Failed` / `Disposed` events — react to failure or disposal. - `Items` — a `Dictionary<string, object>` to stash arbitrary state scoped to this UOW. - `Options`, `Outer` — the options it was started with and the enclosing UOW (if nested). ### ASP.NET Core The UOW system is fully integrated via action/page filters — normally zero config. If you need to widen the scope across other middleware, add `app.UseUnitOfWork();` before `app.UseConfiguredEndpoints();`. `UseUnitOfWork()` is an `IApplicationBuilder` extension in the `Microsoft.AspNetCore.Builder` namespace, shipped by the `Volo.Abp.AspNetCore` package — a plain console/background-worker host that only references `Volo.Abp.Uow` won't have it. ## Validation - Inside a conventional method (controller action, page handler, app service, repository) `IUnitOfWorkManager.Current` is non-null; outside any scope it is `null`. - A manually-started `Begin` scope must call `await uow.CompleteAsync()` to commit. Skipping it means an open transaction is never committed (and is disposed on `Dispose()`), so those changes are not persisted — but note this only covers work still buffered in an uncommitted transaction; non-transactional writes that already reached the database are not undone. - Confirm transaction behavior: only GET requests run without a transaction by default; all other HTTP methods (including QUERY) run transactional (subject to `AbpUnitOfWorkDefaultOptions` and any `NonTransactionalUrls`). - `[UnitOfWork]` takes effect only as the outermost scope — verify it is not being called inside an existing ambient UOW where it is ignored. ## Common Pitfalls - **`[UnitOfWork]` ignored inside an ambient UOW** — the attribute only takes effect when it is the outermost scope; an inner call just joins the surrounding UOW. - **Forgetting `CompleteAsync()` on a manual `Begin`** — `Dispose()` disposes the transaction without committing it (it does not call `RollbackAllAsync`), so an uncommitted transactional UOW discards its changes — even writes already flushed with `SaveChangesAsync` are rolled back when the transaction is disposed (the real commit happens in `CompleteAsync`). What cannot be undone this way is writes made **without** a transaction, which hit the database as they happen. - **`Begin` without `requiresNew: true` inside an existing scope silently joins the outer UOW** instead of starting an independent transaction. - **`[UnitOfWork]` not intercepted** — the method must be `async` (`Task`/`Task<T>`), and `virtual` when the service is not injected over an interface. - **Over-calling `SaveChangesAsync()`** — changes flush automatically when the UOW ends; with a `Guid` PK you never need it just to read the id. - **`IUnitOfWorkManager.Current` is `null` when no UOW surrounds the call** — guard for it outside conventional scopes.
Ver no GitHub