diff --git a/CHANGELOG.md b/CHANGELOG.md index 9350c529..288b019e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## [2.7.0] + +### Added + +- Added support for link entities that use the `Exists` join operator - https://github.com/DynamicsValue/fake-xrm-easy/issues/141 + ## [2.6.0] ### Added diff --git a/src/FakeXrmEasy.Core/FakeXrmEasy.Core.csproj b/src/FakeXrmEasy.Core/FakeXrmEasy.Core.csproj index 28ae8631..055e08e9 100644 --- a/src/FakeXrmEasy.Core/FakeXrmEasy.Core.csproj +++ b/src/FakeXrmEasy.Core/FakeXrmEasy.Core.csproj @@ -25,6 +25,7 @@ True true False + 2.7.0 diff --git a/src/FakeXrmEasy.Core/Query/LinkEntityQueryExtensions.cs b/src/FakeXrmEasy.Core/Query/LinkEntityQueryExtensions.cs index 68af9083..a4838602 100644 --- a/src/FakeXrmEasy.Core/Query/LinkEntityQueryExtensions.cs +++ b/src/FakeXrmEasy.Core/Query/LinkEntityQueryExtensions.cs @@ -87,8 +87,16 @@ internal static IQueryable ToQueryable(this LinkEntity le, IXrmFakedCont , (x, y) => x.outerEl .JoinAttributes(y, new ColumnSet(true), leAlias, context)); - break; +#if FAKE_XRM_EASY_9 + case JoinOperator.Exists: + // This is most likely not the most performant implementation, but it is probably the closest match + // to the generated sql query, which will be a correlated subquery. + query = query.Where(outerEl => + inner.Any(innerEl => + outerEl.KeySelector(linkFromAlias, context).Equals(innerEl.KeySelector(le.LinkToAttributeName, context)))); + break; +#endif default: //This shouldn't be reached as there are only 3 types of Join... throw UnsupportedExceptionFactory.New(context.LicenseContext.Value, string.Format("The join operator {0} is currently not supported. ", le.JoinOperator)); diff --git a/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/RetrieveMultipleRequestTests/QueryLinkEntityTests.cs b/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/RetrieveMultipleRequestTests/QueryLinkEntityTests.cs index a3b2450c..ba1fc7f8 100644 --- a/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/RetrieveMultipleRequestTests/QueryLinkEntityTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/RetrieveMultipleRequestTests/QueryLinkEntityTests.cs @@ -1127,5 +1127,101 @@ public void TestRetriveMultipleWithLinkEntityWithAlternateNullField() // this fails (entity2Value is "value") Assert.Null(entity2Value); } + + [Fact] + public void Should_Find_Record_With_Associations_When_Exists_Join_Is_Used() + { + // Arrange + Entity account = new Entity("account", Guid.NewGuid()); + + Entity contact = new Entity("contact", Guid.NewGuid()) + { + ["parentcustomerid"] = account.ToEntityReference(), + }; + List entities = new List() + { + account, contact + }; + + _context.Initialize(entities); + + QueryExpression queryExpression = new QueryExpression("account"); + queryExpression.ColumnSet = new ColumnSet(true); + + LinkEntity contactQuery = queryExpression.AddLink("contact", "accountid", "parentcustomerid"); + contactQuery.JoinOperator = JoinOperator.Exists; + contactQuery.EntityAlias = "c"; + + // Act + EntityCollection results = _service.RetrieveMultiple(queryExpression); + + // Assert + Assert.Single(results.Entities); + } + + + [Fact] + public void Should_Not_Find_Record_Without_Associations_When_Exists_Join_Is_Used() + { + // Arrange + Entity account = new Entity("account", Guid.NewGuid()); + + Entity contact = new Entity("contact", Guid.NewGuid()); + List entities = new List() + { + account, contact + }; + + _context.Initialize(entities); + + QueryExpression queryExpression = new QueryExpression("account"); + queryExpression.ColumnSet = new ColumnSet(true); + + LinkEntity contactQuery = queryExpression.AddLink("contact", "accountid", "parentcustomerid"); + contactQuery.JoinOperator = JoinOperator.Exists; + contactQuery.EntityAlias = "c"; + + // Act + EntityCollection result = _service.RetrieveMultiple(queryExpression); + + // Assert + Assert.Empty(result.Entities); + } + + [Fact] + public void Should_Return_Only_Outer_Record_When_Exists_Join_Is_Used() + { + // Arrange + Entity account = new Entity("account", Guid.NewGuid()); + + Entity contact = new Entity("contact", Guid.NewGuid()) + { + ["parentcustomerid"] = account.ToEntityReference(), + ["firstname"] = "firstname" + }; + List entities = new List() + { + account, contact + }; + + _context.Initialize(entities); + + QueryExpression queryExpression = new QueryExpression("account"); + queryExpression.ColumnSet = new ColumnSet(true); + + LinkEntity contactQuery = queryExpression.AddLink("contact", "accountid", "parentcustomerid"); + contactQuery.JoinOperator = JoinOperator.Exists; + contactQuery.EntityAlias = "c"; + contactQuery.Columns = new ColumnSet(true); + + // Act + EntityCollection result = _service.RetrieveMultiple(queryExpression); + + // Assert + Assert.Single(result.Entities); + + Entity outerRecord = result.Entities.First(); + Assert.Null(outerRecord.GetAttributeValue("c.firstname")); + } } } \ No newline at end of file