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() {