7153a1974e
Measured first: a scratch scaling probe (seed -> export -> load/preview/apply into a fresh target and over itself, in-memory provider, every repository call counted, DetectChanges counted via ChangeTracker.DetectingAllChanges) showed load, preview and the instance pass scale linearly (500 / 1000 / 2000 instances apply in 0.13 / 0.27 / 0.35 s), but the post-flush template rewire passes were quadratic: 100 / 400 / 800 composing, derived templates applied in 0.5 / 8.1 / 26.5 s, with DetectChanges running 5N+10 times and GetTemplateWithChildrenAsync 2N+2 times. Cause: ResolveAlarmScriptLinksAsync, ResolveCompositionEdgesAsync and ResolveInheritanceEdgesAsync found each template with _dbContext.Templates.Local.FirstOrDefault(...) - per template, and per composition edge / base lookup. Every DbSet.Local access runs a full DetectChanges over everything the import has staged. The first two passes also re-read every template's children one query at a time (a split query per call on SQL Server). Fix (no bundle-format or semantic change): - each pass indexes the tracked templates by name once (SnapshotLocalTemplatesByName; ordinal, first wins like FirstOrDefault). These passes never stage a template or rename one, so the index stays exact for the pass; - the alarm and composition passes read their templates' children in one batched GetTemplatesWithChildrenAsync, matched back by the stub's id; - the alarm pass only loads templates whose bundled alarms name an on-trigger script (the only ones it can rewire). Result on the same probe: 100 / 400 / 800 templates apply in 0.13 / 0.62 / 0.64 s; DetectChanges is a constant 9; per-template children reads 0, batched reads 1 (add) / 2 (overwrite). Evidence: ImportApplyScalingTests (Transport.IntegrationTests) exports 5 and 40 composing, derived templates with alarm -> script links, imports each into an empty target (Add) and again (Overwrite), and asserts the imported inheritance / composition / alarm-script links, zero per-template children reads, at most 3 batched reads, and an identical DetectChanges count for 5 and 40 templates. Fails on the old importer (12 per-template reads), and with a per-DTO Local snapshot reintroduced in one pass (DetectChanges 51 vs 16). Transport unit (198) and integration (138) suites pass. Not changed: preview's per-name lookups for database connections, notification lists and API methods stay per-name - moving them to an in-memory ordinal match could change name matching under a case-insensitive SQL collation. Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV