{"record":{"id":"a7fa9d5f6b5535a9","repo":"TechnitiumSoftware/DnsServer","slug":"cannot-update-record-use-setrecords-for-0-rec-a7fa9d","errorCode":null,"errorMessage":"Cannot update record: use SetRecords() for {0} record","messagePattern":"Cannot update record: use SetRecords\\(\\) for (.+?) record","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"DnsServerCore/Dns/Zones/ForwarderZone.cs","lineNumber":243,"sourceCode":"                    if (TryDeleteRecord(type, rdata, out DnsResourceRecord deletedRecord))\n                    {\n                        CommitAndIncrementSerial([deletedRecord]);\n\n                        TriggerNotify();\n\n                        return true;\n                    }\n\n                    return false;\n            }\n        }\n\n        public override void UpdateRecord(DnsResourceRecord oldRecord, DnsResourceRecord newRecord)\n        {\n            switch (oldRecord.Type)\n            {\n                case DnsResourceRecordType.SOA:\n                    throw new InvalidOperationException(\"Cannot update record: use SetRecords() for \" + oldRecord.Type.ToString() + \" record\");\n\n                default:\n                    if (oldRecord.Type != newRecord.Type)\n                        throw new InvalidOperationException(\"Old and new record types do not match.\");\n\n                    if (newRecord.OriginalTtlValue > GetZoneSoaExpire())\n                        throw new DnsServerException(\"Cannot update record: TTL cannot be greater than SOA EXPIRE.\");\n\n                    if (!TryDeleteRecord(oldRecord.Type, oldRecord.RDATA, out DnsResourceRecord deletedRecord))\n                        throw new DnsServerException(\"Cannot update record: the record does not exists to be updated.\");\n\n                    AddRecord(newRecord, out IReadOnlyList<DnsResourceRecord> addedRecords, out IReadOnlyList<DnsResourceRecord> deletedRecords);\n\n                    List<DnsResourceRecord> allDeletedRecords = new List<DnsResourceRecord>(deletedRecords.Count + 1);\n                    allDeletedRecords.Add(deletedRecord);\n                    allDeletedRecords.AddRange(deletedRecords);\n\n                    CommitAndIncrementSerial(allDeletedRecords, addedRecords);","sourceCodeStart":225,"sourceCodeEnd":261,"githubUrl":"https://github.com/TechnitiumSoftware/DnsServer/blob/d0484b6c1e7439cdc53d67d81e9c876cda2ad756/DnsServerCore/Dns/Zones/ForwarderZone.cs#L225-L261","documentation":"Thrown by ForwarderZone.UpdateRecord() when oldRecord.Type is SOA. SOA records are special single-valued apex records whose semantics (serial, refresh, retry, expire, minimum, name server, responsible person) cannot be patched via the generic add/delete UpdateRecord flow; they must be atomically replaced via SetRecords(SOA, ...), which validates TTL<=Expire, Retry<=Refresh, Refresh<=Expire, and rewrites the responsible person to 'invalid'. The message echoes the offending type so the caller knows exactly which record type to redirect.","triggerScenarios":"Calling zone.UpdateRecord(oldSoaRecord, newSoaRecord) where oldRecord.Type == DnsResourceRecordType.SOA on a ForwarderZone. Happens when generic record-edit UI/API code passes an SOA row into the same update handler used for A/AAAA/CNAME records.","commonSituations":"A generic 'edit record' API endpoint that does not branch on record type; an import/sync routine that round-trips SOA through the update path; clients editing SOA timers (refresh/retry/expire) via the wrong endpoint.","solutions":["Route SOA edits through SetRecords(DnsResourceRecordType.SOA, new[] { newSoaRecord }) instead of UpdateRecord.","Branch in your API/UI layer: if (oldRecord.Type == DnsResourceRecordType.SOA) zone.SetRecords(oldRecord.Type, new[] { newRecord }); else zone.UpdateRecord(oldRecord, newRecord);","Ensure the new SOA passes SetRecords validation (TTL <= Expire, Retry <= Refresh, Refresh <= Expire)."],"exampleFix":"// before\nzone.UpdateRecord(oldRecord, newRecord);\n\n// after\nif (oldRecord.Type == DnsResourceRecordType.SOA)\n    zone.SetRecords(DnsResourceRecordType.SOA, new[] { newRecord });\nelse\n    zone.UpdateRecord(oldRecord, newRecord);","handlingStrategy":"validation","validationCode":"if (oldRecord.Type == DnsResourceRecordType.SOA)\n    zone.SetRecords(DnsResourceRecordType.SOA, new[] { newRecord });\nelse\n    zone.UpdateRecord(oldRecord, newRecord);","typeGuard":"static bool RequiresSetRecordsInsteadOfUpdate(DnsResourceRecordType type) => type == DnsResourceRecordType.SOA;","tryCatchPattern":"try { zone.UpdateRecord(oldRecord, newRecord); }\ncatch (InvalidOperationException ex) when (oldRecord.Type == DnsResourceRecordType.SOA) { zone.SetRecords(DnsResourceRecordType.SOA, new[] { newRecord }); }","preventionTips":["In your record-edit API, branch SOA to SetRecords before calling UpdateRecord.","Disable the record-type field in the edit UI once a record is selected for update.","Document that SOA, like DNSSEC records, is set-only, not updateable."],"tags":["dns","forwarder-zone","soa-record","invalidoperationexception"],"backgroundTag":null,"analyzedSha":"d0484b6c1e7439cdc53d67d81e9c876cda2ad756","analyzedAt":"2026-08-13T22:57:35.508Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}