Generator diagnostics
The Prague.Codegen analyzer validates the model before it emits a cache. Everything it rejects is reported as a CACHExxx diagnostic in the CacheGenerator category (CACHE005–007 use DataCache); all are errors except CACHE042, a warning. The [DataCacheFrom<T>] code fix reports two DATACACHExxx diagnostics of its own. Ids 003, 024–029 and 036–039 are unused.
Two things to know when reading a build log:
- A diagnostic stops the cache from being emitted, so it usually arrives with follow-up compiler errors (
CS0246: The type or namespace name 'OrderCache' could not be found, CS0535 on an index class whose partial half is missing, …). Fix theCACHEline first; the rest disappears with it. - The location is the annotated property, or the class when the attribute sits on the class. Rider and Visual Studio show them live;
dotnet buildprints them with the rest.
The examples below are deliberately broken and are not part of the compiled samples. The attributes themselves are described in Attributes Reference.
CACHE001 — Multiple DataCacheKey attributes
Fires when more than one property of a [DataCache] type carries [DataCacheKey].
[DataCache]
public partial class Order {
[DataCacheKey] public string Id { get; init; }
[DataCacheKey] public int CustomerId { get; init; } // CACHE001
}
Keep one key. If the second property should be queryable, give it [DataCacheIndex]; if the real key is composite, build a record struct and use that.
CACHE002 — Invalid Range index type
Fires when [DataCacheIndex(DataCacheIndexType.Range)] sits on a property whose type implements neither IComparable<T> nor is a built-in comparable (numbers, string, DateTime, DateTimeOffset, Guid, enums, …). Comes with CS0311 from the generated CacheRangeIndex<…> field.
public sealed class Money { public decimal Amount; }
[DataCache]
public partial class Order {
[DataCacheKey] public string Id { get; init; }
[DataCacheIndex(DataCacheIndexType.Range)] public Money Total { get; init; } // CACHE002
}
Index the comparable component (decimal Total) or implement IComparable<Money>.
CACHE004 — Nested cache class not supported
Fires when a [DataCache] type is declared inside another type.
public static class Models {
[DataCache]
public partial class Order { // CACHE004
[DataCacheKey] public string Id { get; init; }
}
}
Move the type to namespace level. Generated partials and extension classes are emitted beside it and cannot reach into a container.
CACHE005 — Cannot clone Dictionary with object or dynamic value type
Fires for a Dictionary<TKey, object> / Dictionary<TKey, dynamic> property. Clone() deep-copies collections and has no safe way to copy an untyped value.
[DataCache]
public partial class Order {
[DataCacheKey] public string Id { get; init; }
public Dictionary<string, object> Extras { get; init; } // CACHE005
}
Use a concrete value type, a JsonElement, or a serialized string.
CACHE006 — Cannot clone collection with object or dynamic element type
Same rule for List<object>, object[], IReadOnlyList<dynamic> and the other sequence types the cloner handles.
public List<object> Tags { get; init; } // CACHE006
CACHE007 — Cannot clone HashSet with object or dynamic element type
Same rule for HashSet<object> / HashSet<dynamic>.
public HashSet<dynamic> Flags { get; init; } // CACHE007
CACHE008 — Invalid foreign key reference
Fires when the T in [DataCacheForeignKey<T>] (or the selector form) is not a [DataCache] type.
public sealed class Customer { public int Id { get; init; } } // no [DataCache]
[DataCache]
public partial class Order {
[DataCacheKey] public string Id { get; init; }
[DataCacheForeignKey<Customer>(DataCacheJoinType.ManyToOne)] public int CustomerId { get; init; } // CACHE008
}
Annotate Customer or point the foreign key at a type that is cached.
CACHE009 — Foreign key type mismatch
Fires when the FK property's type is not the referenced cache's key type.
[DataCache]
public partial class Customer {
[DataCacheKey] public int Id { get; init; }
}
[DataCache]
public partial class Order {
[DataCacheKey] public string Id { get; init; }
[DataCacheForeignKey<Customer>(DataCacheJoinType.ManyToOne)] public string CustomerId { get; init; } // CACHE009: string vs int
}
Change the property type, or — when the value needs converting — use [DataCacheForeignKey<Customer, TSelector>] with a selector that maps string to int.
CACHE010 — Nested external cache class not supported
The [DataCache<T>] counterpart of CACHE004: the cache class is nested.
public static class Caches {
[DataCache<LegacyProductRecord>(nameof(LegacyProductRecord.Code))]
public partial class LegacyProductCache; // CACHE010
}
CACHE011 — External cache class must be partial
Fires when the [DataCache<T>] class is not partial. (The plain [DataCache] form has no equivalent diagnostic; it fails with CS0260, see below.)
[DataCache<LegacyProductRecord>(nameof(LegacyProductRecord.Code))]
public class LegacyProductCache; // CACHE011
CACHE012 — Key property not found
Fires when KeyProperty in [DataCache<T>] names something that is not a public readable property of T.
[DataCache<LegacyProductRecord>("Sku")] // CACHE012: LegacyProductRecord has Code, not Sku
public partial class LegacyProductCache;
Use nameof(LegacyProductRecord.Code) so renames follow.
CACHE013 — Key property required
Fires when [DataCache<T>] has no KeyProperty, T has no [DataCacheKey] and does not implement IDataCacheItem<TKey, TValue>.
[DataCache<LegacyProductRecord>] // CACHE013
public partial class LegacyProductCache;
Pass the key property name, or annotate the item type.
CACHE014 — Index property name required
Fires when a class-level [DataCacheIndex] on a [DataCache<T>] cache does not name a property.
[DataCache<LegacyProductRecord>(nameof(LegacyProductRecord.Code))]
[DataCacheIndex(DataCacheIndexType.Many)] // CACHE014
public partial class LegacyProductCache;
Use [DataCacheIndex(nameof(LegacyProductRecord.Category), DataCacheIndexType.Many)].
CACHE015 — Index property not found
Fires when the property named in a class-level [DataCacheIndex] does not exist on T.
[DataCacheIndex("Categories", DataCacheIndexType.Many)] // CACHE015
CACHE016 — Invalid Range index type
CACHE002 for the class-level form: the named property is not comparable.
[DataCacheIndex(nameof(LegacyProductRecord.Dimensions), DataCacheIndexType.Range)] // CACHE016 when Dimensions is a plain class
CACHE017 — Property name required for DataCacheIgnoreEquality
Fires when a class-level [DataCacheIgnoreEquality] on a [DataCache<T>] cache has no property name.
[DataCache<LegacyProductRecord>(nameof(LegacyProductRecord.Code))]
[DataCacheIgnoreEquality] // CACHE017
public partial class LegacyProductCache;
CACHE018 — Property not found for DataCacheIgnoreEquality
Fires when the property named in a class-level [DataCacheIgnoreEquality] does not exist on T.
[DataCacheIgnoreEquality("LastSynced")] // CACHE018
CACHE019 — Key property conflict
Fires when [DataCache<T>] specifies KeyProperty but T also has a [DataCacheKey], on a different property.
public partial class Record {
[DataCacheKey] public string Code { get; init; }
public int Number { get; init; }
}
[DataCache<Record>(nameof(Record.Number))] // CACHE019
public partial class RecordCache;
Drop the KeyProperty and let the item type's key win.
CACHE020 — Topic conflict
Fires when both the [DataCache<T>] cache class and the item type T carry [DataCacheTopic] with different templates.
[DataCacheTopic("products.v1")]
public partial class Record { [DataCacheKey] public string Code { get; init; } }
[DataCache<Record>]
[DataCacheTopic("products.v2")] // CACHE020
public partial class RecordCache;
Keep one of them — the item type's wins when both are equal.
CACHE021 — Key property cannot be indexed as Many
Fires when the [DataCacheKey] property also carries [DataCacheIndex] with the default or explicit Many type.
[DataCacheKey]
[DataCacheIndex] // CACHE021: default is Many
public string Id { get; init; }
Use Unique or Range on a key, or remove the index — WithKey(…) / With{Key}(…) already exist.
CACHE022 — Invalid HasValue index type
Fires when [DataCacheHasValueIndex] sits on a non-nullable value type other than bool.
[DataCacheHasValueIndex] public int Attempts { get; init; } // CACHE022
Make the property nullable (int?) if "has a value" is meaningful, or use [DataCacheValueIndex(CompareOp.NotEqual, 0)].
The same id is also reported, with the CACHE021 message, when a class-level [DataCacheIndex(nameof(Key), Many)] on a [DataCache<T>] cache targets the key property. Read the message, not the id.
CACHE023 — Invalid HasNotValue index type
The [DataCacheHasNotValueIndex] twin of CACHE022.
[DataCacheHasNotValueIndex] public DateTime ShippedAt { get; init; } // CACHE023: use DateTime?
CACHE030 — Invalid global last update index type
Fires when TIndex in [DataCacheGlobalLastUpdateIndex<TIndex>] does not implement IDataCacheGlobalLastUpdateIndex<TGroupKey>. The generated half of the index class is withheld, so a class that does declare the interface also gets CS0535 for its members.
public partial class OrdersActivity; // no interface
[DataCache]
public partial class Order {
[DataCacheKey]
[DataCacheGlobalLastUpdateIndex<OrdersActivity>] // CACHE030
public string Id { get; init; }
}
Declare public partial class OrdersActivity : IDataCacheGlobalLastUpdateIndex<string>.
CACHE031 — Global last update index type mismatch
Fires when the annotated property's type differs from the index's TGroupKey.
public partial class OrdersActivity : IDataCacheGlobalLastUpdateIndex<int>;
[DataCacheKey]
[DataCacheGlobalLastUpdateIndex<OrdersActivity>] // CACHE031: string vs int
public string Id { get; init; }
Align the two; several caches can feed one index only if their group-key properties share a type.
CACHE032 — Filtered global index: TFilter must implement IDataCacheGlobalLastUpdateFilter<TValue>
Fires when TFilter in [DataCacheGlobalLastUpdateIndex<TIndex, TFilter>] does not implement the filter interface.
public readonly struct OpenOnly; // no interface
[DataCacheGlobalLastUpdateIndex<OrdersActivity, OpenOnly>] // CACHE032
public string Id { get; init; }
CACHE033 — Filtered global index: TValue doesn't match the entity type
Fires when the filter implements IDataCacheGlobalLastUpdateFilter<T> for a different entity than the one it is applied to.
public readonly struct OpenOnly : IDataCacheGlobalLastUpdateFilter<Shipment> {
public bool Include(Shipment s) => s.Status == "open";
}
[DataCache]
public partial class Order {
[DataCacheKey]
[DataCacheGlobalLastUpdateIndex<OrdersActivity, OpenOnly>] // CACHE033: filter is for Shipment
public string Id { get; init; }
}
CACHE034 — Filtered global index: TFilter must be accessible to generated code
Fires when the filter struct is private, nested in a non-accessible type, or file-local. Generated code lives in the same assembly, so internal is enough.
file readonly struct OpenOnly : IDataCacheGlobalLastUpdateFilter<Order> { … } // CACHE034 when used
CACHE035 — Global last update index class must be public and top-level
Fires when TIndex is not public or is nested. The index is registered as a DI singleton and referenced from every cache that feeds it.
internal partial class OrdersActivity : IDataCacheGlobalLastUpdateIndex<string>; // CACHE035 when used
CACHE040 — ManyToOne foreign key on primary key
Fires when [DataCacheForeignKey<T>(ManyToOne)] sits on the [DataCacheKey] property. Many rows cannot share a key, so the cardinality is a contradiction.
[DataCacheKey]
[DataCacheForeignKey<Customer>(DataCacheJoinType.ManyToOne)] // CACHE040
public int CustomerId { get; init; }
Use OneToOne for a PK-to-PK relation, or move the FK to its own property.
CACHE041 — ManyToOne FK conflicts with explicit index type
Fires when a ManyToOne foreign key property also has an explicit [DataCacheIndex(Unique)] or [DataCacheIndex(Range)]. The join needs a symmetric Many index.
[DataCacheIndex(DataCacheIndexType.Range)]
[DataCacheForeignKey<Customer>(DataCacheJoinType.ManyToOne)] // CACHE041
public int CustomerId { get; init; }
Remove the index (the FK creates one) or make it Many. A Range index on an FK is not supported; keep a separate property if you need range queries on the same value.
CACHE042 — ManyToOne FK auto-upgrading explicit index to Symmetric=true (warning)
Fires when a ManyToOne foreign key property carries an explicit [DataCacheIndex(Many)] without Symmetric = true. The generator upgrades it so JoinWith{T} can use the Reverse map and tells you. With TreatWarningsAsErrors this fails the build.
[DataCacheIndex(DataCacheIndexType.Many, 8)]
[DataCacheForeignKey<Customer>(DataCacheJoinType.ManyToOne)] // CACHE042
public int CustomerId { get; init; }
Add Symmetric = true to the index, or drop it when you only wanted the default.
CACHE043 — Dual-form FK references unknown property
Fires when the rightProperty of [DataCacheForeignKey<T>(joinType, rightProperty)] does not exist on T.
[DataCacheKey]
[DataCacheForeignKey<StockLevel>(DataCacheJoinType.OneToMany, "Warehouse")] // CACHE043: StockLevel has WarehouseId
public int Id { get; init; }
Use nameof(StockLevel.WarehouseId).
CACHE044 — Dual-form FK type mismatch
Fires when the anchor property and T.{rightProperty} have different types.
[DataCacheKey]
[DataCacheForeignKey<StockLevel>(DataCacheJoinType.OneToMany, nameof(StockLevel.WarehouseCode))] // CACHE044: int vs string
public int Id { get; init; }
CACHE045 — Selector FK: TSelector must implement IForeignKeySelector<TFk, TPk>
Fires when the selector type in [DataCacheForeignKey<T, TSelector>] does not implement IForeignKeySelector<TFk, TPk>.
public readonly struct OrderRefSelector; // no interface
[DataCacheForeignKey<Order, OrderRefSelector>(DataCacheJoinType.ManyToOne)] // CACHE045
public string OrderRef { get; init; }
CACHE046 — Selector FK: TFk / TPk mismatch
One id, two messages. "TFk doesn't match FK property type" fires when the selector's input type is not the property type; "TPk doesn't match target PK type" when its output type is not T's key type.
public readonly struct OrderRefSelector : IForeignKeySelector<int, string> {
public static string Select(int fk) => fk.ToString();
}
[DataCacheForeignKey<Order, OrderRefSelector>(DataCacheJoinType.ManyToOne)] // CACHE046: TFk is int, property is string
public string OrderRef { get; init; }
CACHE047 — Selector FK only supports OneToOne / ManyToOne
Fires when a selector-form foreign key declares OneToMany. Selectors cannot be inverted, so only the forward direction exists.
[DataCacheForeignKey<Order, OrderRefSelector>(DataCacheJoinType.OneToMany)] // CACHE047
public string OrderRef { get; init; }
CACHE048 — Selector FK: TSelector must be accessible to generated code
Fires when the selector struct is private, nested in an inaccessible type, or file-local. internal is enough.
file readonly struct OrderRefSelector : IForeignKeySelector<string, string> { … } // CACHE048 when used
CACHE049 — Collection foreign key element type mismatch
Fires when a collection FK property (List<TKey>, TKey[], …) has an element type that is not the referenced cache's key type.
[DataCacheForeignKey<Product>(DataCacheJoinType.ManyToOne)] // CACHE049: Product key is int
public List<string> ProductIds { get; init; }
CACHE050 — OneToOne foreign key on collection property
Fires when a collection FK property declares OneToOne. A collection of keys is by nature many-to-many and is declared ManyToOne.
[DataCacheForeignKey<Product>(DataCacheJoinType.OneToOne)] // CACHE050
public List<int> ProductIds { get; init; }
CACHE051 — InitialCapacity is only applicable to Many indexes
Fires when [DataCacheIndex] sets InitialCapacity (constructor or named argument) on a Unique or Range index, property-level or class-level. The hint sizes the per-value buckets of a Many index and means nothing elsewhere.
[DataCacheIndex(DataCacheIndexType.Unique, 16)] // CACHE051
public string Sku { get; init; }
DATACACHE001 — Generate properties from source type (info)
Reported by the [DataCacheFrom<TSource>] analyzer when the attribute has no sourceHash, i.e. the properties have not been generated yet. The code fix appends them and records the hash. Not a build error; it appears in the IDE's light-bulb menu.
[DataCacheFrom<SupplierDto>] // DATACACHE001: apply the fix
[DataCache]
public partial class Supplier;
DATACACHE002 — Source type has changed (warning)
Reported when the recorded hash no longer matches the source type's public properties. The code fix regenerates the missing ones; it never deletes properties you have edited. Under TreatWarningsAsErrors it fails the build until you re-run the fix or update the hash.
Common build errors that are not diagnostics
These are plain C# errors a mis-annotated model produces, each reproduced against the current generator. None of them has a CACHE line, so there is no hint in the log; this table is the hint.
| Error | Cause | Fix |
|---|---|---|
CS0260: Missing partial modifier on declaration of type 'Order' |
A [DataCache] type (or the item type of a [DataCache<T>]) is not partial |
Add partial |
CS0246: The type or namespace name 'OrderCache' could not be found |
Prague.Codegen is not referenced as an analyzer, or an earlier CACHE error stopped emission |
Reference the Prague package (it brings the generator), or fix the diagnostic above it |
CS1061: 'Order' does not contain a definition for 'GetKey' reported in a *.g.cs file |
[DataCache] type with no [DataCacheKey]: the generator assumes a string key and calls GetKey() |
Add [DataCacheKey], or implement IDataCacheItem<TKey, Order> |
CS0311: The type 'byte[]' cannot be used as type parameter 'TKey' |
The key (or a Unique index) type does not implement IEquatable<T> |
Use string, a number, Guid, or a record struct |
CS0579: Duplicate 'DataCacheForeignKey<…>' attribute |
Two [DataCacheForeignKey<T>] with the same T on one property |
One FK per target per property |
CS0535: 'OrdersActivity' does not implement interface member … on the index class |
Comes with CACHE030/031/035: the generated half of the index class is withheld | Fix the CACHE error |
CS0534: 'SamplesJsonContext' does not implement inherited abstract member |
A [DataCacheJsonSerializables] context with no [JsonSerializable] attributes; the Prague generator emits nothing for it |
Add [JsonSerializable(typeof(…))] like any JsonSerializerContext |
CS1061: 'Order' does not contain a definition for 'Nope' in a generated comparer |
[DataCacheSort("ByNope", "Nope:1")] names a property that does not exist |
Fix the name; the generator does not validate sort specs |
CS0117: 'OrderCache' does not contain a definition for 'ByIdComparer' |
[DataCacheSort("ById", "Id")] — an entry without :1 / :-1 is skipped, and a sort with no valid entries emits no comparer |
Write "Id:1" |