{"record":{"id":"d5a2b97beabf5bcd","repo":"TechnitiumSoftware/DnsServer","slug":"cannot-update-record-the-record-does-not-exists-t-d5a2b9","errorCode":null,"errorMessage":"Cannot update record: the record does not exists to be updated.","messagePattern":"Cannot update record: the record does not exists to be updated\\.","errorType":"exception","errorClass":"DnsServerException","httpStatus":null,"severity":"error","filePath":"DnsServerCore/Dns/Zones/ForwarderZone.cs","lineNumber":253,"sourceCode":"            }\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);\n\n                    TriggerNotify();\n                    break;\n            }\n        }\n\n        public override IReadOnlyList<DnsResourceRecord> QueryRecords(DnsResourceRecordType type, bool dnssecOk)\n        {\n            if (this is CatalogZone)\n                return base.QueryRecords(type, dnssecOk);","sourceCodeStart":235,"sourceCodeEnd":271,"githubUrl":"https://github.com/TechnitiumSoftware/DnsServer/blob/d0484b6c1e7439cdc53d67d81e9c876cda2ad756/DnsServerCore/Dns/Zones/ForwarderZone.cs#L235-L271","documentation":"Thrown by ForwarderZone.UpdateRecord() when TryDeleteRecord(oldRecord.Type, oldRecord.RDATA) returns false, meaning no existing record matched the old record's (type, rdata) pair. UpdateRecord is implemented as delete-old-then-add-new inside one commit; if the old record is absent there is nothing to update and the add-then-commit would silently create a record without removing one, so the operation is rejected. The message notes the record 'does not exists' (sic) to be updated. DnsServerException, so catchable by API clients.","triggerScenarios":"Calling zone.UpdateRecord(oldRecord, newRecord) where oldRecord no longer exists in the zone (already deleted, or RDATA does not byte-match an entry), or where oldRecord was constructed with RDATA that differs from the stored record (e.g. canonicalized differently). Concurrent modification between a list-query and the update also produces this.","commonSituations":"Stale client view: user lists records, another process deletes one, user submits an edit on the deleted record; RDATA normalization differences (case, compression, trailing dot) causing TryDeleteRecord to miss; retrying an update after a partial failure.","solutions":["Re-fetch the current record set (QueryRecords) immediately before updating and rebuild oldRecord from the live entry so its RDATA matches exactly.","If the old record is genuinely gone, switch to AddRecord(newRecord) instead of UpdateRecord.","Handle this as a known concurrent-edit case: return a 409 Conflict to the client and let them refresh."],"exampleFix":"// before\nzone.UpdateRecord(staleOldRecord, newRecord);\n\n// after\nvar live = zone.QueryRecords(type, false)\n    .FirstOrDefault(r => r.RDATA.Equals(staleOldRecord.RDATA));\nif (live == null)\n    zone.AddRecord(newRecord); // old gone -> create instead\nelse\n    zone.UpdateRecord(live, newRecord);","handlingStrategy":"validation","validationCode":"var live = zone.QueryRecords(oldRecord.Type, false).FirstOrDefault(r => r.RDATA.Equals(oldRecord.RDATA));\nif (live == null) { zone.AddRecord(newRecord); return; }\nzone.UpdateRecord(live, newRecord);","typeGuard":"static bool RecordStillExists(Zone zone, DnsResourceRecord oldRecord) => zone.QueryRecords(oldRecord.Type, false).Any(r => r.RDATA.Equals(oldRecord.RDATA));","tryCatchPattern":"try { zone.UpdateRecord(oldRecord, newRecord); }\ncatch (DnsServerException ex) when (ex.Message.Contains(\"does not exists\")) { zone.AddRecord(newRecord); }","preventionTips":["Re-query the record set immediately before updating and pass the live record as oldRecord.","Treat 'does not exist' as a concurrent-edit signal; return 409 and let the client refresh.","Normalize RDATA (case, trailing dots) when matching to avoid false misses."],"tags":["dns","forwarder-zone","record-update","concurrency","dnsserverexception"],"backgroundTag":null,"analyzedSha":"d0484b6c1e7439cdc53d67d81e9c876cda2ad756","analyzedAt":"2026-08-13T22:57:35.508Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}