From a82e564a2eadd75175c31697ad5835d4a68a3772 Mon Sep 17 00:00:00 2001 From: Alex Henderson Date: Fri, 18 Nov 2011 11:05:19 +1300 Subject: [PATCH 1/5] Added git ignore. --- .gitignore | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1006534 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ + +/build +AssemblyInfo.cs + +# Standard VS.NET and ReSharper Foo +src/*/obj +src/*/bin +samples/*/obj +samples/*/bin +*.csproj.user +*ReSharper.user +_ReSharper* +*resharper* +*.suo +*.cache +* Thumbs.db \ No newline at end of file From b6d836d8f7a6fff7829cbb201ecbb74d8e8c8e61 Mon Sep 17 00:00:00 2001 From: Alex Henderson Date: Fri, 18 Nov 2011 11:06:51 +1300 Subject: [PATCH 2/5] Updated git ignore a bit more. --- .gitignore | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 1006534..2eeefdc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,6 @@ - -/build -AssemblyInfo.cs - # Standard VS.NET and ReSharper Foo -src/*/obj -src/*/bin -samples/*/obj -samples/*/bin +source/*/obj +source/*/bin *.csproj.user *ReSharper.user _ReSharper* From bfc92b1e80d4c339fa4da6ac44c5778c6274f608 Mon Sep 17 00:00:00 2001 From: Alex Henderson Date: Fri, 18 Nov 2011 11:08:36 +1300 Subject: [PATCH 3/5] Fixed issue with translation of binary expression where LHS or RHS containing UpdatedDateUTC comparison i.e. UpdatedDateUTC > new DateTime(2010,1,1). --- source/XeroApi.Tests/ApiQueryTests.cs | 7 +- source/XeroApi/Linq/ApiQueryTranslator.cs | 106 ++++++++++++++-------- 2 files changed, 71 insertions(+), 42 deletions(-) diff --git a/source/XeroApi.Tests/ApiQueryTests.cs b/source/XeroApi.Tests/ApiQueryTests.cs index 68a7faa..8900756 100644 --- a/source/XeroApi.Tests/ApiQueryTests.cs +++ b/source/XeroApi.Tests/ApiQueryTests.cs @@ -430,13 +430,14 @@ public void TestApiQueryCanCallEmployeeEndpointWithSingleOrDefaultOperator() StubIntegrationProxy integrationProxy = new StubIntegrationProxy(); Repository repository = new Repository(integrationProxy); - repository.Users.SingleOrDefault(u => u.FullName == "Joe Bloggs"); + repository.Invoices.SingleOrDefault(i => i.UpdatedDateUTC > new DateTime(2010, 1, 1) && i.Url != null); var queryDesctipion = integrationProxy.LastQueryDescription; - Assert.AreEqual("User", queryDesctipion.ElementType.Name); + Assert.AreEqual("Invoice", queryDesctipion.ElementType.Name); Assert.AreEqual("SingleOrDefault", queryDesctipion.ClientSideExpression); - Assert.AreEqual("(FullName == \"Joe Bloggs\")", queryDesctipion.Where); + Assert.AreEqual("(Url <> NULL)", queryDesctipion.Where); } + } } diff --git a/source/XeroApi/Linq/ApiQueryTranslator.cs b/source/XeroApi/Linq/ApiQueryTranslator.cs index 5a1913d..5c8ef67 100644 --- a/source/XeroApi/Linq/ApiQueryTranslator.cs +++ b/source/XeroApi/Linq/ApiQueryTranslator.cs @@ -147,6 +147,27 @@ protected override Expression VisitUnary(UnaryExpression u) return u; } + bool IsNotRenderedIntoExpression(Expression e) + { + BinaryExpression b = e as BinaryExpression; + if (b == null) return false; + + MemberExpression mExp = b.Left as MemberExpression; + + // Check if the LHS is an ItemId, ItemNumber or UpdatedDate. If so, record away from the main where clause. + if (mExp != null && (mExp.Member.DeclaringType.Name == _query.ElementName)) + { + if (mExp.Member.Name == _query.ElementIdProperty.SafeName() + || mExp.Member.Name == _query.ElementNumberProperty.SafeName() + || mExp.Member.Name == _query.ElementUpdatedDateProperty.SafeName()) + { + return true; + } + } + + return false; + } + protected override Expression VisitBinary(BinaryExpression b) { MemberExpression mExp = b.Left as MemberExpression; @@ -193,48 +214,55 @@ protected override Expression VisitBinary(BinaryExpression b) return Visit(Expression.Equal(memberExpression, valueExpression)); } - - // Otherswise, parse as a normal (operand1 operator operand2) - Append("("); - Visit(b.Left); - switch (b.NodeType) - { - case ExpressionType.And: - case ExpressionType.AndAlso: - Append(" AND "); - break; - case ExpressionType.Or: - case ExpressionType.OrElse: - Append(" OR "); - break; - case ExpressionType.Equal: - Append(" == "); - break; - case ExpressionType.NotEqual: - Append(" <> "); - break; - case ExpressionType.LessThan: - Append(" < "); - break; - case ExpressionType.LessThanOrEqual: - Append(" <= "); - break; - case ExpressionType.GreaterThan: - Append(" > "); - break; - case ExpressionType.GreaterThanOrEqual: - Append(" >= "); - break; - default: - throw new NotSupportedException(string.Format("The binary operator '{0}' is not supported", b.NodeType)); + if (IsNotRenderedIntoExpression(b.Left)) { + VisitBinary((BinaryExpression)b.Left); + Visit(b.Right); + return b.Right; + } else if (IsNotRenderedIntoExpression(b.Right)) { + Visit(b.Left); + VisitBinary((BinaryExpression)b.Right); + return b.Left; + } else { + // Otherswise, parse as a normal (operand1 operator operand2) + Append("("); + Visit(b.Left); + switch (b.NodeType) + { + case ExpressionType.And: + case ExpressionType.AndAlso: + Append(" AND "); + break; + case ExpressionType.Or: + case ExpressionType.OrElse: + Append(" OR "); + break; + case ExpressionType.Equal: + Append(" == "); + break; + case ExpressionType.NotEqual: + Append(" <> "); + break; + case ExpressionType.LessThan: + Append(" < "); + break; + case ExpressionType.LessThanOrEqual: + Append(" <= "); + break; + case ExpressionType.GreaterThan: + Append(" > "); + break; + case ExpressionType.GreaterThanOrEqual: + Append(" >= "); + break; + default: + throw new NotSupportedException(string.Format("The binary operator '{0}' is not supported", b.NodeType)); + } + Visit(b.Right); + Append(")"); + return b; } - Visit(b.Right); - Append(")"); - return b; } - - protected override Expression VisitConstant(ConstantExpression c) { IQueryable q = c.Value as IQueryable; From 79aeb095a2baa1432c20b324cdb1181f5e4741fc Mon Sep 17 00:00:00 2001 From: Alex Henderson Date: Fri, 18 Nov 2011 11:13:08 +1300 Subject: [PATCH 4/5] Tidied up naming of test --- source/XeroApi.Tests/ApiQueryTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/XeroApi.Tests/ApiQueryTests.cs b/source/XeroApi.Tests/ApiQueryTests.cs index 8900756..5837210 100644 --- a/source/XeroApi.Tests/ApiQueryTests.cs +++ b/source/XeroApi.Tests/ApiQueryTests.cs @@ -425,7 +425,7 @@ public void TestApiQueryCanCallEmployeeEndpointWithSingleOperator() } [Test] - public void TestApiQueryCanCallEmployeeEndpointWithSingleOrDefaultOperator() + public void TestApiQueryCanCallInvoicesEndpointWithUpdatedDateAndUrlFilterCombinedWithAndOperator() { StubIntegrationProxy integrationProxy = new StubIntegrationProxy(); Repository repository = new Repository(integrationProxy); From 737d1ed3e8179508c4873ce3ea5e65d971f635e1 Mon Sep 17 00:00:00 2001 From: Alex Henderson Date: Fri, 18 Nov 2011 11:21:07 +1300 Subject: [PATCH 5/5] Implemented fix to allow for null / not-null checks on Number properties that can only be populated via the API (useful if being used to coordinate synchronization with external systems). --- source/XeroApi.Tests/ApiQueryTests.cs | 27 +++++++++++++++++++++++ source/XeroApi/Linq/ApiQueryTranslator.cs | 9 ++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/source/XeroApi.Tests/ApiQueryTests.cs b/source/XeroApi.Tests/ApiQueryTests.cs index 5837210..716dea8 100644 --- a/source/XeroApi.Tests/ApiQueryTests.cs +++ b/source/XeroApi.Tests/ApiQueryTests.cs @@ -438,6 +438,33 @@ public void TestApiQueryCanCallInvoicesEndpointWithUpdatedDateAndUrlFilterCombin Assert.AreEqual("(Url <> NULL)", queryDesctipion.Where); } + [Test] + public void TestApiQueryCanCallContactsEndpointWithContactNumberIsNull() + { + StubIntegrationProxy integrationProxy = new StubIntegrationProxy(); + Repository repository = new Repository(integrationProxy); + + repository.Contacts.SingleOrDefault(i => i.ContactNumber == null); + + var queryDesctipion = integrationProxy.LastQueryDescription; + Assert.AreEqual("Contact", queryDesctipion.ElementType.Name); + Assert.AreEqual("SingleOrDefault", queryDesctipion.ClientSideExpression); + Assert.AreEqual("(ContactNumber == NULL)", queryDesctipion.Where); + } + + [Test] + public void TestApiQueryCanCallContactsEndpointWithContactNumberIsNotNull() + { + StubIntegrationProxy integrationProxy = new StubIntegrationProxy(); + Repository repository = new Repository(integrationProxy); + + repository.Contacts.SingleOrDefault(i => i.ContactNumber != null); + + var queryDesctipion = integrationProxy.LastQueryDescription; + Assert.AreEqual("Contact", queryDesctipion.ElementType.Name); + Assert.AreEqual("SingleOrDefault", queryDesctipion.ClientSideExpression); + Assert.AreEqual("(ContactNumber <> NULL)", queryDesctipion.Where); + } } } diff --git a/source/XeroApi/Linq/ApiQueryTranslator.cs b/source/XeroApi/Linq/ApiQueryTranslator.cs index 5c8ef67..4dcd311 100644 --- a/source/XeroApi/Linq/ApiQueryTranslator.cs +++ b/source/XeroApi/Linq/ApiQueryTranslator.cs @@ -185,8 +185,13 @@ protected override Expression VisitBinary(BinaryExpression b) } if (mExp.Member.Name == _query.ElementNumberProperty.SafeName()) { - _query.ElementId = EvaluateExpression(b.Right).ToString(); - return b; + var rightValue = EvaluateExpression(b.Right); + + if (rightValue != null) + { + _query.ElementId = rightValue.ToString(); + return b; + } } if (mExp.Member.Name == _query.ElementUpdatedDateProperty.SafeName()) {