From 8ad9ca624be13ca37ba0bd8112ac4ce5601897c2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Sep 2025 08:06:10 +0000 Subject: [PATCH 1/4] Initial plan From 77b2382bd681ae94c0ba1a7704b1c746226ec3a2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Sep 2025 08:18:10 +0000 Subject: [PATCH 2/4] Implement NotAny join operator support for Query Expression Co-authored-by: KristianKlodeD <128026855+KristianKlodeD@users.noreply.github.com> --- .../Plugin/PluginExecutionProvider.cs | 1 + .../RetrieveMultipleRequestHandler.cs | 10 ++++ src/XrmMockupShared/XmlHandling.cs | 2 + .../InstantiateTemplateRequestHandler.cs | 1 + .../RetrieveAllEntitiesRequestHandler.cs | 1 + .../FaxPreOperationPlugin.cs | 1 + tests/SharedTests/TestRetrieveMultiple.cs | 56 +++++++++++++++++++ 7 files changed, 72 insertions(+) create mode 120000 src/XrmMockupShared/Plugin/PluginExecutionProvider.cs create mode 120000 src/XrmMockupShared/requests/InstantiateTemplateRequestHandler.cs create mode 120000 src/XrmMockupShared/requests/RetrieveAllEntitiesRequestHandler.cs create mode 120000 tests/SharedPluginsAndCodeactivites/FaxPreOperationPlugin.cs diff --git a/src/XrmMockupShared/Plugin/PluginExecutionProvider.cs b/src/XrmMockupShared/Plugin/PluginExecutionProvider.cs new file mode 120000 index 00000000..9a184545 --- /dev/null +++ b/src/XrmMockupShared/Plugin/PluginExecutionProvider.cs @@ -0,0 +1 @@ +Plugin/PluginExecutionprovider.cs \ No newline at end of file diff --git a/src/XrmMockupShared/Requests/RetrieveMultipleRequestHandler.cs b/src/XrmMockupShared/Requests/RetrieveMultipleRequestHandler.cs index e6f8211b..60f4bcbb 100644 --- a/src/XrmMockupShared/Requests/RetrieveMultipleRequestHandler.cs +++ b/src/XrmMockupShared/Requests/RetrieveMultipleRequestHandler.cs @@ -208,6 +208,16 @@ private List GetAliasedValuesFromLinkentity(LinkEntity linkEntity, Entit { collection.Add(toAdd); } + else if (linkEntity.JoinOperator == JoinOperator.NotAny && collection.Count == 0) + { + // NotAny join: return parent entity only if no matching linked entities were found + collection.Add(toAdd); + } + else if (linkEntity.JoinOperator == JoinOperator.NotAny && collection.Count > 0) + { + // NotAny join: if matching linked entities were found, don't return anything + collection.Clear(); + } return collection; } diff --git a/src/XrmMockupShared/XmlHandling.cs b/src/XrmMockupShared/XmlHandling.cs index a075f5bc..e23e46a5 100644 --- a/src/XrmMockupShared/XmlHandling.cs +++ b/src/XrmMockupShared/XmlHandling.cs @@ -111,6 +111,8 @@ public static LinkEntity LinkEntityFromXml(string parentLogicalName, string link linkEntity.JoinOperator = JoinOperator.LeftOuter; } else if (joinOperator.Value == "natural") { linkEntity.JoinOperator = JoinOperator.Natural; + } else if (joinOperator.Value == "notany") { + linkEntity.JoinOperator = JoinOperator.NotAny; } if (link.Element("filter") != null) { diff --git a/src/XrmMockupShared/requests/InstantiateTemplateRequestHandler.cs b/src/XrmMockupShared/requests/InstantiateTemplateRequestHandler.cs new file mode 120000 index 00000000..de2d0713 --- /dev/null +++ b/src/XrmMockupShared/requests/InstantiateTemplateRequestHandler.cs @@ -0,0 +1 @@ +../Requests/InstantiateTemplateRequestHandler.cs \ No newline at end of file diff --git a/src/XrmMockupShared/requests/RetrieveAllEntitiesRequestHandler.cs b/src/XrmMockupShared/requests/RetrieveAllEntitiesRequestHandler.cs new file mode 120000 index 00000000..857539f0 --- /dev/null +++ b/src/XrmMockupShared/requests/RetrieveAllEntitiesRequestHandler.cs @@ -0,0 +1 @@ +../Requests/RetrieveAllEntitiesRequestHandler.cs \ No newline at end of file diff --git a/tests/SharedPluginsAndCodeactivites/FaxPreOperationPlugin.cs b/tests/SharedPluginsAndCodeactivites/FaxPreOperationPlugin.cs new file mode 120000 index 00000000..566f07b1 --- /dev/null +++ b/tests/SharedPluginsAndCodeactivites/FaxPreOperationPlugin.cs @@ -0,0 +1 @@ +FaxPreoperationPlugin.cs \ No newline at end of file diff --git a/tests/SharedTests/TestRetrieveMultiple.cs b/tests/SharedTests/TestRetrieveMultiple.cs index 101f7572..b39e5fa8 100644 --- a/tests/SharedTests/TestRetrieveMultiple.cs +++ b/tests/SharedTests/TestRetrieveMultiple.cs @@ -361,6 +361,62 @@ from lead in ls.DefaultIfEmpty() } } + [Fact] + public void TestNotAnyJoin() + { + // Test NotAny join operator - should return contacts that do NOT have any associated leads + var query = new QueryExpression("contact") + { + ColumnSet = new ColumnSet("contactid", "lastname") + }; + + var linkEntity = new LinkEntity() + { + LinkToEntityName = "lead", + LinkToAttributeName = "parentcontactid", + LinkFromEntityName = "contact", + LinkFromAttributeName = "contactid", + Columns = new ColumnSet(false), + EntityAlias = "lead", + JoinOperator = JoinOperator.NotAny, + }; + + query.LinkEntities.Add(linkEntity); + + var result = orgAdminService.RetrieveMultiple(query).Entities.Cast().ToList(); + + // Should return contact3 and contact4 since they have no associated leads + Assert.Equal(2, result.Count); + Assert.Contains(result, x => x.Id == contact3.Id); + Assert.Contains(result, x => x.Id == contact4.Id); + Assert.DoesNotContain(result, x => x.Id == contact1.Id); + Assert.DoesNotContain(result, x => x.Id == contact2.Id); + } + + [Fact] + public void TestNotAnyJoinFetchXml() + { + // Test NotAny join operator via FetchXML - should return contacts that do NOT have any associated leads + var fetchXml = @" + + + + + + + "; + + var fetchExpr = new FetchExpression(fetchXml); + var result = orgAdminService.RetrieveMultiple(fetchExpr).Entities.Cast().ToList(); + + // Should return contact3 and contact4 since they have no associated leads + Assert.Equal(2, result.Count); + Assert.Contains(result, x => x.Id == contact3.Id); + Assert.Contains(result, x => x.Id == contact4.Id); + Assert.DoesNotContain(result, x => x.Id == contact1.Id); + Assert.DoesNotContain(result, x => x.Id == contact2.Id); + } + [Fact] public void TestNestedJoins() { From 665a7f08a7a2a7363280f562fcf62ae829be8421 Mon Sep 17 00:00:00 2001 From: Kristian Holmkvist Klode Date: Wed, 10 Sep 2025 14:32:24 +0200 Subject: [PATCH 3/4] Undid changes to files made by copilot --- .../Plugin/PluginExecutionprovider.cs | 2 +- .../InstantiateTemplateRequestHandler.cs | 44 ++++++++++++++++++- .../RetrieveAllEntitiesRequestHandler.cs | 24 +++++++++- .../FaxPreoperationPlugin.cs | 2 +- 4 files changed, 68 insertions(+), 4 deletions(-) diff --git a/src/XrmMockupShared/Plugin/PluginExecutionprovider.cs b/src/XrmMockupShared/Plugin/PluginExecutionprovider.cs index cb2487e3..0b64e37e 100644 --- a/src/XrmMockupShared/Plugin/PluginExecutionprovider.cs +++ b/src/XrmMockupShared/Plugin/PluginExecutionprovider.cs @@ -1,4 +1,4 @@ -using DG.Tools.XrmMockup; +using DG.Tools.XrmMockup; using System; using System.Collections.Generic; using System.Text; diff --git a/src/XrmMockupShared/requests/InstantiateTemplateRequestHandler.cs b/src/XrmMockupShared/requests/InstantiateTemplateRequestHandler.cs index de2d0713..ef04ac27 120000 --- a/src/XrmMockupShared/requests/InstantiateTemplateRequestHandler.cs +++ b/src/XrmMockupShared/requests/InstantiateTemplateRequestHandler.cs @@ -1 +1,43 @@ -../Requests/InstantiateTemplateRequestHandler.cs \ No newline at end of file +using DG.Tools.XrmMockup.Database; +using Microsoft.Crm.Sdk.Messages; +using Microsoft.Xrm.Sdk; +using System; +using System.ServiceModel; + +namespace DG.Tools.XrmMockup +{ + internal class InstantiateTemplateRequestHandler : RequestHandler + { + public InstantiateTemplateRequestHandler(Core core, XrmDb db, MetadataSkeleton metadata, Security security) : base(core, db, metadata, security, "InstantiateTemplate") { } + + internal override OrganizationResponse Execute(OrganizationRequest orgRequest, EntityReference userRef) + { + var request = MakeRequest(orgRequest); + + if (request.TemplateId == Guid.Empty) + throw new FaultException("Template id should be set."); + + if (request.ObjectId == Guid.Empty) + throw new FaultException("Object id should be set."); + + if (string.IsNullOrEmpty(request.ObjectType)) + throw new FaultException("ObjectType is missing"); + + var entity = new Entity("email"); + + var collection = new EntityCollection(); + collection.Entities.Add(entity); + + var parameters = new ParameterCollection + { + { "EntityCollection", collection } + }; + + return new InstantiateTemplateResponse + { + Results = parameters, + ResponseName = "InstantiateTemplate", + }; + } + } +} \ No newline at end of file diff --git a/src/XrmMockupShared/requests/RetrieveAllEntitiesRequestHandler.cs b/src/XrmMockupShared/requests/RetrieveAllEntitiesRequestHandler.cs index 857539f0..44dea625 120000 --- a/src/XrmMockupShared/requests/RetrieveAllEntitiesRequestHandler.cs +++ b/src/XrmMockupShared/requests/RetrieveAllEntitiesRequestHandler.cs @@ -1 +1,23 @@ -../Requests/RetrieveAllEntitiesRequestHandler.cs \ No newline at end of file +using Microsoft.Xrm.Sdk; +using System.Linq; +using DG.Tools.XrmMockup.Database; +using Microsoft.Xrm.Sdk.Messages; + +namespace DG.Tools.XrmMockup +{ + internal class RetrieveAllEntitiesRequestHandler : RequestHandler + { + internal RetrieveAllEntitiesRequestHandler(Core core, XrmDb db, MetadataSkeleton metadata, Security security) : base(core, db, metadata, security, "RetrieveAllEntities") { } + + internal override OrganizationResponse Execute(OrganizationRequest orgRequest, EntityReference userRef) + { + var request = MakeRequest(orgRequest); + + var response = new RetrieveAllEntitiesResponse(); + response.Results["EntityMetadata"] = metadata.EntityMetadata.Values + .Select(entityMetadata => RetrieveEntityRequestHandler.FilterEntityMetadataProperties(entityMetadata, request.EntityFilters)).ToArray(); + + return response; + } + } +} diff --git a/tests/SharedPluginsAndCodeactivites/FaxPreoperationPlugin.cs b/tests/SharedPluginsAndCodeactivites/FaxPreoperationPlugin.cs index db9ec279..a3142aee 100644 --- a/tests/SharedPluginsAndCodeactivites/FaxPreoperationPlugin.cs +++ b/tests/SharedPluginsAndCodeactivites/FaxPreoperationPlugin.cs @@ -1,4 +1,4 @@ -using DG.XrmFramework.BusinessDomain.ServiceContext; +using DG.XrmFramework.BusinessDomain.ServiceContext; using Microsoft.Xrm.Sdk; using System; using System.Collections.Generic; From 23bf00f0bddefa569089a49339eacddff13c445b Mon Sep 17 00:00:00 2001 From: Kristian Holmkvist Klode Date: Wed, 10 Sep 2025 14:39:51 +0200 Subject: [PATCH 4/4] Revert "Undid changes to files made by copilot" This reverts commit 665a7f08a7a2a7363280f562fcf62ae829be8421. --- .../Plugin/PluginExecutionprovider.cs | 2 +- .../InstantiateTemplateRequestHandler.cs | 44 +------------------ .../RetrieveAllEntitiesRequestHandler.cs | 24 +--------- .../FaxPreoperationPlugin.cs | 2 +- 4 files changed, 4 insertions(+), 68 deletions(-) diff --git a/src/XrmMockupShared/Plugin/PluginExecutionprovider.cs b/src/XrmMockupShared/Plugin/PluginExecutionprovider.cs index 0b64e37e..cb2487e3 100644 --- a/src/XrmMockupShared/Plugin/PluginExecutionprovider.cs +++ b/src/XrmMockupShared/Plugin/PluginExecutionprovider.cs @@ -1,4 +1,4 @@ -using DG.Tools.XrmMockup; +using DG.Tools.XrmMockup; using System; using System.Collections.Generic; using System.Text; diff --git a/src/XrmMockupShared/requests/InstantiateTemplateRequestHandler.cs b/src/XrmMockupShared/requests/InstantiateTemplateRequestHandler.cs index ef04ac27..de2d0713 120000 --- a/src/XrmMockupShared/requests/InstantiateTemplateRequestHandler.cs +++ b/src/XrmMockupShared/requests/InstantiateTemplateRequestHandler.cs @@ -1,43 +1 @@ -using DG.Tools.XrmMockup.Database; -using Microsoft.Crm.Sdk.Messages; -using Microsoft.Xrm.Sdk; -using System; -using System.ServiceModel; - -namespace DG.Tools.XrmMockup -{ - internal class InstantiateTemplateRequestHandler : RequestHandler - { - public InstantiateTemplateRequestHandler(Core core, XrmDb db, MetadataSkeleton metadata, Security security) : base(core, db, metadata, security, "InstantiateTemplate") { } - - internal override OrganizationResponse Execute(OrganizationRequest orgRequest, EntityReference userRef) - { - var request = MakeRequest(orgRequest); - - if (request.TemplateId == Guid.Empty) - throw new FaultException("Template id should be set."); - - if (request.ObjectId == Guid.Empty) - throw new FaultException("Object id should be set."); - - if (string.IsNullOrEmpty(request.ObjectType)) - throw new FaultException("ObjectType is missing"); - - var entity = new Entity("email"); - - var collection = new EntityCollection(); - collection.Entities.Add(entity); - - var parameters = new ParameterCollection - { - { "EntityCollection", collection } - }; - - return new InstantiateTemplateResponse - { - Results = parameters, - ResponseName = "InstantiateTemplate", - }; - } - } -} \ No newline at end of file +../Requests/InstantiateTemplateRequestHandler.cs \ No newline at end of file diff --git a/src/XrmMockupShared/requests/RetrieveAllEntitiesRequestHandler.cs b/src/XrmMockupShared/requests/RetrieveAllEntitiesRequestHandler.cs index 44dea625..857539f0 120000 --- a/src/XrmMockupShared/requests/RetrieveAllEntitiesRequestHandler.cs +++ b/src/XrmMockupShared/requests/RetrieveAllEntitiesRequestHandler.cs @@ -1,23 +1 @@ -using Microsoft.Xrm.Sdk; -using System.Linq; -using DG.Tools.XrmMockup.Database; -using Microsoft.Xrm.Sdk.Messages; - -namespace DG.Tools.XrmMockup -{ - internal class RetrieveAllEntitiesRequestHandler : RequestHandler - { - internal RetrieveAllEntitiesRequestHandler(Core core, XrmDb db, MetadataSkeleton metadata, Security security) : base(core, db, metadata, security, "RetrieveAllEntities") { } - - internal override OrganizationResponse Execute(OrganizationRequest orgRequest, EntityReference userRef) - { - var request = MakeRequest(orgRequest); - - var response = new RetrieveAllEntitiesResponse(); - response.Results["EntityMetadata"] = metadata.EntityMetadata.Values - .Select(entityMetadata => RetrieveEntityRequestHandler.FilterEntityMetadataProperties(entityMetadata, request.EntityFilters)).ToArray(); - - return response; - } - } -} +../Requests/RetrieveAllEntitiesRequestHandler.cs \ No newline at end of file diff --git a/tests/SharedPluginsAndCodeactivites/FaxPreoperationPlugin.cs b/tests/SharedPluginsAndCodeactivites/FaxPreoperationPlugin.cs index a3142aee..db9ec279 100644 --- a/tests/SharedPluginsAndCodeactivites/FaxPreoperationPlugin.cs +++ b/tests/SharedPluginsAndCodeactivites/FaxPreoperationPlugin.cs @@ -1,4 +1,4 @@ -using DG.XrmFramework.BusinessDomain.ServiceContext; +using DG.XrmFramework.BusinessDomain.ServiceContext; using Microsoft.Xrm.Sdk; using System; using System.Collections.Generic;