stride3d/stride · error · InvalidOperationException
The order of the Asset.Id property must be lower than the…
Error message
The order of the Asset.Id property must be lower than the order of the SourceHashes property.
What it means
SourceHashesHelper's static constructor verifies serialization ordering invariants: Asset.Id (its DataMember Order) must be strictly lower than the SourceHashes member order (DefaultOrder), so the asset id is always deserialized before the source hashes. If the Asset metadata model changes those Order values, the static initializer throws InvalidOperationException at first use.
Solutions
- Restore Asset.Id's DataMember Order to a value lower than SourceHashesHelper.DefaultOrder
- Renumber the DataMember Order attributes so Id serializes before SourceHashes
- Update SourceHashesHelper's DefaultOrder to match your modified Asset model if the reorder was intentional
- Diff your Asset.cs against upstream Stride to find the accidental Order change
Example fix
// before (Asset.cs)
[DataMember(-8)] public AssetId Id { get; set; } // >= DefaultOrder
// after
[DataMember(-10)] public AssetId Id { get; set; } // lower than SourceHashes order Defensive patterns
Strategy: try-catch
Validate before calling
var idOrder = typeof(Asset).GetProperty(nameof(Asset.Id))!
.GetCustomAttribute<DataMemberAttribute>()!.Order;
if (idOrder >= /* SourceHashesHelper.DefaultOrder */)
throw new InvalidOperationException("Fix Asset.Id DataMember Order before using SourceHashesHelper"); Try / catch
try { SourceHashesHelperTouch(); }
catch (TypeInitializationException ex) when (ex.InnerException is InvalidOperationException)
{
Log.Fatal("Asset.Id DataMember Order is inconsistent with SourceHashes order: {Msg}", ex.InnerException.Message);
} Prevention
- Never renumber DataMember Order on Asset.Id casually; diff against upstream
- Add a unit test that exercises SourceHashesHelper's static ctor on CI
- Document the ordering invariant next to Asset.Id in forks
- Re-verify the invariant after merging upstream Stride changes
When it happens
Trigger: Modifying Asset.Id's DataMember Order in a fork/custom build so it is >= DefaultOrder; reordering/renumbering DataMember Order attributes on Asset properties; upgrading a patched Stride source where the safety check no longer holds.
Common situations: Custom engine builds that add new serialized Asset properties and shift Order values; merging upstream Stride changes that renumber DataMember orders; copy-pasting Asset into a custom asset pipeline.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Event handlers can't be added or removed after the…
- RoutingSerializer expected in the chain of serializers
- The type of collection does not have a parameterless…
- The given container does not match the expected type.
- The type of dictionary does not have a parameterless…
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/44798998357a6ece.
Report an issue: GitHub.
Appendix: source
Thrown at sources/assets/Stride.Core.Assets/Tracking/SourceHashesHelper.cs:124
internal class SourceHashesDynamicMember : DynamicMemberDescriptorBase
{
public const int DefaultOrder = int.MaxValue;
public static readonly SourceHashesDynamicMember Default = new()
{
ShouldSerialize = static (x, parentTypeMemberDesc) =>
{
return x is Asset asset && TryGet(asset, AbsoluteSourceHashesKey)?.Count > 0;
}
};
static SourceHashesDynamicMember()
{
// Safety check, we need to have the asset id deserialized before the source hashes
var idOrder = typeof(Asset).GetProperty(nameof(Asset.Id))!.GetCustomAttribute<DataMemberAttribute>()!.Order;
if (idOrder >= DefaultOrder)
throw new InvalidOperationException("The order of the Asset.Id property must be lower than the order of the SourceHashes property.");
}
public SourceHashesDynamicMember() : base(MemberName, typeof(Dictionary<UFile, ObjectId>), typeof(Asset))
{
Order = DefaultOrder;
}
public override bool HasSet => true;
public override object? Get(object thisObject)
{
var asset = (Asset)thisObject;
// Id can be empty when the asset is contained in a base.
if (asset.Id == AssetId.Empty)
return null;
lock (LockObj)
{View on GitHub (pinned to 96fad776d2)