Summary
When a key property uses expression-body (=>) or read-only auto-property ({ get; }) syntax with a reference to a static readonly field (rather than a const or string literal), the constant key detection silently fails. The generator then falls through to the normal code path, which emits property assignments in FromDynamoDb() that won't compile because the property has no setter.
This manifests as two user-facing problems:
Property or indexer 'Entity.Sk' cannot be assigned to -- it is read only in FromDynamoDb()
- Generated CRUD methods (
Update(), Get(), Delete()) still require the key parameter instead of omitting it
Reproduction
public static class DynamoDB
{
public static readonly string DefaultSortkeyValue = "PROFILE"; // ❌ static readonly, NOT const
}
[DynamoDbTable("Users")]
public partial class User
{
[PartitionKey(Prefix = "USER")]
[DynamoDbAttribute("PK")]
public string Pk { get; set; } = string.Empty;
// Expression-body referencing a static readonly field
[SortKey]
[DynamoDbAttribute("SK")]
public string Sk => DynamoDB.DefaultSortkeyValue;
}
Expected Behavior
Either:
- Detection succeeds and the key is treated as constant (parameter omitted, no assignment in FromDynamoDb), OR
- The generator emits a clear diagnostic explaining the requirement
Actual Behavior
DetectConstantKeyValue calls semanticModel.GetConstantValue(expr) which returns null for static readonly fields (only resolves compile-time constants)
IsConstantKey remains false
FromDynamoDb() generates entity.Sk = value; → compile error (no setter)
Update(pk, sk) is generated instead of Update(pk) → user must still pass the fixed SK value
Root Cause
Two separate issues:
Issue A: Detection Only Works with Compile-Time Constants
EntityAnalyzer.DetectConstantKeyValue uses SemanticModel.GetConstantValue() which only resolves:
- String literals (
"PROFILE")
- References to
const fields (const string X = "PROFILE")
It does NOT resolve:
static readonly fields
- Property references
- Method calls
This is a fundamental Roslyn limitation — GetConstantValue is specifically for compile-time constants.
Issue B: No Graceful Fallback for Read-Only Properties
When detection fails, the generator doesn't check whether the property actually HAS a setter before emitting assignment code. The normal FromDynamoDb path in MapperGenerator.GeneratePropertyFromAttributeValue unconditionally generates entity.Property = ... without verifying that a setter exists.
Proposed Fix
Fix A: Emit Diagnostic for Unresolvable Constant Key
When a key property is expression-body or read-only auto-property BUT GetConstantValue() returns null, emit a new diagnostic:
FDDB125: Key property '{PropertyName}' uses expression-body/read-only syntax but the value
is not a compile-time constant. Use a string literal or const field reference.
Static readonly fields are not supported.
This makes the requirement explicit instead of generating uncompilable code.
Fix B: Guard Assignment in FromDynamoDb
As a safety net, before generating property assignment code, check if the property has a setter. If it doesn't and IsConstantKey is false, emit FDDB125 instead of generating the assignment.
Detection logic in EntityAnalyzer.DetectConstantKeyValue:
// After both Case 1 and Case 2 fall through without setting ConstantKeyValue:
// Check if the property is read-only (no setter) — this means we have an
// unresolvable constant key that will generate uncompilable code
if (!propertyModel.IsConstantKey && IsReadOnlyProperty(propertyDecl))
{
// Emit diagnostic: read-only key property with non-const value
ReportDiagnostic(DiagnosticDescriptors.ConstantKeyNonConstReference,
propertyDecl.GetLocation(),
propertyModel.PropertyName);
}
Where IsReadOnlyProperty checks:
- Expression-body (
ExpressionBody != null) → always read-only
- Only getter accessor with no set/init → read-only
User Guidance
Document clearly that constant key detection requires compile-time constants:
// ✅ String literal
public string Sk => "PROFILE";
// ✅ const field reference (same class or external)
public const string ProfileKey = "PROFILE";
public string Sk => ProfileKey;
// ✅ const from another class
public string Sk => Constants.ProfileKey; // where ProfileKey is const
// ❌ static readonly — NOT a compile-time constant
public static readonly string ProfileKey = "PROFILE";
public string Sk => ProfileKey; // Detection fails, generates bad code
// ❌ Property reference — NOT a compile-time constant
public string Sk => SomeClass.SomeProperty; // Detection fails
Acceptance Criteria
- New diagnostic FDDB125 emitted when a key property is read-only but value is not resolvable as a compile-time constant
- No uncompilable code generated for read-only key properties (assignment skipped or error emitted)
- Existing tests pass (entities with proper const values still work)
- Documentation updated to clarify the
const requirement
Summary
When a key property uses expression-body (
=>) or read-only auto-property ({ get; }) syntax with a reference to astatic readonlyfield (rather than aconstor string literal), the constant key detection silently fails. The generator then falls through to the normal code path, which emits property assignments inFromDynamoDb()that won't compile because the property has no setter.This manifests as two user-facing problems:
Property or indexer 'Entity.Sk' cannot be assigned to -- it is read onlyinFromDynamoDb()Update(),Get(),Delete()) still require the key parameter instead of omitting itReproduction
Expected Behavior
Either:
Actual Behavior
DetectConstantKeyValuecallssemanticModel.GetConstantValue(expr)which returnsnullforstatic readonlyfields (only resolves compile-time constants)IsConstantKeyremainsfalseFromDynamoDb()generatesentity.Sk = value;→ compile error (no setter)Update(pk, sk)is generated instead ofUpdate(pk)→ user must still pass the fixed SK valueRoot Cause
Two separate issues:
Issue A: Detection Only Works with Compile-Time Constants
EntityAnalyzer.DetectConstantKeyValueusesSemanticModel.GetConstantValue()which only resolves:"PROFILE")constfields (const string X = "PROFILE")It does NOT resolve:
static readonlyfieldsThis is a fundamental Roslyn limitation —
GetConstantValueis specifically for compile-time constants.Issue B: No Graceful Fallback for Read-Only Properties
When detection fails, the generator doesn't check whether the property actually HAS a setter before emitting assignment code. The normal
FromDynamoDbpath inMapperGenerator.GeneratePropertyFromAttributeValueunconditionally generatesentity.Property = ...without verifying that a setter exists.Proposed Fix
Fix A: Emit Diagnostic for Unresolvable Constant Key
When a key property is expression-body or read-only auto-property BUT
GetConstantValue()returns null, emit a new diagnostic:This makes the requirement explicit instead of generating uncompilable code.
Fix B: Guard Assignment in FromDynamoDb
As a safety net, before generating property assignment code, check if the property has a setter. If it doesn't and
IsConstantKeyis false, emit FDDB125 instead of generating the assignment.Detection logic in
EntityAnalyzer.DetectConstantKeyValue:Where
IsReadOnlyPropertychecks:ExpressionBody != null) → always read-onlyUser Guidance
Document clearly that constant key detection requires compile-time constants:
Acceptance Criteria
constrequirement