Build Diagnostics
Reference for SPIDERLY### diagnostic codes emitted by Spiderly source generators when entity definitions violate a contract.
Overview
When the build emits hundreds of CS0246 errors about missing {Entity}DTO types, look for the SPIDERLY-prefixed diagnostic first — that is the real cause. A single contract violation aborts MapperGenerator, which deletes every entity's generated DTO and produces a flood of downstream CS0246 errors that look unrelated.
When an entity class violates a contract the source generators rely on — a missing base class, a malformed [ForeignKey], an ambiguous relationship, a broken [DisplayName] path — Spiderly emits a located Roslyn diagnostic at build time rather than crashing with CS8785 "generator failed to generate source".
Each diagnostic:
- Has a stable
SPIDERLY###ID (never reused, never renumbered). - Renders as a red squiggle in Visual Studio / Rider / VS Code on the offending class or property.
- Appears in the build output with a clickable file and line.
- Is parseable by CI logs and AI agents for automated fix suggestions.
Diagnostics are reported by the generator's SourceProductionContext, so they behave like any other compiler warning or error — you can suppress individual codes via <NoWarn> in the .csproj, treat warnings as errors, or filter them in CI output. Severity is Error for everything except SPIDERLY013 (Warning).
Diagnostic Reference
| ID | Severity | Title |
|---|---|---|
| SPIDERLY001 | Error | Controller type is not a discovered entity or DTO |
| SPIDERLY002 | Error | Many-to-many entity requires exactly two [M2MWithMany] properties |
| SPIDERLY003 | Error | [ForeignKey] references a property that does not exist |
| SPIDERLY004 | Error | Foreign key type does not match target primary key |
| SPIDERLY005 | Error | Foreign key is ambiguous — multiple convention matches |
| SPIDERLY006 | Error | Foreign key nullability does not match navigation property |
| SPIDERLY007 | Error | [DisplayName] path references a property that does not exist |
| SPIDERLY008 | Error | [DisplayName] path segment is not a many-to-one navigation |
| SPIDERLY009 | Error | [DisplayName] navigation target entity not found |
| SPIDERLY010 | Error | Entity missing required BusinessObject<T> / ReadonlyObject<T> base |
| SPIDERLY011 | Error | Controller property type is not resolvable for client generation |
| SPIDERLY012 | Error | One-to-many back-reference missing [M2MWithMany] |
| SPIDERLY013 | Warning | Backend folder not found under calling project |
| SPIDERLY014 | Error | Blob property missing [AcceptedFileTypes] attribute |
| SPIDERLY015 | Error | Many-to-one navigation missing [WithMany] attribute |
| SPIDERLY016 | Error | [WithMany] target collection does not exist on the related entity |
| SPIDERLY017 | Error | [WithMany] target collection has the wrong element type |
| SPIDERLY018 | Error | Entity primary key type must be int, long, or byte |
| SPIDERLY019 | Error | [WithOne] declared on both sides of a one-to-one |
| SPIDERLY020 | Error | [WithOne] inverse navigation does not exist on the principal |
| SPIDERLY021 | Error | [Required] on the principal navigation of a one-to-one is unenforceable |
| SPIDERLY022 | Error | Self-referential one-to-one is not supported |
| SPIDERLY023 | Error | [ComplexManyToManyList] junction has no additional data columns |
| SPIDERLY024 | Error | A Spiderly source generator faulted |
| SPIDERLY025 | Error | [DisplayName] on an entity requires a property path |
| SPIDERLY026 | Error | [UITableColumn] field does not exist |
| SPIDERLY027 | Error | Generic type argument could not be resolved for a base-class property |
| SPIDERLY028 | Error | Nullable annotation disagrees with [Required] |
SPIDERLY001
Controller type is not a discovered entity or DTO.
A controller action takes or returns a class that isn't marked with [SpiderlyDTO] and doesn't inherit from BusinessObject<T> / ReadonlyObject<T>. The generated Angular client would reference an undefined TypeScript type.
Fix: add [SpiderlyDTO] to the class, or make it inherit from a Spiderly entity base class.
SPIDERLY002
Many-to-many entity requires exactly two [M2MWithMany] properties.
An entity declared as a many-to-many join has zero, one, or more than two [M2MWithMany] attributes. A join needs exactly one on each side.
Fix: annotate each of the two navigation properties on the join entity with [M2MWithMany] pointing at the opposite side. See Relationships for a worked example.
SPIDERLY003
[ForeignKey] references a property that does not exist.
[ForeignKey(nameof(X))] on a navigation or scalar points at a member name that isn't declared on the entity.
Fix: correct the nameof(...) argument, or add the missing scalar/navigation property.
SPIDERLY004
Foreign key type does not match target primary key.
The FK scalar's type (e.g. int) doesn't match the type of the target entity's Id (e.g. long).
Fix: change the FK scalar to the target entity's Id type. The primary key type comes from the entity's BusinessObject<T> / ReadonlyObject<T> base.
SPIDERLY005
Foreign key is ambiguous — multiple convention matches.
Multiple scalar properties match the {NavigationName}Id convention for the same navigation, so the generator cannot pick a unique FK.
Fix: use [ForeignKey(nameof(ExplicitFkScalar))] on the navigation to disambiguate.
SPIDERLY006
Foreign key nullability does not match navigation property.
Either a [Required] navigation is paired with a nullable FK scalar, or an optional navigation is paired with a non-nullable FK scalar.
Fix: align them. [Required] Category Category must pair with non-nullable long CategoryId; optional Category Category must pair with nullable long? CategoryId.
SPIDERLY007
[DisplayName] path references a property that does not exist.
A segment of [DisplayName("A.B.C")] names a property that isn't declared on the corresponding entity.
Fix: correct the path, or add the missing property.
SPIDERLY008
[DisplayName] path segment is not a many-to-one navigation.
An intermediate segment of a [DisplayName] path must be a many-to-one navigation so the generator can follow the chain.
Fix: use only M2O navigations for intermediate segments. Scalars and collection properties are only valid as the final segment.
SPIDERLY009
[DisplayName] navigation target entity not found.
The entity type referenced by a [DisplayName] navigation segment is not discovered in the current project or any referenced project with a .Entities namespace.
Fix: ensure the target entity lives in a .Entities namespace and its project is referenced.
SPIDERLY010
Entity missing required BusinessObject<T> / ReadonlyObject<T> base.
Every Spiderly entity must inherit — directly or transitively — from BusinessObject<T> or ReadonlyObject<T>. The generators cannot resolve the entity's Id type otherwise.
Fix: add the appropriate base class. See Add New Entity.
SPIDERLY011
Controller property type is not resolvable for client generation.
A generated controller method references a navigation property whose target entity cannot be discovered, so the Angular autocomplete / dropdown method cannot be generated.
Fix: ensure the target entity exists in a .Entities namespace and its project is referenced.
SPIDERLY012
One-to-many back-reference missing [M2MWithMany].
An entity declares a one-to-many collection into a complex many-to-many join, but the join entity has no property annotated with a matching [M2MWithMany(nameof(...))] pointing back.
Fix: add [M2MWithMany(nameof(ThisCollection))] to the corresponding navigation on the join entity.
SPIDERLY013
Backend folder not found under calling project.
The file-emitting generators (Angular entities/controllers/validators/details/enums) walk up from the calling project to find a Backend folder as an anchor for writing output. They emit this warning and skip file emission if the folder can't be found.
Fix: ensure your backend project lives under a folder named Backend (or pass a custom name if your generator consumer supports it).
SPIDERLY014
Blob property missing [AcceptedFileTypes] attribute.
Every blob property (any property decorated with a StorageAttribute subclass like [S3PublicStorage], [S3PrivateStorage], [DiskStorage], or a custom one) must declare [AcceptedFileTypes("mime/type", ...)] with at least one MIME-typed value. There is no implicit default — the old images-only fallback has been removed so every blob property's upload whitelist is an explicit, auditable decision at the source level.
An attribute that contains only extension values (e.g. [AcceptedFileTypes(".pdf")]) also trips this because the generator filters to MIME-typed entries (values containing /) when emitting the server-side validator.
Fix: add [AcceptedFileTypes(...)] with one or more MIME types. Examples:
[S3PublicStorage]
[AcceptedFileTypes("image/jpeg", "image/png", "image/webp", "image/avif")]
public string ImageUrl { get; set; }
[S3PublicStorage]
[AcceptedFileTypes("application/pdf", ".pdf")]
public string PdfFile { get; set; }SPIDERLY015
Many-to-one navigation missing [WithMany] attribute.
A virtual navigation property on a [SpiderlyEntity] class targets another entity (a many-to-one relationship), but is missing the required [WithMany(nameof(Target.Collection))] attribute. Without it, ApplicationDbContext cannot configure the HasOne(...).WithMany(...) relationship and the parent collection it points at.
Fix: add [WithMany(nameof(Target.Collection))] to the navigation, and declare public virtual List<Source> Collection { get; } = new(); on the target entity if it doesn't exist yet. If the relationship really should be unidirectional, drop the virtual navigation property entirely and configure the relationship manually via a partial OnModelCreating — see Relationships for the worked pattern.
SPIDERLY016
[WithMany] target collection does not exist on the related entity.
[WithMany("OrderItems")] (or [WithMany(nameof(Order.OrderItems))]) is declared on a many-to-one navigation, but the named collection property doesn't exist on the target entity. EF Core would die mid-OnModelCreating with a generic "no such navigation" error; this diagnostic surfaces it at build time with the offending file and line.
Fix: add the named collection (public virtual List<Source> OrderItems { get; } = new();) on the target entity, or correct the [WithMany] argument to match an existing collection name.
SPIDERLY017
[WithMany] target collection has the wrong element type.
[WithMany("OrderItems")] resolves to an existing property on the target entity, but the property's collection element type doesn't match the source entity. For example: [WithMany(nameof(User.OrderItems))] on OrderItem.User, but User.OrderItems is declared as List<Order> rather than List<OrderItem>. Typical cause: a copy-paste mistake or a rename that updated one side but not the other.
Fix: align the back-collection's element type with the source entity (List<OrderItem>), or change the [WithMany] target to a collection of the correct element type.
SPIDERLY018
Entity primary key type must be int, long, or byte.
An entity inherits BusinessObject<T> or ReadonlyObject<T> where T is something other than int, long, or byte (e.g. Guid, decimal, short, DateTime). The C# generic constraint on the base class is where T : struct, which technically admits any value type — but the rest of Spiderly (audit/versioning in ApplicationDbContext, FilterDTO.AdditionalFilterIdInt/Long/Byte, Angular type mapping) only handles the three documented types. Allowing anything else would silently produce broken generated code or, worse, dropped audit rows at runtime.
Fix: change T to int, long, or byte. If you need a public, non-enumerable identifier (UUID-style URLs for security/anti-enumeration), keep the numeric Id as the primary key and add a separate Guid PublicId property alongside it — Guid is a fully supported non-PK base data type.
// ✗ Rejected by SPIDERLY018
public class Order : BusinessObject<Guid> { }
// ✓ Recommended pattern for public-facing identifiers
public class Order : BusinessObject<long>
{
public Guid PublicId { get; set; }
}SPIDERLY019
[WithOne] declared on both sides of a one-to-one.
A one-to-one is declared by placing [WithOne] on the dependent (foreign-key-holding) side only — that single placement is what designates which side owns the FK. If both navigations carry [WithOne], the dependent side is ambiguous.
Fix: keep [WithOne] on the dependent and make the principal a plain single-valued navigation with no attribute.
SPIDERLY020
[WithOne] inverse navigation does not exist on the principal.
[WithOne(nameof(Principal.InverseNav))] names the back-navigation on the principal entity, but the named property doesn't exist (or isn't a single-valued navigation of the dependent's type).
Fix: add public virtual {Dependent} {InverseNav} { get; set; } on the principal, or use the parameterless [WithOne] for a unidirectional one-to-one.
SPIDERLY021
[Required] on the principal navigation of a one-to-one is unenforceable.
A unique foreign-key index guarantees at most one dependent per principal — never at least one. "Every principal has a dependent" cannot be enforced by the schema, so [Required] on the principal-side navigation is meaningless.
Fix: configure requiredness on the dependent ([WithOne]) side — a non-nullable FK means the dependent must have a principal. If you genuinely need "every principal has a dependent", create the dependent in the principal's OnAfter{Entity}Insert hook.
SPIDERLY022
Self-referential one-to-one is not supported.
A [WithOne] navigation whose target type is the declaring entity itself is rejected in this version.
Fix: model it differently (e.g. a self-referential many-to-one), or split the data into two entities.
SPIDERLY023
[ComplexManyToManyList] junction has no additional data columns.
The junction entity named by [ComplexManyToManyList] holds nothing but its two foreign keys. The generated grid tells a linked row apart from an empty placeholder row by whether its data columns are filled, so a junction with no data columns has no way to express "not linked" — every row would be saved as a link.
Fix: use a plain collection (a simple many-to-many) if the relationship carries no extra data, or add the data column the junction exists to hold.
SPIDERLY024
A Spiderly source generator faulted.
A generator threw an unexpected exception, so it contributed no output and everything it generates is missing from the compilation — which usually shows up as a cascade of "type or member does not exist" errors in code that references the missing output. The message names the generator, the exception type, and its message.
This is a bug in Spiderly, not in your code. It is reported instead of the compiler's own CS8785 so that the build fails loudly and names what died, rather than silently omitting generated code.
Fix: please open an issue with the entity shape that triggered it — the exception message plus the entity's attributes and property types is usually enough to reproduce.
SPIDERLY025
[DisplayName] on an entity requires a property path.
[DisplayName] means two different things depending on where it sits. On a property the bare form is correct — it marks that property as the entity's display value. On an entity it must name the path to use, because there is no single property for it to refer to.
// Reported: bare [DisplayName] on the entity.
[DisplayName]
[SpiderlyEntity]
public class ProjectTask : BusinessObject<long> { }
// Correct: name the path.
[DisplayName(nameof(Title))]
[SpiderlyEntity]
public class ProjectTask : BusinessObject<long>
{
public string Title { get; set; } = null!;
}Fix: pass the property path — [DisplayName(nameof(Name))], or a dotted path through a navigation such as [DisplayName("Category.Name")].
SPIDERLY026
[UITableColumn] field does not exist.
[UITableColumn("...")] names a property found on neither the entity nor its generated DTO. Since the column is resolved by name at generation time, the grid would reference a field that does not exist.
Fix: check the spelling, or update the attribute — this most often fires after a property is renamed and the attribute string is left behind.
SPIDERLY027
Generic type argument could not be resolved for a base-class property.
A property inherited from a base class is typed as that base's generic parameter T, and the concrete type argument could not be resolved. A generic base entity declared inside your own project is not supported.
Fix: declare the property with a concrete type, or inherit BusinessObject<T> directly rather than through your own generic base.
SPIDERLY028
Nullable annotation disagrees with [Required].
[Required] is the source of truth for a column's nullability. When your nullable annotation contradicts it, the C# type lies — and the two kinds of property lie about different things, which is why both are reported.
For navigations the attribute decides outright: Spiderly configures .IsRequired([Required] != null) after .HasForeignKey(), so the annotation cannot move the column. What it does instead is claim non-null over a nullable foreign key, and EF then materializes null into a non-nullable property — a NullReferenceException exactly where the type promised there could not be one.
For scalars Spiderly configures nothing and EF's own conventions decide, so which side wins depends on the direction of the disagreement. [Required] on a nullable-annotated property still produces NOT NULL (RequiredPropertyAttributeConvention is a DataAnnotation source and outranks the nullable-reference convention). A non-nullable annotation with no [Required] has nothing opposing NonNullableReferencePropertyConvention, so it silently makes the column NOT NULL: your next migration alters the schema, and a save that legitimately omits the value writes a default instead of NULL.
That last case is the only one where the annotation wins, and rejecting it here is what keeps [Required] authoritative for scalars too. Either way the two must tell the same story.
// Reported: no [Required], so the relationship is optional, but the annotation
// claims the value is always there.
[WithMany(nameof(TaskCategory.ProjectTasks))]
public virtual TaskCategory TaskCategory { get; set; } = null!;
// Reported the other way: [Required] says mandatory, the annotation says optional.
[Required]
public string? Name { get; set; }Fix: make the two agree. Annotate the property nullable (?) if it really is optional, or add [Required] if it really is mandatory — the latter changes the schema, so generate and review the migration.
Value types are never reported: int and int? carry their nullability in the CLR type, so there is nothing for [Required] to contradict. Collections are never reported either — they are initialized = new() and are never null.
This diagnostic only fires for projects on <Nullable>enable</Nullable>. In a nullable-oblivious project public virtual TaskCategory TaskCategory { get; set; } is the absence of an annotation rather than a claim of non-nullability, so nothing can disagree.
Suppressing Diagnostics
To suppress a specific diagnostic project-wide, add it to <NoWarn> in the consuming project's .csproj:
<PropertyGroup>
<NoWarn>$(NoWarn);SPIDERLY013</NoWarn>
</PropertyGroup>Suppress at a single site with a #pragma:
#pragma warning disable SPIDERLY013
// ...code that triggers the diagnostic...
#pragma warning restore SPIDERLY013Use sparingly — each diagnostic points at a real contract violation that will produce broken generated code.