Table Data & Entity Coverage
A diagnostic that snapshots every eligible table in the deployed application and records, per table: its row count, whether it is shared or per-company, its table group, the owning model/module, and which data entities read that table. The output is a planning aid for gold-configuration and data-migration work - it answers "which tables actually hold data, and can I reach each one through a data entity?"
When to run it
- Migration / integration planning - find tables that hold data but have no covering data entity (they can't be exported/imported through DMF/OData without a new entity), and tables covered by several entities (pick the right one).
- Gold-configuration review - inventory which configuration tables are populated and by how much.
- Not a routine job - it is an on-demand analysis. Each run writes a new, independent snapshot.
Run it from System administration → Inquiries → Lattice data entities → Table data and entity coverage (the
MGCLatticeTableEntityCoverage form) via the Analyze menu item
(MGCLatticeTableEntityCoverageAnalyze → …Controller → …Service.analyze()), or schedule
…Controller as a batch.
Run options (contract) and their cost
MGCLatticeTableEntityCoverageContract - defaults set in new():
| Option | Default | Effect | Cost |
|---|---|---|---|
CountRows | true | Count records per table with a count(RecId) aggregate. Clear it for a metadata-only run (row counts = 0). | The dominant cost. One aggregate count per table (in the current company for per-company tables). |
MaxTables | 0 (no cap) | Stop after N eligible tables. | Use a small cap for a quick sample. |
One row per table. Entity coverage is a global metadata fact, so the analysis records exactly one row per eligible table and is deliberately not broken down per legal entity. For a per-company table the row count (when requested) is the current company's; the coverage columns are company-independent.
Cost guidance. CountRows=true on a large database is the expensive setting: it runs an aggregate
count over the full row set of every eligible table. For a fast structural pass (coverage + scope + module,
no counts), run with CountRows=false - the covering-entity index and all metadata columns are still
populated.
How covering-entity resolution works
buildCoverageIndex() builds a reverse index once per run:
- Enumerate every data entity view via
MetadataSupport::GetDataEntityViewNames()- this is cross-package (it sees the whole deployed application, not just Lattice). - For each entity, read
MetadataSupport::GetDataEntityView(name)and walk its query data sources (ViewMetadata.DataSources) recursively (collectDataSourceTables()), recording every table each data source (and nested data source) reads. - Store two maps keyed on
strLwr(tableName):coverageNames(de-duplicated, comma-joined entity list) andcoverageCount(number of covering entities).
analyze() then walks the whole Dictionary (tableCnt/tableCnt2Id), skips ineligible tables,
and for each eligible table looks up its covering entities in the index and writes the snapshot
row(s).
Limits of coverage resolution
- 1000-character truncation.
CoveringEntitiesis capped atCoveringEntitiesMaxLen = 1000; past that the list is cut and suffixed....CoveringEntityCountis not truncated, so a table can showCoveringEntityCount = 40whileCoveringEntitieslists only the first ~30 names. Trust the count, not the string, for "how many". - Config-key-disabled entities are skipped. Entities whose
ConfigurationKeyis disabled don't contribute coverage - correct for "what's reachable in this configuration", but means the result is configuration-relative, not absolute. - Query-data-source-backed only. Resolution walks
ViewMetadata.DataSources.Table. An entity with no query data sources - e.g. a purely computed/virtual entity, or one whose backing is not expressed as a simple data-source table - records no coverage. Such tables may read asHasEntity = Noeven though an entity technically surfaces their data. TreatHasEntity = Noas "no query-data-source entity found", not a hard guarantee of no entity. - Best-effort: entities whose metadata can't be read are silently skipped (coverage is advisory).
Which tables are analyzed - isEligible()
Excluded: views, temp tables, maps, system tables, tables in the Staging table
group, and any table whose name ends in Tmp. Everything else is analyzed. Rationale: these hold
no durable business data of migration interest (temp/staging/system) or aren't real tables
(views/maps).
Shared vs per-company, and row counting
isSharedTable()readsAxTable.SaveDataPerCompanyfrom metadata. Shared (SaveDataPerCompany = No) → counted once,CompanyIdblank,IsSharedTable = Yes. Per-company → also one row,CompanyId= the current company,IsSharedTable = No(any row count is the current company's). The analysis is never broken down per legal entity because coverage is global metadata.countRows()uses kernel reflection: it builds aQueryover the table id and callsSysQuery::countTotal(queryRun). Tables that can't be counted generically (specialized kernel tables) fail soft to 0 rather than erroring.resolveModule()attributes each table to its owning model/module viaMetadataSupport::GetModuleOfMetadataArtifact(fails soft to blank).
Snapshot columns (MGCLatticeTableEntityCoverageSnapshot)
| Column | Meaning |
|---|---|
SnapshotId | Run id. nextSnapshotId() = max existing id + 1. Every Analyze run is a new id; rows from different runs coexist in the table. |
SnapshotDateTime | UTC time the run started (same for every row in the run). |
TableName | AOT table name. |
SourceTableId | Table id at snapshot time. |
CompanyId | Legal entity for a per-company row; blank for shared tables. |
RowCount | Record count (0 when CountRows=false or the table can't be counted). |
IsSharedTable | Yes = shared (counted once); No = per-company. |
TableGroup | The table's TableGroup (Main, Parameter, Transaction, …) as text. |
ModelName | Owning model/module, or blank if unresolved. |
HasEntity | Yes = at least one data entity reads this table (subject to the resolution limits above). |
CoveringEntityCount | Number of covering entities (accurate; not truncated). |
CoveringEntities | Comma-joined entity names, truncated at 1000 chars. |
Index: CoverageIdx.
Reading the output for migration planning
- Tables to build entities for:
RowCount > 0andHasEntity = No- populated tables with no query-data-source entity. These are the gaps a migration/integration must close. - Disambiguate export path:
CoveringEntityCount > 1- more than one entity reads the table; choose based on shape and performance. - Skip the noise:
RowCount = 0tables are unpopulated in this environment; deprioritise. - Always filter to a single
SnapshotIdfirst - the table interleaves every run.
Retention
Lattice does not prune snapshots. nextSnapshotId() only ever increments; nothing deletes old
snapshots, so the table grows by a full table-inventory every run (≈ one row per eligible table).
Recommended retention: keep the few most recent runs and periodically delete older SnapshotIds (a
scheduled cleanup, or manual delete_from by SnapshotDateTime).