Skip to content

Commit adfd8a0

Browse files
authored
Merge pull request #281 from delegateas/copilot/fix-280
Add support for NotAny join operator in Query Expression
2 parents cbf7b1a + df3af93 commit adfd8a0

4 files changed

Lines changed: 68 additions & 0 deletions

File tree

File renamed without changes.

src/XrmMockup365/Requests/RetrieveMultipleRequestHandler.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,16 @@ private List<Entity> GetAliasedValuesFromLinkentity(LinkEntity linkEntity, Entit
216216
{
217217
collection.Add(toAdd);
218218
}
219+
else if (linkEntity.JoinOperator == JoinOperator.NotAny && collection.Count == 0)
220+
{
221+
// NotAny join: return parent entity only if no matching linked entities were found
222+
collection.Add(toAdd);
223+
}
224+
else if (linkEntity.JoinOperator == JoinOperator.NotAny && collection.Count > 0)
225+
{
226+
// NotAny join: if matching linked entities were found, don't return anything
227+
collection.Clear();
228+
}
219229
return collection;
220230
}
221231

src/XrmMockup365/XmlHandling.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ public static LinkEntity LinkEntityFromXml(string parentLogicalName, string link
108108
linkEntity.JoinOperator = JoinOperator.LeftOuter;
109109
} else if (joinOperator.Value == "natural") {
110110
linkEntity.JoinOperator = JoinOperator.Natural;
111+
} else if (joinOperator.Value == "notany") {
112+
linkEntity.JoinOperator = JoinOperator.NotAny;
111113
}
112114

113115
if (link.Element("filter") != null) {

tests/XrmMockup365Test/TestRetrieveMultiple.cs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,62 @@ from lead in ls.DefaultIfEmpty()
371371
}
372372
}
373373

374+
[Fact]
375+
public void TestNotAnyJoin()
376+
{
377+
// Test NotAny join operator - should return contacts that do NOT have any associated leads
378+
var query = new QueryExpression("contact")
379+
{
380+
ColumnSet = new ColumnSet("contactid", "lastname")
381+
};
382+
383+
var linkEntity = new LinkEntity()
384+
{
385+
LinkToEntityName = "lead",
386+
LinkToAttributeName = "parentcontactid",
387+
LinkFromEntityName = "contact",
388+
LinkFromAttributeName = "contactid",
389+
Columns = new ColumnSet(false),
390+
EntityAlias = "lead",
391+
JoinOperator = JoinOperator.NotAny,
392+
};
393+
394+
query.LinkEntities.Add(linkEntity);
395+
396+
var result = orgAdminService.RetrieveMultiple(query).Entities.Cast<Contact>().ToList();
397+
398+
// Should return contact3 and contact4 since they have no associated leads
399+
Assert.Equal(2, result.Count);
400+
Assert.Contains(result, x => x.Id == contact3.Id);
401+
Assert.Contains(result, x => x.Id == contact4.Id);
402+
Assert.DoesNotContain(result, x => x.Id == contact1.Id);
403+
Assert.DoesNotContain(result, x => x.Id == contact2.Id);
404+
}
405+
406+
[Fact]
407+
public void TestNotAnyJoinFetchXml()
408+
{
409+
// Test NotAny join operator via FetchXML - should return contacts that do NOT have any associated leads
410+
var fetchXml = @"
411+
<fetch>
412+
<entity name='contact'>
413+
<attribute name='contactid' />
414+
<attribute name='lastname' />
415+
<link-entity name='lead' from='parentcontactid' to='contactid' link-type='notany' />
416+
</entity>
417+
</fetch>";
418+
419+
var fetchExpr = new FetchExpression(fetchXml);
420+
var result = orgAdminService.RetrieveMultiple(fetchExpr).Entities.Cast<Contact>().ToList();
421+
422+
// Should return contact3 and contact4 since they have no associated leads
423+
Assert.Equal(2, result.Count);
424+
Assert.Contains(result, x => x.Id == contact3.Id);
425+
Assert.Contains(result, x => x.Id == contact4.Id);
426+
Assert.DoesNotContain(result, x => x.Id == contact1.Id);
427+
Assert.DoesNotContain(result, x => x.Id == contact2.Id);
428+
}
429+
374430
[Fact]
375431
public void TestNestedJoins()
376432
{

0 commit comments

Comments
 (0)