MemberJunction / MemberJunction/MJ

IS-A: virtual (joined display-name) fields come back null on any record that has a subtype child

Open
#4,403 0 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
TSQL
Stars
29
Forks
6
Avg merge
1d 8h
Merged PRs (30d)
308

Description

## Summary

When a record has an IS-A child, every **virtual** (joined display-name) field on the parent comes back `null` on the client, even though the server returns the values correctly. The same entity loaded for a record *without* an IS-A child hydrates its virtual fields normally. Any form, panel or column that binds a joined name on a subtyped record therefore renders blank.

## Evidence

Measured on MJ `6.1.0-edge.5` with the BizApps Orders open app (`@mj-biz-apps/orders-ng` 5.11.0), Chromium, 2026-09-11. `Record.Fields.filter(f => f.EntityFieldInfo.IsVirtual)` read straight off the mounted form's record:

| Record | IS-A child | Virtual fields on the loaded record |
|---|---|---|
| Product `STYLE-HB` | none (`ISAChild = null`) | `ProductType="Physical Good"`, `ProductCategory="Handbooks"`, `Company="Blue Cypress Press"` |
| Product `HH-CONF` | `MJ_BizApps_Orders: Event Products` | `ProductType=null`, `ProductCategory=null`, `Company=null`, `RevenueRecognitionType=null` |
| Product `CONF-2027` | `MJ_BizApps_Orders: Event Products` | all six virtual fields `null` |
| Order Line `8817A52A` | none | `OrderHeader="ORD-000001"`, `Product="Style Handbook"`, `Company="Blue Cypress Press"` |
| Order Line `07CE632A` | `MJ_BizApps_Orders: Event Order Lines` | `OrderHeader=null`, `Product=null`, `Company=null`, `ProductPrice=null` |

Two unrelated parent entities, same split: child present means nulls, child absent means values. The FK id columns are correct in every case — only the joined names are lost.

## It is not the server, and not the query

The client asks for the fields. Captured request body for the single-record load:

```
query SinglemjBizAppsOrdersProduct ($ID: String!) { mjBizAppsOrdersProduct(ID: $ID) { ID Name SKU ProductTypeID … ProductType ProductCategory Company SuccessorProduct RevenueRecognitionType SubscriptionType } }
```

The server answers with the values. Captured response for `STYLE-HB`:

```
{"data":{"mjBizAppsOrdersProduct":{"ID":"823B4661-…","ProductTypeID":"0AFB4A94-…","ProductType":"Physical Good","ProductCategory":"Handbooks","Company":"Blue Cypress Press", …}}}
```

The generated resolver reads the base view (`SELECT * FROM vwProducts WHERE ID = @0`), the view holds the correct values (checked in SQL), and the `__mj.EntityField` rows for all six virtual fields exist with `IsVirtual = 1`. So the loss happens client-side, after the response arrives.

## Where I would look

The IS-A chaining path in `packages/MJCore/src/generic/baseEntity.ts` — `EnsureISAChild()` → `discoverChildEntityName()` → `createAndLinkChildEntity()`, and the `Hydrate()` / `ownedFieldsFrom()` pair around lines 1924–1950. The `FindISAChildEntity` GraphQL op fires on exactly the records that lose their virtual fields, and it does not fire on the ones that keep them. I have not isolated the specific line, so treat this as a lead rather than a diagnosis.

## Impact

It is silent and it affects every subtyped record, so the blast radius is every IS-A parent entity in every app. A form that shows a joined name next to its FK simply shows nothing, and the calling code has no way to tell "no related record" from "the name was dropped in transit". Consumers that need the name have to re-resolve it from the id themselves, which is what we have just done in bizapps-orders (`product-lookup-names.ts`, orders PR #193) as a local workaround for the product form — that workaround should be deletable once this is fixed.

## Related

[#3996](https://github.com/MemberJunction/MJ/issues/3996) is a different defect on the same family of fields: read-only virtual fields accept one write and silently drop the rest, which leaves a **stale** name after the second FK pick. This issue is about the name being **absent** from the start on subtyped records. Both were visible on the same form: the rev-rec name came back null on load, then held whatever value the FK picker had written once.

Contributor guide

Open the contributing guide

Research direction

Start in packages/MJCore/src/generic/baseEntity.ts by tracing EnsureISAChild(), discoverChildEntityName(), createAndLinkChildEntity(), and the Hydrate()/ownedFieldsFrom() code around lines 1924–1950. Reproduce a single-record load with and without an IS-A child, then compare the FindISAChildEntity path and response hydration. Done means virtual joined-name fields remain populated on subtyped records without the local workaround.

Written by the indexing model from the issue text.

Assessment

Tech stack
graphql, sql, typescript
Domain
api, frontend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
70/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.