After upgrade and deploy the application (EF core, Micro serivce) , the app data access layer throw timeout exception, here is one of example:
(@p6 uniqueidentifier,@p7 nvarchar(40),@p4 nvarchar(40),@p5 datetime2(7))UPDATE [Agents] SET [ConcurrencyStamp] = @p4, [LastModificationTime] = @p5 OUTPUT 1 WHERE [Id] = @p6 AND [ConcurrencyStamp] = @p7
The agents table has 900+ records only. Azure SQL server's compatible level is SQL Server 2019.
27 Answer(s)
-
0
The SQL you posted is an optimistic concurrency update generated by EF Core (ABP uses
ConcurrencyStampon aggregate roots likeIdentityUser, etc.). A timeout on a single-rowUPDATE ... WHERE Id AND ConcurrencyStampwith ~900 rows is almost never caused by table size; it’s typically caused by blocking/locking (another transaction holds a lock on that row/table) or resource pressure (DTU/vCore throttling) on Azure SQL after the upgrade.With ABP 10 you are very likely also on a newer .NET/EF Core version than ABP 8 (EF Core 8 vs EF Core 6/7 depending on your starting point). That doesn’t “break” this statement, but it can change overall behavior (more queries, different batching, different execution plans), which can expose existing locking issues under load.
Do these ABP-specific fixes/mitigations (safe and commonly used):
- Increase EF Core command timeout for ABP DbContexts
Configure it in your
*.EntityFrameworkCoremodule where you callConfigure<AbpDbContextOptions>:
using Volo.Abp.EntityFrameworkCore; public override void ConfigureServices(ServiceConfigurationContext context) { Configure<AbpDbContextOptions>(options => { options.Configure(cfg => { // Increase as needed (seconds) cfg.DbContextOptionsBuilder.CommandTimeout(180); }); }); }This doesn’t solve blocking, but prevents “false failures” during short spikes/throttling.
Identify and remove blocking transactions (most common root cause) In Azure SQL, check for blocking at the time of the issue (Query Store / “Blocked process” views / running requests). The symptom you see (timeout on a simple update with concurrency check) is consistent with:
- a long-running UOW/transaction holding locks,
- a background worker / migration / seed running concurrently,
- or multiple services updating the same “Agent” row(s) frequently.
ABP microservices commonly use distributed events/outbox patterns; if you have a consumer that updates the same entity repeatedly inside a long unit-of-work, it can block other writers.
Ensure your Azure SQL tier/performance didn’t effectively downgrade After upgrade, overall DB activity usually increases (more features/modules, more permission/setting/cache reads/writes). On low tiers, Azure SQL may throttle and cause timeouts even on trivial statements.
Make sure SQL Server compatibility is supported ABP 8 migration notes remind that starting with EF Core 8.0, generated SQL requires newer SQL Server versions (SQL Server 14+). Azure SQL with compat level 2019 is fine, but this confirms the “version support” angle is not your problem here—again pointing back to blocking/throttling.
If you apply (1) and still see timeouts, treat it as a blocking/throttling problem rather than an ABP/EF bug: the specific statement is a single-row concurrency update, so it should complete instantly unless it’s waiting.
Links (verified):
- https://abp.io/docs/latest/release-info/migration-guides/abp-8-0
- https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/breaking-changes#high-impact-changes
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Is it possible because of ConcurrencyStamp? For above simple statement, it should not take more than 30 seconds;
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
No—a timeout is not caused by
ConcurrencyStampitself.ConcurrencyStampis just an optimistic concurrency token (a string) that EF Core includes in theWHEREclause to ensure “update only if nobody else changed it”. If the stamp doesn’t match, you get anAbpDbConcurrencyException/DbUpdateConcurrencyException(0 rows affected), not a 30s timeout.A 30-second timeout on this statement almost always means SQL Server is waiting on locks (blocking) or being throttled. Even a single-row update can sit and wait if:
- another transaction holds a lock on that row (or a broader lock due to escalation),
- a long-running transaction/UOW is open in your service,
- you have multiple microservice instances/background workers updating the same row(s),
- Azure SQL is under resource pressure.
What to do (actionable, ABP-relevant):
Confirm it’s blocking (most likely) When the timeout happens, you should see the session in a
WAITstate (e.g.,LCK_M_...) on Azure SQL. If it’s blocking, the fix is to find and eliminate the long transaction / hot row contention (not to change concurrency stamp behavior).Increase the EF Core command timeout (mitigation) Configure ABP’s DbContext options to raise command timeout so transient blocking/throttling doesn’t fail at 30 seconds:
using Volo.Abp.EntityFrameworkCore; Configure<AbpDbContextOptions>(options => { options.Configure(cfg => { cfg.DbContextOptionsBuilder.CommandTimeout(180); }); });- If the same “Agent” row is frequently updated, prevent multi-instance contention
In microservices with multiple replicas, if many workers update the same entity (same
Id) you can get lock queues and timeouts. In such cases, you typically need to redesign to reduce hot-row updates (e.g., avoid repeatedly updating audit/concurrency fields for “heartbeat”-like writes), or use a distributed locking / coordination approach around that critical section.
(Concurrency token docs, validated)
- https://abp.io/docs/latest/framework/infrastructure/concurrency-check
- https://abp.io/community/articles/jlkc3w8f
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Hi,
based on what you've described, the timeout on a simple single-row
UPDATE ... WHERE Id AND ConcurrencyStampwith only ~900 records is very unlikely to be a code issue — that statement should complete in milliseconds under normal conditions.The issue might not be related to your code or ABP at all. Could you try reproducing it against a local SQL Server instance with the same data to help narrow down the root cause?
Thanks
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
I notice in an AppService Get method, it has code call repository update method too, I remember ABP automatically add unit of work to App Service public methods, does it apply to get method too?
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Yes. ABP automatically applies Unit of Work (UOW) to all public application service methods by convention, regardless of whether the method is “Get”/read-like or “Create/Update/Delete”. So your
Get...method is inside a UOW scope too.However, being inside a UOW doesn’t always mean a DB transaction is started. With the default
UnitOfWorkTransactionBehavior.Autobehavior for HTTP requests, GET HTTP requests don’t start a transactional UOW by default. That said, you can still write to the database inside a GET method, and it can still cause locks/contention and timeouts (especially if that GET endpoint is hit frequently).
What you should do in your case (since your GET calls
UpdateAsync)- Don’t update data inside a GET. Move that update logic to a separate command endpoint (POST/PUT), or a background job/event handler.
- If you must keep it temporarily, explicitly control the UOW/transaction behavior:
- Disable UOW for that GET if it should be pure read:
[UnitOfWork(IsDisabled = true)] public virtual async Task<AgentDto> GetAsync(Guid id) { // read-only code only }- Or if it truly must update, make it a non-GET endpoint and/or force transactional UOW explicitly (so the behavior is clear and consistent).
This pattern (UOW on application service methods + GET requests not starting transactions by default) is documented in ABP’s Unit of Work docs.
Sources:
- https://abp.io/docs/latest/framework/architecture/domain-driven-design/unit-of-work
- https://abp.io/community/articles/0r248xsr
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Hi,
Yes, ABP automatically wraps all public AppService methods in a Unit of Work, including
Getmethods. However, for methods whose names start withGet, the UoW is non-transactional by default (no explicit DB
transaction is created).That said,
SaveChangesAsync()is still called at the end of the method. So if yourGetmethod also calls a repository update, the changes will be persisted — and that would generate theUPDATE ... SET ConcurrencyStamp ... WHERE Id AND ConcurrencyStampSQL you're seeing.Could you share more details about what the
Getmethod is doing? Specifically, why is it calling a repository update method? That would help us understand the root cause.Also, could you enable EF Core debug logging to see the actual SQL being executed? This will help us understand the full picture. You can refer to this guide:
https://abp.io/support/questions/8622/How-to-enable-Debug-logs-for-troubleshoot-problemsThanks
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
AI gives this hint, we will check if this is the reason of it: In EF Core 8 and earlier, when you called SaveChangesAsync(), it would generate and execute SQL statements one at a time:
In EF Core 9, it introduced command batching — multiple statements are combined into a single SET NOCOUNT ON; ... batch sent to SQL Server in one round trip:
In EF Core 8, even if a transaction leaked, the individual autoSave commits had already released locks progressively. With EF Core 9 batching, all the locks are held simultaneously until the entire batch completes — so a leaked transaction holds locks on Clients + Documents + DocumentVersions + Files all at once, for the entire duration.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
ConcurrencyStampis not the reason for the timeout. If theConcurrencyStampmismatches, EF Core/ABP would typically result in a concurrency exception (0 rows affected), not a “wait 30s then timeout”. A timeout on that statement means SQL Server is waiting (usually on locks) or the DB is throttling.Yes, ABP applies Unit of Work to all public Application Service methods, including
Get...methods.
Additionally, by default ABP’s UOW transaction behavior is Auto, and HTTP GET requests don’t start a transactional UOW (no explicit DB transaction is created). But the method is still inside a UOW pipeline, and if you do writes (likeUpdateAsync) inside a GET, it will still persist changes (viaSaveChangesat the end of the UOW / repositoryautoSave, depending on your code).About the “EF Core 9 command batching holds locks longer” hint: I can’t verify that claim from ABP/EF sources in the available context here, so I won’t confirm it. What is verifiable and relevant in ABP is: doing
UpdateAsyncinside a frequently-called GET method can create unexpected write load and lock contention, which does match your symptom.
What to do (definitive ABP guidance):
Remove any update call from GET methods. Split into:
Get...(read-only)Update...(POST/PUT) for writes
This alone often eliminates these “random timeouts”.
If you must keep the GET read-only and want to enforce it, disable UOW or at least make it explicitly non-transactional and don’t call update:
using Volo.Abp.Uow; [UnitOfWork(IsDisabled = true)] public virtual async Task<AgentDto> GetAsync(Guid id) { // read-only }- If the method truly must write (not recommended for GET), make it a non-GET endpoint and (optionally) force a transactional UOW explicitly so behavior is consistent:
[UnitOfWork(IsTransactional = true)] public virtual async Task<AgentDto> TouchAndGetAsync(Guid id) { // do update + read }(verified references)
- https://abp.io/docs/latest/framework/architecture/domain-driven-design/unit-of-work
- https://abp.io/community/articles/0r248xsr
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Hi,
EF Core has actually had command batching since version 1.0, so the batching behavior itself hasn't changed between EF Core 8 and EF Core 9/10. The timeout is more likely related to lock contention — especially if your Get method is calling a repository update, which would trigger
SaveChangesAsyncand generate thatUPDATE ... SET ConcurrencyStampSQL on every request.Could you enable EF Core debug logging, reproduce the issue, and share the logs with us? The debug logs will show the actual SQL queries and updates being executed, which will help us pinpoint what's happening. Please clear/delete old log files before reproducing to keep the log size manageable.
You can follow this guide to enable debug logging: https://abp.io/support/questions/8622/How-to-enable-Debug-logs-for-troubleshoot-problems
Thanks
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
I feel like it is our code base issue, some code hold long transactions
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
I think it might be our code base issue, though it kind of working in ABP 8, but now with ABP 10 we see quite more chances of Zombie transactions session from SQL server, I do find some code using a unit of work, and call await uow.SaveChangesAsync(); without call await uow.CompleteAsync(); Is it the cause? With ABP 10 has unit of work enhancement?
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Yes — not calling
CompleteAsync()afterSaveChangesAsync()in a manually started Unit of Work is a real bug and can absolutely contribute to “zombie”/long-running transactions and blocking. In ABP,SaveChangesAsync()flushes changes to the database within the current UOW, but the transaction is only permanently committed whenCompleteAsync()is successfully executed. IfCompleteAsync()is never called, the UOW is not considered completed; if it’s transactional, it won’t commit (and may hold resources until disposed), and ABP also won’t run the “end-of-UOW” pipeline (events, cleanup, etc.).This is consistent with your symptom: a simple concurrency update (
... WHERE Id AND ConcurrencyStamp) timing out because it’s blocked by another session/UOW that is kept open too long.
What you should change in your code (manual UOW usage)
- Always structure manual UOW like this:
using Volo.Abp.Uow; using (var uow = _unitOfWorkManager.Begin(isTransactional: true)) { // ... do repository/DbContext operations ... await uow.SaveChangesAsync(); // optional (flush mid-way) await uow.CompleteAsync(); // mandatory (commit + finalize UOW) }- If you don’t actually need an explicit transaction (common for imports/logging), start a non-transactional UOW to reduce locking risk:
using (var uow = _unitOfWorkManager.Begin(isTransactional: false)) { // operations await uow.CompleteAsync(); }Is this “more visible” in ABP 10 because of UOW enhancements?
- ABP’s guidance/semantics around UOW are the same:
CompleteAsync()is the method that finalizes a UOW (commit if transactional + executes UOW end tasks). If your code previously “seemed to work” in ABP 8, it was still incorrect; ABP 10 + newer EF Core / higher load patterns can make the consequences (blocking/timeouts) show up more often.
Verified reference
- https://abp.io/docs/latest/framework/architecture/domain-driven-design/unit-of-work
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
what about code like this style: using unit of work
some db operations try { some other db operations } catch { swallow the exception } other db operations unit of work complete
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
- This style is dangerous in a transactional Unit of Work, and it can definitely be a root cause of the blocking/timeouts you’re seeing.
What happens in ABP UOW when you “swallow” exceptions
- If you do DB operations inside a
try { ... } catch { }and swallow the exception, then execution continues and you later callawait uow.CompleteAsync(). - ABP will then try to commit the transaction (and run end-of-UOW actions) as if everything was successful.
- Meanwhile, the caught exception may indicate:
- the DbContext is in a problematic state for the remainder of the UOW,
- or you partially failed an operation and continued doing more writes,
- or you created contention by doing extra work inside the same long transaction.
So this pattern can lead to longer transactions and more lock holding, which matches “zombie” sessions / timeouts.
Correct patterns
A) If an exception means the business operation should fail → do NOT swallow
using (var uow = _unitOfWorkManager.Begin(isTransactional: true)) { // db ops... try { // db ops... } catch { // optionally log throw; // important: abort the operation } // db ops... await uow.CompleteAsync(); }B) If you intentionally want “best effort” and still commit other changes Then don’t keep everything in one big transactional UOW. Either:
- use a non-transactional UOW for the best-effort part, or
- use a separate
requiresNew: trueUOW for the part that can fail independently.
Example (separate UOW for the optional part):
using (var uow = _unitOfWorkManager.Begin(isTransactional: true)) { // main db ops... try { using (var innerUow = _unitOfWorkManager.Begin(requiresNew: true, isTransactional: true)) { // optional db ops... await innerUow.CompleteAsync(); } } catch { // swallow only if it is truly optional (but log it) } // continue main db ops... await uow.CompleteAsync(); }Key rule (ABP-specific)
- In a transactional UOW,
SaveChangesAsync()/autoSavedoes not finalize changes permanently untilCompleteAsync()succeeds. So swallowing exceptions and still callingCompleteAsync()can cause “commit despite internal failures” patterns and longer locking windows. - If you manually begin a UOW,
CompleteAsync()is mandatory to properly finish it.
Links (validated):
- https://abp.io/docs/latest/framework/architecture/domain-driven-design/unit-of-work
- https://abp.io/community/articles/0r248xsr
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
what about code like this, would it be a is transaction true? would the Unit of work complete gracefully or roll back properly when there is exception? [UnitOfWork] public async Task Create30DaysReminderAsync(Guid fileId) { try { some Db operations; await CurrentUnitOfWork.SaveChangesAsync(); } catch { throw; } }
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
- With
[UnitOfWork]on the method, ABP will create/participate in a UOW scope for that method (assuming interception works: the method should bevirtualif needed per ABP rules). Whether it is transactional depends on ABP’s transaction behavior (default isAutofor HTTP requests: GET is non-transactional, others transactional). If you want it guaranteed transactional, specify it:
[UnitOfWork(IsTransactional = true)] public virtual async Task Create30DaysReminderAsync(Guid fileId) { // ... }- In your exact code:
[UnitOfWork] public async Task Create30DaysReminderAsync(Guid fileId) { try { // some Db operations; await CurrentUnitOfWork.SaveChangesAsync(); } catch { throw; } }Rollback / commit behavior:
If an exception is thrown out of the method (and you
throw;does exactly that), thenCompleteAsync()will not be successfully executed, so:- If the UOW is transactional: the transaction will roll back.
- If the UOW is non-transactional: changes already flushed by
SaveChangesAsync()are already persisted and cannot be rolled back (by definition of non-transactional UOW).
Your
try/catch { throw; }is effectively redundant (same as not catching), but it does not harm rollback behavior.Important nuance from ABP docs:
SaveChangesAsync()(and repositoryautoSave) flushes changes, but they are not permanently committed untilCompleteAsync()is called successfully in a transactional UOW. IfCompleteAsync()is not called (or the method exits by exception), ABP rolls back the transaction.
Validated source:
- https://abp.io/docs/latest/framework/architecture/domain-driven-design/unit-of-work
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) - With
-
0
when a method has [UnitOfWork], should it call unit of work complete automatically by ABP?
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Yes. When you use the
[UnitOfWork]attribute, ABP handles the Unit of Work lifecycle for you via interceptors: it begins the UOW, runs your method, and if the method finishes successfully it callsCompleteAsync()automatically. If the method throws an exception, ABP does not complete the UOW; if it was transactional, it will be rolled back when the UOW is disposed.You should not manually call
CompleteAsync()in a method that is already managed by[UnitOfWork](or by the application service UOW convention). You only callCompleteAsync()when you start a UOW manually withIUnitOfWorkManager.Begin(...).Important: for
[UnitOfWork]to work, the method generally must be interceptable (commonly:public virtualin a DI-resolved class).
Sources (validated):
- https://abp.io/docs/latest/framework/architecture/domain-driven-design/unit-of-work
- https://abp.io/community/articles/0r248xsr
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
What about code style like below, when there is exception, could unit of work roll back or terminate gracefully? [UnitOfWork] public virtual async Task<SaveResult> UpdateDocumentsAsync(Guid id, Guid[] checklistDocumentIds) { try { some db operations
} catch (Exception ex) { _logger.LogException(ex, LogLevel.Error); } }
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
With
[UnitOfWork], ABP will automatically callCompleteAsync()if your method returns successfully (i.e., no exception escapes the method). If an exception escapes, ABP will not complete it; if it was transactional, it will roll back when disposed.In your code, the exception is swallowed, so from ABP’s perspective the method completed successfully. That means:
- ABP will proceed to complete/commit the UOW (if it is transactional).
- So it will not roll back due to that exception (because you didn’t let the exception propagate).
- Any changes already made in the UOW may still be persisted (depending on what actually succeeded before the exception).
So: this does terminate “gracefully” from ABP’s viewpoint, but it does not roll back. It commits whatever is in the UOW unless you explicitly roll it back or rethrow.
Correct options
Option A (recommended): log + rethrow
[UnitOfWork] public virtual async Task<SaveResult> UpdateDocumentsAsync(Guid id, Guid[] checklistDocumentIds) { try { // db operations return SaveResult.Success(); } catch (Exception ex) { _logger.LogException(ex, LogLevel.Error); throw; // ensure ABP doesn't complete/commit the UOW } }Option B: if you intentionally want to swallow, explicitly rollback (only meaningful if the UOW is transactional)
using Volo.Abp.Uow; [UnitOfWork] public virtual async Task<SaveResult> UpdateDocumentsAsync(Guid id, Guid[] checklistDocumentIds) { try { // db operations return SaveResult.Success(); } catch (Exception ex) { _logger.LogException(ex, LogLevel.Error); await CurrentUnitOfWork.RollbackAsync(); // prevents commit return SaveResult.Failed(ex.Message); } }Source (verified)
- https://abp.io/docs/latest/framework/architecture/domain-driven-design/unit-of-work
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
if the code doesn't roll back, would it creates zombie transaction?
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Not rolling back does not, by itself, create a “zombie transaction”. A “zombie” session/transaction (long-running
sleepingsession holding locks, blocking others) is typically caused by a transaction that remains open for a long time (e.g., long-running UOW, dead code path that never completes, waiting on external I/O inside a transactional UOW, etc.).In the pattern you showed earlier (catch + swallow inside a
[UnitOfWork]method), what usually happens is:- The exception is swallowed, so ABP thinks the method succeeded and will call
CompleteAsync()automatically. - If the UOW is transactional,
CompleteAsync()will commit. That means the transaction ends, so it should not become a zombie because of the swallow.
- The exception is swallowed, so ABP thinks the method succeeded and will call
The swallow pattern can still indirectly contribute to long blocking because it may:
- keep the method doing more work after a failure (making the UOW longer),
- or leave the DbContext in a bad state causing retries/slow operations,
- or cause repeated failures under load (more contention), but the key is still how long the transaction stays open, not “no rollback”.
What does commonly create zombie/long transactions in ABP
Manual UOW started but never completed
- Calling
SaveChangesAsync()but forgettingCompleteAsync()is a classic cause of “open transaction until dispose/timeout”. - ABP docs explicitly state that changes are only permanently committed after
CompleteAsync()in a transactional UOW, and ifCompleteAsync()is not called the transaction is rolled back when it ends—however, if the scope is kept alive too long, it can hold locks too long.
- Calling
Long-running work inside a transactional UOW
- Doing network calls, file I/O, heavy loops, waiting for other services, etc. inside a transactional UOW keeps locks longer.
Calling
UpdateAsyncfrom a frequently hit GET- Even if it’s non-transactional by default, it creates constant writes and can amplify contention.
What to enforce in code
- For
[UnitOfWork]methods: do not swallow exceptions unless the failure is truly optional; if you swallow, consider explicitly callingawait CurrentUnitOfWork.RollbackAsync()to guarantee no commit when transactional. - For manual UOW: always:
using (var uow = _unitOfWorkManager.Begin(isTransactional: true)) { // db work await uow.CompleteAsync(); }Reference (verified):
- https://abp.io/docs/latest/framework/architecture/domain-driven-design/unit-of-work
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Hi,
Let me address your questions one by one:
1. Calling
uow.SaveChangesAsync()withoutuow.CompleteAsync()This is a bug.
SaveChangesAsync()only flushes pending changes to the database within the current transaction — it does NOT commit. OnlyCompleteAsync()commits the transaction. If you skipCompleteAsync(), theusingblock will dispose the UoW and the transaction will be rolled back. So your changes are lost, but it's not a zombie transaction.2.
[UnitOfWork]attribute behaviorYes, ABP automatically calls
CompleteAsync()for you when the method returns successfully. You should NOT manually callCompleteAsync()in a[UnitOfWork]method. You only call it when you manually start a UoW via_unitOfWorkManager.Begin(...).3.
[UnitOfWork]+ try/catch/throw[UnitOfWork] public virtual async Task Create30DaysReminderAsync(Guid fileId) { try { /* db ops */ } catch { throw; } }When the exception propagates out of the method, ABP won't call
CompleteAsync(), so the transaction rolls back correctly. Thetry/catch/throwis redundant here (same as not catching), but it does not break rollback behavior.4.
[UnitOfWork]+ swallow exception (no rethrow)[UnitOfWork] public virtual async Task<SaveResult> UpdateDocumentsAsync(...) { try { /* db ops */ } catch (Exception ex) { _logger.LogException(ex, LogLevel.Error); } // swallowed }Because the exception is swallowed, ABP thinks the method succeeded and calls
CompleteAsync(), which commits the transaction. This means partial/inconsistent data can be committed. If you really want to swallow, you should explicitly callawait CurrentUnitOfWork.RollbackAsync()inside the catch block, or rethrow the exception.5. Will it create a zombie transaction?
No. As long as the UoW is wrapped in a
usingblock (or managed by[UnitOfWork]), it will always be disposed properly — the underlyingDbTransactionis released either via commit or rollback on disposal. A true zombie/orphan transaction would only happen if you bypass ABP's UoW (e.g., callingDbContext.Database.BeginTransaction()directly without disposing) or if the process crashes mid-transaction. What you're seeing is more likely long-held locks from long-running transactions, not zombie transactions.I wrote an article that explains all of this in detail, please take a look:
https://abp.io/community/articles/understanding-transactions-in-abp-unit-of-work-0r248xsr
Thanks
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)