Description
When custom C# code changes a generated model to inherit from a CLR base that is narrower than the model's TypeSpec base, properties supplied only by the original TypeSpec base disappear instead of being generated directly on the derived model.
This was initially observed in the Azure management-plane generator, but the underlying asymmetry is in MTG's model construction and applies generically to custom base overrides.
Azure SDK issue: Azure/azure-sdk-for-net#62140
Example
The TypeSpec model is a tracked resource, so its TypeSpec base contributes properties such as location and tags.
Custom code preserves an existing shipped CLR hierarchy:
public partial class BatchAccountData : ResourceData
{
}
ResourceData is narrower than TrackedResourceData: it does not provide Location or Tags.
The generated partial correctly changes its CLR base to ResourceData, but Location and Tags disappear from:
- the generated model surface
- constructors
- serialization and deserialization
- model factory generation
The expected result is for Location and Tags to be generated directly on BatchAccountData because they remain part of the TypeSpec model but are no longer supplied by the effective CLR base.
Root cause
MTG correctly gives CustomCodeView.BaseType precedence when selecting the generated CLR base:
ModelProvider.BuildBaseType() returns the custom base
ModelProvider.BuildBaseModelProvider() resolves the custom base provider instead of the original TypeSpec base provider
However, the property-building pipeline then only has two sources:
_inputModel.Properties, which contains properties directly declared by the derived TypeSpec model
EnumerateBaseModelProviders(), which now follows the effective custom CLR base hierarchy
Properties declared by the original TypeSpec base remain owned by _inputModel.BaseModel. Once the effective BaseModelProvider is replaced by the custom CLR base, that original TypeSpec hierarchy is disconnected from model generation.
As a result, properties that are:
- declared on the original TypeSpec base, and
- not provided by the effective custom CLR base
are neither inherited nor generated directly on the derived model.
This is an asymmetry:
- A broader custom CLR base works because MTG can filter properties already supplied by the effective base.
- A narrower custom CLR base loses properties because MTG has no reconciliation step for properties supplied only by the original TypeSpec base.
Why a post-generation visitor fix is unsafe
An attempted management-generator fix reconciled properties in a visitor after ModelProvider.Properties had already been built. A full Azure SDK regeneration demonstrated that this approach is too late and too broad:
- 183 generated models gained duplicate/generic
Type properties
- TypeSpec
type was compared with C# ResourceType
- TypeSpec discriminator
type was compared with names such as ActivityType or CopySourceType
- a model gained a duplicate
ETag property because input etag was compared with generated ETag
- inherited discriminator properties were materialized again on many polymorphic Data Factory models
- the intended
Location and Tags properties appeared on two models, but constructors and serialization were not updated
This happens because a visitor only sees already-created PropertyProviders while constructors and serialization continue to derive from the original input model/hierarchy. Comparing raw InputModelProperty.Name with generated C# property names also misses renames and framework mappings.
Full regen PR demonstrating the impact: Azure/azure-sdk-for-net#62195
Expected MTG behavior
MTG should reconcile the original TypeSpec hierarchy with the effective CLR hierarchy before building any model artifacts.
Conceptually:
- Preserve the original TypeSpec base chain even when a custom CLR base wins.
- Determine which original TypeSpec base properties are already represented by the effective CLR base.
- Generate the remaining TypeSpec-inherited properties directly on the derived model.
- Use that same reconciled property plan for:
- properties and fields
- public and internal constructors
- serialization/deserialization
- model factories
- Match properties semantically rather than by raw names, accounting for:
- serialized/wire name
- C# renames such as
type to ResourceType
- discriminator renames
- system/external model mappings
- visibility and usage
ShouldSkipDerivedModelProperties
Suggested regression coverage
Add an MTG model customization test with:
- a TypeSpec base model containing properties supplied by both a broad base and a narrower base
- a derived TypeSpec model whose custom partial selects the narrower CLR base
- one inherited property already represented by the custom CLR base under a different C# name (for example
type / ResourceType)
- properties not represented by the custom base (for example
location and tags)
- a renamed discriminator in the original base hierarchy
Verify that:
- the custom CLR base wins
- properties already supplied by the custom base are not duplicated
- missing TypeSpec-base properties are generated on the derived model
- constructors include those properties when appropriate
- serialization and deserialization include those properties
- discriminators are not duplicated under a generic
Type property
Description
When custom C# code changes a generated model to inherit from a CLR base that is narrower than the model's TypeSpec base, properties supplied only by the original TypeSpec base disappear instead of being generated directly on the derived model.
This was initially observed in the Azure management-plane generator, but the underlying asymmetry is in MTG's model construction and applies generically to custom base overrides.
Azure SDK issue: Azure/azure-sdk-for-net#62140
Example
The TypeSpec model is a tracked resource, so its TypeSpec base contributes properties such as
locationandtags.Custom code preserves an existing shipped CLR hierarchy:
ResourceDatais narrower thanTrackedResourceData: it does not provideLocationorTags.The generated partial correctly changes its CLR base to
ResourceData, butLocationandTagsdisappear from:The expected result is for
LocationandTagsto be generated directly onBatchAccountDatabecause they remain part of the TypeSpec model but are no longer supplied by the effective CLR base.Root cause
MTG correctly gives
CustomCodeView.BaseTypeprecedence when selecting the generated CLR base:ModelProvider.BuildBaseType()returns the custom baseModelProvider.BuildBaseModelProvider()resolves the custom base provider instead of the original TypeSpec base providerHowever, the property-building pipeline then only has two sources:
_inputModel.Properties, which contains properties directly declared by the derived TypeSpec modelEnumerateBaseModelProviders(), which now follows the effective custom CLR base hierarchyProperties declared by the original TypeSpec base remain owned by
_inputModel.BaseModel. Once the effectiveBaseModelProvideris replaced by the custom CLR base, that original TypeSpec hierarchy is disconnected from model generation.As a result, properties that are:
are neither inherited nor generated directly on the derived model.
This is an asymmetry:
Why a post-generation visitor fix is unsafe
An attempted management-generator fix reconciled properties in a visitor after
ModelProvider.Propertieshad already been built. A full Azure SDK regeneration demonstrated that this approach is too late and too broad:Typepropertiestypewas compared with C#ResourceTypetypewas compared with names such asActivityTypeorCopySourceTypeETagproperty because inputetagwas compared with generatedETagLocationandTagsproperties appeared on two models, but constructors and serialization were not updatedThis happens because a visitor only sees already-created
PropertyProviders while constructors and serialization continue to derive from the original input model/hierarchy. Comparing rawInputModelProperty.Namewith generated C# property names also misses renames and framework mappings.Full regen PR demonstrating the impact: Azure/azure-sdk-for-net#62195
Expected MTG behavior
MTG should reconcile the original TypeSpec hierarchy with the effective CLR hierarchy before building any model artifacts.
Conceptually:
typetoResourceTypeShouldSkipDerivedModelPropertiesSuggested regression coverage
Add an MTG model customization test with:
type/ResourceType)locationandtags)Verify that:
Typeproperty