Activities of "maliming"

: )

Hi,

We've fixed it on our side, and it will ship in the next 10.5.x patch. Until then the workaround above (uncheck Create unit & integration tests for that entity) keeps you unblocked.

Thanks

Since this turned out to be a Suite bug, we've refunded your ticket.

Thanks

Hi,

This is a Suite bug, not something wrong on your side. It shows up because Location is a child entity in a master-detail relationship. Child entities don't get their own data seed contributor generated under *.Domain.Tests, but when you add a required navigation property that points to one, Suite still tries to read that missing LocationsDataSeedContributor.cs while generating the test seeder for ProcessApproval — so it fails with the "Could not find a part of the path" error. That's also why removing LocationId lets it generate: BusinessUnit is a master entity and does have that file.

As a temporary fix, uncheck Create unit & integration tests on the ProcessApproval entity and generate again:

  • Open ProcessApproval in Suite
  • On the Entity info tab, untick Create unit & integration tests
  • Save and generate

Suite then skips the test seeders entirely, so it generates fine and your required BusinessUnitId / LocationId FKs stay as they are. You can turn tests back on and regenerate once it's fixed.

We'll get this fixed on the Suite side.

Thanks

Hi Paul,

Good news, bulk external editing is possible. The Template Editing UI is only one way in, but under the hood Suite stores each customized template as a plain UTF-8 text file on disk, so you can edit them in any external editor and Suite picks them up at generation time.

How it works:

  • Customized templates live in <your-solution-folder>/.suite/customized-templates/, one file per template (the file name is the template resource name, e.g. Server.Repository.EfCoreRepository.txt).
  • At generation time Suite uses that file if it exists, otherwise it falls back to the default template. So editing these files externally takes effect on the next generation, no need to open each one in the UI.
  • The first line of each file is a metadata header ##<MD5>##. Suite always strips the first line before using the template, so you must keep that line intact and put your edits below it. The hash itself is only used to detect whether the default template changed after an upgrade.

To get all the templates onto disk in one go, you can extract them from the Volo.Abp.Commercial.SuiteTemplates.dll that ends up in your solution's build output (for a tiered app it's under the *.HttpApi.Host project's bin folder). Build the solution first so the DLL is there, then run this from your solution folder:

param(
    [string]$Dll,
    [switch]$Force
)

$prefix = "Volo.Abp.Commercial.SuiteTemplates.Templates."

if (-not $Dll) {
    $Dll = Get-ChildItem -Recurse -Filter "Volo.Abp.Commercial.SuiteTemplates.dll" |
           Sort-Object LastWriteTime -Descending | Select-Object -First 1 -ExpandProperty FullName
}
if (-not $Dll -or -not (Test-Path $Dll)) {
    throw "SuiteTemplates.dll not found. Build the solution first, or pass -Dll <path>."
}

# Suite uses the legacy folder if it exists, otherwise .suite/customized-templates
$legacy = Join-Path (Get-Location) "customized-suite-templates"
$outDir = if (Test-Path $legacy) { $legacy } else { Join-Path (Get-Location) ".suite\customized-templates" }
New-Item -ItemType Directory -Force -Path $outDir | Out-Null

# Load by bytes so the DLL in bin is not locked
$asm  = [System.Reflection.Assembly]::Load([System.IO.File]::ReadAllBytes((Resolve-Path $Dll)))
$md5  = [System.Security.Cryptography.MD5]::Create()
$utf8 = New-Object System.Text.UTF8Encoding($false)

Write-Host "DLL   : $Dll"
Write-Host "Output: $outDir"

$written = 0; $skipped = 0
foreach ($name in $asm.GetManifestResourceNames()) {
    if (-not $name.StartsWith($prefix)) { continue }
    if (-not $name.EndsWith(".txt"))    { continue }   # text templates only

    $file = $name.Substring($prefix.Length)
    $path = Join-Path $outDir $file
    if ((Test-Path $path) -and -not $Force) { $skipped++; continue }

    $reader  = New-Object System.IO.StreamReader($asm.GetManifestResourceStream($name))
    $content = $reader.ReadToEnd(); $reader.Dispose()

    $hash = (($md5.ComputeHash([System.Text.Encoding]::ASCII.GetBytes($content)) |
             ForEach-Object { $_.ToString("X2") }) -join "")

    [System.IO.File]::WriteAllText($path, "##$hash##`r`n" + $content, $utf8)
    $written++
}
Write-Host "Written: $written  Skipped (already exist): $skipped"

A couple of things worth knowing:

  • The script writes the header line with CRLF. If your editor rewrites files to LF-only, the first-line stripping can misfire and produce empty output, so make sure the editor preserves the existing line endings (and keeps that first ##...## line).
  • It skips files that already exist unless you pass -Force, so re-running it after an upgrade won't wipe your own edits.
  • Every file you keep in that folder shadows the default template for good. After an upgrade, only the ones whose default actually changed get flagged as outdated, but any file you leave there will keep overriding future template improvements. So I'd recommend keeping only the templates you actually customize and deleting the rest, rather than leaving all of them in place.

Thanks

Hi,

Yes, a large Migrations/ folder is very likely the main cost here. Each migration ships with a *.Designer.cs file whose BuildTargetModel embeds the full model snapshot at that point in time. With a big ABP model plus your own entities that's easily thousands of lines per file, so hundreds of migrations means hundreds of full snapshots the compiler has to parse and build every time. That's what pushes compile time (and CPU) up as the folder grows.

The safe way to fix this without touching your existing schema is to re-baseline (squash) the migrations. EF Core still has no built-in squash command (see https://github.com/dotnet/efcore/issues/2174), so it's a manual step:

  1. Make sure every environment/database is already migrated up to the latest migration.
  2. Delete the Migrations folder in your *.EntityFrameworkCore project.
  3. Create a single new baseline migration:
dotnet ef migrations add Initial
  1. For each existing database, insert one row into __EFMigrationsHistory so EF treats the new baseline as already applied (the tables already exist, so it must not run). Don't clear the existing rows — leave them in place. Example for SQL Server (adjust for your DB provider):
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20260715120000_Initial', N'9.0.0');

Use the exact MigrationId of the file EF just generated, and your EF Core version for ProductVersion.

New/empty databases are unaffected — the baseline migration runs normally and creates everything.

A few things to keep in mind:

  • Why keep the old history rows: EF only re-runs a migration if its ID exists in your project but not in __EFMigrationsHistory. After step 2 the old IDs are gone from your project, so they'll never re-run. Leaving them in the table keeps your audit/rollback trail and avoids any window where a stray Migrate() tries to re-create existing tables.
  • If any old migration created objects EF can't model (views, triggers, functions via raw migrationBuilder.Sql(...)), copy those into the new baseline so fresh databases still get them. One-off data fixes or column renames from the history don't need to be carried over — a new database only needs the final schema.
  • If you run the same app across multiple customer/tenant databases (or have offline installs), bring them all up to the same latest migration before you switch. A database that's left behind can't be upgraded from the new baseline anymore. The schema stays the same, but the cutover itself is a deployment step worth planning.

Keep using your .DbMigrator project as the deployment entry point as usual — this only changes how many migration files get compiled, not how you apply migrations or seed data.

Thanks

Hi,

ABP-LIC-0016 comes from the runtime module license check. It only runs during local development — when you run the app from your IDE (a debugger is attached) or with ASPNETCORE_ENVIRONMENT=Development. It reads the license from the account you're signed in with via abp login on that machine, asks the server which modules that account is granted, and checks the loaded assemblies against it. So the message means the signed-in account isn't granted these modules for v10.

Account.Pro.Admin and TextTemplateManagement are included in all commercial licenses (Team included), so this isn't a tier problem. Since you're on a tiered/separated setup, the two names are just the modules that logged it in your AuthServer and API host.

The usual cause is the signed-in account. Having a license on the organization, or being only an Owner, isn't enough — the tools grant the license to accounts that hold a Developer seat.

  1. Open https://abp.io/my-organizations and check whether the account you use is listed under Developers (not only under Owners). You can be both at the same time.
  2. If it's missing there, an organization owner adds it to the Developers list.
  3. Re-login on the machine you run the app from — the membership is cached, so restarting isn't enough:
abp logout
abp login <username>

Then start the app again.

If it still fails after signing in with a correct Developer account, send us the account/email you use plus the full startup logs from both hosts (in particular whether an ABP-LIC-ERROR line follows the ABP-LIC-0016), and we'll check what's granted for that account on our side.

To confirm which account is active on the machine showing the error, run abp login-info and share the account it prints.

Thanks

Hi,

We've refunded the credit for this question. Thanks again for the clear write-up — it made confirming the issue straightforward.

Thanks

Hi,

You're right, this is a bug. WorkspaceConfigurationStore builds the cache key from the reading process's ApplicationName, while WorkspaceChangedHandler removes the key built from the entity's ApplicationName. When those differ (a workspace seeded by the DbMigrator but read from another host), the edit removes the entity-scoped key rather than the one the reading host actually uses, so the configuration stays cached until it expires.

This is a confirmed bug on our side and we'll get it fixed. I'll follow up here with the release once it's available.

For a stopgap in the meantime, you can key the cache by workspace name only. The store extension points this needs are virtual only from 10.4.1 onward, so you'd need to update to 10.4.1 or later first. Then replace the store:

using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.AIManagement.Factory;
using Volo.AIManagement.Workspaces;
using Volo.AIManagement.Workspaces.Configuration;

[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(WorkspaceConfigurationStore), typeof(IWorkspaceConfigurationStore))]
public class MyWorkspaceConfigurationStore : WorkspaceConfigurationStore
{
    public MyWorkspaceConfigurationStore(
        IWorkspaceRepository workspaceRepository,
        IDistributedCache<ChatClientCreationConfiguration> singleItemCache,
        IApplicationInfoAccessor applicationInfoAccessor)
        : base(workspaceRepository, singleItemCache, applicationInfoAccessor)
    {
    }

    public override async Task<ChatClientCreationConfiguration?> GetOrNullAsync(string name)
    {
#pragma warning disable CS8603
        var cacheKey = $"WorkspaceConfiguration:{name}";
        return await SingleItemCache.GetOrAddAsync(cacheKey, async () => await GetFromRepositoryAsync(name));
#pragma warning restore CS8603
    }
}

And add a handler that invalidates the same name-only key (the built-in handler still runs and removes the old application-scoped key, which is harmless):

using System.Threading.Tasks;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.EventBus;
using Volo.AIManagement.Factory;
using Volo.AIManagement.Workspaces;

public class MyWorkspaceCacheInvalidator :
    ILocalEventHandler<EntityChangedEventData<Workspace>>,
    ITransientDependency
{
    private readonly IDistributedCache<ChatClientCreationConfiguration> _cache;

    public MyWorkspaceCacheInvalidator(IDistributedCache<ChatClientCreationConfiguration> cache)
    {
        _cache = cache;
    }

    public Task HandleEventAsync(EntityChangedEventData<Workspace> eventData)
    {
        return _cache.RemoveAsync($"WorkspaceConfiguration:{eventData.Entity.Name}", considerUow: true);
    }
}

Deployment:

  • Put the store replacement in every server process that reads workspace configuration from the database (the ones resolving through the local WorkspaceConfigurationStore), and the invalidator in every server process that updates workspaces through the app service/repository.
  • They must use the same distributed cache backend and the same AbpDistributedCacheOptions.KeyPrefix.
  • A migrator that only seeds and never reads afterward doesn't need it.
  • If you run a tiered setup where some hosts fetch configuration over HTTP, the client process itself doesn't need the replacement, but the AI Management host that serves that endpoint does — otherwise those clients still get stale data.

A couple of boundaries: it covers edits that keep the same workspace name — a rename also needs the old name's entry removed, so hold off on renames until the fix ships. And the cache key is tenant-scoped by default, so keep both the resolve and the edit in the host context for now.

On your related note: AIManagementChatClient<T> resolves the inner client once and reuses it, and IChatClient<T> is registered transient, so a Blazor circuit or a long-lived worker that holds the instance keeps the configuration from its first call even after the cache is fixed. Injecting it in a short-lived request scope is fine. For long-lived consumers, create a DI scope per operation (per chat call or per job run) with IServiceScopeFactory.CreateAsyncScope(), resolve IChatClient<T> inside it, and dispose the scope afterward — don't keep the resolved instance in a circuit or singleton field.

Thanks

Hi,

ReIndex All enqueues one background job per document, so all of them are expected to run one after another. To find out why the ones after the first stay on Processing, we need to see what the second document's job does.

Could you enable Debug logs for these namespaces and run ReIndex All once more:

"Logging": {
  "LogLevel": {
    "Volo.AIManagement": "Debug",
    "Volo.Abp.BackgroundJobs": "Debug"
  }
}

Here's how to enable Debug logs if you need it: https://abp.io/support/questions/8622/How-to-enable-Debug-logs-for-troubleshoot-problems

Then send the full log starting from when you click ReIndex All. The key part is what happens right after the first Successfully indexed document ... line — for the second document, does the log:

  • never show Starting document indexing ... (the job didn't start),
  • show Starting ... but then no Successfully indexed and no error (it's stuck on a step), or
  • show a Failed to index document ...?

Two quick questions so we look in the right place:

  1. Which background job provider are you using — the default one or Hangfire?
  2. Which embedder provider is configured for the workspace (OpenAI, Azure OpenAI, or a local/self-hosted one)?

Thanks

Showing 261 to 270 of 12921 entries
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.8.0-preview. Updated on November 02, 2026, 06:56
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.