{"record":{"id":"c35755977cede226","repo":"dotnet/efcore","slug":"compound-assignment-of-private-property-not-yet-su","errorCode":null,"errorMessage":"Compound assignment of private property not yet supported","messagePattern":"Compound assignment of private property not yet supported","errorType":"exception","errorClass":"NotImplementedException","httpStatus":null,"severity":"error","filePath":"src/EFCore.Design/Query/Internal/LinqToCSharpSyntaxTranslator.cs","lineNumber":1736,"sourceCode":"                : memberExpression.Member,\n            forWrite: true);\n\n        // The unsafe accessor declaration has been created; invoke it.\n        Result = memberExpression.Member switch\n        {\n            FieldInfo => AssignmentExpression(\n                assignmentKind,\n                (ExpressionSyntax)_g.InvocationExpression(\n                    _g.IdentifierName(unsafeAccessorDeclaration.Identifier.Text),\n                    Translate<ExpressionSyntax>(memberExpression.Expression)),\n                Translate<ExpressionSyntax>(value)),\n\n            PropertyInfo =>\n                _g.InvocationExpression(\n                    _g.IdentifierName(unsafeAccessorDeclaration.Identifier.Text), Translate<ExpressionSyntax>(memberExpression.Expression),\n                    assignmentKind is SyntaxKind.SimpleAssignmentExpression\n                        ? Translate<ExpressionSyntax>(value)\n                        : throw new NotImplementedException(\"Compound assignment of private property not yet supported\")),\n\n            _ => throw new UnreachableException()\n        };\n    }\n\n    private MethodDeclarationSyntax GetUnsafeAccessorDeclaration(MemberInfo member, bool forWrite = false)\n    {\n        MethodDeclarationSyntax? unsafeAccessorDeclaration;\n\n        switch (member)\n        {\n            case FieldInfo field:\n            {\n                // Note that we generate two accessors for fields (get/set), since the get accessor needs to be used in expression trees,\n                // which don't support ref return\n                if (_fieldUnsafeAccessors.TryGetValue((field, forWrite), out unsafeAccessorDeclaration))\n                {\n                    return unsafeAccessorDeclaration;","sourceCodeStart":1718,"sourceCodeEnd":1754,"githubUrl":"https://github.com/dotnet/efcore/blob/3a2006ef569de08368d59db5e1468aa8f407e4f8/src/EFCore.Design/Query/Internal/LinqToCSharpSyntaxTranslator.cs#L1718-L1754","documentation":"Thrown by LinqToCSharpSyntaxTranslator.TranslateNonPublicMemberAssignment when the lvalue is a private/internal PropertyInfo (accessed via UnsafeAccessor) and the assignment is not a simple SyntaxKind.SimpleAssignmentExpression — i.e. a compound assignment such as +=, -=, *=, etc. The property path only supports direct setter invocation; compound assignments would require read-modify-write across getter and setter, which is not implemented.","triggerScenarios":"An expression tree performs a compound assignment (AddAssign, SubtractAssign, MultiplyAssign, ...) whose left side is a MemberExpression over a non-public property, reaching TranslateNonPublicMemberAssignment with assignmentKind != SimpleAssignmentExpression.","commonSituations":"Reflection-built expression trees that mutate private property state incrementally; provider/test code that does Count++ on a private property; converting C# compound-assignment code into expression trees that target private state.","solutions":["Rewrite the compound assignment as an explicit read-then-write: read via the getter, combine with the right operand, then assign via the setter using simple assignment.","Expose a public method that performs the compound operation and call it instead.","Make the property public so the normal assignment path applies (where compound operators are supported)."],"exampleFix":"// before — compound assign to a private property\nvar prop = typeof(Item).GetProperty(\"Count\",\n    BindingFlags.NonPublic | BindingFlags.Instance)!;\nvar assign = Expression.AddAssign(\n    Expression.Property(itemParam, prop),\n    Expression.Constant(1));\n\n// after — expand to getter + setter with simple assignment\nvar read = Expression.Property(itemParam, prop);\nvar assign = Expression.Assign(\n    Expression.Property(itemParam, prop),\n    Expression.Add(read, Expression.Constant(1)));","handlingStrategy":"validation","validationCode":"// Detect compound assignments targeting non-public properties.\nusing System.Linq.Expressions;\nusing System.Reflection;\n\nstatic readonly HashSet<ExpressionType> CompoundAssignTypes = new()\n{\n    ExpressionType.AddAssign, ExpressionType.AddAssignChecked,\n    ExpressionType.SubtractAssign, ExpressionType.SubtractAssignChecked,\n    ExpressionType.MultiplyAssign, ExpressionType.MultiplyAssignChecked,\n    ExpressionType.DivideAssign, ExpressionType.ModuloAssign,\n    ExpressionType.AndAssign, ExpressionType.OrAssign,\n    ExpressionType.LeftShiftAssign, ExpressionType.RightShiftAssign,\n    ExpressionType.ExclusiveOrAssign, ExpressionType.PowerAssign,\n};\n\nbool HasCompoundAssignOnPrivateProperty(Expression e)\n{\n    bool bad = false;\n    new CompoundChecker(b =>\n    {\n        if (CompoundAssignTypes.Contains(b.NodeType)\n            && b.Left is MemberExpression { Member: PropertyInfo pi }\n            && (pi.SetMethod?.IsPublic ?? false) == false)\n            bad = true;\n    }).Visit(e);\n    return bad;\n}\n\nclass CompoundChecker(Action<BinaryExpression> onBinary) : ExpressionVisitor\n{\n    protected override Expression VisitBinary(BinaryExpression b) { onBinary(b); return base.VisitBinary(b); }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Expand compound assignments on private properties into getter + binary op + setter with simple assignment.","Expose a public method that performs the compound operation and call it from the tree.","Pre-scan trees for compound assignments whose lvalues are non-public properties."],"tags":["ef-core","expression-trees","reflection","unsafe-accessor","compound-assignment","properties","code-generation"],"backgroundTag":null,"analyzedSha":"3a2006ef569de08368d59db5e1468aa8f407e4f8","analyzedAt":"2026-08-11T23:42:04.146Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}