Summary
When model generation uses UseNullableReferenceTypes: false, DataLinq 0.9 can generate nullable reference columns that its own runtime later rejects as non-nullable.
A nullable database varchar is generated in the legacy nullable context as:
#nullable disable
[Nullable]
[Column("BankGiro")]
public abstract string BankGiro { get; }
The source generator then emits contradictory metadata:
new MetadataColumnDraft("BankGiro")
{
Nullable = true,
}
// ...
{
Attributes = [new NullableAttribute(), /* ... */],
CsNullable = false,
}
When SQL returns NULL, v0.9 throws DataLinqNullabilityMismatchException because ValueProperty.CsNullable is false, even though the database column and generated model attribute both declare the column nullable and nullable reference annotations are explicitly disabled.
Verified with DataLinq.CLI 0.9.0-rc.4+9e6b80f6137e7a546a2616f62d5e6d5ec386040a.
Reproduction
Use a model-generation configuration containing:
{
"UseNullableReferenceTypes": false
}
Generate a model from a table containing a nullable reference column, for example:
BankGiro varchar(50) NULL
Then query a row where that column is SQL NULL.
The generated model uses #nullable disable, [Nullable], and a plain string property. At runtime, materialization fails with a nullability mismatch stating that the database column is nullable but the generated model property is non-nullable.
Regenerating the models with the 0.9 CLI does not resolve the mismatch.
Expected behavior
Models generated with UseNullableReferenceTypes: false should remain usable with nullable reference columns. SQL NULL should materialize as null for a generated [Nullable] string property in a disabled nullable-annotation context.
The stricter v0.9 validation should still reject:
- SQL
NULL for non-nullable value types;
- SQL
NULL for reference properties explicitly known to be non-nullable;
- schema/model nullability drift.
Likely cause
SyntaxParser currently derives CsNullable only from nullable type syntax:
property.SetCsNullableCore(propSyntax.Type is NullableTypeSyntax);
That is sufficient for string? with nullable reference types enabled, but not for plain string under #nullable disable. In that context, the absence of ? does not mean the CLR property cannot represent null.
The runtime nullability contract subsequently treats CsNullable = false as proof that the model rejects SQL NULL.
This is related to #11, but is the inverse case: #11 correctly aims to reject SQL NULL for genuinely non-nullable models, while this issue concerns a nullable model generated in the supported legacy nullable mode being classified as non-nullable.
Suggested regression coverage
Add an end-to-end generator/materialization test that:
- generates or parses a model under
#nullable disable;
- uses
[Nullable] public abstract string Value { get; };
- verifies
Column.Nullable == true;
- verifies the effective model contract allows SQL
NULL;
- materializes SQL
NULL as null;
- preserves the existing mismatch behavior for genuinely non-nullable properties.
The fix likely needs to account for the nullable annotation context and/or the generated [Nullable] contract when constructing CsNullable, rather than relying exclusively on NullableTypeSyntax.
Summary
When model generation uses
UseNullableReferenceTypes: false, DataLinq 0.9 can generate nullable reference columns that its own runtime later rejects as non-nullable.A nullable database
varcharis generated in the legacy nullable context as:The source generator then emits contradictory metadata:
When SQL returns
NULL, v0.9 throwsDataLinqNullabilityMismatchExceptionbecauseValueProperty.CsNullableis false, even though the database column and generated model attribute both declare the column nullable and nullable reference annotations are explicitly disabled.Verified with
DataLinq.CLI 0.9.0-rc.4+9e6b80f6137e7a546a2616f62d5e6d5ec386040a.Reproduction
Use a model-generation configuration containing:
{ "UseNullableReferenceTypes": false }Generate a model from a table containing a nullable reference column, for example:
Then query a row where that column is SQL
NULL.The generated model uses
#nullable disable,[Nullable], and a plainstringproperty. At runtime, materialization fails with a nullability mismatch stating that the database column is nullable but the generated model property is non-nullable.Regenerating the models with the 0.9 CLI does not resolve the mismatch.
Expected behavior
Models generated with
UseNullableReferenceTypes: falseshould remain usable with nullable reference columns. SQLNULLshould materialize asnullfor a generated[Nullable] stringproperty in a disabled nullable-annotation context.The stricter v0.9 validation should still reject:
NULLfor non-nullable value types;NULLfor reference properties explicitly known to be non-nullable;Likely cause
SyntaxParsercurrently derivesCsNullableonly from nullable type syntax:That is sufficient for
string?with nullable reference types enabled, but not for plainstringunder#nullable disable. In that context, the absence of?does not mean the CLR property cannot representnull.The runtime nullability contract subsequently treats
CsNullable = falseas proof that the model rejects SQLNULL.This is related to #11, but is the inverse case: #11 correctly aims to reject SQL
NULLfor genuinely non-nullable models, while this issue concerns a nullable model generated in the supported legacy nullable mode being classified as non-nullable.Suggested regression coverage
Add an end-to-end generator/materialization test that:
#nullable disable;[Nullable] public abstract string Value { get; };Column.Nullable == true;NULL;NULLasnull;The fix likely needs to account for the nullable annotation context and/or the generated
[Nullable]contract when constructingCsNullable, rather than relying exclusively onNullableTypeSyntax.