Skip to main content

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():

OptionDefaultEffectCost
CountRowstrueCount 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).
MaxTables0 (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:

  1. Enumerate every data entity view via MetadataSupport::GetDataEntityViewNames() - this is cross-package (it sees the whole deployed application, not just Lattice).
  2. 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.
  3. Store two maps keyed on strLwr(tableName): coverageNames (de-duplicated, comma-joined entity list) and coverageCount (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. CoveringEntities is capped at CoveringEntitiesMaxLen = 1000; past that the list is cut and suffixed .... CoveringEntityCount is not truncated, so a table can show CoveringEntityCount = 40 while CoveringEntities lists only the first ~30 names. Trust the count, not the string, for "how many".
  • Config-key-disabled entities are skipped. Entities whose ConfigurationKey is 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 as HasEntity = No even though an entity technically surfaces their data. Treat HasEntity = No as "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() reads AxTable.SaveDataPerCompany from metadata. Shared (SaveDataPerCompany = No) → counted once, CompanyId blank, 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 a Query over the table id and calls SysQuery::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 via MetadataSupport::GetModuleOfMetadataArtifact (fails soft to blank).

Snapshot columns (MGCLatticeTableEntityCoverageSnapshot)​

ColumnMeaning
SnapshotIdRun id. nextSnapshotId() = max existing id + 1. Every Analyze run is a new id; rows from different runs coexist in the table.
SnapshotDateTimeUTC time the run started (same for every row in the run).
TableNameAOT table name.
SourceTableIdTable id at snapshot time.
CompanyIdLegal entity for a per-company row; blank for shared tables.
RowCountRecord count (0 when CountRows=false or the table can't be counted).
IsSharedTableYes = shared (counted once); No = per-company.
TableGroupThe table's TableGroup (Main, Parameter, Transaction, …) as text.
ModelNameOwning model/module, or blank if unresolved.
HasEntityYes = at least one data entity reads this table (subject to the resolution limits above).
CoveringEntityCountNumber of covering entities (accurate; not truncated).
CoveringEntitiesComma-joined entity names, truncated at 1000 chars.

Index: CoverageIdx.

Reading the output for migration planning​

  • Tables to build entities for: RowCount > 0 and HasEntity = 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 = 0 tables are unpopulated in this environment; deprioritise.
  • Always filter to a single SnapshotId first - 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).