{"record":{"id":"1f3b7b8f51be22fb","repo":"TechnitiumSoftware/DnsServer","slug":"old-and-new-record-types-do-not-match-1f3b7b","errorCode":null,"errorMessage":"Old and new record types do not match.","messagePattern":"Old and new record types do not match\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"DnsServerCore/Dns/Zones/ForwarderZone.cs","lineNumber":247,"sourceCode":"                        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);\n\n                    TriggerNotify();\n                    break;\n            }","sourceCodeStart":229,"sourceCodeEnd":265,"githubUrl":"https://github.com/TechnitiumSoftware/DnsServer/blob/d0484b6c1e7439cdc53d67d81e9c876cda2ad756/DnsServerCore/Dns/Zones/ForwarderZone.cs#L229-L265","documentation":"Thrown by ForwarderZone.UpdateRecord() in the default case when oldRecord.Type != newRecord.Type. UpdateRecord implements record mutation as a delete-then-add with shared commit/serial, and allowing a type change there would corrupt the per-type _entries dictionary and the SOA serial bookkeeping. DNS updates that change a record's type are not an update; they are a delete of one type plus an add of another, so the API enforces type identity. This is a programming-contract violation (InvalidOperationException), not a transient or data condition.","triggerScenarios":"Calling zone.UpdateRecord(oldRecord, newRecord) where oldRecord is e.g. an A record and newRecord is an AAAA record (or any type mismatch). Common when a client builds newRecord from form input that lets the user change the record-type dropdown, or when copying fields between record DTOs without preserving Type.","commonSituations":"Edit-record UI with a mutable type selector that was not disabled for edits; DTO mapping that omits Type and defaults it; refactoring that swaps record constructors.","solutions":["Keep the record type immutable across an update: construct newRecord with the same DnsResourceRecordType as oldRecord.","If a type change is genuinely required, perform zone.DeleteRecord(oldType, oldRdata) then zone.AddRecord(newRecord) as two operations instead of UpdateRecord.","Validate type equality in your API layer and return a 400 before calling UpdateRecord."],"exampleFix":"// before\nvar newRecord = new DnsResourceRecord(name, pickedType, ...);\nzone.UpdateRecord(oldRecord, newRecord);\n\n// after\nvar newRecord = new DnsResourceRecord(name, oldRecord.Type, ...); // keep type\nzone.UpdateRecord(oldRecord, newRecord);","handlingStrategy":"validation","validationCode":"if (oldRecord.Type != newRecord.Type)\n    throw new ArgumentException(\"Record type cannot change on update; delete+add instead.\");\nzone.UpdateRecord(oldRecord, newRecord);","typeGuard":"static bool IsTypeCompatibleUpdate(DnsResourceRecord oldRecord, DnsResourceRecord newRecord) => oldRecord.Type == newRecord.Type;","tryCatchPattern":"try { zone.UpdateRecord(oldRecord, newRecord); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"types do not match\")) { /* perform delete(oldType) + add(newType) instead */ }","preventionTips":["Construct newRecord by copying oldRecord.Type, never from a user-mutable type selector on edits.","Return HTTP 400 for type-mismatched updates at the API boundary instead of reaching the zone layer.","Add a DTO validator: editRecordDto.Type must equal the persisted record's type."],"tags":["dns","forwarder-zone","record-update","invalidoperationexception"],"backgroundTag":null,"analyzedSha":"d0484b6c1e7439cdc53d67d81e9c876cda2ad756","analyzedAt":"2026-08-13T22:57:35.508Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}