From eee88482d42c5a582f1cc6ac9f16bad25a1ab40b Mon Sep 17 00:00:00 2001 From: Betim Beja <11160171+BetimBeja@users.noreply.github.com> Date: Fri, 23 Dec 2022 15:04:57 +0100 Subject: [PATCH 01/37] fixed DynamicsValue/fake-xrm-easy#64 --- .../Extensions/TypeExtensions.cs | 11 ++++ .../Query/TypeCastExpressions.cs | 8 +++ .../FakeXrmEasy.Core.Tests/Issues/Issue64.cs | 54 +++++++++++++++++++ 3 files changed, 73 insertions(+) create mode 100644 tests/FakeXrmEasy.Core.Tests/Issues/Issue64.cs diff --git a/src/FakeXrmEasy.Core/Extensions/TypeExtensions.cs b/src/FakeXrmEasy.Core/Extensions/TypeExtensions.cs index fccd9ae3..10158e89 100644 --- a/src/FakeXrmEasy.Core/Extensions/TypeExtensions.cs +++ b/src/FakeXrmEasy.Core/Extensions/TypeExtensions.cs @@ -23,6 +23,17 @@ public static bool IsOptionSet(this Type t) || nullableType != null && nullableType.IsEnum; } + /// + /// + /// + /// + /// + public static bool IsMoney(this Type t) + { + var nullableType = Nullable.GetUnderlyingType(t); + return t == typeof(Money) || nullableType != null && nullableType == typeof(Money); + } + #if FAKE_XRM_EASY_9 public static bool IsOptionSetValueCollection(this Type t) { diff --git a/src/FakeXrmEasy.Core/Query/TypeCastExpressions.cs b/src/FakeXrmEasy.Core/Query/TypeCastExpressions.cs index 60471560..b966a7b4 100644 --- a/src/FakeXrmEasy.Core/Query/TypeCastExpressions.cs +++ b/src/FakeXrmEasy.Core/Query/TypeCastExpressions.cs @@ -294,6 +294,14 @@ internal static Expression GetAppropiateTypedValueAndType(object value, Type att return Expression.Constant(value, typeof(string)).ToCaseInsensitiveExpression(); } } + else if (value is int) + { + if (attributeType.IsMoney()) + { + return Expression.Constant(((int)value)*1m, typeof(decimal)); + } + return Expression.Constant(value); + } else if (value is EntityReference) { var cast = (value as EntityReference).Id; diff --git a/tests/FakeXrmEasy.Core.Tests/Issues/Issue64.cs b/tests/FakeXrmEasy.Core.Tests/Issues/Issue64.cs new file mode 100644 index 00000000..d37c6d1f --- /dev/null +++ b/tests/FakeXrmEasy.Core.Tests/Issues/Issue64.cs @@ -0,0 +1,54 @@ +using Crm; +using Microsoft.Xrm.Sdk; +using Microsoft.Xrm.Sdk.Query; +using System; +using System.Collections.Generic; +using Xunit; + +namespace FakeXrmEasy.Tests.Issues +{ + // https://github.com/DynamicsValue/fake-xrm-easy/issues/64 + + public class Issue64 : FakeXrmEasyTestsBase + { + SalesOrderDetail salesOrderDetail; + // setup + public Issue64() + { + _context.EnableProxyTypes(typeof(Account).Assembly); + + salesOrderDetail = new SalesOrderDetail() + { + Id = Guid.NewGuid(), + PricePerUnit = new Money(1.0m) + }; + + _context.Initialize(new List { salesOrderDetail }); + } + + // This test currently fails + [Fact] + public void When_Querying_A_Money_Attribute_Using_An_Integer_Value_It_Should_Not_Fail() + { + var qe = new QueryExpression(SalesOrderDetail.EntityLogicalName); + qe.Criteria.AddCondition("priceperunit", ConditionOperator.Equal, 1); + + var entities = _service.RetrieveMultiple(qe).Entities; + + Assert.Equal(entities[0].Id, salesOrderDetail.Id); + } + // This test currently passes + [Fact] + public void When_Querying_A_Money_Attribute_Using_A_Money_Value_It_Should_Not_Fail() + { + var qe = new QueryExpression(SalesOrderDetail.EntityLogicalName); + qe.Criteria.AddCondition("priceperunit", ConditionOperator.Equal, new Money(1.0m)); + + var entities = _service.RetrieveMultiple(qe).Entities; + + Assert.Single(entities); + Assert.Equal(entities[0].Id, salesOrderDetail.Id); + } + + } +} \ No newline at end of file From 414fcbd8ac247f245d462bfcc64f42fa0271c171 Mon Sep 17 00:00:00 2001 From: Betim Beja <11160171+BetimBeja@users.noreply.github.com> Date: Mon, 8 May 2023 12:12:38 +0200 Subject: [PATCH 02/37] fix for DynamicsValue/fake-xrm-easy#96 --- .../Query/ConditionExpressionExtensions.In.cs | 12 ++++---- .../Query/ConditionExpressionExtensions.cs | 16 ++++++++--- .../MultiSelectOptionSetTests.cs | 28 ++++--------------- .../Issues/Issue0096.cs | 20 +++++++++++++ .../FakeXrmEasy.Core.Tests/Issues/Issue180.cs | 4 +-- 5 files changed, 45 insertions(+), 35 deletions(-) create mode 100644 tests/FakeXrmEasy.Core.Tests/Issues/Issue0096.cs diff --git a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.In.cs b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.In.cs index efd42786..39936b74 100644 --- a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.In.cs +++ b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.In.cs @@ -31,12 +31,12 @@ internal static Expression ToInExpression(this TypedConditionExpression tc, Expr { if (value is Array) { - foreach (var a in ((Array)value)) - { - expOrValues = Expression.Or(expOrValues, Expression.Equal( - tc.AttributeType.GetAppropiateCastExpressionBasedOnType(getAttributeValueExpr, a), - TypeCastExpressions.GetAppropiateTypedValueAndType(a, tc.AttributeType))); - } + //foreach (var a in ((Array)value)) + //{ + // expOrValues = Expression.Or(expOrValues, Expression.Equal( + // tc.AttributeType.GetAppropiateCastExpressionBasedOnType(getAttributeValueExpr, a), + // TypeCastExpressions.GetAppropiateTypedValueAndType(a, tc.AttributeType))); + //} } else { diff --git a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.cs b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.cs index 7623ff7e..ce598cd8 100644 --- a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.cs +++ b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.cs @@ -207,10 +207,12 @@ internal static Expression ToExpression(this TypedConditionExpression c, QueryEx break; case ConditionOperator.In: + ValidateInConditionValues(c, entity.Name ?? qe.EntityName); operatorExpression = c.ToInExpression(getNonBasicValueExpr, containsAttributeExpression); break; case ConditionOperator.NotIn: + ValidateInConditionValues(c, entity.Name ?? qe.EntityName); operatorExpression = Expression.Not(c.ToInExpression(getNonBasicValueExpr, containsAttributeExpression)); break; @@ -312,9 +314,15 @@ internal static Expression ToExpression(this TypedConditionExpression c, QueryEx } - - - - + private static void ValidateInConditionValues(TypedConditionExpression c, string name) + { + foreach (object value in c.CondExpression.Values) + { + if (value is Array) + { + throw new Exception($"Condition for attribute '{name}.numberofemployees': expected argument(s) of a different type but received '{value.GetType()}'."); + } + } + } } } diff --git a/tests/FakeXrmEasy.Core.Tests/FakeContextTests/TranslateQueryExpressionTests/OperatorTests/MultiSelectOptionSet/MultiSelectOptionSetTests.cs b/tests/FakeXrmEasy.Core.Tests/FakeContextTests/TranslateQueryExpressionTests/OperatorTests/MultiSelectOptionSet/MultiSelectOptionSetTests.cs index 88db0e87..fa4bb9ae 100644 --- a/tests/FakeXrmEasy.Core.Tests/FakeContextTests/TranslateQueryExpressionTests/OperatorTests/MultiSelectOptionSet/MultiSelectOptionSetTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/FakeContextTests/TranslateQueryExpressionTests/OperatorTests/MultiSelectOptionSet/MultiSelectOptionSetTests.cs @@ -243,9 +243,6 @@ public void When_executing_a_query_expression_in_operator_throws_exception_for_o [Fact] public void When_executing_a_query_expression_in_operator_returns_exact_matches_for_single_int_array_right_hand_side() { - - - _service.Create(new Contact { FirstName = "1,2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); _service.Create(new Contact { FirstName = "2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2), new OptionSetValue(3) } }); @@ -254,7 +251,7 @@ public void When_executing_a_query_expression_in_operator_returns_exact_matches_ var qe = new QueryExpression("contact"); qe.ColumnSet = new ColumnSet(new[] { "firstname" }); - qe.Criteria.AddCondition("new_multiselectattribute", ConditionOperator.In, new[] { 2 }); + qe.Criteria.AddCondition("new_multiselectattribute", ConditionOperator.In, new object[] { 2 }); var entities = _service.RetrieveMultiple(qe).Entities; @@ -265,9 +262,6 @@ public void When_executing_a_query_expression_in_operator_returns_exact_matches_ [Fact] public void When_executing_a_query_expression_in_operator_returns_exact_matches_for_int_array_right_hand_side() { - - - _service.Create(new Contact { FirstName = "1,2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); _service.Create(new Contact { FirstName = "2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2), new OptionSetValue(3) } }); @@ -276,7 +270,7 @@ public void When_executing_a_query_expression_in_operator_returns_exact_matches_ var qe = new QueryExpression("contact"); qe.ColumnSet = new ColumnSet(new[] { "firstname" }); - qe.Criteria.AddCondition("new_multiselectattribute", ConditionOperator.In, new[] { 2, 3 }); + qe.Criteria.AddCondition("new_multiselectattribute", ConditionOperator.In, new object[] { 2, 3 }); var entities = _service.RetrieveMultiple(qe).Entities; @@ -287,9 +281,6 @@ public void When_executing_a_query_expression_in_operator_returns_exact_matches_ [Fact] public void When_executing_a_query_expression_in_operator_returns_exact_matches_for_out_of_order_int_array_right_hand_side() { - - - _service.Create(new Contact { FirstName = "1,2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); _service.Create(new Contact { FirstName = "2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2), new OptionSetValue(3) } }); @@ -298,7 +289,7 @@ public void When_executing_a_query_expression_in_operator_returns_exact_matches_ var qe = new QueryExpression("contact"); qe.ColumnSet = new ColumnSet(new[] { "firstname" }); - qe.Criteria.AddCondition("new_multiselectattribute", ConditionOperator.In, new[] { 3, 1, 2 }); + qe.Criteria.AddCondition("new_multiselectattribute", ConditionOperator.In, new object[] { 3, 1, 2 }); var entities = _service.RetrieveMultiple(qe).Entities; @@ -309,9 +300,6 @@ public void When_executing_a_query_expression_in_operator_returns_exact_matches_ [Fact] public void When_executing_a_query_expression_in_operator_returns_exact_matches_for_out_of_order_int_params_right_hand_side() { - - - _service.Create(new Contact { FirstName = "1,2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); _service.Create(new Contact { FirstName = "2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2), new OptionSetValue(3) } }); @@ -331,9 +319,6 @@ public void When_executing_a_query_expression_in_operator_returns_exact_matches_ [Fact] public void When_executing_a_query_expression_in_operator_returns_exact_matches_for_string_array_right_hand_side() { - - - _service.Create(new Contact { FirstName = "1,2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); _service.Create(new Contact { FirstName = "2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2), new OptionSetValue(3) } }); @@ -396,10 +381,7 @@ public void When_executing_a_query_expression_in_operator_returns_exact_matches_ [Fact] public void When_executing_a_query_expression_notin_operator_excludes_exact_matches_for_int_array_right_hand_side() - { - - - + { _service.Create(new Contact { FirstName = "1,2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); _service.Create(new Contact { FirstName = "2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2), new OptionSetValue(3) } }); @@ -408,7 +390,7 @@ public void When_executing_a_query_expression_notin_operator_excludes_exact_matc var qe = new QueryExpression("contact"); qe.ColumnSet = new ColumnSet(new[] { "firstname" }); - qe.Criteria.AddCondition("new_multiselectattribute", ConditionOperator.NotIn, new[] { 2, 3 }); + qe.Criteria.AddCondition("new_multiselectattribute", ConditionOperator.NotIn, new object[] { 2, 3 }); var entities = _service.RetrieveMultiple(qe).Entities; diff --git a/tests/FakeXrmEasy.Core.Tests/Issues/Issue0096.cs b/tests/FakeXrmEasy.Core.Tests/Issues/Issue0096.cs new file mode 100644 index 00000000..4f4ced0d --- /dev/null +++ b/tests/FakeXrmEasy.Core.Tests/Issues/Issue0096.cs @@ -0,0 +1,20 @@ +using Microsoft.Xrm.Sdk.Query; +using Xunit; + +namespace FakeXrmEasy.Tests.Issues +{ + public class Issue0096 : FakeXrmEasyTestsBase + { + [Fact] + public void Reproduce_issue_96() + { + var query = new QueryExpression("account"); + query.TopCount = 2; + query.Criteria.AddCondition("numberofemployees", ConditionOperator.In, new int[] { 0, 1 }); + + var ex = Record.Exception(() => _service.RetrieveMultiple(query)); + Assert.NotNull(ex); + Assert.Equal("Condition for attribute 'account.numberofemployees': expected argument(s) of a different type but received 'System.Int32[]'.", ex.Message); + } + } +} diff --git a/tests/FakeXrmEasy.Core.Tests/Issues/Issue180.cs b/tests/FakeXrmEasy.Core.Tests/Issues/Issue180.cs index 2c45e03a..0ac100e1 100644 --- a/tests/FakeXrmEasy.Core.Tests/Issues/Issue180.cs +++ b/tests/FakeXrmEasy.Core.Tests/Issues/Issue180.cs @@ -21,7 +21,7 @@ public void When_a_query_on_lookup_with_condition_in_contains_a_match_it_should_ }; _context.Initialize(new List { account }); - var ids = new[] { account.OriginatingLeadId.Id, Guid.NewGuid(), Guid.NewGuid() }; + var ids = new object[] { account.OriginatingLeadId.Id, Guid.NewGuid(), Guid.NewGuid() }; var qe = new QueryExpression(Account.EntityLogicalName); qe.Criteria.AddCondition("originatingleadid", ConditionOperator.In, ids); @@ -44,7 +44,7 @@ public void When_a_query_on_lookup_with_condition_in_contains_no_match_it_should _context.Initialize(new List { account }); - var ids = new[] { Guid.Empty, Guid.Empty, Guid.Empty }; + var ids = new object[] { Guid.Empty, Guid.Empty, Guid.Empty }; var qe = new QueryExpression(Account.EntityLogicalName); qe.Criteria.AddCondition("originatingleadid", ConditionOperator.In, ids); From 5b3e20f446b1363b44351cd77b4b90ff2c491ee3 Mon Sep 17 00:00:00 2001 From: Betim Beja <11160171+BetimBeja@users.noreply.github.com> Date: Sun, 28 May 2023 11:16:37 +0200 Subject: [PATCH 03/37] Correctly Implemented Like Operator --- .../Extensions/XmlExtensionsForFetchXml.cs | 25 +--- ...onditionExpressionExtensions.BeginsWith.cs | 22 +++ .../ConditionExpressionExtensions.Like.cs | 50 ++++--- .../Query/ConditionExpressionExtensions.cs | 14 +- .../Query/FetchXml/ConditionOperatorTests.cs | 24 +-- .../Strings/StringOperatorTests.cs | 140 ++++++++++++++++++ 6 files changed, 220 insertions(+), 55 deletions(-) create mode 100644 src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.BeginsWith.cs diff --git a/src/FakeXrmEasy.Core/Extensions/XmlExtensionsForFetchXml.cs b/src/FakeXrmEasy.Core/Extensions/XmlExtensionsForFetchXml.cs index 8668d083..fc864aa8 100644 --- a/src/FakeXrmEasy.Core/Extensions/XmlExtensionsForFetchXml.cs +++ b/src/FakeXrmEasy.Core/Extensions/XmlExtensionsForFetchXml.cs @@ -550,32 +550,9 @@ public static ConditionExpression ToConditionExpression(this XElement elem, IXrm break; case "like": op = ConditionOperator.Like; - - if (value != null) - { - if (value.StartsWith("%") && !value.EndsWith("%")) - op = ConditionOperator.EndsWith; - else if (!value.StartsWith("%") && value.EndsWith("%")) - op = ConditionOperator.BeginsWith; - else if (value.StartsWith("%") && value.EndsWith("%")) - op = ConditionOperator.Contains; - - value = value.Replace("%", ""); - } break; case "not-like": - op = ConditionOperator.NotLike; - if (value != null) - { - if (value.StartsWith("%") && !value.EndsWith("%")) - op = ConditionOperator.DoesNotEndWith; - else if (!value.StartsWith("%") && value.EndsWith("%")) - op = ConditionOperator.DoesNotBeginWith; - else if (value.StartsWith("%") && value.EndsWith("%")) - op = ConditionOperator.DoesNotContain; - - value = value.Replace("%", ""); - } + op = ConditionOperator.NotLike; break; case "gt": op = ConditionOperator.GreaterThan; diff --git a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.BeginsWith.cs b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.BeginsWith.cs new file mode 100644 index 00000000..ad76a3fb --- /dev/null +++ b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.BeginsWith.cs @@ -0,0 +1,22 @@ +using System.Linq; +using System.Linq.Expressions; +using Microsoft.Xrm.Sdk.Query; + +namespace FakeXrmEasy.Query +{ + public static partial class ConditionExpressionExtensions + { + internal static Expression ToBeginsWithExpression(this TypedConditionExpression tc, Expression getAttributeValueExpr, Expression containsAttributeExpr) + { + var c = tc.CondExpression; + + //Append a ´%´ at the end of each condition value + var computedCondition = new ConditionExpression(c.AttributeName, c.Operator, c.Values.Select(x => x.ToString() + "%").ToList()); + var typedComputedCondition = new TypedConditionExpression(computedCondition); + typedComputedCondition.AttributeType = tc.AttributeType; + + // Perhaps we are introducing some problems by converting a StartsWith to a Like Operator? + return typedComputedCondition.ToLikeExpression(getAttributeValueExpr, containsAttributeExpr); + } + } +} diff --git a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Like.cs b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Like.cs index 67ad74f8..a3194ec2 100644 --- a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Like.cs +++ b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Like.cs @@ -1,6 +1,6 @@ using System; using System.Linq.Expressions; - +using System.Text.RegularExpressions; namespace FakeXrmEasy.Query { @@ -9,37 +9,51 @@ public static partial class ConditionExpressionExtensions internal static Expression ToLikeExpression(this TypedConditionExpression tc, Expression getAttributeValueExpr, Expression containsAttributeExpr) { var c = tc.CondExpression; - BinaryExpression expOrValues = Expression.Or(Expression.Constant(false), Expression.Constant(false)); Expression convertedValueToStr = Expression.Convert(tc.AttributeType.GetAppropiateCastExpressionBasedOnType(getAttributeValueExpr, c.Values[0]), typeof(string)); Expression convertedValueToStrAndToLower = convertedValueToStr.ToCaseInsensitiveExpression(); - string sLikeOperator = "%"; + foreach (object value in c.Values) { - var strValue = value.ToString(); - string sMethod = ""; - - if (strValue.EndsWith(sLikeOperator) && strValue.StartsWith(sLikeOperator)) - sMethod = "Contains"; - - else if (strValue.StartsWith(sLikeOperator)) - sMethod = "EndsWith"; - - else - sMethod = "StartsWith"; + //convert a like into a regular expression + string input = value.ToString(); + string result = "^"; + int lastMatch = 0; + var regex = new Regex("([^\\[]*)(\\[[^\\]]*\\])", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + foreach (Match match in regex.Matches(input)) + { + if (match.Groups[1].Success) + { + result += ConvertToRegexDefinition(match.Groups[1].Value); + } + result += match.Groups[2].Value.Replace("\\", "\\\\"); + lastMatch = match.Index + match.Length; + } + if (input.Length != lastMatch) + { + result += ConvertToRegexDefinition(input.Substring(lastMatch)); + } + result += "$"; + + regex = new Regex(result, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); expOrValues = Expression.Or(expOrValues, Expression.Call( - convertedValueToStrAndToLower, - typeof(string).GetMethod(sMethod, new Type[] { typeof(string) }), - Expression.Constant(value.ToString().ToLowerInvariant().Replace("%", "")) //Linq2CRM adds the percentage value to be executed as a LIKE operator, here we are replacing it to just use the appropiate method - )); + Expression.Constant(regex), + typeof(Regex).GetMethod("IsMatch", new Type[] { typeof(string) }), + convertedValueToStrAndToLower) //Linq2CRM adds the percentage value to be executed as a LIKE operator, here we are replacing it to just use the appropiate method + ); } return Expression.AndAlso( containsAttributeExpr, expOrValues); } + + private static string ConvertToRegexDefinition(string value) + { + return value.Replace("\\", "\\\\").Replace("(", "\\(").Replace("{", "\\{").Replace(".", "\\.").Replace("*", "\\*").Replace("+", "\\+").Replace("?", "\\?").Replace("%", ".*").Replace("_", "."); + } } } diff --git a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.cs b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.cs index 7623ff7e..fb62b22b 100644 --- a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.cs +++ b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.cs @@ -159,6 +159,9 @@ internal static Expression ToExpression(this TypedConditionExpression c, QueryEx break; case ConditionOperator.BeginsWith: + operatorExpression = c.ToBeginsWithExpression(getNonBasicValueExpr, containsAttributeExpression); + break; + case ConditionOperator.Like: operatorExpression = c.ToLikeExpression(getNonBasicValueExpr, containsAttributeExpression); break; @@ -176,12 +179,21 @@ internal static Expression ToExpression(this TypedConditionExpression c, QueryEx break; case ConditionOperator.DoesNotBeginWith: + operatorExpression = Expression.Not(c.ToBeginsWithExpression(getNonBasicValueExpr, containsAttributeExpression)); + break; + case ConditionOperator.DoesNotEndWith: + operatorExpression = Expression.Not(c.ToEndsWithExpression(getNonBasicValueExpr, containsAttributeExpression)); + break; + case ConditionOperator.NotLike: - case ConditionOperator.DoesNotContain: operatorExpression = Expression.Not(c.ToLikeExpression(getNonBasicValueExpr, containsAttributeExpression)); break; + case ConditionOperator.DoesNotContain: + operatorExpression = Expression.Not(c.ToContainsExpression(getNonBasicValueExpr, containsAttributeExpression)); + break; + case ConditionOperator.Null: operatorExpression = c.ToNullExpression(getNonBasicValueExpr, containsAttributeExpression); break; diff --git a/tests/FakeXrmEasy.Core.Tests/Query/FetchXml/ConditionOperatorTests.cs b/tests/FakeXrmEasy.Core.Tests/Query/FetchXml/ConditionOperatorTests.cs index 3b04970d..be9ea79b 100644 --- a/tests/FakeXrmEasy.Core.Tests/Query/FetchXml/ConditionOperatorTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/Query/FetchXml/ConditionOperatorTests.cs @@ -217,8 +217,8 @@ public void FetchXml_Operator_Like() Assert.True(query.Criteria != null); Assert.Single(query.Criteria.Conditions); Assert.Equal("fullname", query.Criteria.Conditions[0].AttributeName); - Assert.Equal(ConditionOperator.Contains, query.Criteria.Conditions[0].Operator); - Assert.Equal("Messi", query.Criteria.Conditions[0].Values[0].ToString()); + Assert.Equal(ConditionOperator.Like, query.Criteria.Conditions[0].Operator); + Assert.Equal("%Messi%", query.Criteria.Conditions[0].Values[0].ToString()); } [Fact] @@ -241,8 +241,8 @@ public void FetchXml_Operator_Like_As_BeginsWith() Assert.True(query.Criteria != null); Assert.Single(query.Criteria.Conditions); Assert.Equal("fullname", query.Criteria.Conditions[0].AttributeName); - Assert.Equal(ConditionOperator.BeginsWith, query.Criteria.Conditions[0].Operator); - Assert.Equal("Messi", query.Criteria.Conditions[0].Values[0].ToString()); + Assert.Equal(ConditionOperator.Like, query.Criteria.Conditions[0].Operator); + Assert.Equal("Messi%", query.Criteria.Conditions[0].Values[0].ToString()); } [Fact] @@ -313,8 +313,8 @@ public void FetchXml_Operator_Like_As_EndsWith() Assert.True(query.Criteria != null); Assert.Single(query.Criteria.Conditions); Assert.Equal("fullname", query.Criteria.Conditions[0].AttributeName); - Assert.Equal(ConditionOperator.EndsWith, query.Criteria.Conditions[0].Operator); - Assert.Equal("Messi", query.Criteria.Conditions[0].Values[0].ToString()); + Assert.Equal(ConditionOperator.Like, query.Criteria.Conditions[0].Operator); + Assert.Equal("%Messi", query.Criteria.Conditions[0].Values[0].ToString()); } [Fact] @@ -385,8 +385,8 @@ public void FetchXml_Operator_NotLike() Assert.True(query.Criteria != null); Assert.Single(query.Criteria.Conditions); Assert.Equal("fullname", query.Criteria.Conditions[0].AttributeName); - Assert.Equal(ConditionOperator.DoesNotContain, query.Criteria.Conditions[0].Operator); - Assert.Equal("Messi", query.Criteria.Conditions[0].Values[0].ToString()); + Assert.Equal(ConditionOperator.NotLike, query.Criteria.Conditions[0].Operator); + Assert.Equal("%Messi%", query.Criteria.Conditions[0].Values[0].ToString()); } [Fact] @@ -409,8 +409,8 @@ public void FetchXml_Operator_NotLike_As_Not_BeginWith() Assert.True(query.Criteria != null); Assert.Single(query.Criteria.Conditions); Assert.Equal("fullname", query.Criteria.Conditions[0].AttributeName); - Assert.Equal(ConditionOperator.DoesNotBeginWith, query.Criteria.Conditions[0].Operator); - Assert.Equal("Messi", query.Criteria.Conditions[0].Values[0].ToString()); + Assert.Equal(ConditionOperator.NotLike, query.Criteria.Conditions[0].Operator); + Assert.Equal("Messi%", query.Criteria.Conditions[0].Values[0].ToString()); } [Fact] @@ -433,8 +433,8 @@ public void FetchXml_Operator_NotLike_As_Not_EndWith() Assert.True(query.Criteria != null); Assert.Single(query.Criteria.Conditions); Assert.Equal("fullname", query.Criteria.Conditions[0].AttributeName); - Assert.Equal(ConditionOperator.DoesNotEndWith, query.Criteria.Conditions[0].Operator); - Assert.Equal("Messi", query.Criteria.Conditions[0].Values[0].ToString()); + Assert.Equal(ConditionOperator.NotLike, query.Criteria.Conditions[0].Operator); + Assert.Equal("%Messi", query.Criteria.Conditions[0].Values[0].ToString()); } [Fact] diff --git a/tests/FakeXrmEasy.Core.Tests/Query/FetchXml/OperatorTests/Strings/StringOperatorTests.cs b/tests/FakeXrmEasy.Core.Tests/Query/FetchXml/OperatorTests/Strings/StringOperatorTests.cs index 9318a7e2..a1a9ec7f 100644 --- a/tests/FakeXrmEasy.Core.Tests/Query/FetchXml/OperatorTests/Strings/StringOperatorTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/Query/FetchXml/OperatorTests/Strings/StringOperatorTests.cs @@ -5,6 +5,7 @@ using System.Reflection; using Xunit; using FakeXrmEasy.Query; +using System.Linq; namespace FakeXrmEasy.Tests.FakeContextTests.FetchXml.OperatorTests.Strings { @@ -111,5 +112,144 @@ public void FetchXml_Operator_Gt_Execution() Assert.Equal("Bob", collection.Entities[0]["nickname"]); Assert.Equal("Nati", collection.Entities[1]["nickname"]); } + + [Fact] + public void FetchXml_Operator_BeginsWith_Execution() + { + var fetchXml = @" + + + + + + + "; + + var ct1 = new Contact() { Id = Guid.NewGuid(), NickName = "Alice" }; + var ct2 = new Contact() { Id = Guid.NewGuid(), NickName = "Bob" }; + var ct3 = new Contact() { Id = Guid.NewGuid(), NickName = "Nati" }; + _context.Initialize(new[] { ct1, ct2, ct3 }); + + var collection = _service.RetrieveMultiple(new FetchExpression(fetchXml)); + + Assert.Equal(1, collection.Entities.Count); + Assert.Equal("Alice", collection.Entities[0]["nickname"]); + } + + [Fact] + public void FetchXml_Operator_DoesNotBeginWith_Execution() + { + var fetchXml = @" + + + + + + + "; + + var ct1 = new Contact() { Id = Guid.NewGuid(), NickName = "Alice" }; + var ct2 = new Contact() { Id = Guid.NewGuid(), NickName = "Bob" }; + var ct3 = new Contact() { Id = Guid.NewGuid(), NickName = "Nati" }; + _context.Initialize(new[] { ct1, ct2, ct3 }); + + var collection = _service.RetrieveMultiple(new FetchExpression(fetchXml)); + + Assert.Equal(2, collection.Entities.Count); + Assert.DoesNotContain("Alice", collection.Entities.Select(e => e["nickname"])); + } + + [Fact] + public void FetchXml_Operator_EndsWith_Execution() + { + var fetchXml = @" + + + + + + + "; + + var ct1 = new Contact() { Id = Guid.NewGuid(), NickName = "Alice" }; + var ct2 = new Contact() { Id = Guid.NewGuid(), NickName = "Bob" }; + var ct3 = new Contact() { Id = Guid.NewGuid(), NickName = "Nati" }; + _context.Initialize(new[] { ct1, ct2, ct3 }); + + var collection = _service.RetrieveMultiple(new FetchExpression(fetchXml)); + + Assert.Equal(1, collection.Entities.Count); + Assert.Equal("Alice", collection.Entities[0]["nickname"]); + } + + [Fact] + public void FetchXml_Operator_DoesNotEndWith_Execution() + { + var fetchXml = @" + + + + + + + "; + + var ct1 = new Contact() { Id = Guid.NewGuid(), NickName = "Alice" }; + var ct2 = new Contact() { Id = Guid.NewGuid(), NickName = "Bob" }; + var ct3 = new Contact() { Id = Guid.NewGuid(), NickName = "Nati" }; + _context.Initialize(new[] { ct1, ct2, ct3 }); + + var collection = _service.RetrieveMultiple(new FetchExpression(fetchXml)); + + Assert.Equal(2, collection.Entities.Count); + Assert.DoesNotContain("Alice", collection.Entities.Select(e => e["nickname"])); + } + + + [Fact] + public void FetchXml_Operator_Like_Execution() + { + var fetchXml = @" + + + + + + + "; + + var ct1 = new Contact() { Id = Guid.NewGuid(), NickName = "Alice" }; + var ct2 = new Contact() { Id = Guid.NewGuid(), NickName = "Bob" }; + var ct3 = new Contact() { Id = Guid.NewGuid(), NickName = "Nati" }; + _context.Initialize(new[] { ct1, ct2, ct3 }); + + var collection = _service.RetrieveMultiple(new FetchExpression(fetchXml)); + + Assert.Equal(1, collection.Entities.Count); + Assert.Equal("Alice", collection.Entities[0]["nickname"]); + } + + [Fact] + public void FetchXml_Operator_NotLike_Execution() + { + var fetchXml = @" + + + + + + + "; + + var ct1 = new Contact() { Id = Guid.NewGuid(), NickName = "Alice" }; + var ct2 = new Contact() { Id = Guid.NewGuid(), NickName = "Bob" }; + var ct3 = new Contact() { Id = Guid.NewGuid(), NickName = "Nati" }; + _context.Initialize(new[] { ct1, ct2, ct3 }); + + var collection = _service.RetrieveMultiple(new FetchExpression(fetchXml)); + + Assert.Equal(2, collection.Entities.Count); + Assert.DoesNotContain("Alice", collection.Entities.Select(e=> e["nickname"])); + } } } From 331780cbd5d65589c63a4ab128fbd779358b0172 Mon Sep 17 00:00:00 2001 From: "temmy.raharjo" Date: Wed, 25 Oct 2023 10:55:32 +0800 Subject: [PATCH 04/37] chore fix linkedentity query --- .../Query/LinkEntityQueryExtensions.cs | 9 ++ .../FakeXrmEasy.Core.Tests/Issues/Issue63.cs | 94 +++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 tests/FakeXrmEasy.Core.Tests/Issues/Issue63.cs diff --git a/src/FakeXrmEasy.Core/Query/LinkEntityQueryExtensions.cs b/src/FakeXrmEasy.Core/Query/LinkEntityQueryExtensions.cs index d00503b2..68af9083 100644 --- a/src/FakeXrmEasy.Core/Query/LinkEntityQueryExtensions.cs +++ b/src/FakeXrmEasy.Core/Query/LinkEntityQueryExtensions.cs @@ -169,6 +169,15 @@ internal static List TranslateLinkedEntityFilterExpressionToExpressi var entityAlias = !string.IsNullOrEmpty(le.EntityAlias) ? le.EntityAlias : le.LinkToEntityName; ce.AttributeName = entityAlias + "." + ce.AttributeName; } + + foreach (var currentFe in fe.Filters) + { + foreach (var ce in currentFe.Conditions) + { + var entityAlias = !string.IsNullOrEmpty(le.EntityAlias) ? le.EntityAlias : le.LinkToEntityName; + ce.AttributeName = entityAlias + "." + ce.AttributeName; + } + } } } diff --git a/tests/FakeXrmEasy.Core.Tests/Issues/Issue63.cs b/tests/FakeXrmEasy.Core.Tests/Issues/Issue63.cs new file mode 100644 index 00000000..a12ab18c --- /dev/null +++ b/tests/FakeXrmEasy.Core.Tests/Issues/Issue63.cs @@ -0,0 +1,94 @@ +using Crm; +using Microsoft.Xrm.Sdk; +using Microsoft.Xrm.Sdk.Query; +using System; +using FakeXrmEasy.Query; +using Xunit; + +namespace FakeXrmEasy.Core.Tests.Issues +{ + // https://github.com/DynamicsValue/fake-xrm-easy/issues/63 + + public class Issue63 : FakeXrmEasyTestsBase + { + // setup + public Issue63() + { + _context.EnableProxyTypes(typeof(Account).Assembly); + + var order = new SalesOrder() { Id = Guid.NewGuid() }; + + var uom = new UoM() + { + Id = Guid.NewGuid(), + Name = "KG" + }; + var detail1 = new SalesOrderDetail() + { + Id = Guid.NewGuid(), + SalesOrderId = order.ToEntityReference(), + Quantity = 1, + ProductDescription = "A", + Description = "B", + UoMId = uom.ToEntityReference() + }; + var detail2 = new SalesOrderDetail() + { + Id = Guid.NewGuid(), + SalesOrderId = order.ToEntityReference(), + Quantity = 1, + ProductDescription = "C", + Description = "D", + UoMId = uom.ToEntityReference() + }; + var detail3 = new SalesOrderDetail() + { + Id = Guid.NewGuid(), + SalesOrderId = order.ToEntityReference(), + Quantity = 1, + ProductDescription = "E", + Description = "F", + UoMId = uom.ToEntityReference() + }; + + _context.Initialize(new Entity[] { order, detail1, detail2, detail3, uom }); + } + + // This test currently fails + [Fact] + public void When_A_QueryExpression_Contains_A_Complex_subquery_On_A_Link_Entity_It_Should_Return_The_Right_Records() + { + var query = new QueryExpression("salesorder"); + // link to salesorderdetail + var query_salesorderdetail = query.AddLink("salesorderdetail", "salesorderid", "salesorderid"); + // Quantity not null + query_salesorderdetail.LinkCriteria.AddCondition("quantity", ConditionOperator.NotNull); + var query_currency = + query_salesorderdetail.AddLink("uom", "uomid", "uomid"); + query_currency.LinkCriteria.AddCondition("name", ConditionOperator.Equal, "KG"); + + // Add an 'Or' filter + var orFilter = new FilterExpression(); + query_salesorderdetail.LinkCriteria.AddFilter(orFilter); + orFilter.FilterOperator = LogicalOperator.Or; + + // Filter with two Ands - A and B - should find detail1 + var aAndBFilter = new FilterExpression(); + aAndBFilter.AddCondition("productdescription", ConditionOperator.Equal, "A"); + aAndBFilter.AddCondition("description", ConditionOperator.Equal, "B"); + + // Filter with two Ands - C and D - should find detail2 + var cAndDFilter = new FilterExpression(); + cAndDFilter.AddCondition("productdescription", ConditionOperator.Equal, "C"); + cAndDFilter.AddCondition("description", ConditionOperator.Equal, "D"); + + // Add the two and filters to the Or Filter + orFilter.AddFilter(aAndBFilter); + orFilter.AddFilter(cAndDFilter); + + var records = _service.RetrieveMultiple(query); + + Assert.Equal(2, records.Entities.Count); + } + } +} \ No newline at end of file From 62b91842e37d29a46dec834668ee69c4fa38bbe0 Mon Sep 17 00:00:00 2001 From: Betim Beja <11160171+BetimBeja@users.noreply.github.com> Date: Sun, 3 Dec 2023 17:27:23 +0100 Subject: [PATCH 05/37] Validate EntityReferences with null logicalName on .Initialize() fixes DynamicsValue/fake-xrm-easy#107 --- src/FakeXrmEasy.Core/XrmFakedContext.cs | 14 +++++++- .../FakeContextTests/FakeContextTests.cs | 32 +++++++++++++------ 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/src/FakeXrmEasy.Core/XrmFakedContext.cs b/src/FakeXrmEasy.Core/XrmFakedContext.cs index e37282da..c28e8813 100644 --- a/src/FakeXrmEasy.Core/XrmFakedContext.cs +++ b/src/FakeXrmEasy.Core/XrmFakedContext.cs @@ -300,14 +300,26 @@ public virtual void Initialize(IEnumerable entities) throw new InvalidOperationException("The entities parameter must be not null"); } - foreach (var e in entities) + foreach (var e in entities) { + ValidateEntityReferences(e); AddEntityWithDefaults(e, true); } Initialised = true; } + private void ValidateEntityReferences(Entity e) + { + foreach (var item in e.Attributes) + { + if (item.Value is EntityReference entityReference && String.IsNullOrEmpty(entityReference.LogicalName)) + { + throw new Exception($"Broken EntityReference record during Initialize() for column '{item.Key}' of a '{e.LogicalName}' record."); + } + } + } + /// /// Initializes the context with a single entity record /// diff --git a/tests/FakeXrmEasy.Core.Tests/FakeContextTests/FakeContextTests.cs b/tests/FakeXrmEasy.Core.Tests/FakeContextTests/FakeContextTests.cs index 5ca0212a..79fb4825 100644 --- a/tests/FakeXrmEasy.Core.Tests/FakeContextTests/FakeContextTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/FakeContextTests/FakeContextTests.cs @@ -284,10 +284,10 @@ public void When_initialising_the_context_after_enabling_proxy_types_exception_i } [Fact] - public void Should_return_entity_by_id() + public void Should_return_entity_by_id() { - var contact = new Contact() - { + var contact = new Contact() + { Id = Guid.NewGuid(), FirstName = "Steve", LastName = "Vai" @@ -297,27 +297,41 @@ public void Should_return_entity_by_id() var retrievedContact = _context.GetEntityById(contact.Id); Assert.Equal(contact.Id, retrievedContact.Id); Assert.Equal(contact.FirstName, retrievedContact.FirstName); - Assert.Equal(contact.LastName, retrievedContact.LastName); + Assert.Equal(contact.LastName, retrievedContact.LastName); } [Fact] public void Should_return_error_if_entity_logical_name_doesnt_exists() { - Assert.Throws(() =>_context.GetEntityById("doesNotExist", Guid.NewGuid())); + Assert.Throws(() =>_context.GetEntityById("doesNotExist", Guid.NewGuid())); } [Fact] - public void Should_return_error_if_entity_id_does_not_exists() + public void Should_return_error_if_entity_id_does_not_exists() { - var contact = new Contact() - { + var contact = new Contact() + { Id = Guid.NewGuid(), FirstName = "Steve", LastName = "Vai" }; _context.Initialize(contact); - Assert.Throws(() =>_context.GetEntityById("contact", Guid.NewGuid())); + Assert.Throws(() =>_context.GetEntityById("contact", Guid.NewGuid())); + } + + [Fact] + public void Should_return_error_if_contains_broken_entityreference() + { + var contact = new Contact() + { + Id = Guid.NewGuid(), + FirstName = "Steve", + LastName = "Vai", + ParentCustomerId = new EntityReference("", Guid.NewGuid()) + }; + + Assert.Throws(() => _context.Initialize(contact)); } [Fact] From a95dc786ea20c5299a7c11b188317984dc2db51b Mon Sep 17 00:00:00 2001 From: Jordi Date: Sun, 10 Mar 2024 23:13:03 +0100 Subject: [PATCH 06/37] adding initial support for bulk operations DynamicsValue/fake-xrm-easy#122 --- CHANGELOG.md | 4 + src/FakeXrmEasy.Core/FakeXrmEasy.Core.csproj | 6 +- .../CreateMultipleRequestExecutor.cs | 126 ++ .../Crud/MiddlewareBuilderExtensions.Crud.cs | 3 + src/FakeXrmEasy.Core/XrmFakedContext.cs | 4 +- .../DataverseEntities.csproj | 8 +- tests/DataverseEntities/Entities/dv_test.cs | 1204 +++++++++++++++++ .../AssertExtensions.cs | 17 + .../FakeXrmEasy.Core.Tests.csproj | 16 +- .../CreateMultipleRequestsTests.cs | 129 ++ 10 files changed, 1499 insertions(+), 18 deletions(-) create mode 100644 src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestExecutor.cs create mode 100644 tests/DataverseEntities/Entities/dv_test.cs create mode 100644 tests/FakeXrmEasy.Core.Tests/AssertExtensions.cs create mode 100644 tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestsTests/CreateMultipleRequestsTests.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a53ff3d..a536f97a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [2.5.0] + +- Added support for bulk operations: CreateMultipleRequest + ## [2.4.2] ### Added diff --git a/src/FakeXrmEasy.Core/FakeXrmEasy.Core.csproj b/src/FakeXrmEasy.Core/FakeXrmEasy.Core.csproj index 9096cbf1..274e3373 100644 --- a/src/FakeXrmEasy.Core/FakeXrmEasy.Core.csproj +++ b/src/FakeXrmEasy.Core/FakeXrmEasy.Core.csproj @@ -8,7 +8,7 @@ net452 net452 FakeXrmEasy.Core - 2.4.2 + 2.5.0 Jordi Montaña Dynamics Value FakeXrmEasy Core @@ -57,8 +57,8 @@ - - + + diff --git a/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestExecutor.cs b/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestExecutor.cs new file mode 100644 index 00000000..391cfbe8 --- /dev/null +++ b/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestExecutor.cs @@ -0,0 +1,126 @@ +using FakeXrmEasy.Abstractions; +using FakeXrmEasy.Abstractions.FakeMessageExecutors; +using Microsoft.Xrm.Sdk; +using Microsoft.Xrm.Sdk.Messages; +using System; +using System.Linq; + +namespace FakeXrmEasy.Middleware.Crud.FakeMessageExecutors +{ + /// + /// CreateMultipleRequest Executor + /// + public class CreateMultipleRequestExecutor : IFakeMessageExecutor + { + /// + /// + /// + /// + /// + public bool CanExecute(OrganizationRequest request) + { + return request is CreateMultipleRequest; + } + + /// + /// Executes the CreateRequestMultiple request + /// + /// + /// + /// + public OrganizationResponse Execute(OrganizationRequest request, IXrmFakedContext ctx) + { + var createMultipleRequest = (CreateMultipleRequest)request; + + ValidateRequest(createMultipleRequest, ctx); + + var response = new CreateMultipleResponse(); + + return new CreateResponse() + { + ResponseName = "CreateMultipleResponse" + }; + } + + private void ValidateRequiredParameters(CreateMultipleRequest request, IXrmFakedContext ctx) + { + if (request.Targets == null) + { + throw FakeOrganizationServiceFaultFactory.New(ErrorCodes.InvalidArgument, + "Required field 'Targets' is missing"); + } + + var targets = request.Targets; + if (string.IsNullOrWhiteSpace(targets.EntityName)) + { + throw FakeOrganizationServiceFaultFactory.New(ErrorCodes.InvalidArgument, + "Required member 'EntityName' missing for field 'Targets'"); + } + } + + private void ValidateEntityName(CreateMultipleRequest request, IXrmFakedContext ctx) + { + var targets = request.Targets; + if (ctx.ProxyTypesAssemblies.Any()) + { + var earlyBoundType = ctx.FindReflectedType(targets.EntityName); + if (earlyBoundType == null) + { + throw FakeOrganizationServiceFaultFactory.New(ErrorCodes.QueryBuilderNoEntity, + $"The entity with a name = '{targets.EntityName}' with namemapping = 'Logical' was not found in the MetadataCache."); + } + } + + if (ctx.CreateMetadataQuery().Any()) + { + var entityMetadata = ctx.CreateMetadataQuery().FirstOrDefault(m => m.LogicalName == targets.EntityName); + if (entityMetadata == null) + { + throw FakeOrganizationServiceFaultFactory.New(ErrorCodes.QueryBuilderNoEntity, + $"The entity with a name = '{targets.EntityName}' with namemapping = 'Logical' was not found in the MetadataCache."); + } + } + } + + private void ValidateRecords(CreateMultipleRequest request, IXrmFakedContext ctx) + { + var records = request.Targets.Entities; + if (records.Count == 0) + { + throw FakeOrganizationServiceFaultFactory.New(ErrorCodes.UnExpected, + $"System.ArgumentException: The value of the parameter 'Targets' cannot be null or empty."); + } + + foreach (var record in records) + { + ValidateRecord(request, record, ctx); + } + } + + private void ValidateRecord(CreateMultipleRequest request, Entity recordToCreate, IXrmFakedContext ctx) + { + var exists = ctx.ContainsEntity(recordToCreate.LogicalName, recordToCreate.Id); + if (exists) + { + throw FakeOrganizationServiceFaultFactory.New(ErrorCodes.DuplicateRecord, + $"Cannot insert duplicate key."); + } + } + + private void ValidateRequest(CreateMultipleRequest request, IXrmFakedContext ctx) + { + ValidateRequiredParameters(request, ctx); + ValidateEntityName(request, ctx); + ValidateRecords(request, ctx); + } + + /// + /// Returns CreateMultipleRequest + /// + /// + public Type GetResponsibleRequestType() + { + return typeof(CreateMultipleRequest); + } + } +} \ No newline at end of file diff --git a/src/FakeXrmEasy.Core/Middleware/Crud/MiddlewareBuilderExtensions.Crud.cs b/src/FakeXrmEasy.Core/Middleware/Crud/MiddlewareBuilderExtensions.Crud.cs index 84d4b7e5..3d3fbd40 100644 --- a/src/FakeXrmEasy.Core/Middleware/Crud/MiddlewareBuilderExtensions.Crud.cs +++ b/src/FakeXrmEasy.Core/Middleware/Crud/MiddlewareBuilderExtensions.Crud.cs @@ -38,6 +38,7 @@ public static IMiddlewareBuilder AddCrud(this IMiddlewareBuilder builder) //Get Crud Message Executors var crudMessageExecutors = new CrudMessageExecutors(); crudMessageExecutors.Add(typeof(CreateRequest), new CreateRequestExecutor()); + crudMessageExecutors.Add(typeof(RetrieveMultipleRequest), new RetrieveMultipleRequestExecutor()); crudMessageExecutors.Add(typeof(RetrieveRequest), new RetrieveRequestExecutor()); crudMessageExecutors.Add(typeof(UpdateRequest), new UpdateRequestExecutor()); @@ -45,6 +46,8 @@ public static IMiddlewareBuilder AddCrud(this IMiddlewareBuilder builder) crudMessageExecutors.Add(typeof(AssociateRequest), new AssociateRequestExecutor()); crudMessageExecutors.Add(typeof(DisassociateRequest), new DisassociateRequestExecutor()); + crudMessageExecutors.Add(typeof(CreateMultipleRequest), new CreateMultipleRequestExecutor()); + #if !FAKE_XRM_EASY && !FAKE_XRM_EASY_2013 && !FAKE_XRM_EASY_2015 crudMessageExecutors.Add(typeof(UpsertRequest), new UpsertRequestExecutor()); #endif diff --git a/src/FakeXrmEasy.Core/XrmFakedContext.cs b/src/FakeXrmEasy.Core/XrmFakedContext.cs index 0e61a5e4..a03ce41f 100644 --- a/src/FakeXrmEasy.Core/XrmFakedContext.cs +++ b/src/FakeXrmEasy.Core/XrmFakedContext.cs @@ -13,21 +13,19 @@ using FakeXrmEasy.Permissions; using FakeXrmEasy.Services; using Microsoft.Xrm.Sdk; -using Microsoft.Xrm.Sdk.Metadata; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; -using FakeXrmEasy.Abstractions.CommercialLicense; [assembly: InternalsVisibleTo("FakeXrmEasy.Core.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c124cb50761165a765adf6078bde555a7c5a2b692ed6e6ec9df0bd7d20da69170bae9bf95e874fa50995cc080af404ccad36515fa509c4ea6599a0502c1642db254a293e023c47c79ce69889c6ba921d124d896d87f0baaa9ea1d87b28589ffbe7b08492606bacef19dc4bc4cefb0d525be63ee722b02dc8c79688a7a8f623a2")] namespace FakeXrmEasy { /// - /// A fake context that stores In-Memory entites indexed by logical name and then Entity records, simulating + /// A fake context that stores In-Memory entities indexed by logical name and then Entity records, simulating /// how entities are persisted in Tables (with the logical name) and then the records themselves /// where the Primary Key is the Guid /// diff --git a/tests/DataverseEntities/DataverseEntities.csproj b/tests/DataverseEntities/DataverseEntities.csproj index 5dad1807..d1924fe2 100644 --- a/tests/DataverseEntities/DataverseEntities.csproj +++ b/tests/DataverseEntities/DataverseEntities.csproj @@ -72,10 +72,10 @@ - - - - + + + + diff --git a/tests/DataverseEntities/Entities/dv_test.cs b/tests/DataverseEntities/Entities/dv_test.cs new file mode 100644 index 00000000..fd99ecd8 --- /dev/null +++ b/tests/DataverseEntities/Entities/dv_test.cs @@ -0,0 +1,1204 @@ +#pragma warning disable CS1591 +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace DataverseEntities +{ + + + [System.Runtime.Serialization.DataContractAttribute()] + [System.CodeDom.Compiler.GeneratedCodeAttribute("Dataverse Model Builder", "2.0.0.6")] + public enum dv_test_dv_choice_multiple + { + + [System.Runtime.Serialization.EnumMemberAttribute()] + Option1 = 121940000, + + [System.Runtime.Serialization.EnumMemberAttribute()] + Option2 = 121940001, + + [System.Runtime.Serialization.EnumMemberAttribute()] + Option3 = 121940002, + } + + [System.Runtime.Serialization.DataContractAttribute()] + [System.CodeDom.Compiler.GeneratedCodeAttribute("Dataverse Model Builder", "2.0.0.6")] + public enum dv_test_dv_choice_single + { + + [System.Runtime.Serialization.EnumMemberAttribute()] + Option1 = 121940000, + + [System.Runtime.Serialization.EnumMemberAttribute()] + Option2 = 121940001, + + [System.Runtime.Serialization.EnumMemberAttribute()] + Option3 = 121940002, + } + + /// + /// Status of the Test Table + /// + [System.Runtime.Serialization.DataContractAttribute()] + [System.CodeDom.Compiler.GeneratedCodeAttribute("Dataverse Model Builder", "2.0.0.6")] + public enum dv_test_statecode + { + + [System.Runtime.Serialization.EnumMemberAttribute()] + Active = 0, + + [System.Runtime.Serialization.EnumMemberAttribute()] + Inactive = 1, + } + + /// + /// Reason for the status of the Test Table + /// + [System.Runtime.Serialization.DataContractAttribute()] + [System.CodeDom.Compiler.GeneratedCodeAttribute("Dataverse Model Builder", "2.0.0.6")] + public enum dv_test_statuscode + { + + [System.Runtime.Serialization.EnumMemberAttribute()] + Active = 1, + + [System.Runtime.Serialization.EnumMemberAttribute()] + Inactive = 2, + } + + [System.Runtime.Serialization.DataContractAttribute()] + [Microsoft.Xrm.Sdk.Client.EntityLogicalNameAttribute("dv_test")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("Dataverse Model Builder", "2.0.0.6")] + public partial class dv_test : Microsoft.Xrm.Sdk.Entity + { + + /// + /// Available fields, a the time of codegen, for the dv_test entity + /// + public partial class Fields + { + public const string CreatedBy = "createdby"; + public const string CreatedByName = "createdbyname"; + public const string CreatedByYomiName = "createdbyyominame"; + public const string CreatedOn = "createdon"; + public const string CreatedOnBehalfBy = "createdonbehalfby"; + public const string CreatedOnBehalfByName = "createdonbehalfbyname"; + public const string CreatedOnBehalfByYomiName = "createdonbehalfbyyominame"; + public const string dv_accountid = "dv_accountid"; + public const string dv_accountidName = "dv_accountidname"; + public const string dv_accountidYomiName = "dv_accountidyominame"; + public const string dv_bool = "dv_bool"; + public const string dv_boolName = "dv_boolname"; + public const string dv_choice_multiple = "dv_choice_multiple"; + public const string dv_choice_multipleName = "dv_choice_multiplename"; + public const string dv_choice_single = "dv_choice_single"; + public const string dv_choice_singleName = "dv_choice_singlename"; + public const string dv_currency = "dv_currency"; + public const string dv_currency_Base = "dv_currency_base"; + public const string dv_customerid = "dv_customerid"; + public const string dv_customeridName = "dv_customeridname"; + public const string dv_customeridYomiName = "dv_customeridyominame"; + public const string dv_date_only = "dv_date_only"; + public const string dv_date_time = "dv_date_time"; + public const string dv_decimal = "dv_decimal"; + public const string dv_duration = "dv_duration"; + public const string dv_email = "dv_email"; + public const string dv_file = "dv_file"; + public const string dv_file_Name = "dv_file_name"; + public const string dv_float = "dv_float"; + public const string dv_image = "dv_image"; + public const string dv_image_Timestamp = "dv_image_timestamp"; + public const string dv_image_URL = "dv_image_url"; + public const string dv_imageId = "dv_imageid"; + public const string dv_int = "dv_int"; + public const string dv_language_code = "dv_language_code"; + public const string dv_phone_number = "dv_phone_number"; + public const string dv_string = "dv_string"; + public const string dv_testId = "dv_testid"; + public const string Id = "dv_testid"; + public const string dv_textarea = "dv_textarea"; + public const string dv_ticker_symbol = "dv_ticker_symbol"; + public const string dv_time_zone = "dv_time_zone"; + public const string dv_url = "dv_url"; + public const string ExchangeRate = "exchangerate"; + public const string ImportSequenceNumber = "importsequencenumber"; + public const string ModifiedBy = "modifiedby"; + public const string ModifiedByName = "modifiedbyname"; + public const string ModifiedByYomiName = "modifiedbyyominame"; + public const string ModifiedOn = "modifiedon"; + public const string ModifiedOnBehalfBy = "modifiedonbehalfby"; + public const string ModifiedOnBehalfByName = "modifiedonbehalfbyname"; + public const string ModifiedOnBehalfByYomiName = "modifiedonbehalfbyyominame"; + public const string OverriddenCreatedOn = "overriddencreatedon"; + public const string OwnerId = "ownerid"; + public const string OwnerIdName = "owneridname"; + public const string OwnerIdYomiName = "owneridyominame"; + public const string OwningBusinessUnit = "owningbusinessunit"; + public const string OwningBusinessUnitName = "owningbusinessunitname"; + public const string OwningTeam = "owningteam"; + public const string OwningUser = "owninguser"; + public const string statecode = "statecode"; + public const string statecodeName = "statecodename"; + public const string statuscode = "statuscode"; + public const string statuscodeName = "statuscodename"; + public const string TimeZoneRuleVersionNumber = "timezoneruleversionnumber"; + public const string TransactionCurrencyId = "transactioncurrencyid"; + public const string TransactionCurrencyIdName = "transactioncurrencyidname"; + public const string UTCConversionTimeZoneCode = "utcconversiontimezonecode"; + public const string VersionNumber = "versionnumber"; + public const string dv_account_dv_test_393 = "dv_account_dv_test_393"; + public const string dv_contact_dv_test_393 = "dv_contact_dv_test_393"; + public const string dv_test_accountid_account = "dv_test_accountid_account"; + } + + /// + /// Default Constructor. + /// + public dv_test() : + base(EntityLogicalName) + { + } + + public const string EntityLogicalName = "dv_test"; + + public const string EntityLogicalCollectionName = "dv_tests"; + + public const string EntitySetName = "dv_tests"; + + public const int EntityTypeCode = 10359; + + /// + /// Unique identifier of the user who created the record. + /// + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("createdby")] + public Microsoft.Xrm.Sdk.EntityReference CreatedBy + { + get + { + return this.GetAttributeValue("createdby"); + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("createdbyname")] + public string CreatedByName + { + get + { + if (this.FormattedValues.Contains("createdby")) + { + return this.FormattedValues["createdby"]; + } + else + { + return default(string); + } + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("createdbyyominame")] + public string CreatedByYomiName + { + get + { + if (this.FormattedValues.Contains("createdby")) + { + return this.FormattedValues["createdby"]; + } + else + { + return default(string); + } + } + } + + /// + /// Date and time when the record was created. + /// + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("createdon")] + public System.Nullable CreatedOn + { + get + { + return this.GetAttributeValue>("createdon"); + } + } + + /// + /// Unique identifier of the delegate user who created the record. + /// + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("createdonbehalfby")] + public Microsoft.Xrm.Sdk.EntityReference CreatedOnBehalfBy + { + get + { + return this.GetAttributeValue("createdonbehalfby"); + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("createdonbehalfbyname")] + public string CreatedOnBehalfByName + { + get + { + if (this.FormattedValues.Contains("createdonbehalfby")) + { + return this.FormattedValues["createdonbehalfby"]; + } + else + { + return default(string); + } + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("createdonbehalfbyyominame")] + public string CreatedOnBehalfByYomiName + { + get + { + if (this.FormattedValues.Contains("createdonbehalfby")) + { + return this.FormattedValues["createdonbehalfby"]; + } + else + { + return default(string); + } + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_accountid")] + public Microsoft.Xrm.Sdk.EntityReference dv_accountid + { + get + { + return this.GetAttributeValue("dv_accountid"); + } + set + { + this.SetAttributeValue("dv_accountid", value); + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_accountidname")] + public string dv_accountidName + { + get + { + if (this.FormattedValues.Contains("dv_accountid")) + { + return this.FormattedValues["dv_accountid"]; + } + else + { + return default(string); + } + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_accountidyominame")] + public string dv_accountidYomiName + { + get + { + if (this.FormattedValues.Contains("dv_accountid")) + { + return this.FormattedValues["dv_accountid"]; + } + else + { + return default(string); + } + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_bool")] + public System.Nullable dv_bool + { + get + { + return this.GetAttributeValue>("dv_bool"); + } + set + { + this.SetAttributeValue("dv_bool", value); + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_boolname")] + public string dv_boolName + { + get + { + if (this.FormattedValues.Contains("dv_bool")) + { + return this.FormattedValues["dv_bool"]; + } + else + { + return default(string); + } + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_choice_multiple")] + public virtual System.Collections.Generic.IEnumerable dv_choice_multiple + { + get + { + return EntityOptionSetEnum.GetMultiEnum(this, "dv_choice_multiple"); + } + set + { + this.SetAttributeValue("dv_choice_multiple", EntityOptionSetEnum.GetMultiEnum(this, "dv_choice_multiple", value)); + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_choice_multiplename")] + public string dv_choice_multipleName + { + get + { + if (this.FormattedValues.Contains("dv_choice_multiple")) + { + return this.FormattedValues["dv_choice_multiple"]; + } + else + { + return default(string); + } + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_choice_single")] + public virtual dv_test_dv_choice_single? dv_choice_single + { + get + { + return ((dv_test_dv_choice_single?)(EntityOptionSetEnum.GetEnum(this, "dv_choice_single"))); + } + set + { + this.SetAttributeValue("dv_choice_single", value.HasValue ? new Microsoft.Xrm.Sdk.OptionSetValue((int)value) : null); + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_choice_singlename")] + public string dv_choice_singleName + { + get + { + if (this.FormattedValues.Contains("dv_choice_single")) + { + return this.FormattedValues["dv_choice_single"]; + } + else + { + return default(string); + } + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_currency")] + public Microsoft.Xrm.Sdk.Money dv_currency + { + get + { + return this.GetAttributeValue("dv_currency"); + } + set + { + this.SetAttributeValue("dv_currency", value); + } + } + + /// + /// Value of the currency in base currency. + /// + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_currency_base")] + public Microsoft.Xrm.Sdk.Money dv_currency_Base + { + get + { + return this.GetAttributeValue("dv_currency_base"); + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_customerid")] + public Microsoft.Xrm.Sdk.EntityReference dv_customerid + { + get + { + return this.GetAttributeValue("dv_customerid"); + } + set + { + this.SetAttributeValue("dv_customerid", value); + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_customeridname")] + public string dv_customeridName + { + get + { + if (this.FormattedValues.Contains("dv_customerid")) + { + return this.FormattedValues["dv_customerid"]; + } + else + { + return default(string); + } + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_customeridyominame")] + public string dv_customeridYomiName + { + get + { + if (this.FormattedValues.Contains("dv_customerid")) + { + return this.FormattedValues["dv_customerid"]; + } + else + { + return default(string); + } + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_date_only")] + public System.Nullable dv_date_only + { + get + { + return this.GetAttributeValue>("dv_date_only"); + } + set + { + this.SetAttributeValue("dv_date_only", value); + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_date_time")] + public System.Nullable dv_date_time + { + get + { + return this.GetAttributeValue>("dv_date_time"); + } + set + { + this.SetAttributeValue("dv_date_time", value); + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_decimal")] + public System.Nullable dv_decimal + { + get + { + return this.GetAttributeValue>("dv_decimal"); + } + set + { + this.SetAttributeValue("dv_decimal", value); + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_duration")] + public System.Nullable dv_duration + { + get + { + return this.GetAttributeValue>("dv_duration"); + } + set + { + this.SetAttributeValue("dv_duration", value); + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_email")] + public string dv_email + { + get + { + return this.GetAttributeValue("dv_email"); + } + set + { + this.SetAttributeValue("dv_email", value); + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_file")] + public object dv_file + { + get + { + return this.GetAttributeValue("dv_file"); + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_file_name")] + public string dv_file_Name + { + get + { + if (this.FormattedValues.Contains("dv_file")) + { + return this.FormattedValues["dv_file"]; + } + else + { + return default(string); + } + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_float")] + public System.Nullable dv_float + { + get + { + return this.GetAttributeValue>("dv_float"); + } + set + { + this.SetAttributeValue("dv_float", value); + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_image")] + public byte[] dv_image + { + get + { + return this.GetAttributeValue("dv_image"); + } + set + { + this.SetAttributeValue("dv_image", value); + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_image_timestamp")] + public System.Nullable dv_image_Timestamp + { + get + { + return this.GetAttributeValue>("dv_image_timestamp"); + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_image_url")] + public string dv_image_URL + { + get + { + return this.GetAttributeValue("dv_image_url"); + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_imageid")] + public System.Nullable dv_imageId + { + get + { + return this.GetAttributeValue>("dv_imageid"); + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_int")] + public System.Nullable dv_int + { + get + { + return this.GetAttributeValue>("dv_int"); + } + set + { + this.SetAttributeValue("dv_int", value); + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_language_code")] + public System.Nullable dv_language_code + { + get + { + return this.GetAttributeValue>("dv_language_code"); + } + set + { + this.SetAttributeValue("dv_language_code", value); + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_phone_number")] + public string dv_phone_number + { + get + { + return this.GetAttributeValue("dv_phone_number"); + } + set + { + this.SetAttributeValue("dv_phone_number", value); + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_string")] + public string dv_string + { + get + { + return this.GetAttributeValue("dv_string"); + } + set + { + this.SetAttributeValue("dv_string", value); + } + } + + /// + /// Unique identifier for entity instances + /// + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_testid")] + public System.Nullable dv_testId + { + get + { + return this.GetAttributeValue>("dv_testid"); + } + set + { + this.SetAttributeValue("dv_testid", value); + if (value.HasValue) + { + base.Id = value.Value; + } + else + { + base.Id = System.Guid.Empty; + } + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_testid")] + public override System.Guid Id + { + get + { + return base.Id; + } + set + { + this.dv_testId = value; + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_textarea")] + public string dv_textarea + { + get + { + return this.GetAttributeValue("dv_textarea"); + } + set + { + this.SetAttributeValue("dv_textarea", value); + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_ticker_symbol")] + public string dv_ticker_symbol + { + get + { + return this.GetAttributeValue("dv_ticker_symbol"); + } + set + { + this.SetAttributeValue("dv_ticker_symbol", value); + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_time_zone")] + public System.Nullable dv_time_zone + { + get + { + return this.GetAttributeValue>("dv_time_zone"); + } + set + { + this.SetAttributeValue("dv_time_zone", value); + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_url")] + public string dv_url + { + get + { + return this.GetAttributeValue("dv_url"); + } + set + { + this.SetAttributeValue("dv_url", value); + } + } + + /// + /// Exchange rate for the currency associated with the entity with respect to the base currency. + /// + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("exchangerate")] + public System.Nullable ExchangeRate + { + get + { + return this.GetAttributeValue>("exchangerate"); + } + } + + /// + /// Sequence number of the import that created this record. + /// + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("importsequencenumber")] + public System.Nullable ImportSequenceNumber + { + get + { + return this.GetAttributeValue>("importsequencenumber"); + } + set + { + this.SetAttributeValue("importsequencenumber", value); + } + } + + /// + /// Unique identifier of the user who modified the record. + /// + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("modifiedby")] + public Microsoft.Xrm.Sdk.EntityReference ModifiedBy + { + get + { + return this.GetAttributeValue("modifiedby"); + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("modifiedbyname")] + public string ModifiedByName + { + get + { + if (this.FormattedValues.Contains("modifiedby")) + { + return this.FormattedValues["modifiedby"]; + } + else + { + return default(string); + } + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("modifiedbyyominame")] + public string ModifiedByYomiName + { + get + { + if (this.FormattedValues.Contains("modifiedby")) + { + return this.FormattedValues["modifiedby"]; + } + else + { + return default(string); + } + } + } + + /// + /// Date and time when the record was modified. + /// + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("modifiedon")] + public System.Nullable ModifiedOn + { + get + { + return this.GetAttributeValue>("modifiedon"); + } + } + + /// + /// Unique identifier of the delegate user who modified the record. + /// + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("modifiedonbehalfby")] + public Microsoft.Xrm.Sdk.EntityReference ModifiedOnBehalfBy + { + get + { + return this.GetAttributeValue("modifiedonbehalfby"); + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("modifiedonbehalfbyname")] + public string ModifiedOnBehalfByName + { + get + { + if (this.FormattedValues.Contains("modifiedonbehalfby")) + { + return this.FormattedValues["modifiedonbehalfby"]; + } + else + { + return default(string); + } + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("modifiedonbehalfbyyominame")] + public string ModifiedOnBehalfByYomiName + { + get + { + if (this.FormattedValues.Contains("modifiedonbehalfby")) + { + return this.FormattedValues["modifiedonbehalfby"]; + } + else + { + return default(string); + } + } + } + + /// + /// Date and time that the record was migrated. + /// + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("overriddencreatedon")] + public System.Nullable OverriddenCreatedOn + { + get + { + return this.GetAttributeValue>("overriddencreatedon"); + } + set + { + this.SetAttributeValue("overriddencreatedon", value); + } + } + + /// + /// Owner Id + /// + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("ownerid")] + public Microsoft.Xrm.Sdk.EntityReference OwnerId + { + get + { + return this.GetAttributeValue("ownerid"); + } + set + { + this.SetAttributeValue("ownerid", value); + } + } + + /// + /// Name of the owner + /// + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("owneridname")] + public string OwnerIdName + { + get + { + if (this.FormattedValues.Contains("ownerid")) + { + return this.FormattedValues["ownerid"]; + } + else + { + return default(string); + } + } + } + + /// + /// Yomi name of the owner + /// + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("owneridyominame")] + public string OwnerIdYomiName + { + get + { + if (this.FormattedValues.Contains("ownerid")) + { + return this.FormattedValues["ownerid"]; + } + else + { + return default(string); + } + } + } + + /// + /// Unique identifier for the business unit that owns the record + /// + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("owningbusinessunit")] + public Microsoft.Xrm.Sdk.EntityReference OwningBusinessUnit + { + get + { + return this.GetAttributeValue("owningbusinessunit"); + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("owningbusinessunitname")] + public string OwningBusinessUnitName + { + get + { + if (this.FormattedValues.Contains("owningbusinessunit")) + { + return this.FormattedValues["owningbusinessunit"]; + } + else + { + return default(string); + } + } + } + + /// + /// Unique identifier for the team that owns the record. + /// + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("owningteam")] + public Microsoft.Xrm.Sdk.EntityReference OwningTeam + { + get + { + return this.GetAttributeValue("owningteam"); + } + } + + /// + /// Unique identifier for the user that owns the record. + /// + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("owninguser")] + public Microsoft.Xrm.Sdk.EntityReference OwningUser + { + get + { + return this.GetAttributeValue("owninguser"); + } + } + + /// + /// Status of the Test Table + /// + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("statecode")] + public virtual dv_test_statecode? statecode + { + get + { + return ((dv_test_statecode?)(EntityOptionSetEnum.GetEnum(this, "statecode"))); + } + set + { + this.SetAttributeValue("statecode", value.HasValue ? new Microsoft.Xrm.Sdk.OptionSetValue((int)value) : null); + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("statecodename")] + public string statecodeName + { + get + { + if (this.FormattedValues.Contains("statecode")) + { + return this.FormattedValues["statecode"]; + } + else + { + return default(string); + } + } + } + + /// + /// Reason for the status of the Test Table + /// + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("statuscode")] + public virtual dv_test_statuscode? statuscode + { + get + { + return ((dv_test_statuscode?)(EntityOptionSetEnum.GetEnum(this, "statuscode"))); + } + set + { + this.SetAttributeValue("statuscode", value.HasValue ? new Microsoft.Xrm.Sdk.OptionSetValue((int)value) : null); + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("statuscodename")] + public string statuscodeName + { + get + { + if (this.FormattedValues.Contains("statuscode")) + { + return this.FormattedValues["statuscode"]; + } + else + { + return default(string); + } + } + } + + /// + /// For internal use only. + /// + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("timezoneruleversionnumber")] + public System.Nullable TimeZoneRuleVersionNumber + { + get + { + return this.GetAttributeValue>("timezoneruleversionnumber"); + } + set + { + this.SetAttributeValue("timezoneruleversionnumber", value); + } + } + + /// + /// Unique identifier of the currency associated with the entity. + /// + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("transactioncurrencyid")] + public Microsoft.Xrm.Sdk.EntityReference TransactionCurrencyId + { + get + { + return this.GetAttributeValue("transactioncurrencyid"); + } + set + { + this.SetAttributeValue("transactioncurrencyid", value); + } + } + + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("transactioncurrencyidname")] + public string TransactionCurrencyIdName + { + get + { + if (this.FormattedValues.Contains("transactioncurrencyid")) + { + return this.FormattedValues["transactioncurrencyid"]; + } + else + { + return default(string); + } + } + } + + /// + /// Time zone code that was in use when the record was created. + /// + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("utcconversiontimezonecode")] + public System.Nullable UTCConversionTimeZoneCode + { + get + { + return this.GetAttributeValue>("utcconversiontimezonecode"); + } + set + { + this.SetAttributeValue("utcconversiontimezonecode", value); + } + } + + /// + /// Version Number + /// + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("versionnumber")] + public System.Nullable VersionNumber + { + get + { + return this.GetAttributeValue>("versionnumber"); + } + } + + /// + /// N:1 dv_account_dv_test_393 + /// + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_customerid")] + [Microsoft.Xrm.Sdk.RelationshipSchemaNameAttribute("dv_account_dv_test_393")] + public DataverseEntities.Account dv_account_dv_test_393 + { + get + { + return this.GetRelatedEntity("dv_account_dv_test_393", null); + } + set + { + this.SetRelatedEntity("dv_account_dv_test_393", null, value); + } + } + + /// + /// N:1 dv_contact_dv_test_393 + /// + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_customerid")] + [Microsoft.Xrm.Sdk.RelationshipSchemaNameAttribute("dv_contact_dv_test_393")] + public DataverseEntities.Contact dv_contact_dv_test_393 + { + get + { + return this.GetRelatedEntity("dv_contact_dv_test_393", null); + } + set + { + this.SetRelatedEntity("dv_contact_dv_test_393", null, value); + } + } + + /// + /// N:1 dv_test_accountid_account + /// + [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_accountid")] + [Microsoft.Xrm.Sdk.RelationshipSchemaNameAttribute("dv_test_accountid_account")] + public DataverseEntities.Account dv_test_accountid_account + { + get + { + return this.GetRelatedEntity("dv_test_accountid_account", null); + } + set + { + this.SetRelatedEntity("dv_test_accountid_account", null, value); + } + } + } +} +#pragma warning restore CS1591 diff --git a/tests/FakeXrmEasy.Core.Tests/AssertExtensions.cs b/tests/FakeXrmEasy.Core.Tests/AssertExtensions.cs new file mode 100644 index 00000000..e0dc6b5c --- /dev/null +++ b/tests/FakeXrmEasy.Core.Tests/AssertExtensions.cs @@ -0,0 +1,17 @@ +using System; +using System.ServiceModel; +using FakeXrmEasy.Abstractions; +using Microsoft.Xrm.Sdk; + +namespace FakeXrmEasy.Core.Tests +{ + public static class XAssert + { + public static FaultException ThrowsFaultCode(ErrorCodes errorCode, Func testCode) + { + var exception = Xunit.Assert.Throws>(testCode); + Xunit.Assert.Equal((int)errorCode, exception.Detail.ErrorCode); + return exception; + } + } +} \ No newline at end of file diff --git a/tests/FakeXrmEasy.Core.Tests/FakeXrmEasy.Core.Tests.csproj b/tests/FakeXrmEasy.Core.Tests/FakeXrmEasy.Core.Tests.csproj index d1c760d9..14999ff4 100644 --- a/tests/FakeXrmEasy.Core.Tests/FakeXrmEasy.Core.Tests.csproj +++ b/tests/FakeXrmEasy.Core.Tests/FakeXrmEasy.Core.Tests.csproj @@ -64,8 +64,8 @@ - - + + @@ -137,22 +137,22 @@ - + - + - + - + - + - + diff --git a/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestsTests/CreateMultipleRequestsTests.cs b/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestsTests/CreateMultipleRequestsTests.cs new file mode 100644 index 00000000..77b5bcf3 --- /dev/null +++ b/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestsTests/CreateMultipleRequestsTests.cs @@ -0,0 +1,129 @@ +using FakeXrmEasy.Abstractions; +using Microsoft.Xrm.Sdk; +using Microsoft.Xrm.Sdk.Messages; +using System.Collections.Generic; +using System.Reflection; +using DataverseEntities; +using Xunit; +using Account = Crm.Account; + +namespace FakeXrmEasy.Core.Tests.Middleware.Crud.FakeMessageExecutors.CreateMultipleRequestTests +{ + public class CreateMultipleRequestTests : FakeXrmEasyTestsBase + { + [Fact] + public void Should_throw_exception_if_targets_was_not_set() + { + var request = new CreateMultipleRequest(); + var ex = XAssert.ThrowsFaultCode(ErrorCodes.InvalidArgument, () => _service.Execute(request)); + Assert.Equal("Required field 'Targets' is missing", ex.Detail.Message); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void Should_throw_exception_if_create_multiple_is_called_with_null_entity_name(string entityLogicalName) + { + List recordsToCreate = new List(); + + var entities = new EntityCollection(recordsToCreate) + { + EntityName = entityLogicalName + }; + + var request = new CreateMultipleRequest() + { + Targets = entities + }; + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.InvalidArgument, () => _service.Execute(request)); + Assert.Equal("Required member 'EntityName' missing for field 'Targets'", ex.Detail.Message); + } + + [Theory] + [InlineData("asdasdasd")] + public void Should_throw_exception_if_create_multiple_is_called_with_invalid_entity_name_and_early_bound_types_are_used(string entityLogicalName) + { + _context.EnableProxyTypes(Assembly.GetAssembly(typeof(Account))); + + List recordsToCreate = new List(); + + var entities = new EntityCollection(recordsToCreate) + { + EntityName = entityLogicalName + }; + + var request = new CreateMultipleRequest() + { + Targets = entities + }; + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.QueryBuilderNoEntity, () => _service.Execute(request)); + Assert.StartsWith($"The entity with a name = '{entityLogicalName}' with namemapping = 'Logical' was not found in the MetadataCache.", ex.Detail.Message); + } + + [Theory] + [InlineData("asdasdasd")] + public void Should_throw_exception_if_create_multiple_is_called_with_invalid_entity_name_and_metadata_is_used(string entityLogicalName) + { + _context.InitializeMetadata(Assembly.GetAssembly(typeof(Account))); + + List recordsToCreate = new List(); + + var entities = new EntityCollection(recordsToCreate) + { + EntityName = entityLogicalName + }; + + var request = new CreateMultipleRequest() + { + Targets = entities + }; + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.QueryBuilderNoEntity, () => _service.Execute(request)); + Assert.StartsWith($"The entity with a name = '{entityLogicalName}' with namemapping = 'Logical' was not found in the MetadataCache.", ex.Detail.Message); + } + + [Fact] + public void Should_throw_exception_if_create_multiple_is_called_with_an_empty_list() + { + List recordsToCreate = new List(); + + // Create an EntityCollection populated with the list of entities. + var entities = new EntityCollection(recordsToCreate) + { + EntityName = dv_test.EntityLogicalName + }; + + var request = new CreateMultipleRequest() + { + Targets = entities + }; + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.UnExpected, () => _service.Execute(request)); + Assert.Equal("System.ArgumentException: The value of the parameter 'Targets' cannot be null or empty.", ex.Detail.Message); + } + + [Fact] + public void Should_throw_exception_if_create_multiple_is_called_with_an_existing_entity_record() + { + var id = _service.Create(new dv_test()); + + List recordsToCreate = new List() { new dv_test() { Id = id } }; + + // Create an EntityCollection populated with the list of entities. + var entities = new EntityCollection(recordsToCreate) + { + EntityName = dv_test.EntityLogicalName + }; + + var request = new CreateMultipleRequest() + { + Targets = entities + }; + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.DuplicateRecord, () => _service.Execute(request)); + Assert.Equal("Cannot insert duplicate key.", ex.Detail.Message); + } + } +} \ No newline at end of file From 541102f856c6768c49eaa2d8f1c9912162776918 Mon Sep 17 00:00:00 2001 From: Jordi Date: Mon, 11 Mar 2024 00:24:00 +0100 Subject: [PATCH 07/37] Limit bulk operations to v9 only DynamicsValue/fake-xrm-easy#122 --- .../FakeMessageExecutors/CreateMultipleRequestExecutor.cs | 6 ++++-- .../Middleware/Crud/MiddlewareBuilderExtensions.Crud.cs | 6 ++++-- .../CreateMultipleRequestsTests.cs | 6 ++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestExecutor.cs b/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestExecutor.cs index 391cfbe8..48b4927d 100644 --- a/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestExecutor.cs +++ b/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestExecutor.cs @@ -1,4 +1,5 @@ -using FakeXrmEasy.Abstractions; +#if FAKE_XRM_EASY_9 +using FakeXrmEasy.Abstractions; using FakeXrmEasy.Abstractions.FakeMessageExecutors; using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Messages; @@ -123,4 +124,5 @@ public Type GetResponsibleRequestType() return typeof(CreateMultipleRequest); } } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/FakeXrmEasy.Core/Middleware/Crud/MiddlewareBuilderExtensions.Crud.cs b/src/FakeXrmEasy.Core/Middleware/Crud/MiddlewareBuilderExtensions.Crud.cs index 3d3fbd40..e8019125 100644 --- a/src/FakeXrmEasy.Core/Middleware/Crud/MiddlewareBuilderExtensions.Crud.cs +++ b/src/FakeXrmEasy.Core/Middleware/Crud/MiddlewareBuilderExtensions.Crud.cs @@ -46,12 +46,14 @@ public static IMiddlewareBuilder AddCrud(this IMiddlewareBuilder builder) crudMessageExecutors.Add(typeof(AssociateRequest), new AssociateRequestExecutor()); crudMessageExecutors.Add(typeof(DisassociateRequest), new DisassociateRequestExecutor()); - crudMessageExecutors.Add(typeof(CreateMultipleRequest), new CreateMultipleRequestExecutor()); - #if !FAKE_XRM_EASY && !FAKE_XRM_EASY_2013 && !FAKE_XRM_EASY_2015 crudMessageExecutors.Add(typeof(UpsertRequest), new UpsertRequestExecutor()); #endif + #if FAKE_XRM_EASY_9 + crudMessageExecutors.Add(typeof(CreateMultipleRequest), new CreateMultipleRequestExecutor()); + #endif + context.SetProperty(crudMessageExecutors); AddFakeCreate(context, service); AddFakeRetrieve(context, service); diff --git a/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestsTests/CreateMultipleRequestsTests.cs b/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestsTests/CreateMultipleRequestsTests.cs index 77b5bcf3..c487f892 100644 --- a/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestsTests/CreateMultipleRequestsTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestsTests/CreateMultipleRequestsTests.cs @@ -1,4 +1,5 @@ -using FakeXrmEasy.Abstractions; +#if FAKE_XRM_EASY_9 +using FakeXrmEasy.Abstractions; using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Messages; using System.Collections.Generic; @@ -126,4 +127,5 @@ public void Should_throw_exception_if_create_multiple_is_called_with_an_existing Assert.Equal("Cannot insert duplicate key.", ex.Detail.Message); } } -} \ No newline at end of file +} +#endif \ No newline at end of file From a81b180511ed1ef878844164dac86480c4d42c58 Mon Sep 17 00:00:00 2001 From: Jordi Date: Wed, 13 Mar 2024 00:01:10 +0100 Subject: [PATCH 08/37] Add remaining tests for create multiple DynamicsValue/fake-xrm-easy#122 --- src/FakeXrmEasy.Core/Db/InMemoryDb.cs | 2 +- .../CreateMultipleRequestExecutor.cs | 21 ++++- src/FakeXrmEasy.Core/XrmFakedContext.Crud.cs | 2 +- .../ValidateReferencesTests.cs | 4 +- .../CreateMultipleRequestsTests.cs | 82 +++++++++++++++++++ 5 files changed, 104 insertions(+), 7 deletions(-) diff --git a/src/FakeXrmEasy.Core/Db/InMemoryDb.cs b/src/FakeXrmEasy.Core/Db/InMemoryDb.cs index c0442292..bacc45b4 100644 --- a/src/FakeXrmEasy.Core/Db/InMemoryDb.cs +++ b/src/FakeXrmEasy.Core/Db/InMemoryDb.cs @@ -13,7 +13,7 @@ namespace FakeXrmEasy.Core.Db internal class InMemoryDb { /// - /// A collection of tables indexed by its logical name + /// A collection of tables indexed by their logical name /// protected internal Dictionary _tables; diff --git a/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestExecutor.cs b/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestExecutor.cs index 48b4927d..8a76b608 100644 --- a/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestExecutor.cs +++ b/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestExecutor.cs @@ -4,6 +4,7 @@ using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Messages; using System; +using System.Collections.Generic; using System.Linq; namespace FakeXrmEasy.Middleware.Crud.FakeMessageExecutors @@ -35,11 +36,19 @@ public OrganizationResponse Execute(OrganizationRequest request, IXrmFakedContex ValidateRequest(createMultipleRequest, ctx); - var response = new CreateMultipleResponse(); + var records = createMultipleRequest.Targets.Entities; + List createdIds = new List(); - return new CreateResponse() + foreach (var record in records) + { + var id = ctx.CreateEntity(record); + createdIds.Add(id); + } + + return new CreateMultipleResponse() { - ResponseName = "CreateMultipleResponse" + ResponseName = "CreateMultipleResponse", + ["Ids"] = createdIds.ToArray() }; } @@ -100,6 +109,12 @@ private void ValidateRecords(CreateMultipleRequest request, IXrmFakedContext ctx private void ValidateRecord(CreateMultipleRequest request, Entity recordToCreate, IXrmFakedContext ctx) { + if (!request.Targets.EntityName.Equals(recordToCreate.LogicalName)) + { + throw FakeOrganizationServiceFaultFactory.New(ErrorCodes.InvalidArgument, + $"This entity cannot be added to the specified collection. The collection can have entities with PlatformName = {request.Targets.EntityName} while this entity has Platform Name: {recordToCreate.LogicalName}"); + } + var exists = ctx.ContainsEntity(recordToCreate.LogicalName, recordToCreate.Id); if (exists) { diff --git a/src/FakeXrmEasy.Core/XrmFakedContext.Crud.cs b/src/FakeXrmEasy.Core/XrmFakedContext.Crud.cs index 1288ccbc..bb72b4aa 100644 --- a/src/FakeXrmEasy.Core/XrmFakedContext.Crud.cs +++ b/src/FakeXrmEasy.Core/XrmFakedContext.Crud.cs @@ -242,7 +242,7 @@ protected EntityReference ResolveEntityReference(EntityReference er) } else { - throw FakeOrganizationServiceFaultFactory.New($"{er.LogicalName} With Id = {er.Id:D} Does Not Exist"); + throw FakeOrganizationServiceFaultFactory.New(ErrorCodes.ObjectDoesNotExist, $"{er.LogicalName} With Ids = {er.Id:D} Do Not Exist"); } } return er; diff --git a/tests/FakeXrmEasy.Core.Tests/FakeContextTests/ValidateReferencesTests.cs b/tests/FakeXrmEasy.Core.Tests/FakeContextTests/ValidateReferencesTests.cs index f46d45d9..0ae37afc 100644 --- a/tests/FakeXrmEasy.Core.Tests/FakeContextTests/ValidateReferencesTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/FakeContextTests/ValidateReferencesTests.cs @@ -59,7 +59,7 @@ public void An_entity_which_references_another_non_existent_entity_can_not_be_cr var ex = Assert.Throws>(() => _serviceWithIntegrity.Create(entity)); - Assert.Equal($"{entity.LogicalName} With Id = {otherEntity:D} Does Not Exist", ex.Message); + Assert.Equal($"{entity.LogicalName} With Ids = {otherEntity:D} Do Not Exist", ex.Message); } [Fact] @@ -111,7 +111,7 @@ public void An_entity_which_references_another_non_existent_entity_can_not_be_up entity["otherEntity"] = new EntityReference("entity", otherEntityId); var ex = Assert.Throws>(() => _serviceWithIntegrity.Update(entity)); - Assert.Equal($"{entity.LogicalName} With Id = {otherEntityId:D} Does Not Exist", ex.Message); + Assert.Equal($"{entity.LogicalName} With Ids = {otherEntityId:D} Do Not Exist", ex.Message); } [Fact] diff --git a/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestsTests/CreateMultipleRequestsTests.cs b/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestsTests/CreateMultipleRequestsTests.cs index c487f892..45885977 100644 --- a/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestsTests/CreateMultipleRequestsTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestsTests/CreateMultipleRequestsTests.cs @@ -1,10 +1,14 @@ #if FAKE_XRM_EASY_9 +using System; using FakeXrmEasy.Abstractions; using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Messages; using System.Collections.Generic; using System.Reflection; using DataverseEntities; +using FakeXrmEasy.Abstractions.Integrity; +using FakeXrmEasy.Integrity; +using Microsoft.Xrm.Sdk.Query; using Xunit; using Account = Crm.Account; @@ -126,6 +130,84 @@ public void Should_throw_exception_if_create_multiple_is_called_with_an_existing var ex = XAssert.ThrowsFaultCode(ErrorCodes.DuplicateRecord, () => _service.Execute(request)); Assert.Equal("Cannot insert duplicate key.", ex.Detail.Message); } + + [Fact] + public void Should_throw_exception_if_create_multiple_is_called_with_an_entity_record_with_a_logical_name_different_than_the_main_logical_name() + { + List recordsToCreate = new List() { new dv_test() }; + + var entities = new EntityCollection(recordsToCreate) + { + EntityName = Account.EntityLogicalName + }; + + var request = new CreateMultipleRequest() + { + Targets = entities + }; + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.InvalidArgument, () => _service.Execute(request)); + Assert.Equal($"This entity cannot be added to the specified collection. The collection can have entities with PlatformName = {Account.EntityLogicalName} while this entity has Platform Name: {dv_test.EntityLogicalName}", ex.Detail.Message); + } + + [Fact] + public void Should_throw_exception_if_create_multiple_is_called_with_non_existing_related_entity_and_integrity_options_is_enabled() + { + _context.SetProperty(new IntegrityOptions()); + + var nonExistingGuid = Guid.NewGuid(); + + List recordsToCreate = new List() { + new dv_test() + { + dv_accountid = new EntityReference(Account.EntityLogicalName, nonExistingGuid) + } + }; + + var entities = new EntityCollection(recordsToCreate) + { + EntityName = dv_test.EntityLogicalName + }; + + var request = new CreateMultipleRequest() + { + Targets = entities + }; + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.ObjectDoesNotExist, () => _service.Execute(request)); + Assert.Equal($"account With Ids = {nonExistingGuid.ToString()} Do Not Exist", ex.Detail.Message); + } + + [Fact] + public void Should_create_two_records_with_create_multiple() + { + var record1 = new dv_test() { Id = Guid.NewGuid() }; + var record2 = new dv_test() { Id = Guid.NewGuid() }; + + List recordsToCreate = new List() { record1, record2 }; + + var entities = new EntityCollection(recordsToCreate) + { + EntityName = dv_test.EntityLogicalName + }; + + var request = new CreateMultipleRequest() + { + Targets = entities + }; + + var response = _service.Execute(request) as CreateMultipleResponse; + + Assert.Equal(2, response.Ids.Length); + Assert.Equal(record1.Id, response.Ids[0]); + Assert.Equal(record2.Id, response.Ids[1]); + + var createdRecord1 = _service.Retrieve(dv_test.EntityLogicalName, record1.Id, new ColumnSet(true)); + var createdRecord2 = _service.Retrieve(dv_test.EntityLogicalName, record2.Id, new ColumnSet(true)); + + Assert.NotNull(createdRecord1); + Assert.NotNull(createdRecord2); + } } } #endif \ No newline at end of file From fc54ae540393140cb9ad257e11636601e4d26c09 Mon Sep 17 00:00:00 2001 From: Jordi Date: Wed, 13 Mar 2024 00:08:55 +0100 Subject: [PATCH 09/37] Add conditional compilation for latest early bound generated types from modelbuilder as MultiOption set don't exist in previous versions --- tests/DataverseEntities/Entities/dv_test.cs | 2 + .../DataverseEntities/EntityOptionSetEnum.cs | 93 ++++++++++--------- 2 files changed, 51 insertions(+), 44 deletions(-) diff --git a/tests/DataverseEntities/Entities/dv_test.cs b/tests/DataverseEntities/Entities/dv_test.cs index fd99ecd8..81908dc3 100644 --- a/tests/DataverseEntities/Entities/dv_test.cs +++ b/tests/DataverseEntities/Entities/dv_test.cs @@ -347,6 +347,7 @@ public string dv_boolName } } + #if FAKE_XRM_EASY_9 [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dv_choice_multiple")] public virtual System.Collections.Generic.IEnumerable dv_choice_multiple { @@ -359,6 +360,7 @@ public virtual System.Collections.Generic.IEnumerable // This code was generated by a tool. -// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -13,49 +12,55 @@ namespace DataverseEntities { - internal sealed class EntityOptionSetEnum - { + internal sealed class EntityOptionSetEnum + { - public static System.Nullable GetEnum(Microsoft.Xrm.Sdk.Entity entity, string attributeLogicalName) - { - if (entity.Attributes.ContainsKey(attributeLogicalName)) - { - Microsoft.Xrm.Sdk.OptionSetValue value = entity.GetAttributeValue(attributeLogicalName); - if (value != null) - { - return value.Value; - } - } - return null; - } - -#if FAKE_XRM_EASY_9 - public static System.Collections.Generic.IEnumerable GetMultiEnum(Microsoft.Xrm.Sdk.Entity entity, string attributeLogicalName) - - { - Microsoft.Xrm.Sdk.OptionSetValueCollection value = entity.GetAttributeValue(attributeLogicalName); - System.Collections.Generic.List list = new System.Collections.Generic.List(); - if (value == null) - { - return list; - } - list.AddRange(System.Linq.Enumerable.Select(value, v => (T)(object)v.Value)); - return list; - } - - public static Microsoft.Xrm.Sdk.OptionSetValueCollection GetMultiEnum(Microsoft.Xrm.Sdk.Entity entity, string attributeLogicalName, System.Collections.Generic.IEnumerable values) + /// + /// Returns the integer version of an OptionSetValue + /// + public static System.Nullable GetEnum(Microsoft.Xrm.Sdk.Entity entity, string attributeLogicalName) + { + if (entity.Attributes.ContainsKey(attributeLogicalName)) + { + Microsoft.Xrm.Sdk.OptionSetValue value = entity.GetAttributeValue(attributeLogicalName); + if (value != null) + { + return value.Value; + } + } + return null; + } + + #if FAKE_XRM_EASY_9 + /// + /// Returns a collection of integer version's of an Multi-Select OptionSetValue for a given attribute on the passed entity + /// + public static System.Collections.Generic.IEnumerable GetMultiEnum(Microsoft.Xrm.Sdk.Entity entity, string attributeLogicalName) + { + Microsoft.Xrm.Sdk.OptionSetValueCollection value = entity.GetAttributeValue(attributeLogicalName); + System.Collections.Generic.List list = new System.Collections.Generic.List(); + if (value == null) + { + return list; + } + list.AddRange(System.Linq.Enumerable.Select(value, v => (T)(object)v.Value)); + return list; + } - { - if (values == null) - { - return null; - } - Microsoft.Xrm.Sdk.OptionSetValueCollection collection = new Microsoft.Xrm.Sdk.OptionSetValueCollection(); - collection.AddRange(System.Linq.Enumerable.Select(values, v => new Microsoft.Xrm.Sdk.OptionSetValue((int)(object)v))); - return collection; - } -#endif - - } + /// + /// Returns a OptionSetValueCollection based on a list of Multi-Select OptionSetValues + /// + public static Microsoft.Xrm.Sdk.OptionSetValueCollection GetMultiEnum(Microsoft.Xrm.Sdk.Entity entity, string attributeLogicalName, System.Collections.Generic.IEnumerable values) + { + if (values == null) + { + return null; + } + Microsoft.Xrm.Sdk.OptionSetValueCollection collection = new Microsoft.Xrm.Sdk.OptionSetValueCollection(); + collection.AddRange(System.Linq.Enumerable.Select(values, v => new Microsoft.Xrm.Sdk.OptionSetValue((int)(object)v))); + return collection; + } + #endif + } } -#pragma warning restore CS1591 \ No newline at end of file +#pragma warning restore CS1591 From b9e93094c42b448cbf9a53b79e10422c554a3c83 Mon Sep 17 00:00:00 2001 From: Jordi Date: Thu, 14 Mar 2024 13:58:50 +0100 Subject: [PATCH 10/37] More CreateMultiple tests, refactor alternate key unit tests --- src/FakeXrmEasy.Core/XrmFakedContext.Crud.cs | 2 +- .../ValidateAlternateKeyReferencesTests.cs | 118 ++++++++++++++++++ .../ValidateReferencesTests.cs | 99 +-------------- .../CreateMultipleRequestsTests.cs | 30 +++++ 4 files changed, 152 insertions(+), 97 deletions(-) create mode 100644 tests/FakeXrmEasy.Core.Tests/FakeContextTests/ValidateAlternateKeyReferencesTests.cs diff --git a/src/FakeXrmEasy.Core/XrmFakedContext.Crud.cs b/src/FakeXrmEasy.Core/XrmFakedContext.Crud.cs index bb72b4aa..f2185e09 100644 --- a/src/FakeXrmEasy.Core/XrmFakedContext.Crud.cs +++ b/src/FakeXrmEasy.Core/XrmFakedContext.Crud.cs @@ -87,7 +87,7 @@ public Guid GetRecordUniqueId(EntityReference record, bool validate = true) } if (validate) { - throw new InvalidOperationException($"The requested key attributes do not exist for the entity {record.LogicalName}"); + throw FakeOrganizationServiceFaultFactory.New(ErrorCodes.InvalidEntityKeyOperation, $"Invalid EntityKey Operation performed : Entity {record.LogicalName} does not contain any key attributes"); } } #endif diff --git a/tests/FakeXrmEasy.Core.Tests/FakeContextTests/ValidateAlternateKeyReferencesTests.cs b/tests/FakeXrmEasy.Core.Tests/FakeContextTests/ValidateAlternateKeyReferencesTests.cs new file mode 100644 index 00000000..0101c1fd --- /dev/null +++ b/tests/FakeXrmEasy.Core.Tests/FakeContextTests/ValidateAlternateKeyReferencesTests.cs @@ -0,0 +1,118 @@ +#if !FAKE_XRM_EASY && !FAKE_XRM_EASY_2013 && !FAKE_XRM_EASY_2015 + +using FakeXrmEasy.Abstractions; +using FakeXrmEasy.Abstractions.Integrity; +using FakeXrmEasy.Integrity; +using FakeXrmEasy.Middleware; +using Microsoft.Xrm.Sdk; +using Microsoft.Xrm.Sdk.Query; +using System; +using System.ServiceModel; +using Xunit; +using FakeXrmEasy.Abstractions.Enums; +using Crm; +using FakeXrmEasy.Extensions; +using System.Collections.Generic; +using Microsoft.Xrm.Sdk.Metadata; + + +namespace FakeXrmEasy.Core.Tests.FakeContextTests +{ + public class ValidateAlternateKeyReferencesTests: FakeXrmEasyTestsBase + { + private readonly IXrmFakedContext _contextWithIntegrity; + private readonly IOrganizationService _serviceWithIntegrity; + + private readonly EntityMetadata _accountMetadata; + private readonly Entity _account; + private readonly Entity _account2; + + public ValidateAlternateKeyReferencesTests(): base() + { + _contextWithIntegrity = XrmFakedContextFactory.New(FakeXrmEasyLicense.RPL_1_5, new IntegrityOptions()); + _serviceWithIntegrity = _contextWithIntegrity.GetOrganizationService(); + + _accountMetadata = new EntityMetadata() + { + LogicalName = Account.EntityLogicalName + }; + var alternateKeyMetadata = new EntityKeyMetadata() + { + KeyAttributes = new string[] { "alternateKey" } + }; + _accountMetadata.SetFieldValue("_keys", new EntityKeyMetadata[] + { + alternateKeyMetadata + }); + + _account = new Entity(Account.EntityLogicalName) + { + Id = Guid.NewGuid(), + ["alternateKey"] = "keyValue" + }; + + _account2 = new Entity(Account.EntityLogicalName) + { + Id = Guid.NewGuid(), + ["alternateKey"] = "keyValue2" + }; + } + [Fact] + public void An_entity_which_references_another_existent_entity_by_alternate_key_can_be_created_when_integrity_is_enabled() + { + _contextWithIntegrity.InitializeMetadata(_accountMetadata); + _contextWithIntegrity.Initialize(new List() { _account }); + + Entity otherEntity = new Entity("otherEntity"); + otherEntity.Id = Guid.NewGuid(); + otherEntity["new_accountId"] = new EntityReference("account", "alternateKey","keyValue") ; + Guid created = _serviceWithIntegrity.Create(otherEntity); + + Entity otherEntityInContext = _serviceWithIntegrity.Retrieve("otherEntity", otherEntity.Id, new ColumnSet(true)); + + Assert.NotEqual(Guid.Empty, created); + Assert.Equal(((EntityReference)otherEntityInContext["new_accountId"]).Id, _account.Id); + } + + [Fact] + public void An_entity_which_references_another_existent_entity_by_alternate_key_can_be_initialised_when_integrity_is_enabled() + { + _contextWithIntegrity.InitializeMetadata(_accountMetadata); + + Entity otherEntity = new Entity("otherEntity"); + otherEntity.Id = Guid.NewGuid(); + otherEntity["new_accountId"] = new EntityReference("account", "alternateKey", "keyValue"); + + _contextWithIntegrity.Initialize(new List() { _account, otherEntity }); + + Entity otherEntityInContext = _serviceWithIntegrity.Retrieve("otherEntity", otherEntity.Id, new ColumnSet(true)); + + Assert.Equal(((EntityReference)otherEntityInContext["new_accountId"]).Id, _account.Id); + } + + [Fact] + public void An_entity_which_references_another_existent_entity_by_alternate_key_can_be_updated_when_integrity_is_enabled() + { + _contextWithIntegrity.InitializeMetadata(_accountMetadata); + + Entity otherEntity = new Entity("otherEntity"); + otherEntity.Id = Guid.NewGuid(); + otherEntity["new_accountId"] = new EntityReference("account", "alternateKey", "keyValue"); + + _contextWithIntegrity.Initialize(new List() { _account, _account2, otherEntity }); + + var entityToUpdate = new Entity("otherEntity") + { + Id = otherEntity.Id, + ["new_accountId"] = new EntityReference("account", "alternateKey", "keyValue2") + }; + _serviceWithIntegrity.Update(entityToUpdate); + + Entity otherEntityInContext = _serviceWithIntegrity.Retrieve("otherEntity", otherEntity.Id, new ColumnSet(true)); + + Assert.Equal(((EntityReference)otherEntityInContext["new_accountId"]).Id, _account2.Id); + } + } +} + +#endif diff --git a/tests/FakeXrmEasy.Core.Tests/FakeContextTests/ValidateReferencesTests.cs b/tests/FakeXrmEasy.Core.Tests/FakeContextTests/ValidateReferencesTests.cs index 0ae37afc..cc75e562 100644 --- a/tests/FakeXrmEasy.Core.Tests/FakeContextTests/ValidateReferencesTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/FakeContextTests/ValidateReferencesTests.cs @@ -12,6 +12,9 @@ using FakeXrmEasy.Extensions; using System.Collections.Generic; +#if !FAKE_XRM_EASY && !FAKE_XRM_EASY_2013 && !FAKE_XRM_EASY_2015 +using Microsoft.Xrm.Sdk.Metadata; +#endif namespace FakeXrmEasy.Core.Tests.FakeContextTests { @@ -143,101 +146,5 @@ public void Should_raise_exception_when_validating_a_null_entity() Assert.Throws(() => (_contextWithIntegrity as XrmFakedContext).ValidateEntity(null)); } - - #if !FAKE_XRM_EASY && !FAKE_XRM_EASY_2013 && !FAKE_XRM_EASY_2015 - [Fact] - public void An_entity_which_references_another_existent_entity_by_alternate_key_can_be_created_when_integrity_is_enabled() - { - var accountMetadata = new Microsoft.Xrm.Sdk.Metadata.EntityMetadata(); - accountMetadata.LogicalName = Account.EntityLogicalName; - var alternateKeyMetadata = new Microsoft.Xrm.Sdk.Metadata.EntityKeyMetadata(); - alternateKeyMetadata.KeyAttributes = new string[] { "alternateKey" }; - accountMetadata.SetFieldValue("_keys", new Microsoft.Xrm.Sdk.Metadata.EntityKeyMetadata[] - { - alternateKeyMetadata - }); - _contextWithIntegrity.InitializeMetadata(accountMetadata); - var account = new Entity(Account.EntityLogicalName); - account.Id = Guid.NewGuid(); - account.Attributes.Add("alternateKey", "keyValue"); - _contextWithIntegrity.Initialize(new List() { account }); - - Entity otherEntity = new Entity("otherEntity"); - otherEntity.Id = Guid.NewGuid(); - otherEntity["new_accountId"] = new EntityReference("account", "alternateKey","keyValue") ; - Guid created = _serviceWithIntegrity.Create(otherEntity); - - Entity otherEntityInContext = _serviceWithIntegrity.Retrieve("otherEntity", otherEntity.Id, new ColumnSet(true)); - - Assert.NotEqual(Guid.Empty, created); - Assert.Equal(((EntityReference)otherEntityInContext["new_accountId"]).Id, account.Id); - } - - [Fact] - public void An_entity_which_references_another_existent_entity_by_alternate_key_can_be_initialised_when_integrity_is_enabled() - { - var accountMetadata = new Microsoft.Xrm.Sdk.Metadata.EntityMetadata(); - accountMetadata.LogicalName = Account.EntityLogicalName; - var alternateKeyMetadata = new Microsoft.Xrm.Sdk.Metadata.EntityKeyMetadata(); - alternateKeyMetadata.KeyAttributes = new string[] { "alternateKey" }; - accountMetadata.SetFieldValue("_keys", new Microsoft.Xrm.Sdk.Metadata.EntityKeyMetadata[] - { - alternateKeyMetadata - }); - _contextWithIntegrity.InitializeMetadata(accountMetadata); - var account = new Entity(Account.EntityLogicalName); - account.Id = Guid.NewGuid(); - account.Attributes.Add("alternateKey", "keyValue"); - - Entity otherEntity = new Entity("otherEntity"); - otherEntity.Id = Guid.NewGuid(); - otherEntity["new_accountId"] = new EntityReference("account", "alternateKey", "keyValue"); - - _contextWithIntegrity.Initialize(new List() { account, otherEntity }); - - Entity otherEntityInContext = _serviceWithIntegrity.Retrieve("otherEntity", otherEntity.Id, new ColumnSet(true)); - - Assert.Equal(((EntityReference)otherEntityInContext["new_accountId"]).Id, account.Id); - } - - [Fact] - public void An_entity_which_references_another_existent_entity_by_alternate_key_can_be_updated_when_integrity_is_enabled() - { - var accountMetadata = new Microsoft.Xrm.Sdk.Metadata.EntityMetadata(); - accountMetadata.LogicalName = Account.EntityLogicalName; - var alternateKeyMetadata = new Microsoft.Xrm.Sdk.Metadata.EntityKeyMetadata(); - alternateKeyMetadata.KeyAttributes = new string[] { "alternateKey" }; - accountMetadata.SetFieldValue("_keys", new Microsoft.Xrm.Sdk.Metadata.EntityKeyMetadata[] - { - alternateKeyMetadata - }); - - _contextWithIntegrity.InitializeMetadata(accountMetadata); - var account = new Entity(Account.EntityLogicalName); - account.Id = Guid.NewGuid(); - account.Attributes.Add("alternateKey", "keyValue"); - - var account2 = new Entity(Account.EntityLogicalName); - account2.Id = Guid.NewGuid(); - account2.Attributes.Add("alternateKey", "keyValue2"); - - Entity otherEntity = new Entity("otherEntity"); - otherEntity.Id = Guid.NewGuid(); - otherEntity["new_accountId"] = new EntityReference("account", "alternateKey", "keyValue"); - - _contextWithIntegrity.Initialize(new List() { account, account2, otherEntity }); - - var entityToUpdate = new Entity("otherEntity") - { - Id = otherEntity.Id, - ["new_accountId"] = new EntityReference("account", "alternateKey", "keyValue2") - }; - _serviceWithIntegrity.Update(entityToUpdate); - - Entity otherEntityInContext = _serviceWithIntegrity.Retrieve("otherEntity", otherEntity.Id, new ColumnSet(true)); - - Assert.Equal(((EntityReference)otherEntityInContext["new_accountId"]).Id, account2.Id); - } -#endif } } diff --git a/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestsTests/CreateMultipleRequestsTests.cs b/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestsTests/CreateMultipleRequestsTests.cs index 45885977..e5dc6e50 100644 --- a/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestsTests/CreateMultipleRequestsTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestsTests/CreateMultipleRequestsTests.cs @@ -178,6 +178,36 @@ public void Should_throw_exception_if_create_multiple_is_called_with_non_existin Assert.Equal($"account With Ids = {nonExistingGuid.ToString()} Do Not Exist", ex.Detail.Message); } + [Fact] + public void Should_throw_exception_if_create_multiple_is_called_with_a_non_existing_alternate_key() + { + _context.SetProperty(new IntegrityOptions()); + + var dummy_attribute_name = "dv_dummy_attribute"; + + var nonExistingGuid = Guid.NewGuid(); + + List recordsToCreate = new List() { + new dv_test() + { + dv_accountid = new EntityReference(Account.EntityLogicalName, dummy_attribute_name, "Microsoft") + } + }; + + var entities = new EntityCollection(recordsToCreate) + { + EntityName = dv_test.EntityLogicalName + }; + + var request = new CreateMultipleRequest() + { + Targets = entities + }; + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.InvalidEntityKeyOperation, () => _service.Execute(request)); + Assert.Equal($"Invalid EntityKey Operation performed : Entity {Account.EntityLogicalName} does not contain an attribute named {dummy_attribute_name}", ex.Detail.Message); + } + [Fact] public void Should_create_two_records_with_create_multiple() { From 8e056d62e2d62ef6ca55d067f3080021174a22db Mon Sep 17 00:00:00 2001 From: Jordi Date: Thu, 14 Mar 2024 15:25:02 +0100 Subject: [PATCH 11/37] Update previous alternate key tests to return new specific fault exception --- src/FakeXrmEasy.Core/XrmFakedContext.Crud.cs | 4 +-- .../ValidateAlternateKeyReferencesTests.cs | 25 ++++++++++++++++++- .../RetrieveRequestTests.cs | 4 +-- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/FakeXrmEasy.Core/XrmFakedContext.Crud.cs b/src/FakeXrmEasy.Core/XrmFakedContext.Crud.cs index f2185e09..5101bd02 100644 --- a/src/FakeXrmEasy.Core/XrmFakedContext.Crud.cs +++ b/src/FakeXrmEasy.Core/XrmFakedContext.Crud.cs @@ -80,14 +80,14 @@ public Guid GetRecordUniqueId(EntityReference record, bool validate = true) } if (validate) { - throw new FaultException(new OrganizationServiceFault() { Message = $"{record.LogicalName} with the specified Alternate Keys Does Not Exist"}); + throw FakeOrganizationServiceFaultFactory.New(ErrorCodes.InvalidEntityKeyOperation, $"Invalid EntityKey Operation performed : Entity {record.LogicalName} does not contain an attribute named {record.KeyAttributes.First().Key}"); } } } } if (validate) { - throw FakeOrganizationServiceFaultFactory.New(ErrorCodes.InvalidEntityKeyOperation, $"Invalid EntityKey Operation performed : Entity {record.LogicalName} does not contain any key attributes"); + throw FakeOrganizationServiceFaultFactory.New(ErrorCodes.InvalidEntityKeyOperation, $"Invalid EntityKey Operation performed : Entity {record.LogicalName} does not contain an attribute named {record.KeyAttributes.First().Key}"); } } #endif diff --git a/tests/FakeXrmEasy.Core.Tests/FakeContextTests/ValidateAlternateKeyReferencesTests.cs b/tests/FakeXrmEasy.Core.Tests/FakeContextTests/ValidateAlternateKeyReferencesTests.cs index 0101c1fd..183dc7a9 100644 --- a/tests/FakeXrmEasy.Core.Tests/FakeContextTests/ValidateAlternateKeyReferencesTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/FakeContextTests/ValidateAlternateKeyReferencesTests.cs @@ -10,10 +10,12 @@ using System.ServiceModel; using Xunit; using FakeXrmEasy.Abstractions.Enums; -using Crm; using FakeXrmEasy.Extensions; using System.Collections.Generic; +using DataverseEntities; +using Microsoft.Xrm.Sdk.Messages; using Microsoft.Xrm.Sdk.Metadata; +using Account = Crm.Account; namespace FakeXrmEasy.Core.Tests.FakeContextTests @@ -73,6 +75,27 @@ public void An_entity_which_references_another_existent_entity_by_alternate_key_ Assert.NotEqual(Guid.Empty, created); Assert.Equal(((EntityReference)otherEntityInContext["new_accountId"]).Id, _account.Id); } + + [Fact] + public void Should_throw_exception_if_create_is_called_with_a_non_existing_alternate_key() + { + var dummy_attribute_name = "dv_dummy_attribute"; + + var nonExistingGuid = Guid.NewGuid(); + + var entity = new dv_test() + { + dv_accountid = new EntityReference(Account.EntityLogicalName, dummy_attribute_name, "Microsoft") + }; + + var request = new CreateRequest() + { + Target = entity + }; + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.InvalidEntityKeyOperation, () => _serviceWithIntegrity.Execute(request)); + Assert.Equal($"Invalid EntityKey Operation performed : Entity {Account.EntityLogicalName} does not contain an attribute named {dummy_attribute_name}", ex.Detail.Message); + } [Fact] public void An_entity_which_references_another_existent_entity_by_alternate_key_can_be_initialised_when_integrity_is_enabled() diff --git a/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/RetrieveRequestTests/RetrieveRequestTests.cs b/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/RetrieveRequestTests/RetrieveRequestTests.cs index 844ff6cf..4f807870 100644 --- a/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/RetrieveRequestTests/RetrieveRequestTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/RetrieveRequestTests/RetrieveRequestTests.cs @@ -1170,8 +1170,8 @@ public void Should_Throw_When_Alternate_Key_Not_In_Metadata() ColumnSet = new ColumnSet(allColumns: true) }; - var exception = Assert.Throws(() => _service.Execute(request)); - Assert.Equal($"The requested key attributes do not exist for the entity {Account.EntityLogicalName}", exception.Message); + var exception = XAssert.ThrowsFaultCode(ErrorCodes.InvalidEntityKeyOperation,() => _service.Execute(request)); + Assert.Equal($"Invalid EntityKey Operation performed : Entity {Account.EntityLogicalName} does not contain an attribute named alternateKey", exception.Message); } #endif } From 564f6a9c7445be1f64bb644904bfc9b9c467b81d Mon Sep 17 00:00:00 2001 From: Jordi Date: Fri, 15 Mar 2024 19:57:06 +0100 Subject: [PATCH 12/37] Add support for UpdateMultipleRequest DynamicsValue/fake-xrm-easy#122. Add file attribute metadata support for MetadataGenerator --- CHANGELOG.md | 5 +- .../Metadata/MetadataGenerator.cs | 7 +- .../UpdateMultipleRequestExecutor.cs | 146 ++++++++ .../Crud/MiddlewareBuilderExtensions.Crud.cs | 1 + .../CreateAttributeMetadataTests.cs | 25 ++ .../MetadataGeneratorTests.cs | 0 .../UpdateMultipleRequestTests.cs | 353 ++++++++++++++++++ 7 files changed, 535 insertions(+), 2 deletions(-) create mode 100644 src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/UpdateMultipleRequestExecutor.cs create mode 100644 tests/FakeXrmEasy.Core.Tests/Metadata/MetadataGeneratorTests/CreateAttributeMetadataTests.cs rename tests/FakeXrmEasy.Core.Tests/Metadata/{ => MetadataGeneratorTests}/MetadataGeneratorTests.cs (100%) create mode 100644 tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/UpdateMultipleRequestTests/UpdateMultipleRequestTests.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index a536f97a..6db158ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ ## [2.5.0] -- Added support for bulk operations: CreateMultipleRequest +### Added + +- Added FileAttributeMetadata support to MetadataGenerator +- Added support for bulk operations: CreateMultipleRequest, UpdateMultipleRequest ## [2.4.2] diff --git a/src/FakeXrmEasy.Core/Metadata/MetadataGenerator.cs b/src/FakeXrmEasy.Core/Metadata/MetadataGenerator.cs index 86ca8780..93d3da4d 100644 --- a/src/FakeXrmEasy.Core/Metadata/MetadataGenerator.cs +++ b/src/FakeXrmEasy.Core/Metadata/MetadataGenerator.cs @@ -192,7 +192,7 @@ private static T GetCustomAttribute(MemberInfo member) where T : Attribute return (T)Attribute.GetCustomAttribute(member, typeof(T)); } - private static AttributeMetadata CreateAttributeMetadata(Type propertyType) + internal static AttributeMetadata CreateAttributeMetadata(Type propertyType) { if (typeof(string) == propertyType) { @@ -287,6 +287,11 @@ private static AttributeMetadata CreateAttributeMetadata(Type propertyType) { return new MultiSelectPicklistAttributeMetadata(); } + else if (typeof(object) == propertyType) + { + + return new FileAttributeMetadata(); + } #endif else { diff --git a/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/UpdateMultipleRequestExecutor.cs b/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/UpdateMultipleRequestExecutor.cs new file mode 100644 index 00000000..d9a82c1a --- /dev/null +++ b/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/UpdateMultipleRequestExecutor.cs @@ -0,0 +1,146 @@ +#if FAKE_XRM_EASY_9 +using FakeXrmEasy.Abstractions; +using FakeXrmEasy.Abstractions.FakeMessageExecutors; +using Microsoft.Xrm.Sdk; +using Microsoft.Xrm.Sdk.Messages; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace FakeXrmEasy.Middleware.Crud.FakeMessageExecutors +{ + /// + /// CreateMultipleRequest Executor + /// + public class UpdateMultipleRequestExecutor : IFakeMessageExecutor + { + /// + /// + /// + /// + /// + public bool CanExecute(OrganizationRequest request) + { + return request is UpdateMultipleRequest; + } + + /// + /// Executes the CreateRequestMultiple request + /// + /// + /// + /// + public OrganizationResponse Execute(OrganizationRequest request, IXrmFakedContext ctx) + { + var updateMultipleRequest = (UpdateMultipleRequest)request; + + ValidateRequest(updateMultipleRequest, ctx); + + var records = updateMultipleRequest.Targets.Entities; + + foreach (var record in records) + { + ctx.UpdateEntity(record); + } + + return new UpdateMultipleResponse() + { + ResponseName = "UpdateMultipleResponse" + }; + } + + private void ValidateRequiredParameters(UpdateMultipleRequest request, IXrmFakedContext ctx) + { + if (request.Targets == null) + { + throw FakeOrganizationServiceFaultFactory.New(ErrorCodes.InvalidArgument, + "Required field 'Targets' is missing"); + } + + var targets = request.Targets; + if (string.IsNullOrWhiteSpace(targets.EntityName)) + { + throw FakeOrganizationServiceFaultFactory.New(ErrorCodes.InvalidArgument, + "Required member 'EntityName' missing for field 'Targets'"); + } + } + + private void ValidateEntityName(UpdateMultipleRequest request, IXrmFakedContext ctx) + { + var targets = request.Targets; + if (ctx.ProxyTypesAssemblies.Any()) + { + var earlyBoundType = ctx.FindReflectedType(targets.EntityName); + if (earlyBoundType == null) + { + throw FakeOrganizationServiceFaultFactory.New(ErrorCodes.QueryBuilderNoEntity, + $"The entity with a name = '{targets.EntityName}' with namemapping = 'Logical' was not found in the MetadataCache."); + } + } + + if (ctx.CreateMetadataQuery().Any()) + { + var entityMetadata = ctx.CreateMetadataQuery().FirstOrDefault(m => m.LogicalName == targets.EntityName); + if (entityMetadata == null) + { + throw FakeOrganizationServiceFaultFactory.New(ErrorCodes.QueryBuilderNoEntity, + $"The entity with a name = '{targets.EntityName}' with namemapping = 'Logical' was not found in the MetadataCache."); + } + } + } + + private void ValidateRecords(UpdateMultipleRequest request, IXrmFakedContext ctx) + { + var records = request.Targets.Entities; + if (records.Count == 0) + { + throw FakeOrganizationServiceFaultFactory.New(ErrorCodes.UnExpected, + $"System.ArgumentException: The value of the parameter 'Targets' cannot be null or empty."); + } + + foreach (var record in records) + { + ValidateRecord(request, record, ctx); + } + } + + private void ValidateRecord(UpdateMultipleRequest request, Entity recordToUpdate, IXrmFakedContext ctx) + { + if (!request.Targets.EntityName.Equals(recordToUpdate.LogicalName)) + { + throw FakeOrganizationServiceFaultFactory.New(ErrorCodes.QueryBuilderNoAttribute, + $"This entity cannot be added to the specified collection. The collection can have entities with PlatformName = {request.Targets.EntityName} while this entity has Platform Name: {recordToUpdate.LogicalName}"); + } + + if (recordToUpdate.Id == Guid.Empty) + { + throw FakeOrganizationServiceFaultFactory.New(ErrorCodes.ObjectDoesNotExist, + $"Entity Id must be specified for Operation"); + } + + var exists = ctx.ContainsEntity(recordToUpdate.LogicalName, recordToUpdate.Id); + if (!exists) + { + throw FakeOrganizationServiceFaultFactory.New(ErrorCodes.ObjectDoesNotExist, + $"{recordToUpdate.LogicalName} With Ids = {recordToUpdate.Id} Do Not Exist"); + } + } + + private void ValidateRequest(UpdateMultipleRequest request, IXrmFakedContext ctx) + { + ValidateRequiredParameters(request, ctx); + ValidateEntityName(request, ctx); + ValidateRecords(request, ctx); + } + + /// + /// Returns CreateMultipleRequest + /// + /// + public Type GetResponsibleRequestType() + { + return typeof(UpdateMultipleRequest); + } + } +} +#endif \ No newline at end of file diff --git a/src/FakeXrmEasy.Core/Middleware/Crud/MiddlewareBuilderExtensions.Crud.cs b/src/FakeXrmEasy.Core/Middleware/Crud/MiddlewareBuilderExtensions.Crud.cs index e8019125..426d9b3d 100644 --- a/src/FakeXrmEasy.Core/Middleware/Crud/MiddlewareBuilderExtensions.Crud.cs +++ b/src/FakeXrmEasy.Core/Middleware/Crud/MiddlewareBuilderExtensions.Crud.cs @@ -52,6 +52,7 @@ public static IMiddlewareBuilder AddCrud(this IMiddlewareBuilder builder) #if FAKE_XRM_EASY_9 crudMessageExecutors.Add(typeof(CreateMultipleRequest), new CreateMultipleRequestExecutor()); + crudMessageExecutors.Add(typeof(UpdateMultipleRequest), new UpdateMultipleRequestExecutor()); #endif context.SetProperty(crudMessageExecutors); diff --git a/tests/FakeXrmEasy.Core.Tests/Metadata/MetadataGeneratorTests/CreateAttributeMetadataTests.cs b/tests/FakeXrmEasy.Core.Tests/Metadata/MetadataGeneratorTests/CreateAttributeMetadataTests.cs new file mode 100644 index 00000000..1628f2d9 --- /dev/null +++ b/tests/FakeXrmEasy.Core.Tests/Metadata/MetadataGeneratorTests/CreateAttributeMetadataTests.cs @@ -0,0 +1,25 @@ +using System; +using DataverseEntities; +using FakeXrmEasy.Metadata; +using Microsoft.Xrm.Sdk.Metadata; +using Xunit; + +namespace FakeXrmEasy.Core.Tests.Metadata +{ + public class CreateAttributeMetadataTests + { + private readonly Type[] _typesTestType; + public CreateAttributeMetadataTests() + { + _typesTestType = new Type[] { typeof(dv_test) }; + } + + [Fact] + public void Should_generate_file_type() + { + var attributeMetadata = MetadataGenerator.CreateAttributeMetadata(typeof(object)); + Assert.NotNull(attributeMetadata); + Assert.IsType(attributeMetadata); + } + } +} \ No newline at end of file diff --git a/tests/FakeXrmEasy.Core.Tests/Metadata/MetadataGeneratorTests.cs b/tests/FakeXrmEasy.Core.Tests/Metadata/MetadataGeneratorTests/MetadataGeneratorTests.cs similarity index 100% rename from tests/FakeXrmEasy.Core.Tests/Metadata/MetadataGeneratorTests.cs rename to tests/FakeXrmEasy.Core.Tests/Metadata/MetadataGeneratorTests/MetadataGeneratorTests.cs diff --git a/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/UpdateMultipleRequestTests/UpdateMultipleRequestTests.cs b/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/UpdateMultipleRequestTests/UpdateMultipleRequestTests.cs new file mode 100644 index 00000000..2563aa1a --- /dev/null +++ b/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/UpdateMultipleRequestTests/UpdateMultipleRequestTests.cs @@ -0,0 +1,353 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using DataverseEntities; +using FakeXrmEasy.Abstractions; +using FakeXrmEasy.Abstractions.Integrity; +using FakeXrmEasy.Integrity; +using Microsoft.Xrm.Sdk; +using Microsoft.Xrm.Sdk.Messages; +using Microsoft.Xrm.Sdk.Query; +using Xunit; + +namespace FakeXrmEasy.Core.Tests.Middleware.Crud.FakeMessageExecutors.UpdateMultipleRequestTests +{ + public class UpdateMultipleRequestTests: FakeXrmEasyTestsBase + { + [Fact] + public void Should_throw_exception_if_targets_was_not_set() + { + var request = new UpdateMultipleRequest(); + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.InvalidArgument, () => _service.Execute(request)); + Assert.Equal("Required field 'Targets' is missing", ex.Detail.Message); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void Should_throw_exception_if_update_multiple_is_called_with_null_entity_name(string entityLogicalName) + { + List recordsToUpdate = new List(); + + var entities = new EntityCollection(recordsToUpdate) + { + EntityName = entityLogicalName + }; + + var request = new UpdateMultipleRequest() + { + Targets = entities + }; + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.InvalidArgument, () => _service.Execute(request)); + Assert.Equal("Required member 'EntityName' missing for field 'Targets'", ex.Detail.Message); + } + + [Theory] + [InlineData("asdasdasd")] + public void Should_throw_exception_if_update_multiple_is_called_with_invalid_entity_name_and_early_bound_types_are_used(string entityLogicalName) + { + _context.EnableProxyTypes(Assembly.GetAssembly(typeof(Account))); + + List recordsToUpdate = new List(); + + var entities = new EntityCollection(recordsToUpdate) + { + EntityName = entityLogicalName + }; + + var request = new UpdateMultipleRequest() + { + Targets = entities + }; + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.QueryBuilderNoEntity, () => _service.Execute(request)); + Assert.StartsWith($"The entity with a name = '{entityLogicalName}' with namemapping = 'Logical' was not found in the MetadataCache.", ex.Detail.Message); + } + + [Theory] + [InlineData("asdasdasd")] + public void Should_throw_exception_if_update_multiple_is_called_with_invalid_entity_name_and_metadata_is_used(string entityLogicalName) + { + _context.InitializeMetadata(Assembly.GetAssembly(typeof(dv_test))); + + List recordsToUpdate = new List(); + + var entities = new EntityCollection(recordsToUpdate) + { + EntityName = entityLogicalName + }; + + var request = new UpdateMultipleRequest() + { + Targets = entities + }; + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.QueryBuilderNoEntity, () => _service.Execute(request)); + Assert.StartsWith($"The entity with a name = '{entityLogicalName}' with namemapping = 'Logical' was not found in the MetadataCache.", ex.Detail.Message); + } + + [Fact] + public void Should_throw_exception_if_update_multiple_is_called_with_an_empty_list() + { + List recordsToUpdate = new List(); + + var entities = new EntityCollection(recordsToUpdate) + { + EntityName = dv_test.EntityLogicalName + }; + + var request = new UpdateMultipleRequest() + { + Targets = entities + }; + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.UnExpected, () => _service.Execute(request)); + Assert.Equal("System.ArgumentException: The value of the parameter 'Targets' cannot be null or empty.", ex.Detail.Message); + } + + [Fact] + public void Should_throw_exception_if_update_multiple_is_called_with_a_non_existing_entity_record() + { + var nonExistingGuid = Guid.NewGuid(); + + List recordsToUpdate = new List() { new dv_test() { Id = nonExistingGuid } }; + + var entities = new EntityCollection(recordsToUpdate) + { + EntityName = dv_test.EntityLogicalName + }; + + var request = new UpdateMultipleRequest() + { + Targets = entities + }; + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.ObjectDoesNotExist, () => _service.Execute(request)); + Assert.Equal($"{dv_test.EntityLogicalName} With Ids = {nonExistingGuid} Do Not Exist", ex.Detail.Message); + } + + [Fact] + public void Should_throw_exception_if_update_multiple_is_called_without_entity_id() + { + var nonExistingGuid = Guid.NewGuid(); + + List recordsToUpdate = new List() { new dv_test() }; + + var entities = new EntityCollection(recordsToUpdate) + { + EntityName = dv_test.EntityLogicalName + }; + + var request = new UpdateMultipleRequest() + { + Targets = entities + }; + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.ObjectDoesNotExist, () => _service.Execute(request)); + Assert.Equal($"Entity Id must be specified for Operation", ex.Detail.Message); + } + + [Fact] + public void Should_throw_exception_if_update_multiple_is_called_with_an_entity_record_with_a_logical_name_different_than_the_main_logical_name() + { + var guid1 = _service.Create(new dv_test()); + + List recordsToUpdate = new List() + { + new dv_test() { Id = guid1 } + }; + + var entities = new EntityCollection(recordsToUpdate) + { + EntityName = Account.EntityLogicalName + }; + + var request = new UpdateMultipleRequest() + { + Targets = entities + }; + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.QueryBuilderNoAttribute, () => _service.Execute(request)); + Assert.Equal($"This entity cannot be added to the specified collection. The collection can have entities with PlatformName = {Account.EntityLogicalName} while this entity has Platform Name: {dv_test.EntityLogicalName}", ex.Detail.Message); + } + + [Fact] + public void Should_throw_exception_if_update_multiple_is_called_with_non_existing_related_entity() + { + _context.SetProperty(new IntegrityOptions()); + + var nonExistingGuid = Guid.NewGuid(); + + var guid1 = _service.Create(new dv_test()); + + List recordsToUpdate = new List() { + new dv_test() + { + Id = guid1, + dv_accountid = new EntityReference(Account.EntityLogicalName, nonExistingGuid) + } + }; + + var entities = new EntityCollection(recordsToUpdate) + { + EntityName = dv_test.EntityLogicalName + }; + + var request = new UpdateMultipleRequest() + { + Targets = entities + }; + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.ObjectDoesNotExist, () => _service.Execute(request)); + Assert.Equal($"account With Ids = {nonExistingGuid} Do Not Exist", ex.Detail.Message); + } + + [Fact] + public void Should_throw_exception_if_update_multiple_is_called_with_non_existing_related_entity_by_alternate_key() + { + _context.SetProperty(new IntegrityOptions()); + + var guid1 = _service.Create(new dv_test()); + + var nonExistingGuid = Guid.NewGuid(); + var missingAttribute = "dv_key_number"; + + List recordsToUpdate = new List() { + new dv_test() + { + Id = guid1, + dv_accountid = new EntityReference(Account.EntityLogicalName, missingAttribute, "Missing number") + } + }; + + var entities = new EntityCollection(recordsToUpdate) + { + EntityName = dv_test.EntityLogicalName + }; + + var request = new UpdateMultipleRequest() + { + Targets = entities + }; + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.InvalidEntityKeyOperation, () => _service.Execute(request)); + Assert.Equal($"Invalid EntityKey Operation performed : Entity {Account.EntityLogicalName} does not contain an attribute named {missingAttribute}", ex.Detail.Message); + } + + [Fact] + public void Should_throw_exception_if_update_multiple_is_called_with_a_non_existing_single_alternate_key() + { + _context.SetProperty(new IntegrityOptions()); + + var guid1 = _service.Create(new dv_test()); + + var dummy_attribute_name = "dv_dummy_attribute"; + + var nonExistingGuid = Guid.NewGuid(); + + List recordsToUpdate = new List() { + new dv_test() + { + Id = guid1, + dv_accountid = new EntityReference(Account.EntityLogicalName, dummy_attribute_name, "Microsoft") + } + }; + + var entities = new EntityCollection(recordsToUpdate) + { + EntityName = dv_test.EntityLogicalName + }; + + var request = new UpdateMultipleRequest() + { + Targets = entities + }; + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.InvalidEntityKeyOperation, () => _service.Execute(request)); + Assert.Equal($"Invalid EntityKey Operation performed : Entity {Account.EntityLogicalName} does not contain an attribute named {dummy_attribute_name}", ex.Detail.Message); + } + + [Fact] + public void Should_throw_exception_if_update_multiple_is_called_with_a_non_existing_multiple_alternate_key() + { + _context.SetProperty(new IntegrityOptions()); + + var guid1 = _service.Create(new dv_test()); + + var dummy_attribute_name1 = "dv_dummy_attribute1"; + var dummy_attribute_name2 = "dv_dummy_attribute2"; + + var nonExistingGuid = Guid.NewGuid(); + + List recordsToUpdate = new List() { + new dv_test() + { + Id = guid1, + dv_accountid = new EntityReference(Account.EntityLogicalName, + new KeyAttributeCollection + { + { dummy_attribute_name2, "value2" }, + { dummy_attribute_name1, "value1" }, + }) + } + }; + + var entities = new EntityCollection(recordsToUpdate) + { + EntityName = dv_test.EntityLogicalName + }; + + var request = new UpdateMultipleRequest() + { + Targets = entities + }; + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.InvalidEntityKeyOperation, () => _service.Execute(request)); + Assert.Equal($"Invalid EntityKey Operation performed : Entity {Account.EntityLogicalName} does not contain an attribute named {dummy_attribute_name2}", ex.Detail.Message); + } + + [Fact] + public void Should_update_two_records_in_update_multiple() + { + var guid1 = _service.Create(new dv_test()); + var guid2 = _service.Create(new dv_test()); + + List recordsToUpdate = new List() + { + new dv_test() + { + Id = guid1, + dv_string = "Record 1" + }, + new dv_test() { + Id = guid2, + dv_string = "Record 2" + } + }; + + var entities = new EntityCollection(recordsToUpdate) + { + EntityName = dv_test.EntityLogicalName + }; + + var request = new UpdateMultipleRequest() + { + Targets = entities + }; + + var response = _service.Execute(request) as UpdateMultipleResponse; + + var updatedRecord1 = _service.Retrieve(dv_test.EntityLogicalName, guid1, new ColumnSet(true)); + var updatedRecord2 = _service.Retrieve(dv_test.EntityLogicalName, guid2, new ColumnSet(true)); + + Assert.NotNull(updatedRecord1); + Assert.NotNull(updatedRecord2); + + Assert.Equal("Record 1", updatedRecord1["dv_string"]); + Assert.Equal("Record 2", updatedRecord2["dv_string"]); + + } + } +} \ No newline at end of file From 98ae63c302eed74a508845247bfd2b92f6769bf0 Mon Sep 17 00:00:00 2001 From: Jordi Date: Fri, 15 Mar 2024 22:28:22 +0100 Subject: [PATCH 13/37] UpdateMultipleRequest only applies to v9 --- .../UpdateMultipleRequestTests/UpdateMultipleRequestTests.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/UpdateMultipleRequestTests/UpdateMultipleRequestTests.cs b/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/UpdateMultipleRequestTests/UpdateMultipleRequestTests.cs index 2563aa1a..8506012c 100644 --- a/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/UpdateMultipleRequestTests/UpdateMultipleRequestTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/UpdateMultipleRequestTests/UpdateMultipleRequestTests.cs @@ -1,3 +1,4 @@ +#if FAKE_XRM_EASY_9 using System; using System.Collections.Generic; using System.Reflection; @@ -347,7 +348,7 @@ public void Should_update_two_records_in_update_multiple() Assert.Equal("Record 1", updatedRecord1["dv_string"]); Assert.Equal("Record 2", updatedRecord2["dv_string"]); - } } -} \ No newline at end of file +} +#endif \ No newline at end of file From ee6d8290f4b42411a3ea6be0f8f20866f53ece48 Mon Sep 17 00:00:00 2001 From: Jordi Date: Fri, 15 Mar 2024 22:40:03 +0100 Subject: [PATCH 14/37] Limit FileAttributeMetadata tests to v9 too --- .../MetadataGeneratorTests/CreateAttributeMetadataTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/FakeXrmEasy.Core.Tests/Metadata/MetadataGeneratorTests/CreateAttributeMetadataTests.cs b/tests/FakeXrmEasy.Core.Tests/Metadata/MetadataGeneratorTests/CreateAttributeMetadataTests.cs index 1628f2d9..4731511c 100644 --- a/tests/FakeXrmEasy.Core.Tests/Metadata/MetadataGeneratorTests/CreateAttributeMetadataTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/Metadata/MetadataGeneratorTests/CreateAttributeMetadataTests.cs @@ -14,6 +14,7 @@ public CreateAttributeMetadataTests() _typesTestType = new Type[] { typeof(dv_test) }; } + #if FAKE_XRM_EASY_9 [Fact] public void Should_generate_file_type() { @@ -21,5 +22,6 @@ public void Should_generate_file_type() Assert.NotNull(attributeMetadata); Assert.IsType(attributeMetadata); } + #endif } } \ No newline at end of file From 4d351a680ac5d14aac0aaa5a776d5756d2875050 Mon Sep 17 00:00:00 2001 From: Jordi Date: Fri, 15 Mar 2024 22:54:23 +0100 Subject: [PATCH 15/37] Refactor common code in bulk operations --- .../BulkOperationsCommon.cs | 31 +++++++++++++++++++ .../CreateMultipleRequestExecutor.cs | 27 +--------------- .../UpdateMultipleRequestExecutor.cs | 26 +--------------- 3 files changed, 33 insertions(+), 51 deletions(-) create mode 100644 src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/BulkOperationsCommon.cs diff --git a/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/BulkOperationsCommon.cs b/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/BulkOperationsCommon.cs new file mode 100644 index 00000000..2e840bbe --- /dev/null +++ b/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/BulkOperationsCommon.cs @@ -0,0 +1,31 @@ +using System.Linq; +using FakeXrmEasy.Abstractions; + +namespace FakeXrmEasy.Middleware.Crud.FakeMessageExecutors +{ + internal class BulkOperationsCommon + { + internal static void ValidateEntityName(string collectionEntityName, IXrmFakedContext ctx) + { + if (ctx.ProxyTypesAssemblies.Any()) + { + var earlyBoundType = ctx.FindReflectedType(collectionEntityName); + if (earlyBoundType == null) + { + throw FakeOrganizationServiceFaultFactory.New(ErrorCodes.QueryBuilderNoEntity, + $"The entity with a name = '{collectionEntityName}' with namemapping = 'Logical' was not found in the MetadataCache."); + } + } + + if (ctx.CreateMetadataQuery().Any()) + { + var entityMetadata = ctx.CreateMetadataQuery().FirstOrDefault(m => m.LogicalName == collectionEntityName); + if (entityMetadata == null) + { + throw FakeOrganizationServiceFaultFactory.New(ErrorCodes.QueryBuilderNoEntity, + $"The entity with a name = '{collectionEntityName}' with namemapping = 'Logical' was not found in the MetadataCache."); + } + } + } + } +} \ No newline at end of file diff --git a/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestExecutor.cs b/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestExecutor.cs index 8a76b608..63711a20 100644 --- a/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestExecutor.cs +++ b/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestExecutor.cs @@ -67,31 +67,6 @@ private void ValidateRequiredParameters(CreateMultipleRequest request, IXrmFaked "Required member 'EntityName' missing for field 'Targets'"); } } - - private void ValidateEntityName(CreateMultipleRequest request, IXrmFakedContext ctx) - { - var targets = request.Targets; - if (ctx.ProxyTypesAssemblies.Any()) - { - var earlyBoundType = ctx.FindReflectedType(targets.EntityName); - if (earlyBoundType == null) - { - throw FakeOrganizationServiceFaultFactory.New(ErrorCodes.QueryBuilderNoEntity, - $"The entity with a name = '{targets.EntityName}' with namemapping = 'Logical' was not found in the MetadataCache."); - } - } - - if (ctx.CreateMetadataQuery().Any()) - { - var entityMetadata = ctx.CreateMetadataQuery().FirstOrDefault(m => m.LogicalName == targets.EntityName); - if (entityMetadata == null) - { - throw FakeOrganizationServiceFaultFactory.New(ErrorCodes.QueryBuilderNoEntity, - $"The entity with a name = '{targets.EntityName}' with namemapping = 'Logical' was not found in the MetadataCache."); - } - } - } - private void ValidateRecords(CreateMultipleRequest request, IXrmFakedContext ctx) { var records = request.Targets.Entities; @@ -126,7 +101,7 @@ private void ValidateRecord(CreateMultipleRequest request, Entity recordToCreate private void ValidateRequest(CreateMultipleRequest request, IXrmFakedContext ctx) { ValidateRequiredParameters(request, ctx); - ValidateEntityName(request, ctx); + BulkOperationsCommon.ValidateEntityName(request.Targets.EntityName, ctx); ValidateRecords(request, ctx); } diff --git a/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/UpdateMultipleRequestExecutor.cs b/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/UpdateMultipleRequestExecutor.cs index d9a82c1a..12cf80de 100644 --- a/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/UpdateMultipleRequestExecutor.cs +++ b/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/UpdateMultipleRequestExecutor.cs @@ -65,30 +65,6 @@ private void ValidateRequiredParameters(UpdateMultipleRequest request, IXrmFaked } } - private void ValidateEntityName(UpdateMultipleRequest request, IXrmFakedContext ctx) - { - var targets = request.Targets; - if (ctx.ProxyTypesAssemblies.Any()) - { - var earlyBoundType = ctx.FindReflectedType(targets.EntityName); - if (earlyBoundType == null) - { - throw FakeOrganizationServiceFaultFactory.New(ErrorCodes.QueryBuilderNoEntity, - $"The entity with a name = '{targets.EntityName}' with namemapping = 'Logical' was not found in the MetadataCache."); - } - } - - if (ctx.CreateMetadataQuery().Any()) - { - var entityMetadata = ctx.CreateMetadataQuery().FirstOrDefault(m => m.LogicalName == targets.EntityName); - if (entityMetadata == null) - { - throw FakeOrganizationServiceFaultFactory.New(ErrorCodes.QueryBuilderNoEntity, - $"The entity with a name = '{targets.EntityName}' with namemapping = 'Logical' was not found in the MetadataCache."); - } - } - } - private void ValidateRecords(UpdateMultipleRequest request, IXrmFakedContext ctx) { var records = request.Targets.Entities; @@ -129,7 +105,7 @@ private void ValidateRecord(UpdateMultipleRequest request, Entity recordToUpdate private void ValidateRequest(UpdateMultipleRequest request, IXrmFakedContext ctx) { ValidateRequiredParameters(request, ctx); - ValidateEntityName(request, ctx); + BulkOperationsCommon.ValidateEntityName(request.Targets.EntityName, ctx); ValidateRecords(request, ctx); } From b68a4ba9c9cdbdb968e8cea8276c8da8ee57d57f Mon Sep 17 00:00:00 2001 From: Jordi Date: Sat, 16 Mar 2024 13:06:00 +0100 Subject: [PATCH 16/37] Adding UpsertMultipleRequest tests DynamicsValue/fake-xrm-easy#122 and resolves DynamicsValue/fake-xrm-easy#138 --- CHANGELOG.md | 4 + .../UpdateMultipleRequestExecutor.cs | 2 +- .../UpsertMultipleRequestExecutor.cs | 113 ++++++ .../Crud/MiddlewareBuilderExtensions.Crud.cs | 1 + src/FakeXrmEasy.Core/XrmFakedContext.Crud.cs | 4 + .../UpsertMultipleRequestTests.cs | 336 ++++++++++++++++++ 6 files changed, 459 insertions(+), 1 deletion(-) create mode 100644 src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/UpsertMultipleRequestExecutor.cs create mode 100644 tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/UpsertMultipleRequestTests/UpsertMultipleRequestTests.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 6db158ef..14763c69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ - Added FileAttributeMetadata support to MetadataGenerator - Added support for bulk operations: CreateMultipleRequest, UpdateMultipleRequest +### Changed + +- Resolves Resolving entity references by Alternate Keys when EntityMetadata is used that doesn't have any Keys. - https://github.com/DynamicsValue/fake-xrm-easy/issues/138 + ## [2.4.2] ### Added diff --git a/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/UpdateMultipleRequestExecutor.cs b/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/UpdateMultipleRequestExecutor.cs index 12cf80de..6ea3fffb 100644 --- a/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/UpdateMultipleRequestExecutor.cs +++ b/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/UpdateMultipleRequestExecutor.cs @@ -10,7 +10,7 @@ namespace FakeXrmEasy.Middleware.Crud.FakeMessageExecutors { /// - /// CreateMultipleRequest Executor + /// UpdateMultipleRequest Executor /// public class UpdateMultipleRequestExecutor : IFakeMessageExecutor { diff --git a/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/UpsertMultipleRequestExecutor.cs b/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/UpsertMultipleRequestExecutor.cs new file mode 100644 index 00000000..d041edfc --- /dev/null +++ b/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/UpsertMultipleRequestExecutor.cs @@ -0,0 +1,113 @@ +#if FAKE_XRM_EASY_9 +using FakeXrmEasy.Abstractions; +using FakeXrmEasy.Abstractions.FakeMessageExecutors; +using Microsoft.Xrm.Sdk; +using Microsoft.Xrm.Sdk.Messages; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace FakeXrmEasy.Middleware.Crud.FakeMessageExecutors +{ + /// + /// UpsertMultipleRequest Executor + /// + public class UpsertMultipleRequestExecutor : IFakeMessageExecutor + { + /// + /// + /// + /// + /// + public bool CanExecute(OrganizationRequest request) + { + return request is UpsertMultipleRequest; + } + + /// + /// Executes the CreateRequestMultiple request + /// + /// + /// + /// + public OrganizationResponse Execute(OrganizationRequest request, IXrmFakedContext ctx) + { + var upsertMultipleRequest = (UpsertMultipleRequest)request; + + ValidateRequest(upsertMultipleRequest, ctx); + + var records = upsertMultipleRequest.Targets.Entities; + List results = new List(); + + var service = ctx.GetOrganizationService(); + + foreach (var record in records) + { + var response = service.Execute(new UpsertRequest() { Target = record }) as UpsertResponse; + results.Add(response); + } + + return new UpsertMultipleResponse() + { + ResponseName = "UpsertMultipleResponse", + ["Results"] = results.ToArray() + }; + } + + private void ValidateRequiredParameters(UpsertMultipleRequest request, IXrmFakedContext ctx) + { + if (request.Targets == null) + { + throw FakeOrganizationServiceFaultFactory.New(ErrorCodes.InvalidArgument, + "'Targets' should be one of the parameters for UpsertMultiple."); + } + + var targets = request.Targets; + if (string.IsNullOrWhiteSpace(targets.EntityName)) + { + throw FakeOrganizationServiceFaultFactory.New(ErrorCodes.UnExpected, + "System.ArgumentException: The value of the parameter 'Targets' cannot be null or empty."); + } + } + private void ValidateRecords(UpsertMultipleRequest request, IXrmFakedContext ctx) + { + var records = request.Targets.Entities; + if (records.Count == 0) + { + throw FakeOrganizationServiceFaultFactory.New(ErrorCodes.UnExpected, + $"System.ArgumentException: The value of the parameter 'Targets' cannot be null or empty."); + } + + foreach (var record in records) + { + ValidateRecord(request, record, ctx); + } + } + + private void ValidateRecord(UpsertMultipleRequest request, Entity recordToCreate, IXrmFakedContext ctx) + { + if (!request.Targets.EntityName.Equals(recordToCreate.LogicalName)) + { + throw FakeOrganizationServiceFaultFactory.New(ErrorCodes.InvalidArgument, + $"This entity cannot be added to the specified collection. The collection can have entities with PlatformName = {request.Targets.EntityName} while this entity has Platform Name: {recordToCreate.LogicalName}"); + } + } + + private void ValidateRequest(UpsertMultipleRequest request, IXrmFakedContext ctx) + { + ValidateRequiredParameters(request, ctx); + BulkOperationsCommon.ValidateEntityName(request.Targets.EntityName, ctx); + ValidateRecords(request, ctx); + } + + /// + /// Returns UpsertMultipleRequest + /// + /// + public Type GetResponsibleRequestType() + { + return typeof(UpsertMultipleRequest); + } + } +} +#endif \ No newline at end of file diff --git a/src/FakeXrmEasy.Core/Middleware/Crud/MiddlewareBuilderExtensions.Crud.cs b/src/FakeXrmEasy.Core/Middleware/Crud/MiddlewareBuilderExtensions.Crud.cs index 426d9b3d..e44146ac 100644 --- a/src/FakeXrmEasy.Core/Middleware/Crud/MiddlewareBuilderExtensions.Crud.cs +++ b/src/FakeXrmEasy.Core/Middleware/Crud/MiddlewareBuilderExtensions.Crud.cs @@ -53,6 +53,7 @@ public static IMiddlewareBuilder AddCrud(this IMiddlewareBuilder builder) #if FAKE_XRM_EASY_9 crudMessageExecutors.Add(typeof(CreateMultipleRequest), new CreateMultipleRequestExecutor()); crudMessageExecutors.Add(typeof(UpdateMultipleRequest), new UpdateMultipleRequestExecutor()); + crudMessageExecutors.Add(typeof(UpsertMultipleRequest), new UpsertMultipleRequestExecutor()); #endif context.SetProperty(crudMessageExecutors); diff --git a/src/FakeXrmEasy.Core/XrmFakedContext.Crud.cs b/src/FakeXrmEasy.Core/XrmFakedContext.Crud.cs index 5101bd02..1b299d7a 100644 --- a/src/FakeXrmEasy.Core/XrmFakedContext.Crud.cs +++ b/src/FakeXrmEasy.Core/XrmFakedContext.Crud.cs @@ -65,6 +65,10 @@ public Guid GetRecordUniqueId(EntityReference record, bool validate = true) if (Db.ContainsTableMetadata(record.LogicalName)) { var entityMetadata = Db.GetTableMetadata(record.LogicalName); + if (entityMetadata.Keys == null && validate) + { + throw FakeOrganizationServiceFaultFactory.New(ErrorCodes.InvalidEntityKeyOperation, $"Invalid EntityKey Operation performed : Entity {record.LogicalName} does not contain an attribute named {record.KeyAttributes.First().Key}"); + } foreach (var key in entityMetadata.Keys) { if (record.KeyAttributes.Keys.Count == key.KeyAttributes.Length && key.KeyAttributes.All(x => record.KeyAttributes.Keys.Contains(x))) diff --git a/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/UpsertMultipleRequestTests/UpsertMultipleRequestTests.cs b/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/UpsertMultipleRequestTests/UpsertMultipleRequestTests.cs new file mode 100644 index 00000000..b2c863c8 --- /dev/null +++ b/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/UpsertMultipleRequestTests/UpsertMultipleRequestTests.cs @@ -0,0 +1,336 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using DataverseEntities; +using FakeXrmEasy.Abstractions; +using FakeXrmEasy.Abstractions.Integrity; +using FakeXrmEasy.Integrity; +using Microsoft.Xrm.Sdk; +using Microsoft.Xrm.Sdk.Messages; +using Microsoft.Xrm.Sdk.Query; +using Xunit; + +namespace FakeXrmEasy.Core.Tests.Middleware.Crud.FakeMessageExecutors.UpsertMultipleRequestTests +{ + public class UpsertMultipleRequestTests: FakeXrmEasyTestsBase + { + [Fact] + public void Should_throw_exception_if_targets_was_not_set() + { + var request = new UpsertMultipleRequest(); + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.InvalidArgument, () => _service.Execute(request)); + Assert.Equal("'Targets' should be one of the parameters for UpsertMultiple.", ex.Detail.Message); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void Should_throw_exception_if_upsert_multiple_is_called_with_null_entity_name(string entityLogicalName) + { + List recordsToUpsert = new List(); + + var entities = new EntityCollection(recordsToUpsert) + { + EntityName = entityLogicalName + }; + + var request = new UpsertMultipleRequest() + { + Targets = entities + }; + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.UnExpected, () => _service.Execute(request)); + Assert.Equal("System.ArgumentException: The value of the parameter 'Targets' cannot be null or empty.", ex.Detail.Message); + } + + [Theory] + [InlineData("asdasdasd")] + public void Should_throw_exception_if_upsert_multiple_is_called_with_invalid_entity_name(string entityLogicalName) + { + List recordsToUpsert = new List(); + + var entities = new EntityCollection(recordsToUpsert) + { + EntityName = entityLogicalName + }; + + var request = new UpsertMultipleRequest() + { + Targets = entities + }; + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.QueryBuilderNoEntity, () => _service.Execute(request)); + Assert.StartsWith($"The entity with a name = '{entityLogicalName}' with namemapping = 'Logical' was not found in the MetadataCache.", ex.Detail.Message); + } + + [Fact] + public void Should_throw_exception_if_upsert_multiple_is_called_with_an_empty_list() + { + List recordsToUpsert = new List(); + + var entities = new EntityCollection(recordsToUpsert) + { + EntityName = dv_test.EntityLogicalName + }; + + var request = new UpsertMultipleRequest() + { + Targets = entities + }; + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.UnExpected, () => _service.Execute(request)); + Assert.Equal("System.ArgumentException: The value of the parameter 'Targets' cannot be null or empty.", ex.Detail.Message); + } + + [Fact] + public void Should_create_record_if_upsert_multiple_is_called_with_a_non_existing_entity_record() + { + _context.InitializeMetadata(Assembly.GetAssembly(typeof(dv_test))); + + var nonExistingGuid = Guid.NewGuid(); + + List recordsToUpsert = new List() { new dv_test() { Id = nonExistingGuid } }; + + var entities = new EntityCollection(recordsToUpsert) + { + EntityName = dv_test.EntityLogicalName + }; + + var request = new UpsertMultipleRequest() + { + Targets = entities + }; + + var response = _service.Execute(request) as UpsertMultipleResponse; + + Assert.Single(response.Results); + + var upsertResponse = response.Results[0]; + var createdRecordId = upsertResponse.Target.Id; + Assert.Equal(nonExistingGuid, createdRecordId); + + var entity = _service.Retrieve(dv_test.EntityLogicalName, createdRecordId, new ColumnSet(true)); + Assert.NotNull(entity); + } + + [Fact] + public void Should_create_record_if_upsert_multiple_is_called_without_entity_id() + { + _context.InitializeMetadata(Assembly.GetAssembly(typeof(dv_test))); + + List recordsToUpsert = new List() { new dv_test() }; + + var entities = new EntityCollection(recordsToUpsert) + { + EntityName = dv_test.EntityLogicalName + }; + + var request = new UpsertMultipleRequest() + { + Targets = entities + }; + + var response = _service.Execute(request) as UpsertMultipleResponse; + + Assert.Single(response.Results); + + var upsertResponse = response.Results[0]; + var createdRecordId = upsertResponse.Target.Id; + + var entity = _service.Retrieve(dv_test.EntityLogicalName, createdRecordId, new ColumnSet(true)); + Assert.NotNull(entity); + } + + [Fact] + public void Should_throw_exception_if_upsert_multiple_is_called_with_an_entity_record_with_a_logical_name_different_than_the_main_logical_name() + { + _context.InitializeMetadata(Assembly.GetAssembly(typeof(dv_test))); + + List recordsToUpsert = new List() + { + new dv_test() + }; + + var entities = new EntityCollection(recordsToUpsert) + { + EntityName = Account.EntityLogicalName + }; + + var request = new UpsertMultipleRequest() + { + Targets = entities + }; + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.InvalidArgument, () => _service.Execute(request)); + Assert.Equal($"This entity cannot be added to the specified collection. The collection can have entities with PlatformName = account while this entity has Platform Name: {dv_test.EntityLogicalName}", ex.Detail.Message); + } + + [Fact] + public void Should_throw_exception_if_upsert_multiple_is_called_with_non_existing_related_entity() + { + _context.SetProperty(new IntegrityOptions()); + _context.InitializeMetadata(Assembly.GetAssembly(typeof(dv_test))); + + var nonExistingGuid = Guid.NewGuid(); + + List recordsToUpsert = new List() { + new dv_test() + { + dv_accountid = new EntityReference(Account.EntityLogicalName, nonExistingGuid) + } + }; + + var entities = new EntityCollection(recordsToUpsert) + { + EntityName = dv_test.EntityLogicalName + }; + + var request = new UpsertMultipleRequest() + { + Targets = entities + }; + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.ObjectDoesNotExist, () => _service.Execute(request)); + Assert.Equal($"account With Ids = {nonExistingGuid} Do Not Exist", ex.Detail.Message); + } + + [Fact] + public void Should_throw_exception_if_upsert_multiple_is_called_with_non_existing_related_entity_by_alternate_key() + { + _context.InitializeMetadata(Assembly.GetAssembly(typeof(dv_test))); + _context.SetProperty(new IntegrityOptions()); + + var nonExistingGuid = Guid.NewGuid(); + var missingAttribute = "dv_key_number"; + + List recordsToUpsert = new List() { + new dv_test() + { + dv_accountid = new EntityReference(Account.EntityLogicalName, missingAttribute, "Missing number") + } + }; + + var entities = new EntityCollection(recordsToUpsert) + { + EntityName = dv_test.EntityLogicalName + }; + + var request = new UpsertMultipleRequest() + { + Targets = entities + }; + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.InvalidEntityKeyOperation, () => _service.Execute(request)); + Assert.Equal($"Invalid EntityKey Operation performed : Entity {Account.EntityLogicalName} does not contain an attribute named {missingAttribute}", ex.Detail.Message); + } + + [Fact] + public void Should_throw_exception_if_upsert_multiple_is_called_with_a_non_existing_single_alternate_key() + { + _context.InitializeMetadata(Assembly.GetAssembly(typeof(dv_test))); + + var dummy_attribute_name = "dv_dummy_attribute"; + + var nonExistingGuid = Guid.NewGuid(); + + List recordsToUpsert = new List() { + new dv_test() + { + dv_accountid = new EntityReference(Account.EntityLogicalName, dummy_attribute_name, "Microsoft") + } + }; + + var entities = new EntityCollection(recordsToUpsert) + { + EntityName = dv_test.EntityLogicalName + }; + + var request = new UpsertMultipleRequest() + { + Targets = entities + }; + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.InvalidEntityKeyOperation, () => _service.Execute(request)); + Assert.Equal($"Invalid EntityKey Operation performed : Entity {Account.EntityLogicalName} does not contain an attribute named {dummy_attribute_name}", ex.Detail.Message); + } + + [Fact] + public void Should_throw_exception_if_upsert_multiple_is_called_with_a_non_existing_multiple_alternate_key() + { + _context.InitializeMetadata(Assembly.GetAssembly(typeof(dv_test))); + + var dummy_attribute_name1 = "dv_dummy_attribute1"; + var dummy_attribute_name2 = "dv_dummy_attribute2"; + + List recordsToUpsert = new List() { + new dv_test() + { + dv_accountid = new EntityReference(Account.EntityLogicalName, + new KeyAttributeCollection + { + { dummy_attribute_name2, "value2" }, + { dummy_attribute_name1, "value1" }, + }) + } + }; + + var entities = new EntityCollection(recordsToUpsert) + { + EntityName = dv_test.EntityLogicalName + }; + + var request = new UpsertMultipleRequest() + { + Targets = entities + }; + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.InvalidEntityKeyOperation, () => _service.Execute(request)); + Assert.Equal($"Invalid EntityKey Operation performed : Entity {Account.EntityLogicalName} does not contain an attribute named {dummy_attribute_name2}", ex.Detail.Message); + } + + [Fact] + public void Should_upsert_two_records_in_upsert_multiple() + { + var guid1 = _service.Create(new dv_test()); + + List recordsToUpsert = new List() + { + new dv_test() + { + Id = guid1, + dv_string = "Record 1" + }, + new dv_test() { + dv_string = "Record 2" + } + }; + + var entities = new EntityCollection(recordsToUpsert) + { + EntityName = dv_test.EntityLogicalName + }; + + var request = new UpsertMultipleRequest() + { + Targets = entities + }; + + var response = _service.Execute(request) as UpsertMultipleResponse; + var upsertResponse1 = response.Results[0]; + var upsertResponse2 = response.Results[1]; + + Assert.Equal(guid1, upsertResponse1.Target.Id); + + var updatedRecord = _service.Retrieve(dv_test.EntityLogicalName, guid1, new ColumnSet(true)); + var createdRecord = _service.Retrieve(dv_test.EntityLogicalName, upsertResponse2.Target.Id, new ColumnSet(true)); + + Assert.NotNull(updatedRecord); + Assert.NotNull(createdRecord); + + Assert.Equal("Record 1", updatedRecord["dv_string"]); + Assert.Equal("Record 2", createdRecord["dv_string"]); + + } + } +} \ No newline at end of file From b7bfcedc57dcaa779704c273746e6fca3769cc95 Mon Sep 17 00:00:00 2001 From: Jordi Date: Sat, 16 Mar 2024 13:08:40 +0100 Subject: [PATCH 17/37] Adds remaining UpsertMultipleRequest tests DynamicsValue/fake-xrm-easy#122 --- .../UpsertMultipleRequestTests/UpsertMultipleRequestTests.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/UpsertMultipleRequestTests/UpsertMultipleRequestTests.cs b/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/UpsertMultipleRequestTests/UpsertMultipleRequestTests.cs index b2c863c8..1025e1c0 100644 --- a/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/UpsertMultipleRequestTests/UpsertMultipleRequestTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/UpsertMultipleRequestTests/UpsertMultipleRequestTests.cs @@ -48,6 +48,8 @@ public void Should_throw_exception_if_upsert_multiple_is_called_with_null_entity [InlineData("asdasdasd")] public void Should_throw_exception_if_upsert_multiple_is_called_with_invalid_entity_name(string entityLogicalName) { + _context.InitializeMetadata(Assembly.GetAssembly(typeof(dv_test))); + List recordsToUpsert = new List(); var entities = new EntityCollection(recordsToUpsert) @@ -229,6 +231,7 @@ public void Should_throw_exception_if_upsert_multiple_is_called_with_non_existin public void Should_throw_exception_if_upsert_multiple_is_called_with_a_non_existing_single_alternate_key() { _context.InitializeMetadata(Assembly.GetAssembly(typeof(dv_test))); + _context.SetProperty(new IntegrityOptions()); var dummy_attribute_name = "dv_dummy_attribute"; @@ -259,6 +262,7 @@ public void Should_throw_exception_if_upsert_multiple_is_called_with_a_non_exist public void Should_throw_exception_if_upsert_multiple_is_called_with_a_non_existing_multiple_alternate_key() { _context.InitializeMetadata(Assembly.GetAssembly(typeof(dv_test))); + _context.SetProperty(new IntegrityOptions()); var dummy_attribute_name1 = "dv_dummy_attribute1"; var dummy_attribute_name2 = "dv_dummy_attribute2"; From bd382255af6f29314ae30cff2b9ffd5b34109d32 Mon Sep 17 00:00:00 2001 From: Jordi Date: Sat, 16 Mar 2024 13:10:53 +0100 Subject: [PATCH 18/37] Limit UpsertMultipleRequests tests to v9 --- .../UpsertMultipleRequestTests/UpsertMultipleRequestTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/UpsertMultipleRequestTests/UpsertMultipleRequestTests.cs b/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/UpsertMultipleRequestTests/UpsertMultipleRequestTests.cs index 1025e1c0..adb55301 100644 --- a/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/UpsertMultipleRequestTests/UpsertMultipleRequestTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/UpsertMultipleRequestTests/UpsertMultipleRequestTests.cs @@ -1,3 +1,4 @@ +#if FAKE_XRM_EASY_9 using System; using System.Collections.Generic; using System.Reflection; @@ -337,4 +338,5 @@ public void Should_upsert_two_records_in_upsert_multiple() } } -} \ No newline at end of file +} +#endif \ No newline at end of file From 9225f635edf2c71db70a0e7f243421552e3fb679 Mon Sep 17 00:00:00 2001 From: Jordi Date: Sat, 16 Mar 2024 13:20:03 +0100 Subject: [PATCH 19/37] Resolve many code smells --- .../FakeMessageExecutors/CreateMultipleRequestExecutor.cs | 4 ++-- .../FakeMessageExecutors/UpdateMultipleRequestExecutor.cs | 4 ++-- .../FakeMessageExecutors/UpsertMultipleRequestExecutor.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestExecutor.cs b/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestExecutor.cs index 63711a20..e40eeb63 100644 --- a/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestExecutor.cs +++ b/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestExecutor.cs @@ -52,7 +52,7 @@ public OrganizationResponse Execute(OrganizationRequest request, IXrmFakedContex }; } - private void ValidateRequiredParameters(CreateMultipleRequest request, IXrmFakedContext ctx) + private void ValidateRequiredParameters(CreateMultipleRequest request) { if (request.Targets == null) { @@ -100,7 +100,7 @@ private void ValidateRecord(CreateMultipleRequest request, Entity recordToCreate private void ValidateRequest(CreateMultipleRequest request, IXrmFakedContext ctx) { - ValidateRequiredParameters(request, ctx); + ValidateRequiredParameters(request); BulkOperationsCommon.ValidateEntityName(request.Targets.EntityName, ctx); ValidateRecords(request, ctx); } diff --git a/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/UpdateMultipleRequestExecutor.cs b/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/UpdateMultipleRequestExecutor.cs index 6ea3fffb..b561b381 100644 --- a/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/UpdateMultipleRequestExecutor.cs +++ b/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/UpdateMultipleRequestExecutor.cs @@ -49,7 +49,7 @@ public OrganizationResponse Execute(OrganizationRequest request, IXrmFakedContex }; } - private void ValidateRequiredParameters(UpdateMultipleRequest request, IXrmFakedContext ctx) + private void ValidateRequiredParameters(UpdateMultipleRequest request) { if (request.Targets == null) { @@ -104,7 +104,7 @@ private void ValidateRecord(UpdateMultipleRequest request, Entity recordToUpdate private void ValidateRequest(UpdateMultipleRequest request, IXrmFakedContext ctx) { - ValidateRequiredParameters(request, ctx); + ValidateRequiredParameters(request); BulkOperationsCommon.ValidateEntityName(request.Targets.EntityName, ctx); ValidateRecords(request, ctx); } diff --git a/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/UpsertMultipleRequestExecutor.cs b/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/UpsertMultipleRequestExecutor.cs index d041edfc..18fd924f 100644 --- a/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/UpsertMultipleRequestExecutor.cs +++ b/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/UpsertMultipleRequestExecutor.cs @@ -54,7 +54,7 @@ public OrganizationResponse Execute(OrganizationRequest request, IXrmFakedContex }; } - private void ValidateRequiredParameters(UpsertMultipleRequest request, IXrmFakedContext ctx) + private void ValidateRequiredParameters(UpsertMultipleRequest request) { if (request.Targets == null) { @@ -95,7 +95,7 @@ private void ValidateRecord(UpsertMultipleRequest request, Entity recordToCreate private void ValidateRequest(UpsertMultipleRequest request, IXrmFakedContext ctx) { - ValidateRequiredParameters(request, ctx); + ValidateRequiredParameters(request); BulkOperationsCommon.ValidateEntityName(request.Targets.EntityName, ctx); ValidateRecords(request, ctx); } From faba6b73083c7d87eb8dc224dbca28556e815874 Mon Sep 17 00:00:00 2001 From: Jordi Date: Sat, 16 Mar 2024 13:26:30 +0100 Subject: [PATCH 20/37] Fix other several code smells --- .../Crud/FakeMessageExecutors/BulkOperationsCommon.cs | 2 +- .../FakeMessageExecutors/UpsertMultipleRequestExecutor.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/BulkOperationsCommon.cs b/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/BulkOperationsCommon.cs index 2e840bbe..e74dc3e2 100644 --- a/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/BulkOperationsCommon.cs +++ b/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/BulkOperationsCommon.cs @@ -3,7 +3,7 @@ namespace FakeXrmEasy.Middleware.Crud.FakeMessageExecutors { - internal class BulkOperationsCommon + internal static class BulkOperationsCommon { internal static void ValidateEntityName(string collectionEntityName, IXrmFakedContext ctx) { diff --git a/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/UpsertMultipleRequestExecutor.cs b/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/UpsertMultipleRequestExecutor.cs index 18fd924f..9ce04cfa 100644 --- a/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/UpsertMultipleRequestExecutor.cs +++ b/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/UpsertMultipleRequestExecutor.cs @@ -69,7 +69,7 @@ private void ValidateRequiredParameters(UpsertMultipleRequest request) "System.ArgumentException: The value of the parameter 'Targets' cannot be null or empty."); } } - private void ValidateRecords(UpsertMultipleRequest request, IXrmFakedContext ctx) + private void ValidateRecords(UpsertMultipleRequest request) { var records = request.Targets.Entities; if (records.Count == 0) @@ -80,11 +80,11 @@ private void ValidateRecords(UpsertMultipleRequest request, IXrmFakedContext ctx foreach (var record in records) { - ValidateRecord(request, record, ctx); + ValidateRecord(request, record); } } - private void ValidateRecord(UpsertMultipleRequest request, Entity recordToCreate, IXrmFakedContext ctx) + private void ValidateRecord(UpsertMultipleRequest request, Entity recordToCreate) { if (!request.Targets.EntityName.Equals(recordToCreate.LogicalName)) { @@ -97,7 +97,7 @@ private void ValidateRequest(UpsertMultipleRequest request, IXrmFakedContext ctx { ValidateRequiredParameters(request); BulkOperationsCommon.ValidateEntityName(request.Targets.EntityName, ctx); - ValidateRecords(request, ctx); + ValidateRecords(request); } /// From 991ad16f5ea4b5e6f7d424302ddad6f13b85ad1f Mon Sep 17 00:00:00 2001 From: Jordi Date: Sat, 16 Mar 2024 20:44:54 +0100 Subject: [PATCH 21/37] Refactor xMultiple messages to use service.Create or service.Update methods as opposed to the internal ones. --- .../FakeMessageExecutors/CreateMultipleRequestExecutor.cs | 4 +++- .../FakeMessageExecutors/UpdateMultipleRequestExecutor.cs | 4 +++- .../CreateMultipleRequestTests.cs} | 2 +- .../UpdateMultipleRequestTests.cs | 2 +- .../UpsertMultipleRequestTests.cs | 2 +- 5 files changed, 9 insertions(+), 5 deletions(-) rename tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/{CreateMultipleRequestsTests/CreateMultipleRequestsTests.cs => BulkOperations/CreateMultipleRequestTests.cs} (99%) rename tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/{UpdateMultipleRequestTests => BulkOperations}/UpdateMultipleRequestTests.cs (99%) rename tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/{UpsertMultipleRequestTests => BulkOperations}/UpsertMultipleRequestTests.cs (99%) diff --git a/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestExecutor.cs b/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestExecutor.cs index e40eeb63..40e24124 100644 --- a/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestExecutor.cs +++ b/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestExecutor.cs @@ -38,10 +38,12 @@ public OrganizationResponse Execute(OrganizationRequest request, IXrmFakedContex var records = createMultipleRequest.Targets.Entities; List createdIds = new List(); + + var service = ctx.GetOrganizationService(); foreach (var record in records) { - var id = ctx.CreateEntity(record); + var id = service.Create(record); createdIds.Add(id); } diff --git a/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/UpdateMultipleRequestExecutor.cs b/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/UpdateMultipleRequestExecutor.cs index b561b381..df4ac05b 100644 --- a/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/UpdateMultipleRequestExecutor.cs +++ b/src/FakeXrmEasy.Core/Middleware/Crud/FakeMessageExecutors/UpdateMultipleRequestExecutor.cs @@ -37,10 +37,12 @@ public OrganizationResponse Execute(OrganizationRequest request, IXrmFakedContex ValidateRequest(updateMultipleRequest, ctx); var records = updateMultipleRequest.Targets.Entities; + + var service = ctx.GetOrganizationService(); foreach (var record in records) { - ctx.UpdateEntity(record); + service.Update(record); } return new UpdateMultipleResponse() diff --git a/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestsTests/CreateMultipleRequestsTests.cs b/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/BulkOperations/CreateMultipleRequestTests.cs similarity index 99% rename from tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestsTests/CreateMultipleRequestsTests.cs rename to tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/BulkOperations/CreateMultipleRequestTests.cs index e5dc6e50..a37dee16 100644 --- a/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/CreateMultipleRequestsTests/CreateMultipleRequestsTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/BulkOperations/CreateMultipleRequestTests.cs @@ -12,7 +12,7 @@ using Xunit; using Account = Crm.Account; -namespace FakeXrmEasy.Core.Tests.Middleware.Crud.FakeMessageExecutors.CreateMultipleRequestTests +namespace FakeXrmEasy.Core.Tests.Middleware.Crud.FakeMessageExecutors.BulkOperations { public class CreateMultipleRequestTests : FakeXrmEasyTestsBase { diff --git a/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/UpdateMultipleRequestTests/UpdateMultipleRequestTests.cs b/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/BulkOperations/UpdateMultipleRequestTests.cs similarity index 99% rename from tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/UpdateMultipleRequestTests/UpdateMultipleRequestTests.cs rename to tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/BulkOperations/UpdateMultipleRequestTests.cs index 8506012c..749bed44 100644 --- a/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/UpdateMultipleRequestTests/UpdateMultipleRequestTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/BulkOperations/UpdateMultipleRequestTests.cs @@ -11,7 +11,7 @@ using Microsoft.Xrm.Sdk.Query; using Xunit; -namespace FakeXrmEasy.Core.Tests.Middleware.Crud.FakeMessageExecutors.UpdateMultipleRequestTests +namespace FakeXrmEasy.Core.Tests.Middleware.Crud.FakeMessageExecutors.BulkOperations { public class UpdateMultipleRequestTests: FakeXrmEasyTestsBase { diff --git a/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/UpsertMultipleRequestTests/UpsertMultipleRequestTests.cs b/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/BulkOperations/UpsertMultipleRequestTests.cs similarity index 99% rename from tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/UpsertMultipleRequestTests/UpsertMultipleRequestTests.cs rename to tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/BulkOperations/UpsertMultipleRequestTests.cs index adb55301..a8d498a0 100644 --- a/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/UpsertMultipleRequestTests/UpsertMultipleRequestTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/Middleware/Crud/FakeMessageExecutors/BulkOperations/UpsertMultipleRequestTests.cs @@ -11,7 +11,7 @@ using Microsoft.Xrm.Sdk.Query; using Xunit; -namespace FakeXrmEasy.Core.Tests.Middleware.Crud.FakeMessageExecutors.UpsertMultipleRequestTests +namespace FakeXrmEasy.Core.Tests.Middleware.Crud.FakeMessageExecutors.BulkOperations { public class UpsertMultipleRequestTests: FakeXrmEasyTestsBase { From a0bd3e54ea2494255f8cdaf4d1993c7b9fb8fc0c Mon Sep 17 00:00:00 2001 From: Jordi Date: Sat, 16 Mar 2024 21:05:36 +0100 Subject: [PATCH 22/37] Resolves DynamicsValue/fake-xrm-easy#107, introduced user-defined exception. --- CHANGELOG.md | 2 ++ ...cordWithInvalidEntityReferenceException.cs | 22 +++++++++++++++++++ src/FakeXrmEasy.Core/XrmFakedContext.cs | 7 +++--- .../FakeContextTests/FakeContextTests.cs | 4 ++-- 4 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 src/FakeXrmEasy.Core/Exceptions/EntityRecordWithInvalidEntityReferenceException.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 14763c69..943cc164 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ - Added FileAttributeMetadata support to MetadataGenerator - Added support for bulk operations: CreateMultipleRequest, UpdateMultipleRequest +- Added new exception to make the initialization of entity records with attributes with a null entity reference more obvious (thanks Betim) - https://github.com/DynamicsValue/fake-xrm-easy/issues/107 + ### Changed diff --git a/src/FakeXrmEasy.Core/Exceptions/EntityRecordWithInvalidEntityReferenceException.cs b/src/FakeXrmEasy.Core/Exceptions/EntityRecordWithInvalidEntityReferenceException.cs new file mode 100644 index 00000000..8d523dff --- /dev/null +++ b/src/FakeXrmEasy.Core/Exceptions/EntityRecordWithInvalidEntityReferenceException.cs @@ -0,0 +1,22 @@ +using System; +using Microsoft.Xrm.Sdk; + +namespace FakeXrmEasy.Core.Exceptions +{ + /// + /// Throws an exception when an entity record has an entity reference attribute with a null logical name + /// + public class NullLogicalNameEntityReferenceException: Exception + { + /// + /// Default constructor + /// + /// The entity record that has the invalid EntityReference attribute + /// The name of the attribute in the entity record with the invalid entity reference + public NullLogicalNameEntityReferenceException(Entity record, string attributeLogicalName) : + base ($"The entity record with logical name '{record.LogicalName}' and Id '{record.Id}' has an entity reference attribute '{attributeLogicalName}' with a null logical name.") + { + + } + } +} \ No newline at end of file diff --git a/src/FakeXrmEasy.Core/XrmFakedContext.cs b/src/FakeXrmEasy.Core/XrmFakedContext.cs index 7e834877..82de95fc 100644 --- a/src/FakeXrmEasy.Core/XrmFakedContext.cs +++ b/src/FakeXrmEasy.Core/XrmFakedContext.cs @@ -19,6 +19,7 @@ using System.Reflection; using System.Runtime.CompilerServices; +using FakeXrmEasy.Core.Exceptions; [assembly: InternalsVisibleTo("FakeXrmEasy.Core.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c124cb50761165a765adf6078bde555a7c5a2b692ed6e6ec9df0bd7d20da69170bae9bf95e874fa50995cc080af404ccad36515fa509c4ea6599a0502c1642db254a293e023c47c79ce69889c6ba921d124d896d87f0baaa9ea1d87b28589ffbe7b08492606bacef19dc4bc4cefb0d525be63ee722b02dc8c79688a7a8f623a2")] @@ -300,7 +301,7 @@ public virtual void Initialize(IEnumerable entities) throw new InvalidOperationException("The entities parameter must be not null"); } - foreach (var e in entities) + foreach (var e in entities) { ValidateEntityReferences(e); AddEntityWithDefaults(e, true); @@ -313,9 +314,9 @@ private void ValidateEntityReferences(Entity e) { foreach (var item in e.Attributes) { - if (item.Value is EntityReference entityReference && String.IsNullOrEmpty(entityReference.LogicalName)) + if (item.Value is EntityReference entityReference && string.IsNullOrEmpty(entityReference.LogicalName)) { - throw new Exception($"Broken EntityReference record during Initialize() for column '{item.Key}' of a '{e.LogicalName}' record."); + throw new NullLogicalNameEntityReferenceException(e, item.Key); } } } diff --git a/tests/FakeXrmEasy.Core.Tests/FakeContextTests/FakeContextTests.cs b/tests/FakeXrmEasy.Core.Tests/FakeContextTests/FakeContextTests.cs index 79fb4825..59bc0d10 100644 --- a/tests/FakeXrmEasy.Core.Tests/FakeContextTests/FakeContextTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/FakeContextTests/FakeContextTests.cs @@ -321,7 +321,7 @@ public void Should_return_error_if_entity_id_does_not_exists() } [Fact] - public void Should_return_error_if_contains_broken_entityreference() + public void Should_return_error_if_contains_an_entity_reference_with_a_null_logical_name() { var contact = new Contact() { @@ -331,7 +331,7 @@ public void Should_return_error_if_contains_broken_entityreference() ParentCustomerId = new EntityReference("", Guid.NewGuid()) }; - Assert.Throws(() => _context.Initialize(contact)); + Assert.Throws(() => _context.Initialize(contact)); } [Fact] From 04ecb4d847d9488247af094729941a5a19a7b497 Mon Sep 17 00:00:00 2001 From: Jordi Date: Sat, 16 Mar 2024 22:12:13 +0100 Subject: [PATCH 23/37] Resolves DynamicsValue/fake-xrm-easy#63 --- CHANGELOG.md | 2 +- .../ConditionExpressionTests.cs | 7 ++----- .../FakeContextTestFormattedValues.cs | 2 +- .../FakeContextTestTranslateQueryExpression.cs | 2 +- .../TranslateQueryExpressionTests/FilterExpressionTests.cs | 4 +--- .../OperatorTests/DateTime/DateTimeOperatorsTests.cs | 2 +- .../MultiSelectOptionSet/MultiSelectOptionSetTests.cs | 2 +- .../OperatorTests/Strings/StringOperatorsTests.cs | 2 +- .../TranslateQueryExpressionTests/OrderByTests.cs | 2 +- 9 files changed, 10 insertions(+), 15 deletions(-) rename tests/FakeXrmEasy.Core.Tests/{FakeContextTests => Query}/TranslateQueryExpressionTests/ConditionExpressionTests.cs (99%) rename tests/FakeXrmEasy.Core.Tests/{FakeContextTests => Query}/TranslateQueryExpressionTests/FakeContextTestFormattedValues.cs (98%) rename tests/FakeXrmEasy.Core.Tests/{FakeContextTests => Query}/TranslateQueryExpressionTests/FakeContextTestTranslateQueryExpression.cs (99%) rename tests/FakeXrmEasy.Core.Tests/{FakeContextTests => Query}/TranslateQueryExpressionTests/FilterExpressionTests.cs (96%) rename tests/FakeXrmEasy.Core.Tests/{FakeContextTests => Query}/TranslateQueryExpressionTests/OperatorTests/DateTime/DateTimeOperatorsTests.cs (98%) rename tests/FakeXrmEasy.Core.Tests/{FakeContextTests => Query}/TranslateQueryExpressionTests/OperatorTests/MultiSelectOptionSet/MultiSelectOptionSetTests.cs (99%) rename tests/FakeXrmEasy.Core.Tests/{FakeContextTests => Query}/TranslateQueryExpressionTests/OperatorTests/Strings/StringOperatorsTests.cs (98%) rename tests/FakeXrmEasy.Core.Tests/{FakeContextTests => Query}/TranslateQueryExpressionTests/OrderByTests.cs (99%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 943cc164..bc21bd00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,9 @@ - Added support for bulk operations: CreateMultipleRequest, UpdateMultipleRequest - Added new exception to make the initialization of entity records with attributes with a null entity reference more obvious (thanks Betim) - https://github.com/DynamicsValue/fake-xrm-easy/issues/107 - ### Changed +- Resolves referencing EntityAlias or EntityName in conditions inside nested filters of a LinkedEntity (thanks Temmy) - https://github.com/DynamicsValue/fake-xrm-easy/issues/63 - Resolves Resolving entity references by Alternate Keys when EntityMetadata is used that doesn't have any Keys. - https://github.com/DynamicsValue/fake-xrm-easy/issues/138 ## [2.4.2] diff --git a/tests/FakeXrmEasy.Core.Tests/FakeContextTests/TranslateQueryExpressionTests/ConditionExpressionTests.cs b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/ConditionExpressionTests.cs similarity index 99% rename from tests/FakeXrmEasy.Core.Tests/FakeContextTests/TranslateQueryExpressionTests/ConditionExpressionTests.cs rename to tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/ConditionExpressionTests.cs index 2572a71a..2e4ff81f 100644 --- a/tests/FakeXrmEasy.Core.Tests/FakeContextTests/TranslateQueryExpressionTests/ConditionExpressionTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/ConditionExpressionTests.cs @@ -8,11 +8,10 @@ using System.Linq; using Xunit; -namespace FakeXrmEasy.Core.Tests.FakeContextTests.TranslateQueryExpressionTests +namespace FakeXrmEasy.Core.Tests.Query.TranslateQueryExpressionTests { public class ConditionExpressionTests: FakeXrmEasyTestsBase { - [Fact] public void When_executing_a_query_expression_with_an_unsupported_exception_is_thrown() { @@ -136,7 +135,6 @@ public void When_executing_a_query_expression_with_a_null_operator_right_result_ [Fact] public void When_executing_a_query_expression_equals_operator_is_case_insensitive() { - _service.Create(new Contact { FirstName = "Jimmy" }); var qe = new QueryExpression("contact"); @@ -144,8 +142,7 @@ public void When_executing_a_query_expression_equals_operator_is_case_insensitiv Assert.Single(_service.RetrieveMultiple(qe).Entities); } - - + [Fact] public void When_executing_a_query_expression_attributes_returned_are_case_sensitive() { diff --git a/tests/FakeXrmEasy.Core.Tests/FakeContextTests/TranslateQueryExpressionTests/FakeContextTestFormattedValues.cs b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/FakeContextTestFormattedValues.cs similarity index 98% rename from tests/FakeXrmEasy.Core.Tests/FakeContextTests/TranslateQueryExpressionTests/FakeContextTestFormattedValues.cs rename to tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/FakeContextTestFormattedValues.cs index 466a5576..08e063aa 100644 --- a/tests/FakeXrmEasy.Core.Tests/FakeContextTests/TranslateQueryExpressionTests/FakeContextTestFormattedValues.cs +++ b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/FakeContextTestFormattedValues.cs @@ -7,7 +7,7 @@ using System.Linq; using Xunit; -namespace FakeXrmEasy.Core.Tests.FakeContextTests.TranslateQueryExpressionTests +namespace FakeXrmEasy.Core.Tests.Query.TranslateQueryExpressionTests { public class FormattedValuesTests: FakeXrmEasyTestsBase { diff --git a/tests/FakeXrmEasy.Core.Tests/FakeContextTests/TranslateQueryExpressionTests/FakeContextTestTranslateQueryExpression.cs b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/FakeContextTestTranslateQueryExpression.cs similarity index 99% rename from tests/FakeXrmEasy.Core.Tests/FakeContextTests/TranslateQueryExpressionTests/FakeContextTestTranslateQueryExpression.cs rename to tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/FakeContextTestTranslateQueryExpression.cs index 14cc79cb..8e47a8ca 100644 --- a/tests/FakeXrmEasy.Core.Tests/FakeContextTests/TranslateQueryExpressionTests/FakeContextTestTranslateQueryExpression.cs +++ b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/FakeContextTestTranslateQueryExpression.cs @@ -10,7 +10,7 @@ using FakeXrmEasy.Abstractions; using FakeXrmEasy.Query; -namespace FakeXrmEasy.Core.Tests +namespace FakeXrmEasy.Core.Tests.Query.TranslateQueryExpressionTests { public class FakeContextTestTranslateQueryExpression: FakeXrmEasyTestsBase { diff --git a/tests/FakeXrmEasy.Core.Tests/FakeContextTests/TranslateQueryExpressionTests/FilterExpressionTests.cs b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/FilterExpressionTests.cs similarity index 96% rename from tests/FakeXrmEasy.Core.Tests/FakeContextTests/TranslateQueryExpressionTests/FilterExpressionTests.cs rename to tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/FilterExpressionTests.cs index cbbe55e8..715255f5 100644 --- a/tests/FakeXrmEasy.Core.Tests/FakeContextTests/TranslateQueryExpressionTests/FilterExpressionTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/FilterExpressionTests.cs @@ -6,14 +6,13 @@ using System.Linq; using Xunit; -namespace FakeXrmEasy.Core.Tests.FakeContextTests.TranslateQueryExpressionTests +namespace FakeXrmEasy.Core.Tests.Query.TranslateQueryExpressionTests { public class FilterExpressionTests: FakeXrmEasyTestsBase { [Fact] public void When_executing_a_query_expression_with_2_filters_combined_with_an_or_filter_right_result_is_returned() { - var contact1 = new Entity("contact") { Id = Guid.NewGuid() }; contact1["fullname"] = "Contact 1"; contact1["firstname"] = "First 1"; var contact2 = new Entity("contact") { Id = Guid.NewGuid() }; contact2["fullname"] = "Contact 2"; contact2["firstname"] = "First 2"; @@ -40,7 +39,6 @@ public void When_executing_a_query_expression_with_2_filters_combined_with_an_or [Fact] public void When_executing_a_query_expression_with_1_filters_combined_with_1_condition_and_or_filter_right_result_is_returned() { - var contact1 = new Entity("contact") { Id = Guid.NewGuid() }; contact1["fullname"] = "Contact 1"; contact1["firstname"] = "First 1"; var contact2 = new Entity("contact") { Id = Guid.NewGuid() }; contact2["fullname"] = "Contact 2"; contact2["firstname"] = "First 2"; diff --git a/tests/FakeXrmEasy.Core.Tests/FakeContextTests/TranslateQueryExpressionTests/OperatorTests/DateTime/DateTimeOperatorsTests.cs b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/DateTime/DateTimeOperatorsTests.cs similarity index 98% rename from tests/FakeXrmEasy.Core.Tests/FakeContextTests/TranslateQueryExpressionTests/OperatorTests/DateTime/DateTimeOperatorsTests.cs rename to tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/DateTime/DateTimeOperatorsTests.cs index 61d31111..0bc72f07 100644 --- a/tests/FakeXrmEasy.Core.Tests/FakeContextTests/TranslateQueryExpressionTests/OperatorTests/DateTime/DateTimeOperatorsTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/DateTime/DateTimeOperatorsTests.cs @@ -7,7 +7,7 @@ using Xunit; using FakeXrmEasy.Query; -namespace FakeXrmEasy.Core.Tests.FakeContextTests.TranslateQueryExpressionTests.OperatorTests.DateTimes +namespace FakeXrmEasy.Core.Tests.Query.TranslateQueryExpressionTests.OperatorTests.DateTimes { public class DateTimeOperatorsTests: FakeXrmEasyTestsBase { diff --git a/tests/FakeXrmEasy.Core.Tests/FakeContextTests/TranslateQueryExpressionTests/OperatorTests/MultiSelectOptionSet/MultiSelectOptionSetTests.cs b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/MultiSelectOptionSet/MultiSelectOptionSetTests.cs similarity index 99% rename from tests/FakeXrmEasy.Core.Tests/FakeContextTests/TranslateQueryExpressionTests/OperatorTests/MultiSelectOptionSet/MultiSelectOptionSetTests.cs rename to tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/MultiSelectOptionSet/MultiSelectOptionSetTests.cs index 87a28849..e00f9485 100644 --- a/tests/FakeXrmEasy.Core.Tests/FakeContextTests/TranslateQueryExpressionTests/OperatorTests/MultiSelectOptionSet/MultiSelectOptionSetTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/MultiSelectOptionSet/MultiSelectOptionSetTests.cs @@ -6,7 +6,7 @@ using Microsoft.Xrm.Sdk.Query; using Xunit; -namespace FakeXrmEasy.Core.Tests.FakeContextTests.TranslateQueryExpressionTests.OperatorTests.MultiSelectOptionSet +namespace FakeXrmEasy.Core.Tests.Query.TranslateQueryExpressionTests.OperatorTests.MultiSelectOptionSet { public class MultiSelectOptionSetTests: FakeXrmEasyTestsBase { diff --git a/tests/FakeXrmEasy.Core.Tests/FakeContextTests/TranslateQueryExpressionTests/OperatorTests/Strings/StringOperatorsTests.cs b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/Strings/StringOperatorsTests.cs similarity index 98% rename from tests/FakeXrmEasy.Core.Tests/FakeContextTests/TranslateQueryExpressionTests/OperatorTests/Strings/StringOperatorsTests.cs rename to tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/Strings/StringOperatorsTests.cs index 4defa99d..9a824f53 100644 --- a/tests/FakeXrmEasy.Core.Tests/FakeContextTests/TranslateQueryExpressionTests/OperatorTests/Strings/StringOperatorsTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/Strings/StringOperatorsTests.cs @@ -7,7 +7,7 @@ using Xunit; using FakeXrmEasy.Query; -namespace FakeXrmEasy.Core.Tests.FakeContextTests.TranslateQueryExpressionTests.OperatorTests.Strings +namespace FakeXrmEasy.Core.Tests.Query.TranslateQueryExpressionTests.OperatorTests.Strings { public class StringOperatorsTests: FakeXrmEasyTestsBase { diff --git a/tests/FakeXrmEasy.Core.Tests/FakeContextTests/TranslateQueryExpressionTests/OrderByTests.cs b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OrderByTests.cs similarity index 99% rename from tests/FakeXrmEasy.Core.Tests/FakeContextTests/TranslateQueryExpressionTests/OrderByTests.cs rename to tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OrderByTests.cs index fc4c4b8f..4a7ffa0c 100644 --- a/tests/FakeXrmEasy.Core.Tests/FakeContextTests/TranslateQueryExpressionTests/OrderByTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OrderByTests.cs @@ -6,7 +6,7 @@ using System.Linq; using Xunit; -namespace FakeXrmEasy.Core.Tests.FakeContextTests.TranslateQueryExpressionTests +namespace FakeXrmEasy.Core.Tests.Query.TranslateQueryExpressionTests { public class OrderByTests: FakeXrmEasyTestsBase { From a675a4822c5f8ea0f740a3fb76f27faf8cd7a243 Mon Sep 17 00:00:00 2001 From: Jordi Date: Sun, 17 Mar 2024 16:09:35 +0100 Subject: [PATCH 24/37] Add further tests for DynamicsValue/fake-xrm-easy#139 --- CHANGELOG.md | 3 +- ...onditionExpressionExtensions.BeginsWith.cs | 3 +- .../ConditionExpressionExtensions.EndsWith.cs | 2 +- .../Query/QueryTests/BusinessIdTests.cs | 1 - .../Strings/LikeOperatorTests.cs | 147 ++++++++++++++++++ .../ProjectionTests.cs | 2 +- .../TopCountTests.cs | 2 +- 7 files changed, 153 insertions(+), 7 deletions(-) create mode 100644 tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/Strings/LikeOperatorTests.cs rename tests/FakeXrmEasy.Core.Tests/Query/{QueryTranslationTests => TranslateQueryExpressionTests}/ProjectionTests.cs (93%) rename tests/FakeXrmEasy.Core.Tests/Query/{QueryExpressionTests => TranslateQueryExpressionTests}/TopCountTests.cs (98%) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc21bd00..f68e7760 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,9 @@ ### Changed +- Added extended wildcard support for the Like operator (thanks Betim) - https://github.com/DynamicsValue/fake-xrm-easy/issues/139 - Resolves referencing EntityAlias or EntityName in conditions inside nested filters of a LinkedEntity (thanks Temmy) - https://github.com/DynamicsValue/fake-xrm-easy/issues/63 -- Resolves Resolving entity references by Alternate Keys when EntityMetadata is used that doesn't have any Keys. - https://github.com/DynamicsValue/fake-xrm-easy/issues/138 +- Resolves Resolving entity references by Alternate Keys when EntityMetadata doesn't have any Keys. - https://github.com/DynamicsValue/fake-xrm-easy/issues/138 ## [2.4.2] diff --git a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.BeginsWith.cs b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.BeginsWith.cs index ad76a3fb..1a26e822 100644 --- a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.BeginsWith.cs +++ b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.BeginsWith.cs @@ -14,8 +14,7 @@ internal static Expression ToBeginsWithExpression(this TypedConditionExpression var computedCondition = new ConditionExpression(c.AttributeName, c.Operator, c.Values.Select(x => x.ToString() + "%").ToList()); var typedComputedCondition = new TypedConditionExpression(computedCondition); typedComputedCondition.AttributeType = tc.AttributeType; - - // Perhaps we are introducing some problems by converting a StartsWith to a Like Operator? + return typedComputedCondition.ToLikeExpression(getAttributeValueExpr, containsAttributeExpr); } } diff --git a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.EndsWith.cs b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.EndsWith.cs index e7fcc3d9..d1a8d417 100644 --- a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.EndsWith.cs +++ b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.EndsWith.cs @@ -10,7 +10,7 @@ internal static Expression ToEndsWithExpression(this TypedConditionExpression tc { var c = tc.CondExpression; - //Append a ´%´at the end of each condition value + //Append a ´%´at the beginning of each condition value var computedCondition = new ConditionExpression(c.AttributeName, c.Operator, c.Values.Select(x => "%" + x.ToString()).ToList()); var typedComputedCondition = new TypedConditionExpression(computedCondition); typedComputedCondition.AttributeType = tc.AttributeType; diff --git a/tests/FakeXrmEasy.Core.Tests/Query/QueryTests/BusinessIdTests.cs b/tests/FakeXrmEasy.Core.Tests/Query/QueryTests/BusinessIdTests.cs index ec795222..8aa726ea 100644 --- a/tests/FakeXrmEasy.Core.Tests/Query/QueryTests/BusinessIdTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/Query/QueryTests/BusinessIdTests.cs @@ -66,6 +66,5 @@ public void FetchXml_Operator_EqualBusinessId_Execution() Assert.Single(_collection.Entities); Assert.Equal(Guid.Parse(_resource2Id), _collection.Entities[0].Id); } - } } diff --git a/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/Strings/LikeOperatorTests.cs b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/Strings/LikeOperatorTests.cs new file mode 100644 index 00000000..d1cb50e4 --- /dev/null +++ b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/Strings/LikeOperatorTests.cs @@ -0,0 +1,147 @@ +using System; +using Crm; +using Microsoft.Xrm.Sdk.Query; +using Xunit; +using Contact = DataverseEntities.Contact; + +namespace FakeXrmEasy.Core.Tests.Query.TranslateQueryExpressionTests.OperatorTests.Strings +{ + public class LikeOperatorTests: FakeXrmEasyTestsBase + { + private readonly Contact _contact; + + public LikeOperatorTests() + { + _contact = new Contact { Id = Guid.NewGuid(), FirstName = "Jimmy" }; + } + + [Fact] + public void Should_return_records_where_percentage_wildcard_is_at_the_end() + { + _context.Initialize(_contact); + + var qe = new QueryExpression("contact"); + qe.Criteria.AddCondition("firstname", ConditionOperator.Like, "jim%"); + + Assert.Single(_service.RetrieveMultiple(qe).Entities); + } + + [Fact] + public void Should_return_records_where_percentage_wildcard_is_at_the_beginning() + { + _context.Initialize(_contact); + + var qe = new QueryExpression("contact"); + qe.Criteria.AddCondition("firstname", ConditionOperator.Like, "%mmy"); + + Assert.Single(_service.RetrieveMultiple(qe).Entities); + } + + [Fact] + public void Should_return_records_where_percentage_wildcard_is_in_the_middle() + { + _context.Initialize(_contact); + + var qe = new QueryExpression("contact"); + qe.Criteria.AddCondition("firstname", ConditionOperator.Like, "j%my"); + + Assert.Single(_service.RetrieveMultiple(qe).Entities); + } + + [Fact] + public void Should_return_records_with_underscore_wildcard() + { + _context.Initialize(_contact); + + var qe = new QueryExpression("contact"); + qe.Criteria.AddCondition("firstname", ConditionOperator.Like, "j_mm_"); + + Assert.Single(_service.RetrieveMultiple(qe).Entities); + } + + [Theory] + [InlineData("Jimmy", "[i-k]immy")] + [InlineData("Alan", "[a-c]lan")] + public void Should_return_records_with_character_range_wildcard(string firstName, string conditionValue) + { + _contact.FirstName = firstName; + _context.Initialize(_contact); + + var qe = new QueryExpression("contact"); + qe.Criteria.AddCondition("firstname", ConditionOperator.Like, conditionValue); + + Assert.Single(_service.RetrieveMultiple(qe).Entities); + } + + [Theory] + [InlineData("Jimmy", "[^a-i]immy")] + [InlineData("Alan", "a[^m-z]an")] + public void Should_return_records_outside_character_range_wildcard(string firstName, string conditionValue) + { + _contact.FirstName = firstName; + _context.Initialize(_contact); + + var qe = new QueryExpression("contact"); + qe.Criteria.AddCondition("firstname", ConditionOperator.Like, conditionValue); + + Assert.Single(_service.RetrieveMultiple(qe).Entities); + } + + [Theory] + [InlineData("Jim", "[jk]im")] + [InlineData("Kim", "[jk]im")] + public void Should_return_records_with_character_set_wildcard(string firstName, string conditionValue) + { + _contact.FirstName = firstName; + _context.Initialize(_contact); + + var qe = new QueryExpression("contact"); + qe.Criteria.AddCondition("firstname", ConditionOperator.Like, conditionValue); + + Assert.Single(_service.RetrieveMultiple(qe).Entities); + } + + [Theory] + [InlineData("Jim", "[^qwertyuiopasdfghklzxcvbnm]im")] + [InlineData("Kim", "[^qwertyuiopasdfghjlñzxcvbnm]im")] + public void Should_return_records_outside_character_set_wildcard(string firstName, string conditionValue) + { + _contact.FirstName = firstName; + _context.Initialize(_contact); + + var qe = new QueryExpression("contact"); + qe.Criteria.AddCondition("firstname", ConditionOperator.Like, conditionValue); + + Assert.Single(_service.RetrieveMultiple(qe).Entities); + } + + [Theory] + [InlineData("INV-761")] + [InlineData("INV-300")] + public void Should_return_records_with_a_combination_of_wildcards(string invoiceNumber) + { + var invoice = new Invoice() { Id = Guid.NewGuid(), Name = invoiceNumber }; + _context.Initialize(invoice); + + var qe = new QueryExpression("invoice"); + qe.Criteria.AddCondition("name", ConditionOperator.Like, "INV-[0-9][0-9][0-9]"); + + Assert.Single(_service.RetrieveMultiple(qe).Entities); + } + + [Theory] + [InlineData("INV-4253")] + [InlineData("INV-65D")] + public void Should_not_return_records_that_do_not_match_a_combination_of_wildcards(string invoiceNumber) + { + var invoice = new Invoice() { Id = Guid.NewGuid(), Name = invoiceNumber }; + _context.Initialize(invoice); + + var qe = new QueryExpression("invoice"); + qe.Criteria.AddCondition("name", ConditionOperator.Like, "INV-[0-9][0-9][0-9]"); + + Assert.Empty(_service.RetrieveMultiple(qe).Entities); + } + + } +} \ No newline at end of file diff --git a/tests/FakeXrmEasy.Core.Tests/Query/QueryTranslationTests/ProjectionTests.cs b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/ProjectionTests.cs similarity index 93% rename from tests/FakeXrmEasy.Core.Tests/Query/QueryTranslationTests/ProjectionTests.cs rename to tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/ProjectionTests.cs index 95b59762..49b43d97 100644 --- a/tests/FakeXrmEasy.Core.Tests/Query/QueryTranslationTests/ProjectionTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/ProjectionTests.cs @@ -3,7 +3,7 @@ using System; using Xunit; -namespace FakeXrmEasy.Core.Tests.FakeContextTests.QueryTranslationTests +namespace FakeXrmEasy.Core.Tests.Query.TranslateQueryExpressionTests { public class ProjectionTests : FakeXrmEasyTestsBase { diff --git a/tests/FakeXrmEasy.Core.Tests/Query/QueryExpressionTests/TopCountTests.cs b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/TopCountTests.cs similarity index 98% rename from tests/FakeXrmEasy.Core.Tests/Query/QueryExpressionTests/TopCountTests.cs rename to tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/TopCountTests.cs index 3197091e..a7a6d8bc 100644 --- a/tests/FakeXrmEasy.Core.Tests/Query/QueryExpressionTests/TopCountTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/TopCountTests.cs @@ -6,7 +6,7 @@ using System.ServiceModel; using Xunit; -namespace FakeXrmEasy.Core.Tests.Query.QueryExpressionTests +namespace FakeXrmEasy.Core.Tests.Query.TranslateQueryExpressionTests { public class TopCountTests : FakeXrmEasyTestsBase { From 87471b37ba6bd74dda95579c45c1fa2dfef595cf Mon Sep 17 00:00:00 2001 From: Jordi Date: Sun, 17 Mar 2024 18:32:59 +0100 Subject: [PATCH 25/37] Update formatting --- .../ConditionExpressionExtensions.Like.cs | 12 +- .../Query/FetchXml/ConditionOperatorTests.cs | 181 +----------------- .../Strings/LikeOperatorTests.cs | 154 +++++++++++++++ .../Strings/LikeOperatorTests.cs | 1 - 4 files changed, 167 insertions(+), 181 deletions(-) create mode 100644 tests/FakeXrmEasy.Core.Tests/Query/FetchXml/OperatorTests/Strings/LikeOperatorTests.cs diff --git a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Like.cs b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Like.cs index a3194ec2..107e4155 100644 --- a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Like.cs +++ b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Like.cs @@ -42,7 +42,7 @@ internal static Expression ToLikeExpression(this TypedConditionExpression tc, Ex expOrValues = Expression.Or(expOrValues, Expression.Call( Expression.Constant(regex), typeof(Regex).GetMethod("IsMatch", new Type[] { typeof(string) }), - convertedValueToStrAndToLower) //Linq2CRM adds the percentage value to be executed as a LIKE operator, here we are replacing it to just use the appropiate method + convertedValueToStrAndToLower) //Linq2CRM adds the percentage value to be executed as a LIKE operator, here we are replacing it to just use the appropriate method ); } @@ -53,7 +53,15 @@ internal static Expression ToLikeExpression(this TypedConditionExpression tc, Ex private static string ConvertToRegexDefinition(string value) { - return value.Replace("\\", "\\\\").Replace("(", "\\(").Replace("{", "\\{").Replace(".", "\\.").Replace("*", "\\*").Replace("+", "\\+").Replace("?", "\\?").Replace("%", ".*").Replace("_", "."); + return value.Replace("\\", "\\\\") + .Replace("(", "\\(") + .Replace("{", "\\{") + .Replace(".", "\\.") + .Replace("*", "\\*") + .Replace("+", "\\+") + .Replace("?", "\\?") + .Replace("%", ".*") + .Replace("_", "."); } } } diff --git a/tests/FakeXrmEasy.Core.Tests/Query/FetchXml/ConditionOperatorTests.cs b/tests/FakeXrmEasy.Core.Tests/Query/FetchXml/ConditionOperatorTests.cs index 1291b456..4f919ed4 100644 --- a/tests/FakeXrmEasy.Core.Tests/Query/FetchXml/ConditionOperatorTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/Query/FetchXml/ConditionOperatorTests.cs @@ -152,7 +152,6 @@ public void FetchXml_Operator_Eq() [Fact] public void FetchXml_Operator_Ne() { - var fetchXml = @" @@ -176,7 +175,6 @@ public void FetchXml_Operator_Ne() [Fact] public void FetchXml_Operator_Neq() { - var fetchXml = @" @@ -196,59 +194,10 @@ public void FetchXml_Operator_Neq() Assert.Equal(ConditionOperator.NotEqual, query.Criteria.Conditions[0].Operator); Assert.Equal("Messi", query.Criteria.Conditions[0].Values[0].ToString()); } - - [Fact] - public void FetchXml_Operator_Like() - { - - var fetchXml = @" - - - - - - - - - "; - - var query = fetchXml.ToQueryExpression(_context); - - Assert.True(query.Criteria != null); - Assert.Single(query.Criteria.Conditions); - Assert.Equal("fullname", query.Criteria.Conditions[0].AttributeName); - Assert.Equal(ConditionOperator.Like, query.Criteria.Conditions[0].Operator); - Assert.Equal("%Messi%", query.Criteria.Conditions[0].Values[0].ToString()); - } - - [Fact] - public void FetchXml_Operator_Like_As_BeginsWith() - { - - var fetchXml = @" - - - - - - - - - "; - - var query = fetchXml.ToQueryExpression(_context); - - Assert.True(query.Criteria != null); - Assert.Single(query.Criteria.Conditions); - Assert.Equal("fullname", query.Criteria.Conditions[0].AttributeName); - Assert.Equal(ConditionOperator.Like, query.Criteria.Conditions[0].Operator); - Assert.Equal("Messi%", query.Criteria.Conditions[0].Values[0].ToString()); - } - + [Fact] public void FetchXml_Operator_BeginsWith() { - var fetchXml = @" @@ -272,7 +221,6 @@ public void FetchXml_Operator_BeginsWith() [Fact] public void FetchXml_Operator_NotBeginWith() { - var fetchXml = @" @@ -292,35 +240,10 @@ public void FetchXml_Operator_NotBeginWith() Assert.Equal(ConditionOperator.DoesNotBeginWith, query.Criteria.Conditions[0].Operator); Assert.Equal("Messi", query.Criteria.Conditions[0].Values[0].ToString()); } - - [Fact] - public void FetchXml_Operator_Like_As_EndsWith() - { - - var fetchXml = @" - - - - - - - - - "; - - var query = fetchXml.ToQueryExpression(_context); - - Assert.True(query.Criteria != null); - Assert.Single(query.Criteria.Conditions); - Assert.Equal("fullname", query.Criteria.Conditions[0].AttributeName); - Assert.Equal(ConditionOperator.Like, query.Criteria.Conditions[0].Operator); - Assert.Equal("%Messi", query.Criteria.Conditions[0].Values[0].ToString()); - } - + [Fact] public void FetchXml_Operator_EndsWith() { - var fetchXml = @" @@ -344,7 +267,6 @@ public void FetchXml_Operator_EndsWith() [Fact] public void FetchXml_Operator_NotEndWith() { - var fetchXml = @" @@ -365,82 +287,9 @@ public void FetchXml_Operator_NotEndWith() Assert.Equal("Messi", query.Criteria.Conditions[0].Values[0].ToString()); } - [Fact] - public void FetchXml_Operator_NotLike() - { - - var fetchXml = @" - - - - - - - - - "; - - var query = fetchXml.ToQueryExpression(_context); - - Assert.True(query.Criteria != null); - Assert.Single(query.Criteria.Conditions); - Assert.Equal("fullname", query.Criteria.Conditions[0].AttributeName); - Assert.Equal(ConditionOperator.NotLike, query.Criteria.Conditions[0].Operator); - Assert.Equal("%Messi%", query.Criteria.Conditions[0].Values[0].ToString()); - } - - [Fact] - public void FetchXml_Operator_NotLike_As_Not_BeginWith() - { - - var fetchXml = @" - - - - - - - - - "; - - var query = fetchXml.ToQueryExpression(_context); - - Assert.True(query.Criteria != null); - Assert.Single(query.Criteria.Conditions); - Assert.Equal("fullname", query.Criteria.Conditions[0].AttributeName); - Assert.Equal(ConditionOperator.NotLike, query.Criteria.Conditions[0].Operator); - Assert.Equal("Messi%", query.Criteria.Conditions[0].Values[0].ToString()); - } - - [Fact] - public void FetchXml_Operator_NotLike_As_Not_EndWith() - { - - var fetchXml = @" - - - - - - - - - "; - - var query = fetchXml.ToQueryExpression(_context); - - Assert.True(query.Criteria != null); - Assert.Single(query.Criteria.Conditions); - Assert.Equal("fullname", query.Criteria.Conditions[0].AttributeName); - Assert.Equal(ConditionOperator.NotLike, query.Criteria.Conditions[0].Operator); - Assert.Equal("%Messi", query.Criteria.Conditions[0].Values[0].ToString()); - } - [Fact] public void FetchXml_Operator_In() { - var fetchXml = @" @@ -468,7 +317,6 @@ public void FetchXml_Operator_In() [Fact] public void FetchXml_Operator_NotIn() { - var fetchXml = @" @@ -525,7 +373,6 @@ public void FetchXml_Operator_In_MultiSelectOptionSet() [Fact] public void FetchXml_Operator_NotIn_MultiSelectOptionSet() { - _context.EnableProxyTypes(Assembly.GetAssembly(typeof(Contact))); var fetchXml = @" @@ -554,7 +401,6 @@ public void FetchXml_Operator_NotIn_MultiSelectOptionSet() [Fact] public void FetchXml_Operator_Null() { - var fetchXml = @" @@ -578,7 +424,6 @@ public void FetchXml_Operator_Null() [Fact] public void FetchXml_Operator_NotNull() { - var fetchXml = @" @@ -602,7 +447,6 @@ public void FetchXml_Operator_NotNull() [Fact] public void FetchXml_Operator_Gt_Translation() { - _context.EnableProxyTypes(Assembly.GetAssembly(typeof(Contact))); var fetchXml = @" @@ -615,9 +459,7 @@ public void FetchXml_Operator_Gt_Translation() "; - - - + var query = fetchXml.ToQueryExpression(_context); Assert.True(query.Criteria != null); @@ -630,8 +472,6 @@ public void FetchXml_Operator_Gt_Translation() [Fact] public void FetchXml_Operator_Gt_Execution() { - - var fetchXml = @" @@ -646,7 +486,6 @@ public void FetchXml_Operator_Gt_Execution() var ct3 = new Contact() { Id = Guid.NewGuid(), Address1_Longitude = 1.2345 }; _context.Initialize(new[] { ct1, ct2, ct3 }); - var collection = _service.RetrieveMultiple(new FetchExpression(fetchXml)); Assert.Single(collection.Entities); @@ -656,7 +495,6 @@ public void FetchXml_Operator_Gt_Execution() [Fact] public void FetchXml_Operator_Ge_Translation() { - _context.EnableProxyTypes(Assembly.GetAssembly(typeof(Contact))); var fetchXml = @" @@ -682,7 +520,6 @@ public void FetchXml_Operator_Ge_Translation() [Fact] public void FetchXml_Operator_Older_Than_X_Months_Translation() { - _context.EnableProxyTypes(Assembly.GetAssembly(typeof(Contact))); var fetchXml = @" @@ -708,7 +545,6 @@ public void FetchXml_Operator_Older_Than_X_Months_Translation() [Fact] public void FetchXml_Operator_Older_Than_X_Months_Execution() { - _context.EnableProxyTypes(Assembly.GetAssembly(typeof(Contact))); var fetchXml = @" @@ -736,7 +572,6 @@ public void FetchXml_Operator_Older_Than_X_Months_Execution() var ct3 = new Contact() { Id = Guid.NewGuid(), Anniversary = date.AddMonths(-3) }; //Should be returned _context.Initialize(new[] { ct1, ct2, ct3 }); - var collection = _service.RetrieveMultiple(new FetchExpression(fetchXml)); Assert.Single(collection.Entities); @@ -748,7 +583,6 @@ public void FetchXml_Operator_Older_Than_X_Months_Execution() [Fact] public void FetchXml_Operator_Older_Than_X_Minutes_Translation() { - _context.EnableProxyTypes(Assembly.GetAssembly(typeof(Contact))); var fetchXml = @" @@ -774,7 +608,6 @@ public void FetchXml_Operator_Older_Than_X_Minutes_Translation() [Fact] public void FetchXml_Operator_Older_Than_X_Minutes_Execution() { - _context.EnableProxyTypes(Assembly.GetAssembly(typeof(Contact))); var fetchXml = @" @@ -802,7 +635,6 @@ public void FetchXml_Operator_Older_Than_X_Minutes_Execution() var ct3 = new Contact() { Id = Guid.NewGuid(), Anniversary = date.AddMinutes(-3) }; //Should be returned _context.Initialize(new[] { ct1, ct2, ct3 }); - var collection = _service.RetrieveMultiple(new FetchExpression(fetchXml)); Assert.Single(collection.Entities); @@ -813,7 +645,6 @@ public void FetchXml_Operator_Older_Than_X_Minutes_Execution() [Fact] public void FetchXml_Operator_Older_Than_X_Hours_Translation() { - _context.EnableProxyTypes(Assembly.GetAssembly(typeof(Contact))); var fetchXml = @" @@ -839,7 +670,6 @@ public void FetchXml_Operator_Older_Than_X_Hours_Translation() [Fact] public void FetchXml_Operator_Older_Than_X_Hours_Execution() { - _context.EnableProxyTypes(Assembly.GetAssembly(typeof(Contact))); var fetchXml = @" @@ -878,7 +708,6 @@ public void FetchXml_Operator_Older_Than_X_Hours_Execution() [Fact] public void FetchXml_Operator_Older_Than_X_Days_Translation() { - _context.EnableProxyTypes(Assembly.GetAssembly(typeof(Contact))); var fetchXml = @" @@ -904,7 +733,6 @@ public void FetchXml_Operator_Older_Than_X_Days_Translation() [Fact] public void FetchXml_Operator_Older_Than_X_Days_Execution() { - _context.EnableProxyTypes(Assembly.GetAssembly(typeof(Contact))); var fetchXml = @" @@ -943,7 +771,6 @@ public void FetchXml_Operator_Older_Than_X_Days_Execution() [Fact] public void FetchXml_Operator_Older_Than_X_Weeks_Translation() { - _context.EnableProxyTypes(Assembly.GetAssembly(typeof(Contact))); var fetchXml = @" @@ -2581,7 +2408,6 @@ public void FetchXml_Operator_Last_X_Days_Translation() [Fact] public void FetchXml_Operator_Last_X_Days_Execution() { - _context.EnableProxyTypes(Assembly.GetAssembly(typeof(Contact))); var fetchXml = @" @@ -2971,7 +2797,6 @@ public void FetchXml_Operator_Last_X_Years_Execution() [Fact] public void FetchXml_Operator_Next_X_Years_Translation() { - _context.EnableProxyTypes(Assembly.GetAssembly(typeof(Contact))); var fetchXml = @" diff --git a/tests/FakeXrmEasy.Core.Tests/Query/FetchXml/OperatorTests/Strings/LikeOperatorTests.cs b/tests/FakeXrmEasy.Core.Tests/Query/FetchXml/OperatorTests/Strings/LikeOperatorTests.cs new file mode 100644 index 00000000..641c042f --- /dev/null +++ b/tests/FakeXrmEasy.Core.Tests/Query/FetchXml/OperatorTests/Strings/LikeOperatorTests.cs @@ -0,0 +1,154 @@ +using System.Reflection; +using DataverseEntities; +using FakeXrmEasy.Query; +using Microsoft.Xrm.Sdk.Query; +using Xunit; + +namespace FakeXrmEasy.Core.Tests.FakeContextTests.FetchXml.OperatorTests.Strings +{ + public class LikeOperatorTests: FakeXrmEasyTestsBase + { + public LikeOperatorTests() + { + _context.EnableProxyTypes(Assembly.GetAssembly(typeof(Contact))); + } + + [Fact] + public void FetchXml_Operator_Like_As_BeginsWith() + { + var fetchXml = @" + + + + + + + + + "; + + var query = fetchXml.ToQueryExpression(_context); + + Assert.True(query.Criteria != null); + Assert.Single(query.Criteria.Conditions); + Assert.Equal("fullname", query.Criteria.Conditions[0].AttributeName); + Assert.Equal(ConditionOperator.Like, query.Criteria.Conditions[0].Operator); + Assert.Equal("Messi%", query.Criteria.Conditions[0].Values[0].ToString()); + } + + [Fact] + public void FetchXml_Operator_Like_As_EndsWith() + { + var fetchXml = @" + + + + + + + + + "; + + var query = fetchXml.ToQueryExpression(_context); + + Assert.True(query.Criteria != null); + Assert.Single(query.Criteria.Conditions); + Assert.Equal("fullname", query.Criteria.Conditions[0].AttributeName); + Assert.Equal(ConditionOperator.Like, query.Criteria.Conditions[0].Operator); + Assert.Equal("%Messi", query.Criteria.Conditions[0].Values[0].ToString()); + } + + [Fact] + public void FetchXml_Operator_Like() + { + var fetchXml = @" + + + + + + + + + "; + + var query = fetchXml.ToQueryExpression(_context); + + Assert.True(query.Criteria != null); + Assert.Single(query.Criteria.Conditions); + Assert.Equal("fullname", query.Criteria.Conditions[0].AttributeName); + Assert.Equal(ConditionOperator.Like, query.Criteria.Conditions[0].Operator); + Assert.Equal("%Messi%", query.Criteria.Conditions[0].Values[0].ToString()); + } + + [Fact] + public void FetchXml_Operator_NotLike() + { + var fetchXml = @" + + + + + + + + + "; + + var query = fetchXml.ToQueryExpression(_context); + + Assert.True(query.Criteria != null); + Assert.Single(query.Criteria.Conditions); + Assert.Equal("fullname", query.Criteria.Conditions[0].AttributeName); + Assert.Equal(ConditionOperator.NotLike, query.Criteria.Conditions[0].Operator); + Assert.Equal("%Messi%", query.Criteria.Conditions[0].Values[0].ToString()); + } + + [Fact] + public void FetchXml_Operator_NotLike_As_Not_BeginWith() + { + var fetchXml = @" + + + + + + + + + "; + + var query = fetchXml.ToQueryExpression(_context); + + Assert.True(query.Criteria != null); + Assert.Single(query.Criteria.Conditions); + Assert.Equal("fullname", query.Criteria.Conditions[0].AttributeName); + Assert.Equal(ConditionOperator.NotLike, query.Criteria.Conditions[0].Operator); + Assert.Equal("Messi%", query.Criteria.Conditions[0].Values[0].ToString()); + } + + [Fact] + public void FetchXml_Operator_NotLike_As_Not_EndWith() + { + var fetchXml = @" + + + + + + + + + "; + + var query = fetchXml.ToQueryExpression(_context); + + Assert.True(query.Criteria != null); + Assert.Single(query.Criteria.Conditions); + Assert.Equal("fullname", query.Criteria.Conditions[0].AttributeName); + Assert.Equal(ConditionOperator.NotLike, query.Criteria.Conditions[0].Operator); + Assert.Equal("%Messi", query.Criteria.Conditions[0].Values[0].ToString()); + } + } +} \ No newline at end of file diff --git a/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/Strings/LikeOperatorTests.cs b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/Strings/LikeOperatorTests.cs index d1cb50e4..4a6fcb58 100644 --- a/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/Strings/LikeOperatorTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/Strings/LikeOperatorTests.cs @@ -142,6 +142,5 @@ public void Should_not_return_records_that_do_not_match_a_combination_of_wildcar Assert.Empty(_service.RetrieveMultiple(qe).Entities); } - } } \ No newline at end of file From 89ae113bedc2a90750be9353015da6fdb8648727 Mon Sep 17 00:00:00 2001 From: Jordi Date: Sat, 23 Mar 2024 20:50:05 +0100 Subject: [PATCH 26/37] Resolves DynamicsValue/fake-xrm-easy#96 and initial work on DynamicsValue/fake-xrm-easy#140 --- CHANGELOG.md | 4 +- .../Extensions/EntityExtensions.cs | 23 +- .../OptionSetValueCollectionExtensions.cs | 20 +- .../Extensions/TypeExtensions.cs | 22 +- ...onditionExpressionExtensions.BeginsWith.cs | 2 +- .../ConditionExpressionExtensions.Contains.cs | 2 +- .../ConditionExpressionExtensions.EndsWith.cs | 2 +- .../Query/ConditionExpressionExtensions.In.cs | 8 +- .../Query/ConditionExpressionExtensions.cs | 46 +-- .../Query/QueryExpressionExtensions.cs | 7 +- .../Query/TypedConditionExpression.cs | 38 +- .../XrmFakedContext.Queries.cs | 2 +- .../EntityExtensions/ActivityPartyComparer.cs | 24 ++ .../EntityExtensions/CloneAttributeTests.cs | 99 +++++ .../EntityExtensions/EntityExtensionsTests.cs | 41 +++ .../Extensions/EntityExtensionsTests.cs | 107 ------ ...OptionSetValueCollectionExtensionsTests.cs | 70 ++++ .../Issues/Issue0096.cs | 7 +- .../OperatorTests/InOperatorTests.cs | 109 ++++++ .../MultiSelectOptionSetTests.cs | 345 ++++++++---------- 20 files changed, 610 insertions(+), 368 deletions(-) create mode 100644 tests/FakeXrmEasy.Core.Tests/Extensions/EntityExtensions/ActivityPartyComparer.cs create mode 100644 tests/FakeXrmEasy.Core.Tests/Extensions/EntityExtensions/CloneAttributeTests.cs create mode 100644 tests/FakeXrmEasy.Core.Tests/Extensions/EntityExtensions/EntityExtensionsTests.cs delete mode 100644 tests/FakeXrmEasy.Core.Tests/Extensions/EntityExtensionsTests.cs create mode 100644 tests/FakeXrmEasy.Core.Tests/Extensions/OptionSetValueCollectionExtensionsTests.cs create mode 100644 tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/InOperatorTests.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index f68e7760..72d53946 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,14 +3,16 @@ ### Added - Added FileAttributeMetadata support to MetadataGenerator -- Added support for bulk operations: CreateMultipleRequest, UpdateMultipleRequest +- Added support for bulk operations: CreateMultipleRequest, UpdateMultipleRequest, UpsertMultipleRequest - https://github.com/DynamicsValue/fake-xrm-easy/issues/122 - Added new exception to make the initialization of entity records with attributes with a null entity reference more obvious (thanks Betim) - https://github.com/DynamicsValue/fake-xrm-easy/issues/107 +- Add support for OptionSetValueCollection attributes when they are generated as an IEnumerable (using EBG or pac modelbuilder) - https://github.com/DynamicsValue/fake-xrm-easy/issues/140 ### Changed - Added extended wildcard support for the Like operator (thanks Betim) - https://github.com/DynamicsValue/fake-xrm-easy/issues/139 - Resolves referencing EntityAlias or EntityName in conditions inside nested filters of a LinkedEntity (thanks Temmy) - https://github.com/DynamicsValue/fake-xrm-easy/issues/63 - Resolves Resolving entity references by Alternate Keys when EntityMetadata doesn't have any Keys. - https://github.com/DynamicsValue/fake-xrm-easy/issues/138 +- Resolves an issue where a ConditionExpression with an In operator should to not take array of integers as an input, but instead separate values (thanks Ben and Betim) - https://github.com/DynamicsValue/fake-xrm-easy/issues/96 ## [2.4.2] diff --git a/src/FakeXrmEasy.Core/Extensions/EntityExtensions.cs b/src/FakeXrmEasy.Core/Extensions/EntityExtensions.cs index a96520c0..7faa6596 100644 --- a/src/FakeXrmEasy.Core/Extensions/EntityExtensions.cs +++ b/src/FakeXrmEasy.Core/Extensions/EntityExtensions.cs @@ -1,6 +1,7 @@ using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Query; using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using FakeXrmEasy.Abstractions; @@ -212,7 +213,7 @@ public static Entity RemoveNullAttributes(Entity entity) } /// - /// + /// Clones an attribute value to make sure the object reference in memory is different to the original attribute present in the In-Memory database /// /// /// @@ -311,6 +312,26 @@ internal static object CloneAttribute(object attributeValue, IXrmFakedContext co var copy = new OptionSetValueCollection(original.ToArray()); return copy; } + else if (typeof(IEnumerable).IsAssignableFrom(type)) + { + if (type.IsGenericType) + { + var genericTypeArguments = type.GenericTypeArguments; + if (genericTypeArguments.Length == 1 && genericTypeArguments[0].IsEnum) + { + //MultiOption set value + return (attributeValue as IEnumerable).Copy(); + } + } + else if (type.IsArray) + { + var elementType = type.GetElementType(); + if (elementType.IsEnum) + { + return attributeValue.Copy(); + } + } + } #endif else if (type == typeof(int) || type == typeof(Int64)) return attributeValue; //Not a reference type diff --git a/src/FakeXrmEasy.Core/Extensions/OptionSetValueCollectionExtensions.cs b/src/FakeXrmEasy.Core/Extensions/OptionSetValueCollectionExtensions.cs index 77856ae0..7084a2db 100644 --- a/src/FakeXrmEasy.Core/Extensions/OptionSetValueCollectionExtensions.cs +++ b/src/FakeXrmEasy.Core/Extensions/OptionSetValueCollectionExtensions.cs @@ -1,5 +1,6 @@ #if FAKE_XRM_EASY_9 +using System.Collections; using System.Collections.Generic; using System.Linq; using Microsoft.Xrm.Sdk; @@ -18,7 +19,7 @@ public static class OptionSetValueCollectionExtensions /// /// /// - public static HashSet ConvertToHashSetOfInt(object input, bool isOptionSetValueCollectionAccepted) + public static HashSet ConvertToHashSetOfInt(this object input, bool isOptionSetValueCollectionAccepted) { var set = new HashSet(); @@ -29,6 +30,8 @@ public static HashSet ConvertToHashSetOfInt(object input, bool isOptionSetV $"Consider changing the implementation of the ResolveName method on your DataContractResolver to return a non-null value for name " + $"'{input?.GetType()}' and namespace 'http://schemas.microsoft.com/xrm/2011/Contracts'.'. Please see InnerException for more details."; + var type = input.GetType(); + if (input is int) { set.Add((int)input); @@ -74,6 +77,21 @@ public static HashSet ConvertToHashSetOfInt(object input, bool isOptionSetV { set.UnionWith((input as OptionSetValueCollection).Select(osv => osv.Value)); } + else if (typeof(IEnumerable).IsAssignableFrom(type)) + { + if (type.IsGenericType) + { + var genericTypeArguments = type.GenericTypeArguments; + if (genericTypeArguments.Length == 1 && genericTypeArguments[0].IsEnum) + { + var enumerator = (input as IEnumerable).GetEnumerator(); + while (enumerator.MoveNext()) + { + set.Add((int)enumerator.Current); + } + } + } + } else { throw FakeOrganizationServiceFaultFactory.New(faultReason); diff --git a/src/FakeXrmEasy.Core/Extensions/TypeExtensions.cs b/src/FakeXrmEasy.Core/Extensions/TypeExtensions.cs index 8b595b5a..0cd3071a 100644 --- a/src/FakeXrmEasy.Core/Extensions/TypeExtensions.cs +++ b/src/FakeXrmEasy.Core/Extensions/TypeExtensions.cs @@ -32,8 +32,26 @@ public static bool IsOptionSet(this Type t) /// public static bool IsOptionSetValueCollection(this Type t) { - var nullableType = Nullable.GetUnderlyingType(t); - return t == typeof(OptionSetValueCollection); + var isActualOptionSetValueCollection = t == typeof(OptionSetValueCollection); + var typeIsConvertibleToOptionSetValueCollection = false; + if (t.IsGenericType) + { + var genericTypeArguments = t.GenericTypeArguments; + if (genericTypeArguments.Length == 1 && genericTypeArguments[0].IsEnum) + { + typeIsConvertibleToOptionSetValueCollection = true; + } + } + else if (t.IsArray) + { + var elementType = t.GetElementType(); + if (elementType.IsEnum) + { + typeIsConvertibleToOptionSetValueCollection = true; + } + } + + return isActualOptionSetValueCollection || typeIsConvertibleToOptionSetValueCollection; } #endif diff --git a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.BeginsWith.cs b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.BeginsWith.cs index 1a26e822..6999a5ea 100644 --- a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.BeginsWith.cs +++ b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.BeginsWith.cs @@ -12,7 +12,7 @@ internal static Expression ToBeginsWithExpression(this TypedConditionExpression //Append a ´%´ at the end of each condition value var computedCondition = new ConditionExpression(c.AttributeName, c.Operator, c.Values.Select(x => x.ToString() + "%").ToList()); - var typedComputedCondition = new TypedConditionExpression(computedCondition); + var typedComputedCondition = new TypedConditionExpression(computedCondition, tc.QueryExpression); typedComputedCondition.AttributeType = tc.AttributeType; return typedComputedCondition.ToLikeExpression(getAttributeValueExpr, containsAttributeExpr); diff --git a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Contains.cs b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Contains.cs index e7bc7d42..5fd07744 100644 --- a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Contains.cs +++ b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Contains.cs @@ -13,7 +13,7 @@ internal static Expression ToContainsExpression(this TypedConditionExpression tc //Append a ´%´at the end of each condition value var computedCondition = new ConditionExpression(c.AttributeName, c.Operator, c.Values.Select(x => "%" + x.ToString() + "%").ToList()); - var computedTypedCondition = new TypedConditionExpression(computedCondition); + var computedTypedCondition = new TypedConditionExpression(computedCondition, tc.QueryExpression); computedTypedCondition.AttributeType = tc.AttributeType; return computedTypedCondition.ToLikeExpression(getAttributeValueExpr, containsAttributeExpr); diff --git a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.EndsWith.cs b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.EndsWith.cs index d1a8d417..d803959d 100644 --- a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.EndsWith.cs +++ b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.EndsWith.cs @@ -12,7 +12,7 @@ internal static Expression ToEndsWithExpression(this TypedConditionExpression tc //Append a ´%´at the beginning of each condition value var computedCondition = new ConditionExpression(c.AttributeName, c.Operator, c.Values.Select(x => "%" + x.ToString()).ToList()); - var typedComputedCondition = new TypedConditionExpression(computedCondition); + var typedComputedCondition = new TypedConditionExpression(computedCondition, tc.QueryExpression); typedComputedCondition.AttributeType = tc.AttributeType; return typedComputedCondition.ToLikeExpression(getAttributeValueExpr, containsAttributeExpr); diff --git a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.In.cs b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.In.cs index 39936b74..f6d830de 100644 --- a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.In.cs +++ b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.In.cs @@ -15,7 +15,7 @@ internal static Expression ToInExpression(this TypedConditionExpression tc, Expr BinaryExpression expOrValues = Expression.Or(Expression.Constant(false), Expression.Constant(false)); #if FAKE_XRM_EASY_9 - if (tc.AttributeType == typeof(OptionSetValueCollection)) + if (tc.AttributeType?.IsOptionSetValueCollection() == true) { var leftHandSideExpression = tc.AttributeType.GetAppropiateCastExpressionBasedOnType(getAttributeValueExpr, null); var rightHandSideExpression = Expression.Constant(OptionSetValueCollectionExtensions.ConvertToHashSetOfInt(c.Values, isOptionSetValueCollectionAccepted: false)); @@ -31,12 +31,6 @@ internal static Expression ToInExpression(this TypedConditionExpression tc, Expr { if (value is Array) { - //foreach (var a in ((Array)value)) - //{ - // expOrValues = Expression.Or(expOrValues, Expression.Equal( - // tc.AttributeType.GetAppropiateCastExpressionBasedOnType(getAttributeValueExpr, a), - // TypeCastExpressions.GetAppropiateTypedValueAndType(a, tc.AttributeType))); - //} } else { diff --git a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.cs b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.cs index 847f7b32..71b7504a 100644 --- a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.cs +++ b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.cs @@ -24,7 +24,7 @@ internal static BinaryExpression TranslateMultipleConditionExpressions(this List { var cEntityName = sEntityName; //Create a new typed expression - var typedConditionExpression = new TypedConditionExpression(c); + var typedConditionExpression = new TypedConditionExpression(c, qe); typedConditionExpression.IsOuter = bIsOuter; string sAttributeName = c.AttributeName; @@ -97,49 +97,20 @@ internal static Expression ToExpression(this TypedConditionExpression c, QueryEx entity, "Attributes" ); - - - //If the attribute comes from a joined entity, then we need to access the attribute from the join - //But the entity name attribute only exists >= 2013 -#if FAKE_XRM_EASY_2013 || FAKE_XRM_EASY_2015 || FAKE_XRM_EASY_2016 || FAKE_XRM_EASY_365 || FAKE_XRM_EASY_9 - string attributeName = ""; - - //Do not prepend the entity name if the EntityLogicalName is the same as the QueryExpression main logical name - - if (!string.IsNullOrWhiteSpace(c.CondExpression.EntityName) && !c.CondExpression.EntityName.Equals(qe.EntityName)) - { - attributeName = c.CondExpression.EntityName + "." + c.CondExpression.AttributeName; - } - else - attributeName = c.CondExpression.AttributeName; - + + string attributeName = c.GetAttributeName(); Expression containsAttributeExpression = Expression.Call( attributesProperty, typeof(AttributeCollection).GetMethod("ContainsKey", new Type[] { typeof(string) }), Expression.Constant(attributeName) - ); + ); Expression getAttributeValueExpr = Expression.Property( - attributesProperty, "Item", - Expression.Constant(attributeName, typeof(string)) - ); -#else - Expression containsAttributeExpression = Expression.Call( - attributesProperty, - typeof(AttributeCollection).GetMethod("ContainsKey", new Type[] { typeof(string) }), - Expression.Constant(c.CondExpression.AttributeName) - ); - - Expression getAttributeValueExpr = Expression.Property( - attributesProperty, "Item", - Expression.Constant(c.CondExpression.AttributeName, typeof(string)) - ); -#endif - - + attributesProperty, "Item", + Expression.Constant(attributeName, typeof(string)) + ); Expression getNonBasicValueExpr = getAttributeValueExpr; - Expression operatorExpression = null; switch (c.CondExpression.Operator) @@ -332,7 +303,8 @@ private static void ValidateInConditionValues(TypedConditionExpression c, string { if (value is Array) { - throw new Exception($"Condition for attribute '{name}.numberofemployees': expected argument(s) of a different type but received '{value.GetType()}'."); + throw FakeOrganizationServiceFaultFactory.New(ErrorCodes.InvalidArgument, + $"Condition for attribute '{name}.{c.GetAttributeName()}': expected argument(s) of a different type but received '{value.GetType()}'."); } } } diff --git a/src/FakeXrmEasy.Core/Query/QueryExpressionExtensions.cs b/src/FakeXrmEasy.Core/Query/QueryExpressionExtensions.cs index 26c928a4..d4870c2e 100644 --- a/src/FakeXrmEasy.Core/Query/QueryExpressionExtensions.cs +++ b/src/FakeXrmEasy.Core/Query/QueryExpressionExtensions.cs @@ -21,7 +21,7 @@ public static class QueryExpressionExtensions /// /// /// - public static string GetEntityNameFromAlias(this QueryExpression qe, string sAlias) + internal static string GetEntityNameFromAlias(this QueryExpression qe, string sAlias) { if (sAlias == null) return qe.EntityName; @@ -44,7 +44,7 @@ public static string GetEntityNameFromAlias(this QueryExpression qe, string sAli /// /// Query Expression /// - public static QueryExpression Clone(this QueryExpression qe) + internal static QueryExpression Clone(this QueryExpression qe) { return qe.Copy(); } @@ -55,7 +55,7 @@ public static QueryExpression Clone(this QueryExpression qe) /// /// /// - public static IQueryable ToQueryable(this QueryExpression qe, IXrmFakedContext context) + internal static IQueryable ToQueryable(this QueryExpression qe, IXrmFakedContext context) { if (qe == null) return null; @@ -67,7 +67,6 @@ public static IQueryable ToQueryable(this QueryExpression qe, IXrmFakedC //Start form the root entity and build a LINQ query to execute the query against the In-Memory context: context.EnsureEntityNameExistsInMetadata(qe.EntityName); - IQueryable query = null; query = context.CreateQuery(qe.EntityName); diff --git a/src/FakeXrmEasy.Core/Query/TypedConditionExpression.cs b/src/FakeXrmEasy.Core/Query/TypedConditionExpression.cs index 86039788..b63a1022 100644 --- a/src/FakeXrmEasy.Core/Query/TypedConditionExpression.cs +++ b/src/FakeXrmEasy.Core/Query/TypedConditionExpression.cs @@ -13,16 +13,21 @@ namespace FakeXrmEasy.Query /// public class TypedConditionExpression { + /// + /// The QueryExpression to which this condition belongs + /// + internal QueryExpression QueryExpression { get; set; } + /// /// The original condition expression /// - public ConditionExpression CondExpression { get; set; } + internal ConditionExpression CondExpression { get; set; } /// /// The attribute type of the condition expression, if known (i.e. was generated via a strongly-typed generation tool) /// - public Type AttributeType { get; set; } - + internal Type AttributeType { get; set; } + /// /// True if the condition came from a left outer join, in which case should be applied only if not null /// @@ -32,10 +37,11 @@ public class TypedConditionExpression /// Creates a TypedConditionExpression from an existing ConditionExpression with no attribute type information /// /// - public TypedConditionExpression(ConditionExpression c) + public TypedConditionExpression(ConditionExpression c, QueryExpression qe) { IsOuter = false; CondExpression = c; + QueryExpression = qe; } internal void ValidateSupportedTypedExpression() @@ -94,5 +100,29 @@ internal object GetSingleConditionValue() return conditionValue; } + + /// + /// Returns the attribute name that participates in this condition expression + /// + /// + internal string GetAttributeName() + { + string attributeName = ""; + //If the attribute comes from a joined entity, then we need to access the attribute from the join + //But the entity name attribute only exists >= 2013 +#if FAKE_XRM_EASY_2013 || FAKE_XRM_EASY_2015 || FAKE_XRM_EASY_2016 || FAKE_XRM_EASY_365 || FAKE_XRM_EASY_9 + //Do not prepend the entity name if the EntityLogicalName is the same as the QueryExpression main logical name + + if (!string.IsNullOrWhiteSpace(CondExpression.EntityName) && !CondExpression.EntityName.Equals(QueryExpression.EntityName)) + { + attributeName = CondExpression.EntityName + "." + CondExpression.AttributeName; + } + else + attributeName = CondExpression.AttributeName; +#else + attributeName = CondExpression.AttributeName; +#endif + return attributeName; + } } } \ No newline at end of file diff --git a/src/FakeXrmEasy.Core/XrmFakedContext.Queries.cs b/src/FakeXrmEasy.Core/XrmFakedContext.Queries.cs index 1248f661..2f90d770 100644 --- a/src/FakeXrmEasy.Core/XrmFakedContext.Queries.cs +++ b/src/FakeXrmEasy.Core/XrmFakedContext.Queries.cs @@ -164,7 +164,7 @@ public Type FindReflectedAttributeType(Type earlyBoundType, string sEntityName, return injectedType; } - if (attributeInfo.PropertyType.FullName.EndsWith("Enum") || attributeInfo.PropertyType.BaseType.FullName.EndsWith("Enum")) + if (attributeInfo.PropertyType.FullName.EndsWith("Enum") || attributeInfo.PropertyType.BaseType?.FullName.EndsWith("Enum") == true) { return typeof(int); } diff --git a/tests/FakeXrmEasy.Core.Tests/Extensions/EntityExtensions/ActivityPartyComparer.cs b/tests/FakeXrmEasy.Core.Tests/Extensions/EntityExtensions/ActivityPartyComparer.cs new file mode 100644 index 00000000..150f1f39 --- /dev/null +++ b/tests/FakeXrmEasy.Core.Tests/Extensions/EntityExtensions/ActivityPartyComparer.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using Microsoft.Xrm.Sdk; + +namespace FakeXrmEasy.Core.Tests.Extensions +{ + internal class ActivityPartyComparer: EqualityComparer + { + public override bool Equals(Entity x, Entity y) + { + var partyId_X = x["partyid"] as EntityReference; + var partyId_Y = y["partyid"] as EntityReference; + + if (partyId_X?.LogicalName != partyId_Y?.LogicalName) return false; + if (partyId_X?.Id != partyId_Y?.Id) return false; + + return true; + } + + public override int GetHashCode(Entity obj) + { + return obj.LogicalName.GetHashCode() * obj["partyid"].GetHashCode(); + } + } +} \ No newline at end of file diff --git a/tests/FakeXrmEasy.Core.Tests/Extensions/EntityExtensions/CloneAttributeTests.cs b/tests/FakeXrmEasy.Core.Tests/Extensions/EntityExtensions/CloneAttributeTests.cs new file mode 100644 index 00000000..72229840 --- /dev/null +++ b/tests/FakeXrmEasy.Core.Tests/Extensions/EntityExtensions/CloneAttributeTests.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using DataverseEntities; +using FakeXrmEasy.Extensions; +using Microsoft.Xrm.Sdk; +using Xunit; + +namespace FakeXrmEasy.Core.Tests.Extensions +{ + public class CloneAttributeTests + { + [Fact] + public void Should_clone_activity_parties() + { + IEnumerable activityParties = Enumerable.Range(1, 2).Select(index => + { + var activityParty = new Entity("activityparty"); + activityParty["partyid"] = new EntityReference("contact", Guid.NewGuid()); + + return activityParty; + }).ToArray(); + + var e = new Entity("email"); + e["to"] = activityParties; + + var clone = EntityExtensions.CloneAttribute(e["to"]) as IEnumerable; + + Assert.NotNull(clone); + Assert.Equal(2, clone.Count()); + + Assert.Equal(activityParties, clone, new ActivityPartyComparer()); + } + +#if FAKE_XRM_EASY_9 + [Fact] + public void Should_clone_multi_option_set_values_as_an_option_set_value_collection() + { + var e = new Crm.Contact() { Id = Guid.NewGuid() }; + e.new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1) , new OptionSetValue(2) }; + + var clone = EntityExtensions.CloneAttribute(e.new_MultiSelectAttribute) as OptionSetValueCollection; + Assert.NotNull(clone); + Assert.True(e.new_MultiSelectAttribute != clone); + + Assert.Equal(1, clone[0].Value); + Assert.Equal(2, clone[1].Value); + } + + [Fact] + public void Should_clone_multi_option_set_values_as_an_enumerable_of_enum() + { + var e = new dv_test() { Id = Guid.NewGuid() }; + e.dv_choice_multiple = new List() { dv_test_dv_choice_multiple.Option1 , dv_test_dv_choice_multiple.Option2 }; + + var clone = EntityExtensions.CloneAttribute(e.dv_choice_multiple) as IEnumerable; + Assert.NotNull(clone); + Assert.True(e.dv_choice_multiple != clone); + + Assert.Contains(dv_test_dv_choice_multiple.Option1, clone); + Assert.Contains(dv_test_dv_choice_multiple.Option2, clone); + } + + [Fact] + public void Should_clone_multi_option_set_values_as_an_array_of_enum() + { + var optionSetValues = new [] { dv_test_dv_choice_multiple.Option1 , dv_test_dv_choice_multiple.Option2 }; + + var clone = EntityExtensions.CloneAttribute(optionSetValues) as dv_test_dv_choice_multiple[]; + Assert.NotNull(clone); + Assert.True(optionSetValues != clone); + + Assert.Contains(dv_test_dv_choice_multiple.Option1, clone); + Assert.Contains(dv_test_dv_choice_multiple.Option2, clone); + } + #endif + +#if !FAKE_XRM_EASY + //Entity images aren't supported in versions prior to 2013, so no need to support byte arrays as attributes + + [Fact] + public void Should_clone_byte_array() + { + var random = new Random(); + byte[] image = new byte[2000]; + random.NextBytes(image); + + var e = new Entity("account"); + e["entityimage"] = image; + + var clone = EntityExtensions.CloneAttribute(e["entityimage"]) as byte[]; + + Assert.NotNull(clone); + Assert.Equal(2000, clone.Length); + Assert.Equal(image, clone); + } +#endif + } +} \ No newline at end of file diff --git a/tests/FakeXrmEasy.Core.Tests/Extensions/EntityExtensions/EntityExtensionsTests.cs b/tests/FakeXrmEasy.Core.Tests/Extensions/EntityExtensions/EntityExtensionsTests.cs new file mode 100644 index 00000000..51a9dd76 --- /dev/null +++ b/tests/FakeXrmEasy.Core.Tests/Extensions/EntityExtensions/EntityExtensionsTests.cs @@ -0,0 +1,41 @@ +using FakeXrmEasy.Extensions; +using Microsoft.Xrm.Sdk; +using System; +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace FakeXrmEasy.Core.Tests.Extensions +{ + public class EntityExtensionsTests + { + [Fact] + public void SetValueIfEmpty_should_not_override_existing_values() + { + var e = new Entity("account"); + e["name"] = "Some name"; + + e.SetValueIfEmpty("name", "another name"); + Assert.Equal("Some name", e["name"].ToString()); + } + + [Fact] + public void SetValueIfEmpty_should_override_if_null() + { + var e = new Entity("account"); + e["name"] = null; + + e.SetValueIfEmpty("name", "new name"); + Assert.Equal("new name", e["name"].ToString()); + } + + [Fact] + public void SetValueIfEmpty_should_override_if_doesnt_contains_key() + { + var e = new Entity("account"); + + e.SetValueIfEmpty("name", "new name"); + Assert.Equal("new name", e["name"].ToString()); + } + } +} \ No newline at end of file diff --git a/tests/FakeXrmEasy.Core.Tests/Extensions/EntityExtensionsTests.cs b/tests/FakeXrmEasy.Core.Tests/Extensions/EntityExtensionsTests.cs deleted file mode 100644 index 431ce04f..00000000 --- a/tests/FakeXrmEasy.Core.Tests/Extensions/EntityExtensionsTests.cs +++ /dev/null @@ -1,107 +0,0 @@ -using FakeXrmEasy.Extensions; -using Microsoft.Xrm.Sdk; -using System; -using System.Collections.Generic; -using System.Linq; -using Xunit; - -namespace FakeXrmEasy.Core.Tests.Extensions -{ - public class EntityExtensionsTests - { - [Fact] - public void SetValueIfEmpty_should_not_override_existing_values() - { - var e = new Entity("account"); - e["name"] = "Some name"; - - e.SetValueIfEmpty("name", "another name"); - Assert.Equal("Some name", e["name"].ToString()); - } - - [Fact] - public void SetValueIfEmpty_should_override_if_null() - { - var e = new Entity("account"); - e["name"] = null; - - e.SetValueIfEmpty("name", "new name"); - Assert.Equal("new name", e["name"].ToString()); - } - - [Fact] - public void SetValueIfEmpty_should_override_if_doesnt_contains_key() - { - var e = new Entity("account"); - - e.SetValueIfEmpty("name", "new name"); - Assert.Equal("new name", e["name"].ToString()); - } - - [Fact] - public void CloneAttribute_should_support_enumerable_of_entity() - { - IEnumerable activityParties = Enumerable.Range(1, 2).Select(index => - { - var activityParty = new Entity("activityparty"); - activityParty["partyid"] = new EntityReference("contact", Guid.NewGuid()); - - return activityParty; - }).ToArray(); - - var e = new Entity("email"); - e["to"] = activityParties; - - var clone = EntityExtensions.CloneAttribute(e["to"]) as IEnumerable; - - Assert.NotNull(clone); - Assert.Equal(2, clone.Count()); - - Assert.Equal(activityParties, clone, new ActivityPartyComparer()); - } - -#if !FAKE_XRM_EASY - //Enity images aren't supported in versions prior to 2013, so no need to support byte arrays as attributes - - [Fact] - public void CloneAttribute_should_support_byte_array() - { - var random = new Random(); - byte[] image = new byte[2000]; - random.NextBytes(image); - - var e = new Entity("account"); - e["entityimage"] = image; - - var clone = EntityExtensions.CloneAttribute(e["entityimage"]) as byte[]; - - Assert.NotNull(clone); - Assert.Equal(2000, clone.Length); - Assert.Equal(image, clone); - } -#endif - - #region Private Helper Classes - - private class ActivityPartyComparer : EqualityComparer - { - public override bool Equals(Entity x, Entity y) - { - var partyId_X = x["partyid"] as EntityReference; - var partyId_Y = y["partyid"] as EntityReference; - - if (partyId_X?.LogicalName != partyId_Y?.LogicalName) return false; - if (partyId_X?.Id != partyId_Y?.Id) return false; - - return true; - } - - public override int GetHashCode(Entity obj) - { - return obj.LogicalName.GetHashCode() * obj["partyid"].GetHashCode(); - } - } - - #endregion Private Helper Classes - } -} \ No newline at end of file diff --git a/tests/FakeXrmEasy.Core.Tests/Extensions/OptionSetValueCollectionExtensionsTests.cs b/tests/FakeXrmEasy.Core.Tests/Extensions/OptionSetValueCollectionExtensionsTests.cs new file mode 100644 index 00000000..ab8078ac --- /dev/null +++ b/tests/FakeXrmEasy.Core.Tests/Extensions/OptionSetValueCollectionExtensionsTests.cs @@ -0,0 +1,70 @@ + + +#if FAKE_XRM_EASY_9 + +using System.Collections.Generic; +using DataverseEntities; +using Microsoft.Xrm.Sdk; +using FakeXrmEasy.Extensions; +using Xunit; + +namespace FakeXrmEasy.Core.Tests.Extensions +{ + public class OptionSetValueCollectionExtensionsTests + { + [Fact] + public void Should_convert_to_a_hash_of_int_from_an_option_set_value_collection() + { + var optionSetValueCollection = new OptionSetValueCollection() + { + new OptionSetValue(1), new OptionSetValue(2), new OptionSetValue(3) + }; + + var hashOfInt = optionSetValueCollection.ConvertToHashSetOfInt(isOptionSetValueCollectionAccepted: true); + Assert.NotNull(hashOfInt); + + Assert.Contains(1, hashOfInt); + Assert.Contains(2, hashOfInt); + Assert.Contains(3, hashOfInt); + } + + [Fact] + public void Should_convert_to_a_hash_of_int_from_an_array_of_int() + { + var arrayOfInts = new[] { 1, 2, 3 }; + + var hashOfInt = arrayOfInts.ConvertToHashSetOfInt(isOptionSetValueCollectionAccepted: true); + Assert.NotNull(hashOfInt); + + Assert.Contains(1, hashOfInt); + Assert.Contains(2, hashOfInt); + Assert.Contains(3, hashOfInt); + } + + [Fact] + public void Should_convert_to_a_hash_of_int_from_an_array_of_strings() + { + var arrayOfInts = new[] { "1", "2", "3" }; + + var hashOfInt = arrayOfInts.ConvertToHashSetOfInt(isOptionSetValueCollectionAccepted: true); + Assert.NotNull(hashOfInt); + + Assert.Contains(1, hashOfInt); + Assert.Contains(2, hashOfInt); + Assert.Contains(3, hashOfInt); + } + + [Fact] + public void Should_convert_to_a_hash_of_int_from_an_enumerable_of_enums() + { + var enumerableOfEnum = new List { dv_test_dv_choice_multiple.Option1, dv_test_dv_choice_multiple.Option2 }; + + var hashOfInt = enumerableOfEnum.ConvertToHashSetOfInt(isOptionSetValueCollectionAccepted: true); + Assert.NotNull(hashOfInt); + + Assert.Contains((int) dv_test_dv_choice_multiple.Option1, hashOfInt); + Assert.Contains((int) dv_test_dv_choice_multiple.Option2, hashOfInt); + } + } +} +#endif \ No newline at end of file diff --git a/tests/FakeXrmEasy.Core.Tests/Issues/Issue0096.cs b/tests/FakeXrmEasy.Core.Tests/Issues/Issue0096.cs index 4f4ced0d..00430aca 100644 --- a/tests/FakeXrmEasy.Core.Tests/Issues/Issue0096.cs +++ b/tests/FakeXrmEasy.Core.Tests/Issues/Issue0096.cs @@ -1,7 +1,8 @@ -using Microsoft.Xrm.Sdk.Query; +using FakeXrmEasy.Abstractions; +using Microsoft.Xrm.Sdk.Query; using Xunit; -namespace FakeXrmEasy.Tests.Issues +namespace FakeXrmEasy.Core.Tests.Issues { public class Issue0096 : FakeXrmEasyTestsBase { @@ -12,7 +13,7 @@ public void Reproduce_issue_96() query.TopCount = 2; query.Criteria.AddCondition("numberofemployees", ConditionOperator.In, new int[] { 0, 1 }); - var ex = Record.Exception(() => _service.RetrieveMultiple(query)); + var ex = XAssert.ThrowsFaultCode(ErrorCodes.InvalidArgument, () => _service.RetrieveMultiple(query)); Assert.NotNull(ex); Assert.Equal("Condition for attribute 'account.numberofemployees': expected argument(s) of a different type but received 'System.Int32[]'.", ex.Message); } diff --git a/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/InOperatorTests.cs b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/InOperatorTests.cs new file mode 100644 index 00000000..9c87e747 --- /dev/null +++ b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/InOperatorTests.cs @@ -0,0 +1,109 @@ +using System; +using DataverseEntities; +using FakeXrmEasy.Abstractions; +using Microsoft.Xrm.Sdk; +using Microsoft.Xrm.Sdk.Query; +using Xunit; + +namespace FakeXrmEasy.Core.Tests.Query.TranslateQueryExpressionTests.OperatorTests +{ + public class InOperatorTests: FakeXrmEasyTestsBase + { + private readonly dv_test _testEntity; + private readonly Entity _testLateBoundEntity; + private readonly Account _account; + + public InOperatorTests() + { + _account = new Account() + { + Id = Guid.NewGuid(), + StatusCode = account_statuscode.Active + }; + _testEntity = new dv_test() + { + Id = Guid.NewGuid(), + dv_choice_multiple = new[] { dv_test_dv_choice_multiple.Option1 , dv_test_dv_choice_multiple.Option2 } + }; + _testLateBoundEntity = new Entity("dv_test") + { + Id = Guid.NewGuid(), + ["dv_choice_multiple"] = new[] { dv_test_dv_choice_multiple.Option1 , dv_test_dv_choice_multiple.Option2 } + }; + } + [Fact] + public void Should_throw_exception_when_an_array_of_int_is_used_to_filter_status_code() + { + _context.Initialize(_account); + + QueryExpression query = new QueryExpression("account") { TopCount = 10 }; + query.Criteria.AddCondition("statuscode", ConditionOperator.In, new int[] { 0, 1 }); + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.InvalidArgument, () => _service.RetrieveMultiple(query)); + Assert.Equal("Condition for attribute 'account.statuscode': expected argument(s) of a different type but received 'System.Int32[]'.", ex.Message); + } + + [Fact] + public void Should_throw_exception_when_an_array_of_enum_is_used_to_filter_status_code() + { + _context.Initialize(_account); + + QueryExpression query = new QueryExpression("account") { TopCount = 10 }; + query.Criteria.AddCondition("statuscode", ConditionOperator.In, new account_statuscode[] { account_statuscode.Active }); + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.InvalidArgument, () => _service.RetrieveMultiple(query)); + Assert.Equal("Condition for attribute 'account.statuscode': expected argument(s) of a different type but received 'DataverseEntities.account_statuscode[]'.", ex.Message); + } + + [Fact] + public void Should_succeed_when_several_int_parameters_are_used_to_filter_status_code() + { + _context.Initialize(_account); + + QueryExpression query = new QueryExpression("account") { TopCount = 10 }; + query.Criteria.AddCondition("statuscode", ConditionOperator.In, 0, 1); + + var result = _service.RetrieveMultiple(query); + Assert.NotEmpty(result.Entities); + } + + + [Fact] + public void Should_throw_exception_when_an_array_of_int_is_used_to_filter_a_multi_option_set() + { + _context.Initialize(_testEntity); + + QueryExpression query = new QueryExpression(dv_test.EntityLogicalName) { TopCount = 10 }; + query.Criteria.AddCondition(dv_test.Fields.dv_choice_multiple, ConditionOperator.In, new int[] { 0, 1 }); + + var ex = XAssert.ThrowsFaultCode(ErrorCodes.InvalidArgument, () => _service.RetrieveMultiple(query)); + Assert.Equal("Condition for attribute 'dv_test.dv_choice_multiple': expected argument(s) of a different type but received 'System.Int32[]'.", ex.Message); + } + + [Fact] + public void Should_return_results_when_a_non_empty_string_array_is_used_to_filter_a_multi_option_set_early_bound_entity_record() + { + _context.Initialize(_testEntity); + + QueryExpression query = new QueryExpression(dv_test.EntityLogicalName) { TopCount = 10 }; + query.Criteria.AddCondition(dv_test.Fields.dv_choice_multiple, ConditionOperator.In, + new string[] { ((int)dv_test_dv_choice_multiple.Option1).ToString(),((int)dv_test_dv_choice_multiple.Option2).ToString() }); + + var result = _service.RetrieveMultiple(query); + Assert.NotEmpty(result.Entities); + } + + [Fact] + public void Should_return_invalid_cast_exception_a_non_empty_string_array_is_used_to_filter_a_multi_option_set_late_bound_entity_record() + { + _context.Initialize(_testLateBoundEntity); + + QueryExpression query = new QueryExpression(dv_test.EntityLogicalName) { TopCount = 10 }; + query.Criteria.AddCondition(dv_test.Fields.dv_choice_multiple, ConditionOperator.In, + new string[] { ((int)dv_test_dv_choice_multiple.Option1).ToString(),((int)dv_test_dv_choice_multiple.Option2).ToString() }); + + //There is no type information to convert a string to an option set value collection and an integer value is assumed in that case + Assert.Throws(() => _service.RetrieveMultiple(query)); + } + } +} \ No newline at end of file diff --git a/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/MultiSelectOptionSet/MultiSelectOptionSetTests.cs b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/MultiSelectOptionSet/MultiSelectOptionSetTests.cs index 0c1acabd..3e66c993 100644 --- a/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/MultiSelectOptionSet/MultiSelectOptionSetTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/MultiSelectOptionSet/MultiSelectOptionSetTests.cs @@ -1,4 +1,5 @@ #if FAKE_XRM_EASY_9 +using System; using System.Linq; using System.ServiceModel; using Crm; @@ -10,15 +11,56 @@ namespace FakeXrmEasy.Core.Tests.Query.TranslateQueryExpressionTests.OperatorTes { public class MultiSelectOptionSetTests: FakeXrmEasyTestsBase { + private readonly Contact _contactWithOptions12; + private readonly Contact _contactWithOption2; + private readonly Contact _contactWithOptions23; + private readonly Contact _contactWithOptions123; + private readonly Contact _contactWithNullOptions; + + public MultiSelectOptionSetTests() + { + _contactWithOptions12 = new Contact() + { + Id = Guid.NewGuid(), + FirstName = "1,2", + new_MultiSelectAttribute = new OptionSetValueCollection() + { new OptionSetValue(1), new OptionSetValue(2) } + }; + + _contactWithOption2 = new Contact() + { + Id = Guid.NewGuid(), + FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } + }; + + _contactWithOptions23 = new Contact() + { + Id = Guid.NewGuid(), + FirstName = "2,3", + new_MultiSelectAttribute = new OptionSetValueCollection() + { new OptionSetValue(2), new OptionSetValue(3) } + }; + _contactWithOptions123 = new Contact() + { + Id = Guid.NewGuid(), + FirstName = "1,2,3", + new_MultiSelectAttribute = new OptionSetValueCollection() + { new OptionSetValue(1), new OptionSetValue(2), new OptionSetValue(3) } + }; + _contactWithNullOptions = new Contact() + { + Id = Guid.NewGuid(), + FirstName = "null" + }; + } + [Fact] public void When_executing_a_query_expression_equal_operator_returns_exact_matches_for_int_right_hand_side() { - - _service.Create(new Contact { FirstName = "1,2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "1,2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "null" }); + _context.Initialize(new [] + { + _contactWithOptions12, _contactWithOption2, _contactWithOptions23, _contactWithOptions123, _contactWithNullOptions + }); var qe = new QueryExpression("contact"); qe.ColumnSet = new ColumnSet(new[] { "firstname" }); @@ -33,11 +75,10 @@ public void When_executing_a_query_expression_equal_operator_returns_exact_match [Fact] public void When_executing_a_query_expression_equal_operator_returns_exact_matches_for_string_right_hand_side() { - _service.Create(new Contact { FirstName = "1,2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "1,2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "null" }); + _context.Initialize(new [] + { + _contactWithOptions12, _contactWithOption2, _contactWithOptions23, _contactWithOptions123, _contactWithNullOptions + }); var qe = new QueryExpression("contact"); qe.ColumnSet = new ColumnSet(new[] { "firstname" }); @@ -63,9 +104,6 @@ public void When_executing_a_query_expression_equal_operator_throws_exception_fo [Fact] public void When_executing_a_query_expression_equal_operator_throws_exception_for_optionsetvaluecollection_right_hand_side() { - - - _service.Create(new Contact { FirstName = "1,2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); var qe = new QueryExpression("contact"); @@ -77,14 +115,10 @@ public void When_executing_a_query_expression_equal_operator_throws_exception_fo [Fact] public void When_executing_a_query_expression_equal_operator_returns_exact_matches_for_single_int_array_right_hand_side() { - - - - _service.Create(new Contact { FirstName = "1,2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "1,2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "null" }); + _context.Initialize(new [] + { + _contactWithOptions12, _contactWithOption2, _contactWithOptions23, _contactWithOptions123, _contactWithNullOptions + }); var qe = new QueryExpression("contact"); qe.ColumnSet = new ColumnSet(new[] { "firstname" }); @@ -99,9 +133,6 @@ public void When_executing_a_query_expression_equal_operator_returns_exact_match [Fact] public void When_executing_a_query_expression_equal_operator_throws_exception_for_int_array_right_hand_side() { - - - _service.Create(new Contact { FirstName = "1,2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2), new OptionSetValue(3) } }); var qe = new QueryExpression("contact"); @@ -113,9 +144,6 @@ public void When_executing_a_query_expression_equal_operator_throws_exception_fo [Fact] public void When_executing_a_query_expression_equal_operator_throws_exception_for_string_array_right_hand_side() { - - - _service.Create(new Contact { FirstName = "1,2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2), new OptionSetValue(3) } }); var qe = new QueryExpression("contact"); @@ -127,14 +155,10 @@ public void When_executing_a_query_expression_equal_operator_throws_exception_fo [Fact] public void When_executing_a_query_expression_notequal_operator_excludes_exact_matches_for_int_right_hand_side() { - - - - _service.Create(new Contact { FirstName = "1,2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "1,2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "null" }); + _context.Initialize(new [] + { + _contactWithOptions12, _contactWithOption2, _contactWithOptions23, _contactWithOptions123, _contactWithNullOptions + }); var qe = new QueryExpression("contact"); qe.ColumnSet = new ColumnSet(new[] { "firstname" }); @@ -149,14 +173,10 @@ public void When_executing_a_query_expression_notequal_operator_excludes_exact_m [Fact] public void When_executing_a_query_expression_notequal_operator_excludes_exact_matches_for_single_int_array_right_hand_side() { - - - - _service.Create(new Contact { FirstName = "1,2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "1,2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "null" }); + _context.Initialize(new [] + { + _contactWithOptions12, _contactWithOption2, _contactWithOptions23, _contactWithOptions123, _contactWithNullOptions + }); var qe = new QueryExpression("contact"); qe.ColumnSet = new ColumnSet(new[] { "firstname" }); @@ -171,14 +191,10 @@ public void When_executing_a_query_expression_notequal_operator_excludes_exact_m [Fact] public void When_executing_a_query_expression_in_operator_returns_exact_matches_for_int_right_hand_side() { - - - - _service.Create(new Contact { FirstName = "1,2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "1,2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "null" }); + _context.Initialize(new [] + { + _contactWithOptions12, _contactWithOption2, _contactWithOptions23, _contactWithOptions123, _contactWithNullOptions + }); var qe = new QueryExpression("contact"); qe.ColumnSet = new ColumnSet(new[] { "firstname" }); @@ -193,14 +209,10 @@ public void When_executing_a_query_expression_in_operator_returns_exact_matches_ [Fact] public void When_executing_a_query_expression_in_operator_returns_exact_matches_for_string_right_hand_side() { - - - - _service.Create(new Contact { FirstName = "1,2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "1,2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "null" }); + _context.Initialize(new [] + { + _contactWithOptions12, _contactWithOption2, _contactWithOptions23, _contactWithOptions123, _contactWithNullOptions + }); var qe = new QueryExpression("contact"); qe.ColumnSet = new ColumnSet(new[] { "firstname" }); @@ -215,9 +227,6 @@ public void When_executing_a_query_expression_in_operator_returns_exact_matches_ [Fact] public void When_executing_a_query_expression_in_operator_throws_exception_for_optionsetvalue_right_hand_side() { - - - _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); var qe = new QueryExpression("contact"); @@ -229,9 +238,6 @@ public void When_executing_a_query_expression_in_operator_throws_exception_for_o [Fact] public void When_executing_a_query_expression_in_operator_throws_exception_for_optionsetvaluecollection_right_hand_side() { - - - _service.Create(new Contact { FirstName = "1,2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); var qe = new QueryExpression("contact"); @@ -243,11 +249,10 @@ public void When_executing_a_query_expression_in_operator_throws_exception_for_o [Fact] public void When_executing_a_query_expression_in_operator_returns_exact_matches_for_single_int_array_right_hand_side() { - _service.Create(new Contact { FirstName = "1,2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "1,2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "null" }); + _context.Initialize(new [] + { + _contactWithOptions12, _contactWithOption2, _contactWithOptions23, _contactWithOptions123, _contactWithNullOptions + }); var qe = new QueryExpression("contact"); qe.ColumnSet = new ColumnSet(new[] { "firstname" }); @@ -262,11 +267,10 @@ public void When_executing_a_query_expression_in_operator_returns_exact_matches_ [Fact] public void When_executing_a_query_expression_in_operator_returns_exact_matches_for_int_array_right_hand_side() { - _service.Create(new Contact { FirstName = "1,2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "1,2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "null" }); + _context.Initialize(new [] + { + _contactWithOptions12, _contactWithOption2, _contactWithOptions23, _contactWithOptions123, _contactWithNullOptions + }); var qe = new QueryExpression("contact"); qe.ColumnSet = new ColumnSet(new[] { "firstname" }); @@ -281,11 +285,10 @@ public void When_executing_a_query_expression_in_operator_returns_exact_matches_ [Fact] public void When_executing_a_query_expression_in_operator_returns_exact_matches_for_out_of_order_int_array_right_hand_side() { - _service.Create(new Contact { FirstName = "1,2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "1,2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "null" }); + _context.Initialize(new [] + { + _contactWithOptions12, _contactWithOption2, _contactWithOptions23, _contactWithOptions123, _contactWithNullOptions + }); var qe = new QueryExpression("contact"); qe.ColumnSet = new ColumnSet(new[] { "firstname" }); @@ -300,11 +303,10 @@ public void When_executing_a_query_expression_in_operator_returns_exact_matches_ [Fact] public void When_executing_a_query_expression_in_operator_returns_exact_matches_for_out_of_order_int_params_right_hand_side() { - _service.Create(new Contact { FirstName = "1,2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "1,2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "null" }); + _context.Initialize(new [] + { + _contactWithOptions12, _contactWithOption2, _contactWithOptions23, _contactWithOptions123, _contactWithNullOptions + }); var qe = new QueryExpression("contact"); qe.ColumnSet = new ColumnSet(new[] { "firstname" }); @@ -319,11 +321,10 @@ public void When_executing_a_query_expression_in_operator_returns_exact_matches_ [Fact] public void When_executing_a_query_expression_in_operator_returns_exact_matches_for_string_array_right_hand_side() { - _service.Create(new Contact { FirstName = "1,2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "1,2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "null" }); + _context.Initialize(new [] + { + _contactWithOptions12, _contactWithOption2, _contactWithOptions23, _contactWithOptions123, _contactWithNullOptions + }); var qe = new QueryExpression("contact"); qe.ColumnSet = new ColumnSet(new[] { "firstname" }); @@ -338,14 +339,10 @@ public void When_executing_a_query_expression_in_operator_returns_exact_matches_ [Fact] public void When_executing_a_query_expression_in_operator_returns_exact_matches_for_string_params_right_hand_side() { - - - - _service.Create(new Contact { FirstName = "1,2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "1,2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "null" }); + _context.Initialize(new [] + { + _contactWithOptions12, _contactWithOption2, _contactWithOptions23, _contactWithOptions123, _contactWithNullOptions + }); var qe = new QueryExpression("contact"); qe.ColumnSet = new ColumnSet(new[] { "firstname" }); @@ -360,14 +357,10 @@ public void When_executing_a_query_expression_in_operator_returns_exact_matches_ [Fact] public void When_executing_a_query_expression_in_operator_returns_exact_matches_for_out_of_order_string_array_right_hand_side() { - - - - _service.Create(new Contact { FirstName = "1,2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "1,2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "null" }); + _context.Initialize(new [] + { + _contactWithOptions12, _contactWithOption2, _contactWithOptions23, _contactWithOptions123, _contactWithNullOptions + }); var qe = new QueryExpression("contact"); qe.ColumnSet = new ColumnSet(new[] { "firstname" }); @@ -382,11 +375,10 @@ public void When_executing_a_query_expression_in_operator_returns_exact_matches_ [Fact] public void When_executing_a_query_expression_notin_operator_excludes_exact_matches_for_int_array_right_hand_side() { - _service.Create(new Contact { FirstName = "1,2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "1,2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "null" }); + _context.Initialize(new [] + { + _contactWithOptions12, _contactWithOption2, _contactWithOptions23, _contactWithOptions123, _contactWithNullOptions + }); var qe = new QueryExpression("contact"); qe.ColumnSet = new ColumnSet(new[] { "firstname" }); @@ -402,14 +394,10 @@ public void When_executing_a_query_expression_notin_operator_excludes_exact_matc [Fact] public void When_executing_a_query_expression_notin_operator_excludes_exact_matches_for_out_of_order_string_params_right_hand_side() { - - - - _service.Create(new Contact { FirstName = "1,2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "1,2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "null" }); + _context.Initialize(new [] + { + _contactWithOptions12, _contactWithOption2, _contactWithOptions23, _contactWithOptions123, _contactWithNullOptions + }); var qe = new QueryExpression("contact"); qe.ColumnSet = new ColumnSet(new[] { "firstname" }); @@ -424,14 +412,10 @@ public void When_executing_a_query_expression_notin_operator_excludes_exact_matc [Fact] public void When_executing_a_query_expression_containvalues_operator_returns_partial_matches_for_int_right_hand_side() { - - - - _service.Create(new Contact { FirstName = "1,2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "1,2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "null" }); + _context.Initialize(new [] + { + _contactWithOptions12, _contactWithOption2, _contactWithOptions23, _contactWithOptions123, _contactWithNullOptions + }); var qe = new QueryExpression("contact"); qe.ColumnSet = new ColumnSet(new[] { "firstname" }); @@ -446,14 +430,10 @@ public void When_executing_a_query_expression_containvalues_operator_returns_par [Fact] public void When_executing_a_query_expression_containvalues_operator_returns_partial_matches_for_string_right_hand_side() { - - - - _service.Create(new Contact { FirstName = "1,2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "1,2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "null" }); + _context.Initialize(new [] + { + _contactWithOptions12, _contactWithOption2, _contactWithOptions23, _contactWithOptions123, _contactWithNullOptions + }); var qe = new QueryExpression("contact"); qe.ColumnSet = new ColumnSet(new[] { "firstname" }); @@ -468,9 +448,6 @@ public void When_executing_a_query_expression_containvalues_operator_returns_par [Fact] public void When_executing_a_query_expression_containvalues_operator_throws_exception_for_optionsetvalue_right_hand_side() { - - - _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); var qe = new QueryExpression("contact"); @@ -493,11 +470,10 @@ public void When_executing_a_query_expression_containvalues_operator_throws_exce [Fact] public void When_executing_a_query_expression_containvalues_operator_returns_partial_matches_for_single_int_array_right_hand_side() { - _service.Create(new Contact { FirstName = "1,2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "1,2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "null" }); + _context.Initialize(new [] + { + _contactWithOptions12, _contactWithOption2, _contactWithOptions23, _contactWithOptions123, _contactWithNullOptions + }); var qe = new QueryExpression("contact"); qe.ColumnSet = new ColumnSet(new[] { "firstname" }); @@ -512,14 +488,10 @@ public void When_executing_a_query_expression_containvalues_operator_returns_par [Fact] public void When_executing_a_query_expression_containvalues_operator_returns_partial_matches_for_int_array_right_hand_side() { - - - - _service.Create(new Contact { FirstName = "1,2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "1,2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "null" }); + _context.Initialize(new [] + { + _contactWithOptions12, _contactWithOption2, _contactWithOptions23, _contactWithOptions123, _contactWithNullOptions + }); var qe = new QueryExpression("contact"); qe.ColumnSet = new ColumnSet(new[] { "firstname" }); @@ -534,11 +506,10 @@ public void When_executing_a_query_expression_containvalues_operator_returns_par [Fact] public void When_executing_a_query_expression_containvalues_operator_returns_partial_matches_for_int_params_right_hand_side() { - _service.Create(new Contact { FirstName = "1,2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "1,2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "null" }); + _context.Initialize(new [] + { + _contactWithOptions12, _contactWithOption2, _contactWithOptions23, _contactWithOptions123, _contactWithNullOptions + }); var qe = new QueryExpression("contact"); qe.ColumnSet = new ColumnSet(new[] { "firstname" }); @@ -553,14 +524,10 @@ public void When_executing_a_query_expression_containvalues_operator_returns_par [Fact] public void When_executing_a_query_expression_containvalues_operator_returns_partial_matches_for_out_of_order_int_array_right_hand_side() { - - - - _service.Create(new Contact { FirstName = "1,2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "1,2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "null" }); + _context.Initialize(new [] + { + _contactWithOptions12, _contactWithOption2, _contactWithOptions23, _contactWithOptions123, _contactWithNullOptions + }); var qe = new QueryExpression("contact"); qe.ColumnSet = new ColumnSet(new[] { "firstname" }); @@ -575,14 +542,10 @@ public void When_executing_a_query_expression_containvalues_operator_returns_par [Fact] public void When_executing_a_query_expression_containvalues_operator_returns_partial_matches_for_string_array_right_hand_side() { - - - - _service.Create(new Contact { FirstName = "1,2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "1,2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "null" }); + _context.Initialize(new [] + { + _contactWithOptions12, _contactWithOption2, _contactWithOptions23, _contactWithOptions123, _contactWithNullOptions + }); var qe = new QueryExpression("contact"); qe.ColumnSet = new ColumnSet(new[] { "firstname" }); @@ -597,14 +560,10 @@ public void When_executing_a_query_expression_containvalues_operator_returns_par [Fact] public void When_executing_a_query_expression_containvalues_operator_returns_partial_matches_for_out_of_order_string_array_right_hand_side() { - - - - _service.Create(new Contact { FirstName = "1,2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "1,2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "null" }); + _context.Initialize(new [] + { + _contactWithOptions12, _contactWithOption2, _contactWithOptions23, _contactWithOptions123, _contactWithNullOptions + }); var qe = new QueryExpression("contact"); qe.ColumnSet = new ColumnSet(new[] { "firstname" }); @@ -619,14 +578,10 @@ public void When_executing_a_query_expression_containvalues_operator_returns_par [Fact] public void When_executing_a_query_expression_doesnotcontainvalues_operator_excludes_partial_matches_for_int_array_right_hand_side() { - - - - _service.Create(new Contact { FirstName = "1,2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "1,2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "null" }); + _context.Initialize(new [] + { + _contactWithOptions12, _contactWithOption2, _contactWithOptions23, _contactWithOptions123, _contactWithNullOptions + }); var qe = new QueryExpression("contact"); qe.ColumnSet = new ColumnSet(new[] { "firstname" }); @@ -641,14 +596,10 @@ public void When_executing_a_query_expression_doesnotcontainvalues_operator_excl [Fact] public void When_executing_a_query_expression_doesnotcontainvalues_operator_excludes_partial_matches_for_out_of_order_string_params_right_hand_side() { - - - - _service.Create(new Contact { FirstName = "1,2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2) } }); - _service.Create(new Contact { FirstName = "2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "1,2,3", new_MultiSelectAttribute = new OptionSetValueCollection() { new OptionSetValue(1), new OptionSetValue(2), new OptionSetValue(3) } }); - _service.Create(new Contact { FirstName = "null" }); + _context.Initialize(new [] + { + _contactWithOptions12, _contactWithOption2, _contactWithOptions23, _contactWithOptions123, _contactWithNullOptions + }); var qe = new QueryExpression("contact"); qe.ColumnSet = new ColumnSet(new[] { "firstname" }); From 4d0da5f416f09af2abcf511eae274bbd26c5553f Mon Sep 17 00:00:00 2001 From: Jordi Date: Sat, 23 Mar 2024 20:53:22 +0100 Subject: [PATCH 27/37] Limit the new tests to v9 only --- .../OperatorTests/InOperatorTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/InOperatorTests.cs b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/InOperatorTests.cs index 9c87e747..d0f02450 100644 --- a/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/InOperatorTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/InOperatorTests.cs @@ -1,3 +1,4 @@ +#if FAKE_XRM_EASY_9 using System; using DataverseEntities; using FakeXrmEasy.Abstractions; @@ -106,4 +107,5 @@ public void Should_return_invalid_cast_exception_a_non_empty_string_array_is_use Assert.Throws(() => _service.RetrieveMultiple(query)); } } -} \ No newline at end of file +} +#endif \ No newline at end of file From f7755951a3a9b83bb353a8c3b42cf3e1ae0deb5a Mon Sep 17 00:00:00 2001 From: Jordi Date: Sat, 23 Mar 2024 21:04:53 +0100 Subject: [PATCH 28/37] Cater for null values in OptionSetValueCollectionExtensions --- .../Extensions/OptionSetValueCollectionExtensions.cs | 2 ++ .../Extensions/OptionSetValueCollectionExtensionsTests.cs | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/src/FakeXrmEasy.Core/Extensions/OptionSetValueCollectionExtensions.cs b/src/FakeXrmEasy.Core/Extensions/OptionSetValueCollectionExtensions.cs index 7084a2db..1350712f 100644 --- a/src/FakeXrmEasy.Core/Extensions/OptionSetValueCollectionExtensions.cs +++ b/src/FakeXrmEasy.Core/Extensions/OptionSetValueCollectionExtensions.cs @@ -21,6 +21,8 @@ public static class OptionSetValueCollectionExtensions /// public static HashSet ConvertToHashSetOfInt(this object input, bool isOptionSetValueCollectionAccepted) { + if (input == null) return null; + var set = new HashSet(); var faultReason = $"The formatter threw an exception while trying to deserialize the message: There was an error while trying to deserialize parameter" + diff --git a/tests/FakeXrmEasy.Core.Tests/Extensions/OptionSetValueCollectionExtensionsTests.cs b/tests/FakeXrmEasy.Core.Tests/Extensions/OptionSetValueCollectionExtensionsTests.cs index ab8078ac..2f76b1e1 100644 --- a/tests/FakeXrmEasy.Core.Tests/Extensions/OptionSetValueCollectionExtensionsTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/Extensions/OptionSetValueCollectionExtensionsTests.cs @@ -12,6 +12,12 @@ namespace FakeXrmEasy.Core.Tests.Extensions { public class OptionSetValueCollectionExtensionsTests { + [Fact] + public void Should_convert_to_null_if_null() + { + Assert.Null(OptionSetValueCollectionExtensions.ConvertToHashSetOfInt(null, true)); + } + [Fact] public void Should_convert_to_a_hash_of_int_from_an_option_set_value_collection() { From 8cbfae96ebc37b0fcff88d666b91a3364abb8c6d Mon Sep 17 00:00:00 2001 From: Jordi Date: Sun, 24 Mar 2024 00:13:22 +0100 Subject: [PATCH 29/37] remove unnecessary null checks --- .../Extensions/OptionSetValueCollectionExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/FakeXrmEasy.Core/Extensions/OptionSetValueCollectionExtensions.cs b/src/FakeXrmEasy.Core/Extensions/OptionSetValueCollectionExtensions.cs index 1350712f..4236aef1 100644 --- a/src/FakeXrmEasy.Core/Extensions/OptionSetValueCollectionExtensions.cs +++ b/src/FakeXrmEasy.Core/Extensions/OptionSetValueCollectionExtensions.cs @@ -28,9 +28,9 @@ public static HashSet ConvertToHashSetOfInt(this object input, bool isOptio var faultReason = $"The formatter threw an exception while trying to deserialize the message: There was an error while trying to deserialize parameter" + $" http://schemas.microsoft.com/xrm/2011/Contracts/Services:query. The InnerException message was 'Error in line 1 position 8295. Element " + $"'http://schemas.microsoft.com/2003/10/Serialization/Arrays:anyType' contains data from a type that maps to the name " + - $"'http://schemas.microsoft.com/xrm/2011/Contracts:{input?.GetType()}'. The deserializer has no knowledge of any type that maps to this name. " + + $"'http://schemas.microsoft.com/xrm/2011/Contracts:{input.GetType()}'. The deserializer has no knowledge of any type that maps to this name. " + $"Consider changing the implementation of the ResolveName method on your DataContractResolver to return a non-null value for name " + - $"'{input?.GetType()}' and namespace 'http://schemas.microsoft.com/xrm/2011/Contracts'.'. Please see InnerException for more details."; + $"'{input.GetType()}' and namespace 'http://schemas.microsoft.com/xrm/2011/Contracts'.'. Please see InnerException for more details."; var type = input.GetType(); From a052d4b7b840611e7def3e2702826171118724e2 Mon Sep 17 00:00:00 2001 From: Jordi Date: Sun, 24 Mar 2024 00:18:09 +0100 Subject: [PATCH 30/37] Reduce cognitive complexity in MetadataGenerator --- .../Metadata/MetadataGenerator.cs | 77 ++++++++++--------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/src/FakeXrmEasy.Core/Metadata/MetadataGenerator.cs b/src/FakeXrmEasy.Core/Metadata/MetadataGenerator.cs index 93d3da4d..6444ecf2 100644 --- a/src/FakeXrmEasy.Core/Metadata/MetadataGenerator.cs +++ b/src/FakeXrmEasy.Core/Metadata/MetadataGenerator.cs @@ -212,7 +212,46 @@ internal static AttributeMetadata CreateAttributeMetadata(Type propertyType) } else if (propertyType.IsGenericType) { - Type genericType = propertyType.GetGenericArguments().FirstOrDefault(); + return CreateAttributeMetadataFromGenericType(propertyType); + } + else if (typeof(BooleanManagedProperty) == propertyType) + { + var booleanManaged = new BooleanAttributeMetadata(); + booleanManaged.SetSealedPropertyValue("AttributeType", AttributeTypeCode.ManagedProperty); + return booleanManaged; + } +#if !FAKE_XRM_EASY && !FAKE_XRM_EASY_2013 + else if (typeof(Guid) == propertyType) + { + return new UniqueIdentifierAttributeMetadata(); + } +#endif +#if !FAKE_XRM_EASY + else if (typeof(byte[]) == propertyType) + { + + return new ImageAttributeMetadata(); + } +#endif +#if FAKE_XRM_EASY_9 + else if (typeof(OptionSetValueCollection).IsAssignableFrom(propertyType)) + { + return new MultiSelectPicklistAttributeMetadata(); + } + else if (typeof(object) == propertyType) + { + return new FileAttributeMetadata(); + } +#endif + else + { + throw new Exception($"Type {propertyType.Name} has not been mapped to an AttributeMetadata."); + } + } + + internal static AttributeMetadata CreateAttributeMetadataFromGenericType(Type propertyType) + { + Type genericType = propertyType.GetGenericArguments().FirstOrDefault(); if (propertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) { if (typeof(int) == genericType) @@ -262,43 +301,7 @@ internal static AttributeMetadata CreateAttributeMetadata(Type propertyType) { throw new Exception($"Type {propertyType.Name}{genericType?.Name} has not been mapped to an AttributeMetadata."); } - } - else if (typeof(BooleanManagedProperty) == propertyType) - { - var booleanManaged = new BooleanAttributeMetadata(); - booleanManaged.SetSealedPropertyValue("AttributeType", AttributeTypeCode.ManagedProperty); - return booleanManaged; - } -#if !FAKE_XRM_EASY && !FAKE_XRM_EASY_2013 - else if (typeof(Guid) == propertyType) - { - return new UniqueIdentifierAttributeMetadata(); - } -#endif -#if !FAKE_XRM_EASY - else if (typeof(byte[]) == propertyType) - { - - return new ImageAttributeMetadata(); - } -#endif -#if FAKE_XRM_EASY_9 - else if (typeof(OptionSetValueCollection).IsAssignableFrom(propertyType)) - { - return new MultiSelectPicklistAttributeMetadata(); - } - else if (typeof(object) == propertyType) - { - - return new FileAttributeMetadata(); - } -#endif - else - { - throw new Exception($"Type {propertyType.Name} has not been mapped to an AttributeMetadata."); - } } - private static OneToManyRelationshipMetadata CreateOneToManyRelationshipMetadata(Type referencingEntity, PropertyInfo referencingAttribute, Type referencedEntity, From 627e16e12bbf0ef1c9d648faeba2dac9d9801238 Mon Sep 17 00:00:00 2001 From: Jordi Date: Sun, 24 Mar 2024 00:24:02 +0100 Subject: [PATCH 31/37] Replace string concatenation by StringBuilder DynamicsValue/fake-xrm-easy#139 --- .../Query/ConditionExpressionExtensions.In.cs | 9 +++------ .../Query/ConditionExpressionExtensions.Like.cs | 13 +++++++------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.In.cs b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.In.cs index f6d830de..b8a55a73 100644 --- a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.In.cs +++ b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.In.cs @@ -29,14 +29,11 @@ internal static Expression ToInExpression(this TypedConditionExpression tc, Expr { foreach (object value in c.Values) { - if (value is Array) - { - } - else + if (!(value is Array)) { expOrValues = Expression.Or(expOrValues, Expression.Equal( - tc.AttributeType.GetAppropiateCastExpressionBasedOnType(getAttributeValueExpr, value), - TypeCastExpressions.GetAppropiateTypedValueAndType(value, tc.AttributeType))); + tc.AttributeType.GetAppropiateCastExpressionBasedOnType(getAttributeValueExpr, value), + TypeCastExpressions.GetAppropiateTypedValueAndType(value, tc.AttributeType))); } } } diff --git a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Like.cs b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Like.cs index 107e4155..19258586 100644 --- a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Like.cs +++ b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Like.cs @@ -1,5 +1,6 @@ using System; using System.Linq.Expressions; +using System.Text; using System.Text.RegularExpressions; namespace FakeXrmEasy.Query @@ -19,25 +20,25 @@ internal static Expression ToLikeExpression(this TypedConditionExpression tc, Ex { //convert a like into a regular expression string input = value.ToString(); - string result = "^"; + StringBuilder regExBuilder = new StringBuilder("^"); int lastMatch = 0; var regex = new Regex("([^\\[]*)(\\[[^\\]]*\\])", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); foreach (Match match in regex.Matches(input)) { if (match.Groups[1].Success) { - result += ConvertToRegexDefinition(match.Groups[1].Value); + regExBuilder.Append(ConvertToRegexDefinition(match.Groups[1].Value)); } - result += match.Groups[2].Value.Replace("\\", "\\\\"); + regExBuilder.Append(match.Groups[2].Value.Replace("\\", "\\\\")); lastMatch = match.Index + match.Length; } if (input.Length != lastMatch) { - result += ConvertToRegexDefinition(input.Substring(lastMatch)); + regExBuilder.Append(ConvertToRegexDefinition(input.Substring(lastMatch))); } - result += "$"; + regExBuilder.Append("$"); - regex = new Regex(result, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + regex = new Regex(regExBuilder.ToString(), RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); expOrValues = Expression.Or(expOrValues, Expression.Call( Expression.Constant(regex), From ef3d714bd18ef29897a0b9b6caed713403836375 Mon Sep 17 00:00:00 2001 From: Jordi Date: Sun, 24 Mar 2024 00:36:03 +0100 Subject: [PATCH 32/37] Resolve various code smells --- CHANGELOG.md | 2 +- .../OperatorTests/Strings/StringOperatorTests.cs | 6 +++--- .../FakeContextTestTranslateQueryExpression.cs | 14 ++------------ .../OperatorTests/Strings/StringOperatorsTests.cs | 9 --------- 4 files changed, 6 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72d53946..ec9a91f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,10 @@ - Added support for bulk operations: CreateMultipleRequest, UpdateMultipleRequest, UpsertMultipleRequest - https://github.com/DynamicsValue/fake-xrm-easy/issues/122 - Added new exception to make the initialization of entity records with attributes with a null entity reference more obvious (thanks Betim) - https://github.com/DynamicsValue/fake-xrm-easy/issues/107 - Add support for OptionSetValueCollection attributes when they are generated as an IEnumerable (using EBG or pac modelbuilder) - https://github.com/DynamicsValue/fake-xrm-easy/issues/140 +- Added extended wildcard support for the Like operator (thanks Betim) - https://github.com/DynamicsValue/fake-xrm-easy/issues/139 ### Changed -- Added extended wildcard support for the Like operator (thanks Betim) - https://github.com/DynamicsValue/fake-xrm-easy/issues/139 - Resolves referencing EntityAlias or EntityName in conditions inside nested filters of a LinkedEntity (thanks Temmy) - https://github.com/DynamicsValue/fake-xrm-easy/issues/63 - Resolves Resolving entity references by Alternate Keys when EntityMetadata doesn't have any Keys. - https://github.com/DynamicsValue/fake-xrm-easy/issues/138 - Resolves an issue where a ConditionExpression with an In operator should to not take array of integers as an input, but instead separate values (thanks Ben and Betim) - https://github.com/DynamicsValue/fake-xrm-easy/issues/96 diff --git a/tests/FakeXrmEasy.Core.Tests/Query/FetchXml/OperatorTests/Strings/StringOperatorTests.cs b/tests/FakeXrmEasy.Core.Tests/Query/FetchXml/OperatorTests/Strings/StringOperatorTests.cs index ad75399d..c928c19c 100644 --- a/tests/FakeXrmEasy.Core.Tests/Query/FetchXml/OperatorTests/Strings/StringOperatorTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/Query/FetchXml/OperatorTests/Strings/StringOperatorTests.cs @@ -132,7 +132,7 @@ public void FetchXml_Operator_BeginsWith_Execution() var collection = _service.RetrieveMultiple(new FetchExpression(fetchXml)); - Assert.Equal(1, collection.Entities.Count); + Assert.Single(collection.Entities); Assert.Equal("Alice", collection.Entities[0]["nickname"]); } @@ -178,7 +178,7 @@ public void FetchXml_Operator_EndsWith_Execution() var collection = _service.RetrieveMultiple(new FetchExpression(fetchXml)); - Assert.Equal(1, collection.Entities.Count); + Assert.Single(collection.Entities); Assert.Equal("Alice", collection.Entities[0]["nickname"]); } @@ -225,7 +225,7 @@ public void FetchXml_Operator_Like_Execution() var collection = _service.RetrieveMultiple(new FetchExpression(fetchXml)); - Assert.Equal(1, collection.Entities.Count); + Assert.Single(collection.Entities); Assert.Equal("Alice", collection.Entities[0]["nickname"]); } diff --git a/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/FakeContextTestTranslateQueryExpression.cs b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/FakeContextTestTranslateQueryExpression.cs index 8e47a8ca..12c3f711 100644 --- a/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/FakeContextTestTranslateQueryExpression.cs +++ b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/FakeContextTestTranslateQueryExpression.cs @@ -132,7 +132,6 @@ public void When_executing_a_query_expression_with_a_left_join_all_left_hand_sid [Fact] public void When_executing_a_query_expression_join_with_orphans_these_are_not_returned() { - var contact1 = new Entity("contact") { Id = Guid.NewGuid() }; contact1["fullname"] = "Contact 1"; var contact2 = new Entity("contact") { Id = Guid.NewGuid() }; contact2["fullname"] = "Contact 2"; var contact3 = new Entity("contact") { Id = Guid.NewGuid() }; contact3["fullname"] = "Contact 3"; @@ -216,7 +215,6 @@ public void When_executing_a_query_expression_only_the_selected_columns_in_the_c [Fact] public void When_executing_a_query_expression_with_an_attribute_in_columnset_that_doesnt_exists_no_value_is_returned_with_dynamic_entities() { - var contact1 = new Entity("contact") { Id = Guid.NewGuid() }; contact1["fullname"] = "Contact 1"; contact1["firstname"] = "First 1"; var account = new Entity("account") { Id = Guid.NewGuid() }; @@ -239,7 +237,6 @@ public void When_executing_a_query_expression_with_an_attribute_in_columnset_tha [Fact] public void When_executing_a_query_expression_with_an_attribute_in_columnset_that_doesnt_exists_exception_is_raised_with_early_bound_entities() { - var contact1 = new Contact() { Id = Guid.NewGuid() }; contact1["fullname"] = "Contact 1"; contact1["firstname"] = "First 1"; var account = new Account() { Id = Guid.NewGuid() }; @@ -254,14 +251,12 @@ public void When_executing_a_query_expression_with_an_attribute_in_columnset_tha //We only select fullname and parentcustomerid, firstname should not be included qe.ColumnSet = new ColumnSet(new string[] { "this attribute doesnt exists!" }); - var exception = Assert.Throws>(() => qe.ToQueryable(_context).ToList()); - Assert.Equal(exception.Detail.ErrorCode, (int)ErrorCodes.QueryBuilderNoAttribute); + XAssert.ThrowsFaultCode(ErrorCodes.QueryBuilderNoAttribute, () => qe.ToQueryable(_context).ToList()); } [Fact] public void When_executing_a_query_expression_with_an_attribute_in_columnset_in_a_linked_entity_that_doesnt_exists_descriptive_exception_is_thrown() { - var contact1 = new Contact() { Id = Guid.NewGuid() }; contact1["fullname"] = "Contact 1"; contact1["firstname"] = "First 1"; var contact2 = new Contact() { Id = Guid.NewGuid() }; contact2["fullname"] = "Contact 2"; contact2["firstname"] = "First 2"; var contact3 = new Contact() { Id = Guid.NewGuid() }; contact3["fullname"] = "Contact 3"; contact3["firstname"] = "First 3"; @@ -290,14 +285,12 @@ public void When_executing_a_query_expression_with_an_attribute_in_columnset_in_ //We only select fullname and parentcustomerid, firstname should not be included qe.ColumnSet = new ColumnSet(new string[] { "this attribute doesnt exists!" }); - var exception = Assert.Throws>(() => qe.ToQueryable(_context).ToList()); - Assert.Equal(exception.Detail.ErrorCode, (int)ErrorCodes.QueryBuilderNoAttribute); + XAssert.ThrowsFaultCode(ErrorCodes.QueryBuilderNoAttribute, () => qe.ToQueryable(_context).ToList()); } [Fact] public void When_executing_a_query_expression_with_all_attributes_all_of_them_are_returned() { - var contact1 = new Entity("contact") { Id = Guid.NewGuid() }; contact1["fullname"] = "Contact 1"; contact1["firstname"] = "First 1"; var contact2 = new Entity("contact") { Id = Guid.NewGuid() }; contact2["fullname"] = "Contact 2"; contact2["firstname"] = "First 2"; var contact3 = new Entity("contact") { Id = Guid.NewGuid() }; contact3["fullname"] = "Contact 3"; contact3["firstname"] = "First 3"; @@ -341,7 +334,6 @@ public void When_executing_a_query_expression_with_all_attributes_all_of_them_ar [Fact] public void When_executing_a_query_expression_without_columnset_no_attributes_are_returned() { - var contact1 = new Entity("contact") { Id = Guid.NewGuid() }; contact1["fullname"] = "Contact 1"; contact1["firstname"] = "First 1"; var contact2 = new Entity("contact") { Id = Guid.NewGuid() }; contact2["fullname"] = "Contact 2"; contact2["firstname"] = "First 2"; var contact3 = new Entity("contact") { Id = Guid.NewGuid() }; contact3["fullname"] = "Contact 3"; contact3["firstname"] = "First 3"; @@ -381,7 +373,6 @@ public void When_executing_a_query_expression_without_columnset_no_attributes_ar [Fact] public void When_executing_a_query_expression_with_a_columnset_in_a_linkedentity_attribute_is_returned_with_a_prefix() { - var contact1 = new Entity("contact") { Id = Guid.NewGuid() }; contact1["fullname"] = "Contact 1"; contact1["firstname"] = "First 1"; var contact2 = new Entity("contact") { Id = Guid.NewGuid() }; contact2["fullname"] = "Contact 2"; contact2["firstname"] = "First 2"; var contact3 = new Entity("contact") { Id = Guid.NewGuid() }; contact3["fullname"] = "Contact 3"; contact3["firstname"] = "First 3"; @@ -426,7 +417,6 @@ public void When_executing_a_query_expression_with_a_columnset_in_a_linkedentity [Fact] public void When_executing_a_query_expression_with_a_columnset_in_a_linkedentity_attribute_is_returned_with_an_alias() { - var contact1 = new Entity("contact") { Id = Guid.NewGuid() }; contact1["fullname"] = "Contact 1"; contact1["firstname"] = "First 1"; var contact2 = new Entity("contact") { Id = Guid.NewGuid() }; contact2["fullname"] = "Contact 2"; contact2["firstname"] = "First 2"; var contact3 = new Entity("contact") { Id = Guid.NewGuid() }; contact3["fullname"] = "Contact 3"; contact3["firstname"] = "First 3"; diff --git a/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/Strings/StringOperatorsTests.cs b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/Strings/StringOperatorsTests.cs index 9a824f53..c79cdb2f 100644 --- a/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/Strings/StringOperatorsTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/Query/TranslateQueryExpressionTests/OperatorTests/Strings/StringOperatorsTests.cs @@ -14,7 +14,6 @@ public class StringOperatorsTests: FakeXrmEasyTestsBase [Fact] public void When_executing_a_query_expression_begins_with_operator_is_case_insensitive() { - _service.Create(new Contact { FirstName = "Jimmy" }); var qe = new QueryExpression("contact"); @@ -26,9 +25,6 @@ public void When_executing_a_query_expression_begins_with_operator_is_case_insen [Fact] public void When_executing_a_query_expression_ends_with_operator_is_case_insensitive() { - - - _service.Create(new Contact { FirstName = "JimmY" }); var qe = new QueryExpression("contact"); @@ -51,7 +47,6 @@ public void When_executing_a_query_expression_like_operator_is_case_insensitive( [Fact] public void When_executing_a_query_expression_with_endswith_operator_right_result_is_returned() { - var contact1 = new Entity("contact") { Id = Guid.NewGuid() }; contact1["fullname"] = "Contact 1"; contact1["firstname"] = "First 1"; var contact2 = new Entity("contact") { Id = Guid.NewGuid() }; contact2["fullname"] = "Contact 2"; contact2["firstname"] = "First 2"; @@ -71,7 +66,6 @@ public void When_executing_a_query_expression_with_endswith_operator_right_resul [Fact] public void When_executing_a_query_expression_with_beginswith_operator_right_result_is_returned() { - var contact1 = new Entity("contact") { Id = Guid.NewGuid() }; contact1["fullname"] = "1 Contact"; contact1["firstname"] = "First 1"; var contact2 = new Entity("contact") { Id = Guid.NewGuid() }; contact2["fullname"] = "2 Contact"; contact2["firstname"] = "First 2"; @@ -91,7 +85,6 @@ public void When_executing_a_query_expression_with_beginswith_operator_right_res [Fact] public void When_executing_a_query_expression_with_contains_operator_right_result_is_returned() { - var contact1 = new Entity("contact") { Id = Guid.NewGuid() }; contact1["fullname"] = "1 Contact"; contact1["firstname"] = "First 1"; var contact2 = new Entity("contact") { Id = Guid.NewGuid() }; contact2["fullname"] = "2 Contact"; contact2["firstname"] = "First 2"; var contact3 = new Entity("contact") { Id = Guid.NewGuid() }; contact3["fullname"] = "Other"; contact3["firstname"] = "First 2"; @@ -157,7 +150,6 @@ public void When_executing_a_query_expression_with_lessthanorequal_operator_righ [Fact] public void When_executing_a_query_expression_with_greaterthan_operator_right_result_is_returned() { - var ct1 = new Contact() { Id = Guid.NewGuid(), NickName = "Al" }; var ct2 = new Contact() { Id = Guid.NewGuid(), NickName = "Bob" }; var ct3 = new Contact() { Id = Guid.NewGuid(), NickName = "Charlie" }; @@ -179,7 +171,6 @@ public void When_executing_a_query_expression_with_greaterthan_operator_right_re [Fact] public void When_executing_a_query_expression_with_greaterthanorequal_operator_right_result_is_returned() { - var ct1 = new Contact() { Id = Guid.NewGuid(), NickName = "Al" }; var ct2 = new Contact() { Id = Guid.NewGuid(), NickName = "Bob" }; var ct3 = new Contact() { Id = Guid.NewGuid(), NickName = "Charlie" }; From 5fcc6307ed479552ad606a1b2b086791cb947587 Mon Sep 17 00:00:00 2001 From: Jordi Date: Sun, 24 Mar 2024 00:42:16 +0100 Subject: [PATCH 33/37] Mark TypedCondictionExpression as internal, no need to expose it as a public class --- src/FakeXrmEasy.Core/Query/TypedConditionExpression.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/FakeXrmEasy.Core/Query/TypedConditionExpression.cs b/src/FakeXrmEasy.Core/Query/TypedConditionExpression.cs index b63a1022..8584edaf 100644 --- a/src/FakeXrmEasy.Core/Query/TypedConditionExpression.cs +++ b/src/FakeXrmEasy.Core/Query/TypedConditionExpression.cs @@ -11,7 +11,7 @@ namespace FakeXrmEasy.Query /// /// A condition expression with a decorated type of the attribute in the condition expression /// - public class TypedConditionExpression + internal class TypedConditionExpression { /// /// The QueryExpression to which this condition belongs @@ -37,7 +37,8 @@ public class TypedConditionExpression /// Creates a TypedConditionExpression from an existing ConditionExpression with no attribute type information /// /// - public TypedConditionExpression(ConditionExpression c, QueryExpression qe) + /// + internal TypedConditionExpression(ConditionExpression c, QueryExpression qe) { IsOuter = false; CondExpression = c; From 02a2b4b6b7436b0dab99c3e030fa07f260d93758 Mon Sep 17 00:00:00 2001 From: Jordi Date: Sun, 24 Mar 2024 16:26:17 +0100 Subject: [PATCH 34/37] Resolves DynamicsValue/fake-xrm-easy#64, updat enamespace --- CHANGELOG.md | 2 ++ tests/FakeXrmEasy.Core.Tests/Issues/Issue64.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec9a91f1..1839f580 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ - Resolves referencing EntityAlias or EntityName in conditions inside nested filters of a LinkedEntity (thanks Temmy) - https://github.com/DynamicsValue/fake-xrm-easy/issues/63 - Resolves Resolving entity references by Alternate Keys when EntityMetadata doesn't have any Keys. - https://github.com/DynamicsValue/fake-xrm-easy/issues/138 - Resolves an issue where a ConditionExpression with an In operator should to not take array of integers as an input, but instead separate values (thanks Ben and Betim) - https://github.com/DynamicsValue/fake-xrm-easy/issues/96 +- Resolves filtering Money attributes by an integer value (thanks Ben and Betim) - https://github.com/DynamicsValue/fake-xrm-easy/issues/64 + ## [2.4.2] diff --git a/tests/FakeXrmEasy.Core.Tests/Issues/Issue64.cs b/tests/FakeXrmEasy.Core.Tests/Issues/Issue64.cs index d37c6d1f..fb7f1dea 100644 --- a/tests/FakeXrmEasy.Core.Tests/Issues/Issue64.cs +++ b/tests/FakeXrmEasy.Core.Tests/Issues/Issue64.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using Xunit; -namespace FakeXrmEasy.Tests.Issues +namespace FakeXrmEasy.Core.Tests.Issues { // https://github.com/DynamicsValue/fake-xrm-easy/issues/64 From 78ece40b3ec8896dad799b88a23b329d7bb49eb4 Mon Sep 17 00:00:00 2001 From: Jordi Date: Sun, 24 Mar 2024 19:40:32 +0100 Subject: [PATCH 35/37] Rename typos in method names, make all condition expression extensions internal, fix some code smells. --- ...onditionExpressionExtensions.BeginsWith.cs | 2 +- .../ConditionExpressionExtensions.Between.cs | 10 +-- ...ditionExpressionExtensions.BetweenDates.cs | 2 +- .../ConditionExpressionExtensions.Contains.cs | 2 +- ...tionExpressionExtensions.ContainsValues.cs | 4 +- .../ConditionExpressionExtensions.EndsWith.cs | 2 +- .../ConditionExpressionExtensions.Equal.cs | 12 +-- ...nditionExpressionExtensions.GreaterThan.cs | 12 +-- .../Query/ConditionExpressionExtensions.In.cs | 8 +- .../ConditionExpressionExtensions.LastX.cs | 2 +- .../ConditionExpressionExtensions.LessThan.cs | 12 +-- .../ConditionExpressionExtensions.Like.cs | 4 +- .../ConditionExpressionExtensions.NextX.cs | 2 +- .../ConditionExpressionExtensions.Null.cs | 2 +- ...ConditionExpressionExtensions.OlderThan.cs | 6 +- .../Query/ConditionExpressionExtensions.cs | 2 +- .../Query/ExpressionExtensions.cs | 2 +- .../Query/FetchXmlExtensions.cs | 8 +- ...ons.cs => TypeCastExpressionExtensions.cs} | 81 +++++++++---------- src/FakeXrmEasy.Core/XrmFakedContext.cs | 18 ++--- .../FakeXrmEasy.Core.Tests/Issues/Issue64.cs | 3 +- 21 files changed, 97 insertions(+), 99 deletions(-) rename src/FakeXrmEasy.Core/Query/{TypeCastExpressions.cs => TypeCastExpressionExtensions.cs} (74%) diff --git a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.BeginsWith.cs b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.BeginsWith.cs index 6999a5ea..2dada470 100644 --- a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.BeginsWith.cs +++ b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.BeginsWith.cs @@ -4,7 +4,7 @@ namespace FakeXrmEasy.Query { - public static partial class ConditionExpressionExtensions + internal static partial class ConditionExpressionExtensions { internal static Expression ToBeginsWithExpression(this TypedConditionExpression tc, Expression getAttributeValueExpr, Expression containsAttributeExpr) { diff --git a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Between.cs b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Between.cs index 52b32c8b..49b65aed 100644 --- a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Between.cs +++ b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Between.cs @@ -5,7 +5,7 @@ namespace FakeXrmEasy.Query /// /// ConditionExpression Extensions /// - public static partial class ConditionExpressionExtensions + internal static partial class ConditionExpressionExtensions { internal static Expression ToBetweenExpression(this TypedConditionExpression tc, Expression getAttributeValueExpr, Expression containsAttributeExpr) { @@ -18,12 +18,12 @@ internal static Expression ToBetweenExpression(this TypedConditionExpression tc, //Between the range... var exp = Expression.And( Expression.GreaterThanOrEqual( - tc.AttributeType.GetAppropiateCastExpressionBasedOnType(getAttributeValueExpr, value1), - TypeCastExpressions.GetAppropiateTypedValueAndType(value1, tc.AttributeType)), + tc.AttributeType.GetAppropriateCastExpressionBasedOnType(getAttributeValueExpr, value1), + TypeCastExpressionExtensions.GetAppropriateTypedValueAndType(value1, tc.AttributeType)), Expression.LessThanOrEqual( - tc.AttributeType.GetAppropiateCastExpressionBasedOnType(getAttributeValueExpr, value2), - TypeCastExpressions.GetAppropiateTypedValueAndType(value2, tc.AttributeType))); + tc.AttributeType.GetAppropriateCastExpressionBasedOnType(getAttributeValueExpr, value2), + TypeCastExpressionExtensions.GetAppropriateTypedValueAndType(value2, tc.AttributeType))); //and... attribute exists too diff --git a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.BetweenDates.cs b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.BetweenDates.cs index 001f2510..56281242 100644 --- a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.BetweenDates.cs +++ b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.BetweenDates.cs @@ -8,7 +8,7 @@ namespace FakeXrmEasy.Query { - public static partial class ConditionExpressionExtensions + internal static partial class ConditionExpressionExtensions { /// /// Takes a condition expression which needs translating into a 'between two dates' expression and works out the relevant dates diff --git a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Contains.cs b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Contains.cs index 5fd07744..097491a7 100644 --- a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Contains.cs +++ b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Contains.cs @@ -5,7 +5,7 @@ namespace FakeXrmEasy.Query { - public static partial class ConditionExpressionExtensions + internal static partial class ConditionExpressionExtensions { internal static Expression ToContainsExpression(this TypedConditionExpression tc, Expression getAttributeValueExpr, Expression containsAttributeExpr) { diff --git a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.ContainsValues.cs b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.ContainsValues.cs index 39321eb6..d6ad1d87 100644 --- a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.ContainsValues.cs +++ b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.ContainsValues.cs @@ -6,11 +6,11 @@ namespace FakeXrmEasy.Query { - public static partial class ConditionExpressionExtensions + internal static partial class ConditionExpressionExtensions { internal static Expression ToContainsValuesExpression(this TypedConditionExpression tc, Expression getAttributeValueExpr, Expression containsAttributeExpr) { - var leftHandSideExpression = typeof(OptionSetValueCollection).GetAppropiateCastExpressionBasedOnType(getAttributeValueExpr, null); + var leftHandSideExpression = typeof(OptionSetValueCollection).GetAppropriateCastExpressionBasedOnType(getAttributeValueExpr, null); var rightHandSideExpression = Expression.Constant(OptionSetValueCollectionExtensions.ConvertToHashSetOfInt(tc.CondExpression.Values, isOptionSetValueCollectionAccepted: false)); return Expression.AndAlso( diff --git a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.EndsWith.cs b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.EndsWith.cs index d803959d..b4e14786 100644 --- a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.EndsWith.cs +++ b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.EndsWith.cs @@ -4,7 +4,7 @@ namespace FakeXrmEasy.Query { - public static partial class ConditionExpressionExtensions + internal static partial class ConditionExpressionExtensions { internal static Expression ToEndsWithExpression(this TypedConditionExpression tc, Expression getAttributeValueExpr, Expression containsAttributeExpr) { diff --git a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Equal.cs b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Equal.cs index f3719dbd..cba5be32 100644 --- a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Equal.cs +++ b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Equal.cs @@ -8,7 +8,7 @@ namespace FakeXrmEasy.Query { - public static partial class ConditionExpressionExtensions + internal static partial class ConditionExpressionExtensions { internal static Expression ToEqualExpression(this TypedConditionExpression c, IXrmFakedContext context, Expression getAttributeValueExpr, Expression containsAttributeExpr) { @@ -42,18 +42,18 @@ internal static Expression ToEqualExpression(this TypedConditionExpression c, IX if (unaryOperatorValue != null) { //c.Values empty in this case - var leftHandSideExpression = c.AttributeType.GetAppropiateCastExpressionBasedOnType(getAttributeValueExpr, unaryOperatorValue); + var leftHandSideExpression = c.AttributeType.GetAppropriateCastExpressionBasedOnType(getAttributeValueExpr, unaryOperatorValue); var transformedExpression = leftHandSideExpression.TransformValueBasedOnOperator(c.CondExpression.Operator); expOrValues = Expression.Equal(transformedExpression, - TypeCastExpressions.GetAppropiateTypedValueAndType(unaryOperatorValue, c.AttributeType)); + TypeCastExpressionExtensions.GetAppropriateTypedValueAndType(unaryOperatorValue, c.AttributeType)); } #if FAKE_XRM_EASY_9 else if (c.AttributeType == typeof(OptionSetValueCollection)) { var conditionValue = c.GetSingleConditionValue(); - var leftHandSideExpression = c.AttributeType.GetAppropiateCastExpressionBasedOnType(getAttributeValueExpr, conditionValue); + var leftHandSideExpression = c.AttributeType.GetAppropriateCastExpressionBasedOnType(getAttributeValueExpr, conditionValue); var rightHandSideExpression = Expression.Constant(OptionSetValueCollectionExtensions.ConvertToHashSetOfInt(conditionValue, isOptionSetValueCollectionAccepted: false)); expOrValues = Expression.Equal( @@ -65,12 +65,12 @@ internal static Expression ToEqualExpression(this TypedConditionExpression c, IX { foreach (object value in c.CondExpression.Values) { - var leftHandSideExpression = c.AttributeType.GetAppropiateCastExpressionBasedOnType(getAttributeValueExpr, value); + var leftHandSideExpression = c.AttributeType.GetAppropriateCastExpressionBasedOnType(getAttributeValueExpr, value); var transformedExpression = leftHandSideExpression.TransformValueBasedOnOperator(c.CondExpression.Operator); expOrValues = Expression.Or(expOrValues, Expression.Equal(transformedExpression, - TypeCastExpressions.GetAppropiateTypedValueAndType(value, c.AttributeType) + TypeCastExpressionExtensions.GetAppropriateTypedValueAndType(value, c.AttributeType) .TransformValueBasedOnOperator(c.CondExpression.Operator))); diff --git a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.GreaterThan.cs b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.GreaterThan.cs index 334eefa8..7f99148a 100644 --- a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.GreaterThan.cs +++ b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.GreaterThan.cs @@ -5,7 +5,7 @@ namespace FakeXrmEasy.Query { - public static partial class ConditionExpressionExtensions + internal static partial class ConditionExpressionExtensions { internal static Expression ToGreaterThanExpression(this TypedConditionExpression tc, Expression getAttributeValueExpr, Expression containsAttributeExpr) { @@ -20,7 +20,7 @@ internal static Expression ToGreaterThanExpression(this TypedConditionExpression { return tc.ToGreaterThanStringExpression(getAttributeValueExpr, containsAttributeExpr); } - else if (TypeCastExpressions.GetAppropiateTypeForValue(c.Values[0]) == typeof(string)) + else if (TypeCastExpressionExtensions.GetAppropriateTypeForValue(c.Values[0]) == typeof(string)) { return tc.ToGreaterThanStringExpression(getAttributeValueExpr, containsAttributeExpr); } @@ -29,13 +29,13 @@ internal static Expression ToGreaterThanExpression(this TypedConditionExpression BinaryExpression expOrValues = Expression.Or(Expression.Constant(false), Expression.Constant(false)); foreach (object value in c.Values) { - var leftHandSideExpression = tc.AttributeType.GetAppropiateCastExpressionBasedOnType(getAttributeValueExpr, value); + var leftHandSideExpression = tc.AttributeType.GetAppropriateCastExpressionBasedOnType(getAttributeValueExpr, value); var transformedExpression = leftHandSideExpression.TransformValueBasedOnOperator(tc.CondExpression.Operator); expOrValues = Expression.Or(expOrValues, Expression.GreaterThan( transformedExpression, - TypeCastExpressions.GetAppropiateTypedValueAndType(value, tc.AttributeType).TransformValueBasedOnOperator(tc.CondExpression.Operator))); + TypeCastExpressionExtensions.GetAppropriateTypedValueAndType(value, tc.AttributeType).TransformValueBasedOnOperator(tc.CondExpression.Operator))); } return Expression.AndAlso( containsAttributeExpr, @@ -60,11 +60,11 @@ internal static Expression ToGreaterThanStringExpression(this TypedConditionExpr BinaryExpression expOrValues = Expression.Or(Expression.Constant(false), Expression.Constant(false)); foreach (object value in c.Values) { - var leftHandSideExpression = tc.AttributeType.GetAppropiateCastExpressionBasedOnType(getAttributeValueExpr, value); + var leftHandSideExpression = tc.AttributeType.GetAppropriateCastExpressionBasedOnType(getAttributeValueExpr, value); var transformedExpression = leftHandSideExpression.TransformValueBasedOnOperator(tc.CondExpression.Operator); var left = transformedExpression; - var right = TypeCastExpressions.GetAppropiateTypedValueAndType(value, tc.AttributeType).TransformValueBasedOnOperator(tc.CondExpression.Operator); + var right = TypeCastExpressionExtensions.GetAppropriateTypedValueAndType(value, tc.AttributeType).TransformValueBasedOnOperator(tc.CondExpression.Operator); var methodCallExpr = left.ToCompareToExpression(right); diff --git a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.In.cs b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.In.cs index b8a55a73..e97a218a 100644 --- a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.In.cs +++ b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.In.cs @@ -6,7 +6,7 @@ namespace FakeXrmEasy.Query { - public static partial class ConditionExpressionExtensions + internal static partial class ConditionExpressionExtensions { internal static Expression ToInExpression(this TypedConditionExpression tc, Expression getAttributeValueExpr, Expression containsAttributeExpr) { @@ -17,7 +17,7 @@ internal static Expression ToInExpression(this TypedConditionExpression tc, Expr #if FAKE_XRM_EASY_9 if (tc.AttributeType?.IsOptionSetValueCollection() == true) { - var leftHandSideExpression = tc.AttributeType.GetAppropiateCastExpressionBasedOnType(getAttributeValueExpr, null); + var leftHandSideExpression = tc.AttributeType.GetAppropriateCastExpressionBasedOnType(getAttributeValueExpr, null); var rightHandSideExpression = Expression.Constant(OptionSetValueCollectionExtensions.ConvertToHashSetOfInt(c.Values, isOptionSetValueCollectionAccepted: false)); expOrValues = Expression.Equal( @@ -32,8 +32,8 @@ internal static Expression ToInExpression(this TypedConditionExpression tc, Expr if (!(value is Array)) { expOrValues = Expression.Or(expOrValues, Expression.Equal( - tc.AttributeType.GetAppropiateCastExpressionBasedOnType(getAttributeValueExpr, value), - TypeCastExpressions.GetAppropiateTypedValueAndType(value, tc.AttributeType))); + tc.AttributeType.GetAppropriateCastExpressionBasedOnType(getAttributeValueExpr, value), + TypeCastExpressionExtensions.GetAppropriateTypedValueAndType(value, tc.AttributeType))); } } } diff --git a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.LastX.cs b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.LastX.cs index 6cea323e..9eb226a4 100644 --- a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.LastX.cs +++ b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.LastX.cs @@ -5,7 +5,7 @@ namespace FakeXrmEasy.Query { - public static partial class ConditionExpressionExtensions + internal static partial class ConditionExpressionExtensions { internal static Expression ToLastXExpression(this TypedConditionExpression tc, Expression getAttributeValueExpr, Expression containsAttributeExpr) { diff --git a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.LessThan.cs b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.LessThan.cs index ffce1d1e..73bd3e5e 100644 --- a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.LessThan.cs +++ b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.LessThan.cs @@ -5,7 +5,7 @@ namespace FakeXrmEasy.Query { - public static partial class ConditionExpressionExtensions + internal static partial class ConditionExpressionExtensions { internal static Expression ToLessThanExpression(this TypedConditionExpression tc, Expression getAttributeValueExpr, Expression containsAttributeExpr) { @@ -20,7 +20,7 @@ internal static Expression ToLessThanExpression(this TypedConditionExpression tc { return tc.ToLessThanStringExpression(getAttributeValueExpr, containsAttributeExpr); } - else if (TypeCastExpressions.GetAppropiateTypeForValue(c.Values[0]) == typeof(string)) + else if (TypeCastExpressionExtensions.GetAppropriateTypeForValue(c.Values[0]) == typeof(string)) { return tc.ToLessThanStringExpression(getAttributeValueExpr, containsAttributeExpr); } @@ -29,13 +29,13 @@ internal static Expression ToLessThanExpression(this TypedConditionExpression tc BinaryExpression expOrValues = Expression.Or(Expression.Constant(false), Expression.Constant(false)); foreach (object value in c.Values) { - var leftHandSideExpression = tc.AttributeType.GetAppropiateCastExpressionBasedOnType(getAttributeValueExpr, value); + var leftHandSideExpression = tc.AttributeType.GetAppropriateCastExpressionBasedOnType(getAttributeValueExpr, value); var transformedExpression = leftHandSideExpression.TransformValueBasedOnOperator(tc.CondExpression.Operator); expOrValues = Expression.Or(expOrValues, Expression.LessThan( transformedExpression, - TypeCastExpressions.GetAppropiateTypedValueAndType(value, tc.AttributeType).TransformValueBasedOnOperator(tc.CondExpression.Operator))); + TypeCastExpressionExtensions.GetAppropriateTypedValueAndType(value, tc.AttributeType).TransformValueBasedOnOperator(tc.CondExpression.Operator))); } return Expression.AndAlso( containsAttributeExpr, @@ -59,10 +59,10 @@ internal static Expression ToLessThanStringExpression(this TypedConditionExpress BinaryExpression expOrValues = Expression.Or(Expression.Constant(false), Expression.Constant(false)); foreach (object value in c.Values) { - var leftHandSideExpression = tc.AttributeType.GetAppropiateCastExpressionBasedOnType(getAttributeValueExpr, value); + var leftHandSideExpression = tc.AttributeType.GetAppropriateCastExpressionBasedOnType(getAttributeValueExpr, value); var transformedLeftHandSideExpression = leftHandSideExpression.TransformValueBasedOnOperator(tc.CondExpression.Operator); - var rightHandSideExpression = TypeCastExpressions.GetAppropiateTypedValueAndType(value, tc.AttributeType).TransformValueBasedOnOperator(tc.CondExpression.Operator); + var rightHandSideExpression = TypeCastExpressionExtensions.GetAppropriateTypedValueAndType(value, tc.AttributeType).TransformValueBasedOnOperator(tc.CondExpression.Operator); var compareToMethodCall = transformedLeftHandSideExpression.ToCompareToExpression(rightHandSideExpression); diff --git a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Like.cs b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Like.cs index 19258586..8cc7cccb 100644 --- a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Like.cs +++ b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Like.cs @@ -5,13 +5,13 @@ namespace FakeXrmEasy.Query { - public static partial class ConditionExpressionExtensions + internal static partial class ConditionExpressionExtensions { internal static Expression ToLikeExpression(this TypedConditionExpression tc, Expression getAttributeValueExpr, Expression containsAttributeExpr) { var c = tc.CondExpression; BinaryExpression expOrValues = Expression.Or(Expression.Constant(false), Expression.Constant(false)); - Expression convertedValueToStr = Expression.Convert(tc.AttributeType.GetAppropiateCastExpressionBasedOnType(getAttributeValueExpr, c.Values[0]), typeof(string)); + Expression convertedValueToStr = Expression.Convert(tc.AttributeType.GetAppropriateCastExpressionBasedOnType(getAttributeValueExpr, c.Values[0]), typeof(string)); Expression convertedValueToStrAndToLower = convertedValueToStr.ToCaseInsensitiveExpression(); diff --git a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.NextX.cs b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.NextX.cs index 58ed7550..14f46092 100644 --- a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.NextX.cs +++ b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.NextX.cs @@ -5,7 +5,7 @@ namespace FakeXrmEasy.Query { - public static partial class ConditionExpressionExtensions + internal static partial class ConditionExpressionExtensions { internal static Expression ToNextXExpression(this TypedConditionExpression tc, Expression getAttributeValueExpr, Expression containsAttributeExpr) { diff --git a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Null.cs b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Null.cs index 2cc28a4c..9ed38b6f 100644 --- a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Null.cs +++ b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.Null.cs @@ -2,7 +2,7 @@ namespace FakeXrmEasy.Query { - public static partial class ConditionExpressionExtensions + internal static partial class ConditionExpressionExtensions { internal static Expression ToNullExpression(this TypedConditionExpression tc, Expression getAttributeValueExpr, Expression containsAttributeExpr) { diff --git a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.OlderThan.cs b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.OlderThan.cs index ab82c0b3..0d1f52ae 100644 --- a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.OlderThan.cs +++ b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.OlderThan.cs @@ -5,7 +5,7 @@ namespace FakeXrmEasy.Query { - public static partial class ConditionExpressionExtensions + internal static partial class ConditionExpressionExtensions { internal static Expression ToOlderThanExpression(this TypedConditionExpression tc, Expression getAttributeValueExpr, Expression containsAttributeExpr) { @@ -55,8 +55,8 @@ internal static Expression ToOlderThanExpression(this TypedConditionExpression t internal static Expression ToOlderThanExpression(this TypedConditionExpression tc, Expression getAttributeValueExpr, Expression containsAttributeExpr, DateTime olderThanDate) { var lessThanExpression = Expression.LessThan( - tc.AttributeType.GetAppropiateCastExpressionBasedOnType(getAttributeValueExpr, olderThanDate), - TypeCastExpressions.GetAppropiateTypedValueAndType(olderThanDate, tc.AttributeType)); + tc.AttributeType.GetAppropriateCastExpressionBasedOnType(getAttributeValueExpr, olderThanDate), + TypeCastExpressionExtensions.GetAppropriateTypedValueAndType(olderThanDate, tc.AttributeType)); return Expression.AndAlso(containsAttributeExpr, Expression.AndAlso(Expression.NotEqual(getAttributeValueExpr, Expression.Constant(null)), diff --git a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.cs b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.cs index 71b7504a..a3e81d62 100644 --- a/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.cs +++ b/src/FakeXrmEasy.Core/Query/ConditionExpressionExtensions.cs @@ -10,7 +10,7 @@ namespace FakeXrmEasy.Query { - public static partial class ConditionExpressionExtensions + internal static partial class ConditionExpressionExtensions { internal static BinaryExpression TranslateMultipleConditionExpressions(this List conditions, QueryExpression qe, IXrmFakedContext context, string sEntityName, LogicalOperator op, ParameterExpression entity, bool bIsOuter) { diff --git a/src/FakeXrmEasy.Core/Query/ExpressionExtensions.cs b/src/FakeXrmEasy.Core/Query/ExpressionExtensions.cs index 4adf80b7..127e6d99 100644 --- a/src/FakeXrmEasy.Core/Query/ExpressionExtensions.cs +++ b/src/FakeXrmEasy.Core/Query/ExpressionExtensions.cs @@ -7,7 +7,7 @@ namespace FakeXrmEasy.Query /// /// /// - public static class ExpressionExtensions + internal static class ExpressionExtensions { internal static Expression TransformValueBasedOnOperator(this Expression input, ConditionOperator op) { diff --git a/src/FakeXrmEasy.Core/Query/FetchXmlExtensions.cs b/src/FakeXrmEasy.Core/Query/FetchXmlExtensions.cs index d5f73b5e..863563bb 100644 --- a/src/FakeXrmEasy.Core/Query/FetchXmlExtensions.cs +++ b/src/FakeXrmEasy.Core/Query/FetchXmlExtensions.cs @@ -14,7 +14,7 @@ namespace FakeXrmEasy.Query /// /// /// - public static class FetchXmlExtensions + internal static class FetchXmlExtensions { /// /// @@ -22,7 +22,7 @@ public static class FetchXmlExtensions /// /// /// - public static QueryExpression ToQueryExpression(this string fetchXml, IXrmFakedContext context) + internal static QueryExpression ToQueryExpression(this string fetchXml, IXrmFakedContext context) { var xlDoc = fetchXml.ToXmlDocument(); return xlDoc.ToQueryExpression(context); @@ -93,7 +93,7 @@ private static QueryExpression ToQueryExpression(this XDocument xlDoc, IXrmFaked /// /// /// - public static XDocument ToXmlDocument(this string fetchXml) + internal static XDocument ToXmlDocument(this string fetchXml) { try { @@ -114,7 +114,7 @@ public static XDocument ToXmlDocument(this string fetchXml) /// /// /// - public static List Aggregate(this List resultOfQuery, XrmFakedContext ctx, XDocument xmlDoc) + internal static List Aggregate(this List resultOfQuery, XrmFakedContext ctx, XDocument xmlDoc) { // Validate that is not present, // that all attributes have groupby or aggregate, and an alias, diff --git a/src/FakeXrmEasy.Core/Query/TypeCastExpressions.cs b/src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions.cs similarity index 74% rename from src/FakeXrmEasy.Core/Query/TypeCastExpressions.cs rename to src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions.cs index 0a5d22a1..e1fad732 100644 --- a/src/FakeXrmEasy.Core/Query/TypeCastExpressions.cs +++ b/src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions.cs @@ -9,11 +9,11 @@ namespace FakeXrmEasy.Query /// /// /// - public static class TypeCastExpressions + public static class TypeCastExpressionExtensions { - internal static Expression GetAppropiateCastExpressionBasedOnType(this Type t, Expression input, object value) + internal static Expression GetAppropriateCastExpressionBasedOnType(this Type t, Expression input, object value) { - var typedExpression = t.GetAppropiateCastExpressionBasedOnAttributeTypeOrValue(input, value); + var typedExpression = t.GetAppropriateCastExpressionBasedOnAttributeTypeOrValue(input, value); //Now, any value (entity reference, string, int, etc,... could be wrapped in an AliasedValue object //So let's add this @@ -21,14 +21,14 @@ internal static Expression GetAppropiateCastExpressionBasedOnType(this Type t, E typeof(AliasedValue).GetMethod("get_Value")); var exp = Expression.Condition(Expression.TypeIs(input, typeof(AliasedValue)), - t.GetAppropiateCastExpressionBasedOnAttributeTypeOrValue(getValueFromAliasedValueExp, value), + t.GetAppropriateCastExpressionBasedOnAttributeTypeOrValue(getValueFromAliasedValueExp, value), typedExpression //Not an aliased value ); return exp; } - internal static Expression GetAppropiateCastExpressionBasedOnAttributeTypeOrValue(this Type attributeType, Expression input, object value) + internal static Expression GetAppropriateCastExpressionBasedOnAttributeTypeOrValue(this Type attributeType, Expression input, object value) { if (attributeType != null) { @@ -37,31 +37,31 @@ internal static Expression GetAppropiateCastExpressionBasedOnAttributeTypeOrValu attributeType = Nullable.GetUnderlyingType(attributeType); } if (attributeType == typeof(Guid)) - return GetAppropiateCastExpressionBasedGuid(input); + return GetAppropriateCastExpressionBasedGuid(input); if (attributeType == typeof(EntityReference)) - return GetAppropiateCastExpressionBasedOnEntityReference(input, value); + return GetAppropriateCastExpressionBasedOnEntityReference(input, value); if (attributeType == typeof(int) || attributeType == typeof(Nullable) || attributeType.IsOptionSet()) - return GetAppropiateCastExpressionBasedOnInt(input); + return GetAppropriateCastExpressionBasedOnInt(input); if (attributeType == typeof(decimal) || attributeType == typeof(Money)) - return GetAppropiateCastExpressionBasedOnDecimal(input); + return GetAppropriateCastExpressionBasedOnDecimal(input); if (attributeType == typeof(bool) || attributeType == typeof(BooleanManagedProperty)) - return GetAppropiateCastExpressionBasedOnBoolean(input); + return GetAppropriateCastExpressionBasedOnBoolean(input); if (attributeType == typeof(string)) - return GetAppropiateCastExpressionBasedOnStringAndType(input, value, attributeType); + return GetAppropriateCastExpressionBasedOnStringAndType(input, value, attributeType); if (attributeType.IsDateTime()) - return GetAppropiateCastExpressionBasedOnDateTime(input, value); + return GetAppropriateCastExpressionBasedOnDateTime(input, value); #if FAKE_XRM_EASY_9 if (attributeType.IsOptionSetValueCollection()) - return GetAppropiateCastExpressionBasedOnOptionSetValueCollection(input); + return GetAppropriateCastExpressionBasedOnOptionSetValueCollection(input); #endif - return GetAppropiateCastExpressionDefault(input, value); //any other type + return GetAppropriateCastExpressionDefault(input, value); //any other type } - return GetAppropiateCastExpressionBasedOnValueInherentType(input, value); //Dynamic / late bound entities + return GetAppropriateCastExpressionBasedOnValueInherentType(input, value); //Dynamic / late bound entities } - internal static Expression GetAppropiateCastExpressionBasedGuid(Expression input) + internal static Expression GetAppropriateCastExpressionBasedGuid(Expression input) { var getIdFromEntityReferenceExpr = Expression.Call(Expression.TypeAs(input, typeof(EntityReference)), typeof(EntityReference).GetMethod("get_Id")); @@ -77,7 +77,7 @@ internal static Expression GetAppropiateCastExpressionBasedGuid(Expression input } - internal static Expression GetAppropiateCastExpressionBasedOnEntityReference(Expression input, object value) + internal static Expression GetAppropriateCastExpressionBasedOnEntityReference(Expression input, object value) { Guid guid; if (value is string && !Guid.TryParse((string)value, out guid)) @@ -104,7 +104,7 @@ internal static Expression GetAppropiateCastExpressionBasedOnEntityReference(Exp } - internal static Expression GetAppropiateCastExpressionBasedOnInt(Expression input) + internal static Expression GetAppropriateCastExpressionBasedOnInt(Expression input) { return Expression.Condition( Expression.TypeIs(input, typeof(OptionSetValue)), @@ -115,7 +115,7 @@ internal static Expression GetAppropiateCastExpressionBasedOnInt(Expression inpu Expression.Convert(input, typeof(int))); } - internal static Expression GetAppropiateCastExpressionBasedOnDecimal(Expression input) + internal static Expression GetAppropriateCastExpressionBasedOnDecimal(Expression input) { return Expression.Condition( Expression.TypeIs(input, typeof(Money)), @@ -129,7 +129,7 @@ internal static Expression GetAppropiateCastExpressionBasedOnDecimal(Expression } - internal static Expression GetAppropiateCastExpressionBasedOnBoolean(Expression input) + internal static Expression GetAppropriateCastExpressionBasedOnBoolean(Expression input) { return Expression.Condition( Expression.TypeIs(input, typeof(BooleanManagedProperty)), @@ -143,15 +143,15 @@ internal static Expression GetAppropiateCastExpressionBasedOnBoolean(Expression } - internal static Expression GetAppropiateCastExpressionBasedOnStringAndType(Expression input, object value, Type attributeType) + internal static Expression GetAppropriateCastExpressionBasedOnStringAndType(Expression input, object value, Type attributeType) { - var defaultStringExpression = GetAppropiateCastExpressionDefault(input, value).ToCaseInsensitiveExpression(); + var defaultStringExpression = GetAppropriateCastExpressionDefault(input, value).ToCaseInsensitiveExpression(); int iValue; if (attributeType.IsOptionSet() && int.TryParse(value.ToString(), out iValue)) { return Expression.Condition(Expression.TypeIs(input, typeof(OptionSetValue)), - GetAppropiateCastExpressionBasedOnInt(input).ToStringExpression(), + GetAppropriateCastExpressionBasedOnInt(input).ToStringExpression(), defaultStringExpression ); } @@ -159,7 +159,7 @@ internal static Expression GetAppropiateCastExpressionBasedOnStringAndType(Expre return defaultStringExpression; } - internal static Expression GetAppropiateCastExpressionBasedOnDateTime(Expression input, object value) + internal static Expression GetAppropriateCastExpressionBasedOnDateTime(Expression input, object value) { // Convert to DateTime if string DateTime _; @@ -172,37 +172,37 @@ internal static Expression GetAppropiateCastExpressionBasedOnDateTime(Expression } #if FAKE_XRM_EASY_9 - internal static Expression GetAppropiateCastExpressionBasedOnOptionSetValueCollection(Expression input) + internal static Expression GetAppropriateCastExpressionBasedOnOptionSetValueCollection(Expression input) { return Expression.Call(typeof(OptionSetValueCollectionExtensions).GetMethod("ConvertToHashSetOfInt"), input, Expression.Constant(true)); } #endif - internal static Expression GetAppropiateCastExpressionDefault(Expression input, object value) + internal static Expression GetAppropriateCastExpressionDefault(Expression input, object value) { return Expression.Convert(input, value.GetType()); //Default type conversion } - internal static Expression GetAppropiateCastExpressionBasedOnValueInherentType(Expression input, object value) + internal static Expression GetAppropriateCastExpressionBasedOnValueInherentType(Expression input, object value) { if (value is Guid || value is EntityReference) - return GetAppropiateCastExpressionBasedGuid(input); //Could be compared against an EntityReference + return GetAppropriateCastExpressionBasedGuid(input); //Could be compared against an EntityReference if (value is int || value is OptionSetValue) - return GetAppropiateCastExpressionBasedOnInt(input); //Could be compared against an OptionSet + return GetAppropriateCastExpressionBasedOnInt(input); //Could be compared against an OptionSet if (value is decimal || value is Money) - return GetAppropiateCastExpressionBasedOnDecimal(input); //Could be compared against a Money + return GetAppropriateCastExpressionBasedOnDecimal(input); //Could be compared against a Money if (value is bool) - return GetAppropiateCastExpressionBasedOnBoolean(input); //Could be a BooleanManagedProperty + return GetAppropriateCastExpressionBasedOnBoolean(input); //Could be a BooleanManagedProperty if (value is string) { - return GetAppropiateCastExpressionBasedOnString(input, value); + return GetAppropriateCastExpressionBasedOnString(input, value); } - return GetAppropiateCastExpressionDefault(input, value); //any other type + return GetAppropriateCastExpressionDefault(input, value); //any other type } - internal static Expression GetAppropiateCastExpressionBasedOnString(Expression input, object value) + internal static Expression GetAppropriateCastExpressionBasedOnString(Expression input, object value) { - var defaultStringExpression = GetAppropiateCastExpressionDefault(input, value).ToCaseInsensitiveExpression(); + var defaultStringExpression = GetAppropriateCastExpressionDefault(input, value).ToCaseInsensitiveExpression(); DateTime dtDateTimeConversion; if (DateTime.TryParse(value.ToString(), out dtDateTimeConversion)) @@ -214,7 +214,7 @@ internal static Expression GetAppropiateCastExpressionBasedOnString(Expression i if (int.TryParse(value.ToString(), out iValue)) { return Expression.Condition(Expression.TypeIs(input, typeof(OptionSetValue)), - GetAppropiateCastExpressionBasedOnInt(input).ToStringExpression(), + GetAppropriateCastExpressionBasedOnInt(input).ToStringExpression(), defaultStringExpression ); } @@ -223,8 +223,7 @@ internal static Expression GetAppropiateCastExpressionBasedOnString(Expression i } - - internal static Expression GetAppropiateTypedValue(object value) + internal static Expression GetAppropriateTypedValue(object value) { //Basic types conversions //Special case => datetime is sent as a string @@ -258,10 +257,10 @@ internal static Expression GetAppropiateTypedValue(object value) return Expression.Constant(value); } - internal static Expression GetAppropiateTypedValueAndType(object value, Type attributeType) + internal static Expression GetAppropriateTypedValueAndType(object value, Type attributeType) { if (attributeType == null) - return GetAppropiateTypedValue(value); + return GetAppropriateTypedValue(value); if (Nullable.GetUnderlyingType(attributeType) != null) { @@ -320,7 +319,7 @@ internal static Expression GetAppropiateTypedValueAndType(object value, Type att return Expression.Constant(value); } - internal static Type GetAppropiateTypeForValue(object value) + internal static Type GetAppropriateTypeForValue(object value) { //Basic types conversions //Special case => datetime is sent as a string diff --git a/src/FakeXrmEasy.Core/XrmFakedContext.cs b/src/FakeXrmEasy.Core/XrmFakedContext.cs index 82de95fc..7bcc9de6 100644 --- a/src/FakeXrmEasy.Core/XrmFakedContext.cs +++ b/src/FakeXrmEasy.Core/XrmFakedContext.cs @@ -310,6 +310,15 @@ public virtual void Initialize(IEnumerable entities) Initialised = true; } + /// + /// Initializes the context with a single entity record + /// + /// Entity record that will be used to initialize the In-Memory context + public void Initialize(Entity entity) + { + this.Initialize(new List() { entity }); + } + private void ValidateEntityReferences(Entity e) { foreach (var item in e.Attributes) @@ -321,15 +330,6 @@ private void ValidateEntityReferences(Entity e) } } - /// - /// Initializes the context with a single entity record - /// - /// Entity record that will be used to initialize the In-Memory context - public void Initialize(Entity entity) - { - this.Initialize(new List() { entity }); - } - /// /// Enables support for the early-cound types exposed in a specified assembly. /// diff --git a/tests/FakeXrmEasy.Core.Tests/Issues/Issue64.cs b/tests/FakeXrmEasy.Core.Tests/Issues/Issue64.cs index fb7f1dea..5352712b 100644 --- a/tests/FakeXrmEasy.Core.Tests/Issues/Issue64.cs +++ b/tests/FakeXrmEasy.Core.Tests/Issues/Issue64.cs @@ -26,7 +26,6 @@ public Issue64() _context.Initialize(new List { salesOrderDetail }); } - // This test currently fails [Fact] public void When_Querying_A_Money_Attribute_Using_An_Integer_Value_It_Should_Not_Fail() { @@ -37,7 +36,7 @@ public void When_Querying_A_Money_Attribute_Using_An_Integer_Value_It_Should_Not Assert.Equal(entities[0].Id, salesOrderDetail.Id); } - // This test currently passes + [Fact] public void When_Querying_A_Money_Attribute_Using_A_Money_Value_It_Should_Not_Fail() { From 72622578d0e1bcd6b3a86df99a058867d8e1f779 Mon Sep 17 00:00:00 2001 From: Jordi Date: Sun, 24 Mar 2024 19:58:12 +0100 Subject: [PATCH 36/37] Refactor TypeCastExpressions into separate files --- .../TypeCastExpressionExtensions.Bool.cs | 28 ++++ .../TypeCastExpressionExtensions.DateTime.cs | 26 ++++ .../TypeCastExpressionExtensions.Decimal.cs | 28 ++++ ...astExpressionExtensions.EntityReference.cs | 41 +++++ .../TypeCastExpressionExtensions.Guid.cs | 29 ++++ .../TypeCastExpressionExtensions.Int.cs | 25 +++ ...sionExtensions.OptionSetValueCollection.cs | 21 +++ .../TypeCastExpressionExtensions.String.cs | 51 +++++++ .../TypeCastExpressionExtensions.cs | 142 +----------------- 9 files changed, 250 insertions(+), 141 deletions(-) create mode 100644 src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.Bool.cs create mode 100644 src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.DateTime.cs create mode 100644 src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.Decimal.cs create mode 100644 src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.EntityReference.cs create mode 100644 src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.Guid.cs create mode 100644 src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.Int.cs create mode 100644 src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.OptionSetValueCollection.cs create mode 100644 src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.String.cs rename src/FakeXrmEasy.Core/Query/{ => TypeCastExpressionExtensions}/TypeCastExpressionExtensions.cs (55%) diff --git a/src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.Bool.cs b/src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.Bool.cs new file mode 100644 index 00000000..b5c2c7ac --- /dev/null +++ b/src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.Bool.cs @@ -0,0 +1,28 @@ +using System; +using System.Globalization; +using System.Linq.Expressions; +using FakeXrmEasy.Extensions; +using Microsoft.Xrm.Sdk; + +namespace FakeXrmEasy.Query +{ + /// + /// + /// + internal static partial class TypeCastExpressionExtensions + { + internal static Expression GetAppropriateCastExpressionBasedOnBoolean(Expression input) + { + return Expression.Condition( + Expression.TypeIs(input, typeof(BooleanManagedProperty)), + Expression.Convert( + Expression.Call(Expression.TypeAs(input, typeof(BooleanManagedProperty)), + typeof(BooleanManagedProperty).GetMethod("get_Value")), + typeof(bool)), + Expression.Condition(Expression.TypeIs(input, typeof(bool)), + Expression.Convert(input, typeof(bool)), + Expression.Constant(false))); + + } + } +} diff --git a/src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.DateTime.cs b/src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.DateTime.cs new file mode 100644 index 00000000..60fa7aa4 --- /dev/null +++ b/src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.DateTime.cs @@ -0,0 +1,26 @@ +using System; +using System.Globalization; +using System.Linq.Expressions; +using FakeXrmEasy.Extensions; +using Microsoft.Xrm.Sdk; + +namespace FakeXrmEasy.Query +{ + /// + /// + /// + internal static partial class TypeCastExpressionExtensions + { + internal static Expression GetAppropriateCastExpressionBasedOnDateTime(Expression input, object value) + { + // Convert to DateTime if string + DateTime _; + if (value is DateTime || value is string && DateTime.TryParse(value.ToString(), out _)) + { + return Expression.Convert(input, typeof(DateTime)); + } + + return input; // return directly + } + } +} diff --git a/src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.Decimal.cs b/src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.Decimal.cs new file mode 100644 index 00000000..c84a7bf7 --- /dev/null +++ b/src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.Decimal.cs @@ -0,0 +1,28 @@ +using System; +using System.Globalization; +using System.Linq.Expressions; +using FakeXrmEasy.Extensions; +using Microsoft.Xrm.Sdk; + +namespace FakeXrmEasy.Query +{ + /// + /// + /// + internal static partial class TypeCastExpressionExtensions + { + internal static Expression GetAppropriateCastExpressionBasedOnDecimal(Expression input) + { + return Expression.Condition( + Expression.TypeIs(input, typeof(Money)), + Expression.Convert( + Expression.Call(Expression.TypeAs(input, typeof(Money)), + typeof(Money).GetMethod("get_Value")), + typeof(decimal)), + Expression.Condition(Expression.TypeIs(input, typeof(decimal)), + Expression.Convert(input, typeof(decimal)), + Expression.Constant(0.0M))); + + } + } +} diff --git a/src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.EntityReference.cs b/src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.EntityReference.cs new file mode 100644 index 00000000..87507010 --- /dev/null +++ b/src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.EntityReference.cs @@ -0,0 +1,41 @@ +using System; +using System.Globalization; +using System.Linq.Expressions; +using FakeXrmEasy.Extensions; +using Microsoft.Xrm.Sdk; + +namespace FakeXrmEasy.Query +{ + /// + /// + /// + internal static partial class TypeCastExpressionExtensions + { + internal static Expression GetAppropriateCastExpressionBasedOnEntityReference(Expression input, object value) + { + Guid guid; + if (value is string && !Guid.TryParse((string)value, out guid)) + { + var getNameFromEntityReferenceExpr = Expression.Call(Expression.TypeAs(input, typeof(EntityReference)), + typeof(EntityReference).GetMethod("get_Name")); + + return Expression.Condition(Expression.TypeIs(input, typeof(EntityReference)), + Expression.Convert(getNameFromEntityReferenceExpr, typeof(string)), + Expression.Constant(string.Empty, typeof(string))).ToCaseInsensitiveExpression(); + } + + var getIdFromEntityReferenceExpr = Expression.Call(Expression.TypeAs(input, typeof(EntityReference)), + typeof(EntityReference).GetMethod("get_Id")); + + return Expression.Condition( + Expression.TypeIs(input, typeof(EntityReference)), //If input is an entity reference, compare the Guid against the Id property + Expression.Convert( + getIdFromEntityReferenceExpr, + typeof(Guid)), + Expression.Condition(Expression.TypeIs(input, typeof(Guid)), //If any other case, then just compare it as a Guid directly + Expression.Convert(input, typeof(Guid)), + Expression.Constant(Guid.Empty, typeof(Guid)))); + + } + } +} diff --git a/src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.Guid.cs b/src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.Guid.cs new file mode 100644 index 00000000..4868333d --- /dev/null +++ b/src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.Guid.cs @@ -0,0 +1,29 @@ +using System; +using System.Globalization; +using System.Linq.Expressions; +using FakeXrmEasy.Extensions; +using Microsoft.Xrm.Sdk; + +namespace FakeXrmEasy.Query +{ + /// + /// + /// + internal static partial class TypeCastExpressionExtensions + { + internal static Expression GetAppropriateCastExpressionBasedGuid(Expression input) + { + var getIdFromEntityReferenceExpr = Expression.Call(Expression.TypeAs(input, typeof(EntityReference)), + typeof(EntityReference).GetMethod("get_Id")); + + return Expression.Condition( + Expression.TypeIs(input, typeof(EntityReference)), //If input is an entity reference, compare the Guid against the Id property + Expression.Convert( + getIdFromEntityReferenceExpr, + typeof(Guid)), + Expression.Condition(Expression.TypeIs(input, typeof(Guid)), //If any other case, then just compare it as a Guid directly + Expression.Convert(input, typeof(Guid)), + Expression.Constant(Guid.Empty, typeof(Guid)))); + } + } +} diff --git a/src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.Int.cs b/src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.Int.cs new file mode 100644 index 00000000..3a192cbd --- /dev/null +++ b/src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.Int.cs @@ -0,0 +1,25 @@ +using System; +using System.Globalization; +using System.Linq.Expressions; +using FakeXrmEasy.Extensions; +using Microsoft.Xrm.Sdk; + +namespace FakeXrmEasy.Query +{ + /// + /// + /// + internal static partial class TypeCastExpressionExtensions + { + internal static Expression GetAppropriateCastExpressionBasedOnInt(Expression input) + { + return Expression.Condition( + Expression.TypeIs(input, typeof(OptionSetValue)), + Expression.Convert( + Expression.Call(Expression.TypeAs(input, typeof(OptionSetValue)), + typeof(OptionSetValue).GetMethod("get_Value")), + typeof(int)), + Expression.Convert(input, typeof(int))); + } + } +} diff --git a/src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.OptionSetValueCollection.cs b/src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.OptionSetValueCollection.cs new file mode 100644 index 00000000..90d8596c --- /dev/null +++ b/src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.OptionSetValueCollection.cs @@ -0,0 +1,21 @@ +using System; +using System.Globalization; +using System.Linq.Expressions; +using FakeXrmEasy.Extensions; +using Microsoft.Xrm.Sdk; + +namespace FakeXrmEasy.Query +{ + /// + /// + /// + internal static partial class TypeCastExpressionExtensions + { +#if FAKE_XRM_EASY_9 + internal static Expression GetAppropriateCastExpressionBasedOnOptionSetValueCollection(Expression input) + { + return Expression.Call(typeof(OptionSetValueCollectionExtensions).GetMethod("ConvertToHashSetOfInt"), input, Expression.Constant(true)); + } +#endif + } +} diff --git a/src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.String.cs b/src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.String.cs new file mode 100644 index 00000000..31a103af --- /dev/null +++ b/src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.String.cs @@ -0,0 +1,51 @@ +using System; +using System.Globalization; +using System.Linq.Expressions; +using FakeXrmEasy.Extensions; +using Microsoft.Xrm.Sdk; + +namespace FakeXrmEasy.Query +{ + /// + /// + /// + internal static partial class TypeCastExpressionExtensions + { + internal static Expression GetAppropriateCastExpressionBasedOnStringAndType(Expression input, object value, Type attributeType) + { + var defaultStringExpression = GetAppropriateCastExpressionDefault(input, value).ToCaseInsensitiveExpression(); + + int iValue; + if (attributeType.IsOptionSet() && int.TryParse(value.ToString(), out iValue)) + { + return Expression.Condition(Expression.TypeIs(input, typeof(OptionSetValue)), + GetAppropriateCastExpressionBasedOnInt(input).ToStringExpression(), + defaultStringExpression + ); + } + + return defaultStringExpression; + } + internal static Expression GetAppropriateCastExpressionBasedOnString(Expression input, object value) + { + var defaultStringExpression = GetAppropriateCastExpressionDefault(input, value).ToCaseInsensitiveExpression(); + + DateTime dtDateTimeConversion; + if (DateTime.TryParse(value.ToString(), out dtDateTimeConversion)) + { + return Expression.Convert(input, typeof(DateTime)); + } + + int iValue; + if (int.TryParse(value.ToString(), out iValue)) + { + return Expression.Condition(Expression.TypeIs(input, typeof(OptionSetValue)), + GetAppropriateCastExpressionBasedOnInt(input).ToStringExpression(), + defaultStringExpression + ); + } + + return defaultStringExpression; + } + } +} diff --git a/src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions.cs b/src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.cs similarity index 55% rename from src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions.cs rename to src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.cs index e1fad732..ce3d41f1 100644 --- a/src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions.cs +++ b/src/FakeXrmEasy.Core/Query/TypeCastExpressionExtensions/TypeCastExpressionExtensions.cs @@ -9,7 +9,7 @@ namespace FakeXrmEasy.Query /// /// /// - public static class TypeCastExpressionExtensions + internal static partial class TypeCastExpressionExtensions { internal static Expression GetAppropriateCastExpressionBasedOnType(this Type t, Expression input, object value) { @@ -61,123 +61,6 @@ internal static Expression GetAppropriateCastExpressionBasedOnAttributeTypeOrVal return GetAppropriateCastExpressionBasedOnValueInherentType(input, value); //Dynamic / late bound entities } - internal static Expression GetAppropriateCastExpressionBasedGuid(Expression input) - { - var getIdFromEntityReferenceExpr = Expression.Call(Expression.TypeAs(input, typeof(EntityReference)), - typeof(EntityReference).GetMethod("get_Id")); - - return Expression.Condition( - Expression.TypeIs(input, typeof(EntityReference)), //If input is an entity reference, compare the Guid against the Id property - Expression.Convert( - getIdFromEntityReferenceExpr, - typeof(Guid)), - Expression.Condition(Expression.TypeIs(input, typeof(Guid)), //If any other case, then just compare it as a Guid directly - Expression.Convert(input, typeof(Guid)), - Expression.Constant(Guid.Empty, typeof(Guid)))); - } - - - internal static Expression GetAppropriateCastExpressionBasedOnEntityReference(Expression input, object value) - { - Guid guid; - if (value is string && !Guid.TryParse((string)value, out guid)) - { - var getNameFromEntityReferenceExpr = Expression.Call(Expression.TypeAs(input, typeof(EntityReference)), - typeof(EntityReference).GetMethod("get_Name")); - - return Expression.Condition(Expression.TypeIs(input, typeof(EntityReference)), - Expression.Convert(getNameFromEntityReferenceExpr, typeof(string)), - Expression.Constant(string.Empty, typeof(string))).ToCaseInsensitiveExpression(); - } - - var getIdFromEntityReferenceExpr = Expression.Call(Expression.TypeAs(input, typeof(EntityReference)), - typeof(EntityReference).GetMethod("get_Id")); - - return Expression.Condition( - Expression.TypeIs(input, typeof(EntityReference)), //If input is an entity reference, compare the Guid against the Id property - Expression.Convert( - getIdFromEntityReferenceExpr, - typeof(Guid)), - Expression.Condition(Expression.TypeIs(input, typeof(Guid)), //If any other case, then just compare it as a Guid directly - Expression.Convert(input, typeof(Guid)), - Expression.Constant(Guid.Empty, typeof(Guid)))); - - } - - internal static Expression GetAppropriateCastExpressionBasedOnInt(Expression input) - { - return Expression.Condition( - Expression.TypeIs(input, typeof(OptionSetValue)), - Expression.Convert( - Expression.Call(Expression.TypeAs(input, typeof(OptionSetValue)), - typeof(OptionSetValue).GetMethod("get_Value")), - typeof(int)), - Expression.Convert(input, typeof(int))); - } - - internal static Expression GetAppropriateCastExpressionBasedOnDecimal(Expression input) - { - return Expression.Condition( - Expression.TypeIs(input, typeof(Money)), - Expression.Convert( - Expression.Call(Expression.TypeAs(input, typeof(Money)), - typeof(Money).GetMethod("get_Value")), - typeof(decimal)), - Expression.Condition(Expression.TypeIs(input, typeof(decimal)), - Expression.Convert(input, typeof(decimal)), - Expression.Constant(0.0M))); - - } - - internal static Expression GetAppropriateCastExpressionBasedOnBoolean(Expression input) - { - return Expression.Condition( - Expression.TypeIs(input, typeof(BooleanManagedProperty)), - Expression.Convert( - Expression.Call(Expression.TypeAs(input, typeof(BooleanManagedProperty)), - typeof(BooleanManagedProperty).GetMethod("get_Value")), - typeof(bool)), - Expression.Condition(Expression.TypeIs(input, typeof(bool)), - Expression.Convert(input, typeof(bool)), - Expression.Constant(false))); - - } - - internal static Expression GetAppropriateCastExpressionBasedOnStringAndType(Expression input, object value, Type attributeType) - { - var defaultStringExpression = GetAppropriateCastExpressionDefault(input, value).ToCaseInsensitiveExpression(); - - int iValue; - if (attributeType.IsOptionSet() && int.TryParse(value.ToString(), out iValue)) - { - return Expression.Condition(Expression.TypeIs(input, typeof(OptionSetValue)), - GetAppropriateCastExpressionBasedOnInt(input).ToStringExpression(), - defaultStringExpression - ); - } - - return defaultStringExpression; - } - - internal static Expression GetAppropriateCastExpressionBasedOnDateTime(Expression input, object value) - { - // Convert to DateTime if string - DateTime _; - if (value is DateTime || value is string && DateTime.TryParse(value.ToString(), out _)) - { - return Expression.Convert(input, typeof(DateTime)); - } - - return input; // return directly - } - -#if FAKE_XRM_EASY_9 - internal static Expression GetAppropriateCastExpressionBasedOnOptionSetValueCollection(Expression input) - { - return Expression.Call(typeof(OptionSetValueCollectionExtensions).GetMethod("ConvertToHashSetOfInt"), input, Expression.Constant(true)); - } -#endif - internal static Expression GetAppropriateCastExpressionDefault(Expression input, object value) { return Expression.Convert(input, value.GetType()); //Default type conversion @@ -200,29 +83,6 @@ internal static Expression GetAppropriateCastExpressionBasedOnValueInherentType( return GetAppropriateCastExpressionDefault(input, value); //any other type } - internal static Expression GetAppropriateCastExpressionBasedOnString(Expression input, object value) - { - var defaultStringExpression = GetAppropriateCastExpressionDefault(input, value).ToCaseInsensitiveExpression(); - - DateTime dtDateTimeConversion; - if (DateTime.TryParse(value.ToString(), out dtDateTimeConversion)) - { - return Expression.Convert(input, typeof(DateTime)); - } - - int iValue; - if (int.TryParse(value.ToString(), out iValue)) - { - return Expression.Condition(Expression.TypeIs(input, typeof(OptionSetValue)), - GetAppropriateCastExpressionBasedOnInt(input).ToStringExpression(), - defaultStringExpression - ); - } - - return defaultStringExpression; - } - - internal static Expression GetAppropriateTypedValue(object value) { //Basic types conversions From 9080ad5664401dc5779456b51e229e2e93474a6a Mon Sep 17 00:00:00 2001 From: Jordi Date: Sun, 24 Mar 2024 20:59:27 +0100 Subject: [PATCH 37/37] Increment abstractions package version to 2.5.0 --- src/FakeXrmEasy.Core/FakeXrmEasy.Core.csproj | 12 ++++++------ .../FakeXrmEasy.Core.Tests.csproj | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/FakeXrmEasy.Core/FakeXrmEasy.Core.csproj b/src/FakeXrmEasy.Core/FakeXrmEasy.Core.csproj index 274e3373..7bba3402 100644 --- a/src/FakeXrmEasy.Core/FakeXrmEasy.Core.csproj +++ b/src/FakeXrmEasy.Core/FakeXrmEasy.Core.csproj @@ -102,22 +102,22 @@ - + - + - + - + - + - + diff --git a/tests/FakeXrmEasy.Core.Tests/FakeXrmEasy.Core.Tests.csproj b/tests/FakeXrmEasy.Core.Tests/FakeXrmEasy.Core.Tests.csproj index 14999ff4..3b5f4f13 100644 --- a/tests/FakeXrmEasy.Core.Tests/FakeXrmEasy.Core.Tests.csproj +++ b/tests/FakeXrmEasy.Core.Tests/FakeXrmEasy.Core.Tests.csproj @@ -114,22 +114,22 @@ - + - + - + - + - + - +