Skip to content

Commit

Permalink
Merge pull request #6 from MarkMpn/issue5
Browse files Browse the repository at this point in the history
Many to many fix
  • Loading branch information
MarkMpn authored Nov 25, 2024
2 parents 4226671 + bccd6ab commit 81de3f1
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 14 deletions.
96 changes: 91 additions & 5 deletions MarkMpn.FetchXmlToWebAPI.Tests/FetchXmlConversionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,7 @@ public void FilterAll()

var odata = ConvertFetchToOData(fetch);

Assert.AreEqual("https://example.crm.dynamics.com/api/data/v9.0/accounts?$filter=(contact_customer_accounts/all(x1:(x1/firstname eq 'Mark')))", odata);
Assert.AreEqual("https://example.crm.dynamics.com/api/data/v9.0/accounts?$select=accountid&$filter=(contact_customer_accounts/all(x1:(x1/firstname eq 'Mark')))", odata);
}

[TestMethod]
Expand All @@ -607,7 +607,7 @@ public void FilterAny()

var odata = ConvertFetchToOData(fetch);

Assert.AreEqual("https://example.crm.dynamics.com/api/data/v9.0/accounts?$filter=(contact_customer_accounts/any(x1:(x1/firstname eq 'Mark')))", odata);
Assert.AreEqual("https://example.crm.dynamics.com/api/data/v9.0/accounts?$select=accountid&$filter=(contact_customer_accounts/any(x1:(x1/firstname eq 'Mark')))", odata);
}

[TestMethod]
Expand All @@ -628,7 +628,7 @@ public void FilterNotAny()

var odata = ConvertFetchToOData(fetch);

Assert.AreEqual("https://example.crm.dynamics.com/api/data/v9.0/accounts?$filter=(not contact_customer_accounts/any(x1:(x1/firstname ne 'Mark')))", odata);
Assert.AreEqual("https://example.crm.dynamics.com/api/data/v9.0/accounts?$select=accountid&$filter=(not contact_customer_accounts/any(x1:(x1/firstname ne 'Mark')))", odata);
}

[TestMethod]
Expand All @@ -649,7 +649,7 @@ public void FilterNotAll()

var odata = ConvertFetchToOData(fetch);

Assert.AreEqual("https://example.crm.dynamics.com/api/data/v9.0/accounts?$filter=(not contact_customer_accounts/all(x1:(x1/firstname ne 'Mark')))", odata);
Assert.AreEqual("https://example.crm.dynamics.com/api/data/v9.0/accounts?$select=accountid&$filter=(not contact_customer_accounts/all(x1:(x1/firstname ne 'Mark')))", odata);
}

[TestMethod]
Expand All @@ -674,7 +674,39 @@ public void FilterNotAllNestedNotAny()

var odata = ConvertFetchToOData(fetch);

Assert.AreEqual("https://example.crm.dynamics.com/api/data/v9.0/accounts?$filter=(not contact_customer_accounts/all(x1:(x1/account_primarycontact/any(x2:(x2/name eq 'Data8')))))", odata);
Assert.AreEqual("https://example.crm.dynamics.com/api/data/v9.0/accounts?$select=accountid&$filter=(not contact_customer_accounts/all(x1:(x1/account_primarycontact/any(x2:(x2/name eq 'Data8')))))", odata);
}

[TestMethod]
public void SelectAllAttributes()
{
var fetch = @"
<fetch>
<entity name='contact'>
<all-attributes />
</entity>
</fetch>";

var odata = ConvertFetchToOData(fetch);

Assert.AreEqual("https://example.crm.dynamics.com/api/data/v9.0/contacts", odata);
}

[TestMethod]
public void InnerJoinManyToManyWithNoChildren()
{
var fetch = @"
<fetch>
<entity name='contact'>
<link-entity name='listmember' from='entityid' to='contactid' link-type='inner' intersect='true'>
<link-entity name='list' from='listid' to='listid' link-type='inner' />
</link-entity>
</entity>
</fetch>";

var odata = ConvertFetchToOData(fetch);

Assert.AreEqual("https://example.crm.dynamics.com/api/data/v9.0/contacts?$select=contactid&$filter=(lists/any(o1:(o1/listid ne null)))", odata);
}

private string ConvertFetchToOData(string fetch)
Expand All @@ -701,6 +733,19 @@ private string ConvertFetchToOData(string fetch)
ReferencingAttribute = "primarycontactid"
}
};
var nnRelationships = new[]
{
new ManyToManyRelationshipMetadata
{
SchemaName = "contact_list",
Entity1LogicalName = "contact",
Entity1IntersectAttribute = "entityid",
Entity1NavigationPropertyName = "lists",
Entity2LogicalName = "list",
Entity2IntersectAttribute = "listid",
Entity2NavigationPropertyName = "contacts"
}
};

var entities = new[]
{
Expand Down Expand Up @@ -733,6 +778,16 @@ private string ConvertFetchToOData(string fetch)
{
LogicalName = "incident",
EntitySetName = "incidents"
},
new EntityMetadata
{
LogicalName = "list",
EntitySetName = "lists"
},
new EntityMetadata
{
LogicalName = "listmember",
EntitySetName = "listmembers"
}
};

Expand Down Expand Up @@ -833,13 +888,44 @@ private string ConvertFetchToOData(string fetch)
{
LogicalName = "iscustomizable"
}
},
["listmember"] = new AttributeMetadata[]
{
new UniqueIdentifierAttributeMetadata
{
LogicalName = "listmemberid"
},
new LookupAttributeMetadata
{
LogicalName = "entityid",
Targets = new[] { "contact" }
},
new LookupAttributeMetadata
{
LogicalName = "listid",
Targets = new[] { "list" }
}
},
["list"] = new AttributeMetadata[]
{
new UniqueIdentifierAttributeMetadata
{
LogicalName = "listid"
},
new StringAttributeMetadata
{
LogicalName = "name"
}
}
};

SetSealedProperty(attributes["webresource"].Single(a => a.LogicalName == "iscustomizable"), nameof(ManagedPropertyAttributeMetadata.ValueAttributeTypeCode), AttributeTypeCode.Boolean);
SetRelationships(entities, relationships);
SetAttributes(entities, attributes);
SetSealedProperty(entities.Single(e => e.LogicalName == "incident"), nameof(EntityMetadata.ObjectTypeCode), 112);
SetSealedProperty(entities.Single(e => e.LogicalName == "contact"), nameof(EntityMetadata.ManyToManyRelationships), nnRelationships);
SetSealedProperty(entities.Single(e => e.LogicalName == "listmember"), nameof(EntityMetadata.ManyToManyRelationships), nnRelationships);
SetSealedProperty(entities.Single(e => e.LogicalName == "list"), nameof(EntityMetadata.ManyToManyRelationships), nnRelationships);

foreach (var entity in entities)
context.SetEntityMetadata(entity);
Expand Down
30 changes: 21 additions & 9 deletions MarkMpn.FetchXmlToWebAPI/FetchXmlToWebAPIConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -339,33 +339,45 @@ private List<FilterOData> ConvertInnerJoinFilters(string entityName, object[] it

private IEnumerable<LinkEntityOData> ConvertJoins(string entityName, object[] items, object[] rootEntityItems)
{
foreach (var linkEntity in items.OfType<FetchLinkEntityType>().Where(l => l.Items != null && l.Items.Any()))
foreach (var linkEntity in items.OfType<FetchLinkEntityType>())
{
var currentLinkEntity = linkEntity;
var expand = new LinkEntityOData();
expand.PropertyName = LinkItemToNavigationProperty(entityName, currentLinkEntity, out var child, out var manyToManyNextLink);
currentLinkEntity = manyToManyNextLink ?? currentLinkEntity;
expand.Select.AddRange(ConvertSelect(currentLinkEntity.name, currentLinkEntity.Items));

if (linkEntity.linktype == "outer" || child)
if (currentLinkEntity.Items != null)
{
// Don't need to add filters at this point for single-valued properties in inner joins, they'll be added separately later
expand.Filter.AddRange(ConvertFilters(currentLinkEntity.name, currentLinkEntity.Items, rootEntityItems));
}
expand.Select.AddRange(ConvertSelect(currentLinkEntity.name, currentLinkEntity.Items));

// Recurse into child joins
expand.Expand.AddRange(ConvertJoins(currentLinkEntity.name, currentLinkEntity.Items, rootEntityItems));
if (linkEntity.linktype == "outer" || child)
{
// Don't need to add filters at this point for single-valued properties in inner joins, they'll be added separately later
expand.Filter.AddRange(ConvertFilters(currentLinkEntity.name, currentLinkEntity.Items, rootEntityItems));
}

yield return expand;
// Recurse into child joins
expand.Expand.AddRange(ConvertJoins(currentLinkEntity.name, currentLinkEntity.Items, rootEntityItems));

yield return expand;
}
}
}

private IEnumerable<string> ConvertSelect(string entityName, object[] items)
{
// A missing $select is equivalent to selecting all attributes
if (items.OfType<allattributes>().Any())
return Array.Empty<string>();

var attributeitems = items
.OfType<FetchAttributeType>()
.Where(i => i.name != null);

// If we don't want to select any attributes, just include the primary key
if (!attributeitems.Any())
return new[] { _metadata.GetEntity(entityName).PrimaryIdAttribute };

return GetAttributeNames(entityName, attributeitems);
}

Expand Down

0 comments on commit 81de3f1

Please sign in to comment.