From 985abf963d6bb5fd118dd6d6b96f314855fcef4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Mon, 26 Jan 2026 13:29:45 +0000 Subject: [PATCH 1/3] Fix: response name assignment in request handler execution --- src/XrmMockup365/Core.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/XrmMockup365/Core.cs b/src/XrmMockup365/Core.cs index 23188561..64d4c749 100644 --- a/src/XrmMockup365/Core.cs +++ b/src/XrmMockup365/Core.cs @@ -1000,7 +1000,12 @@ private OrganizationResponse ExecuteRequest( var handler = RequestHandlers.FirstOrDefault(x => x.HandlesRequest(request.RequestName)); if (handler != null) { - return handler.Execute(request, userRef); + var response = handler.Execute(request, userRef); + if (response != null && string.IsNullOrEmpty(response.ResponseName)) + { + response.ResponseName = request.RequestName; + } + return response; } if (settings.ExceptionFreeRequests?.Contains(request.RequestName) ?? false) From 32fe70b51bc64e96b8b4ef1332355202ca5920e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Thu, 29 Jan 2026 20:18:01 +0000 Subject: [PATCH 2/3] fix: Add EntityCollection deserialization support for snapshot restore Fixes deserialization errors when restoring snapshots that contain EntityCollection fields (e.g., Email.From, Email.To, PhoneCall.From, Appointment.OptionalAttendees). Issue: - Microsoft.Xrm.Sdk.EntityCollection contains a DataCollection property that lacks a parameterless constructor, causing System.Text.Json deserialization to fail with: "Deserialization of types without a parameterless constructor... is not supported" - This affects any activity entity with activity party collections Solution: - Created EntityCollectionDTO for JSON deserialization using JsonElement - Added special handling in ConvertValueFromSerializableDTO() to: 1. Parse JSON manually using JsonElement (avoiding constructor requirement) 2. Reconstruct Entity objects property-by-property (LogicalName, Id, Attributes) 3. Create new EntityCollection with reconstructed entities 4. Restore collection metadata (MoreRecords, PagingCookie, etc.) This follows the existing pattern used for OptionSetValueCollection deserialization. Tested by successfully restoring a snapshot containing email entities with From/To fields via XrmEmulator service startup. --- src/XrmMockup365/Internal/Utility.cs | 55 +++++++++++++++++++ .../Serialization/EntityCollectionDTO.cs | 16 ++++++ 2 files changed, 71 insertions(+) create mode 100644 src/XrmMockup365/Serialization/EntityCollectionDTO.cs diff --git a/src/XrmMockup365/Internal/Utility.cs b/src/XrmMockup365/Internal/Utility.cs index 2d6c80d8..c4801384 100644 --- a/src/XrmMockup365/Internal/Utility.cs +++ b/src/XrmMockup365/Internal/Utility.cs @@ -1015,6 +1015,61 @@ public static object ConvertValueFromSerializableDTO(TableColumnDTO colToSeriali var newCollection = new OptionSetValueCollection(typed.Values.Select(x => new OptionSetValue(x)).ToList()); return newCollection; } + else if (type == typeof(EntityCollection)) + { + var node = JsonNode.Parse(colToSerialize.Value); + var typed = (EntityCollectionDTO)node.Deserialize(typeof(EntityCollectionDTO)); + + // Manually deserialize entities + var entities = new List(); + if (typed.Entities.ValueKind == JsonValueKind.Array) + { + foreach (var entityElement in typed.Entities.EnumerateArray()) + { + var entity = new Entity(); + + // Deserialize LogicalName + if (entityElement.TryGetProperty("LogicalName", out var logicalNameProp)) + { + entity.LogicalName = logicalNameProp.GetString(); + } + + // Deserialize Id + if (entityElement.TryGetProperty("Id", out var idProp) && idProp.ValueKind == JsonValueKind.String) + { + entity.Id = Guid.Parse(idProp.GetString()); + } + + // Deserialize Attributes + if (entityElement.TryGetProperty("Attributes", out var attributesProp) && attributesProp.ValueKind == JsonValueKind.Array) + { + foreach (var attr in attributesProp.EnumerateArray()) + { + if (attr.TryGetProperty("Key", out var keyProp) && attr.TryGetProperty("Value", out var valueProp)) + { + var key = keyProp.GetString(); + // Recursively deserialize attribute values (could be EntityReference, etc.) + var value = JsonSerializer.Deserialize(valueProp.GetRawText()); + entity[key] = value; + } + } + } + + entities.Add(entity); + } + } + + var newCollection = new EntityCollection(entities) + { + MoreRecords = typed.MoreRecords, + PagingCookie = typed.PagingCookie, + MinActiveRowVersion = typed.MinActiveRowVersion, + TotalRecordCount = typed.TotalRecordCount, + TotalRecordCountLimitExceeded = typed.TotalRecordCountLimitExceeded, + EntityName = typed.EntityName + }; + return newCollection; + } else { var node = JsonNode.Parse(colToSerialize.Value); diff --git a/src/XrmMockup365/Serialization/EntityCollectionDTO.cs b/src/XrmMockup365/Serialization/EntityCollectionDTO.cs new file mode 100644 index 00000000..2794b88f --- /dev/null +++ b/src/XrmMockup365/Serialization/EntityCollectionDTO.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.Text.Json; + +namespace DG.Tools.XrmMockup.Serialization +{ + public class EntityCollectionDTO + { + public JsonElement Entities { get; set; } + public bool MoreRecords { get; set; } + public string PagingCookie { get; set; } + public string MinActiveRowVersion { get; set; } + public int TotalRecordCount { get; set; } + public bool TotalRecordCountLimitExceeded { get; set; } + public string EntityName { get; set; } + } +} From dae5ccfb61faae613d537f539b637708658e2df7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poul=20Kjeldager=20S=C3=B8rensen?= Date: Sat, 31 Jan 2026 15:57:00 +0000 Subject: [PATCH 3/3] fix: Correctly close the new collection creation in ConvertValueFromSerializableDTO --- src/XrmMockup365/Internal/Utility.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/XrmMockup365/Internal/Utility.cs b/src/XrmMockup365/Internal/Utility.cs index 21cc91f5..15064846 100644 --- a/src/XrmMockup365/Internal/Utility.cs +++ b/src/XrmMockup365/Internal/Utility.cs @@ -1098,6 +1098,7 @@ public static object ConvertValueFromSerializableDTO(TableColumnDTO colToSeriali EntityName = typed.EntityName }; return newCollection; + } else if (type == typeof(BooleanManagedProperty)) { var node = JsonNode.Parse(colToSerialize.Value);