abpframework / abpframework/abp

Low-Code: fixed per-mutation overhead — distributed lock + blob manifest read, post-mutation re-read, uncached descriptor hash

Open
#26,095 1 comment 0 reactions 1 assignee View on GitHub

@salihozkara is already working on this.

Since Aug 31, 2026.

Dominant language
C#
Stars
14.4k
Forks
3.7k
Avg merge
15h 32m
Merged PRs (30d)
106

Description

Description

Every Low-Code create/update/delete carries fixed overhead that is paid even in the common case where none of it is needed:

  1. Distributed lock + blob read per mutation. DynamicEntityMutationGate.ExecuteAsync wraps every mutation and (a) reads the entity-blob-cleanup manifest from the blob container (EntityBlobCleanupManifestStore.GetIntentAsync — a blob read + JSON parse), then (b) acquires IAbpDistributedLock.TryAcquireAsync. With a Redis lock provider and remote blob storage this is 2+ extra network round-trips on every mutation of every dynamic entity — including entities that have no file/image properties at all, which is what the manifest/gate machinery exists for.
  2. Full re-read to build the response. After a create or update, the pipeline re-reads the record via a full SingleEntityQuery (with relation joins and collection hydrators) to build the returned DynamicEntityDto — an extra SELECT (plus hydrator queries) per mutation, where a conventional CrudAppService maps the tracked entity it already has.
  3. Uncached descriptor-version hash per SaveChanges. DynamicEntityStaleModelSaveChangesInterceptor compares model versions on every SaveChanges that touches dynamic entities; computing the current descriptor version (DynamicModelManager.GetEntityDescriptorVersion) serializes the descriptor to JSON and hashes it on every call — the result is deterministic for a given model Version and could be cached until the model changes. (The DB round-trip correctly happens only on version mismatch, and SuppressStaleModelCheck() exists — this point is only about the recomputed hash.)
Configuration
  • ABP 10.7.0-rc.3 (Volo.Abp.LowCode.Application / .EntityFrameworkCore / .Domain), Low-Code module
  • .NET 10, PostgreSQL, app-nolayers modular monolith
Regression?

Not known to be a regression.

Data

Static inspection of the shipped assemblies (decompiled for analysis), not yet profiled. The gate path (manifest read + TryAcquireAsync), the post-mutation SingleEntityQuery re-read, and the per-call serialize+hash in GetEntityDescriptorVersion are directly visible in the mutation pipeline.

Analysis

Suggested directions:

  • Skip the mutation gate (manifest read + distributed lock) entirely for entity types whose descriptor declares no file/image/blob-backed properties — the descriptor is available before the gate runs.
  • Build the response DTO from the tracked entity plus the already-known FK display values where possible, falling back to the re-read only when deferred/computed projections require it.
  • Cache the descriptor-version hash keyed by (entity name, model Version), invalidated by the existing model-changed notification.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.