Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
---|---|---|---|
RegisterPluginStep(...) | 1 | 100 | 100 |
RegisterPluginStep(...) | 8 | 100 | 72.73 |
ExecutePipelineStage(...) | 1 | 100 | 100 |
ExecutePipelineStage(...) | 2 | 75 | 66.67 |
ExecutePipelinePlugins(...) | 3 | 100 | 100 |
GetPluginMethod(...) | 1 | 100 | 100 |
GetStepsForStage(...) | 3 | 100 | 66.67 |
DefaultDateBehaviour() | 1 | 100 | 100 |
ProcessAggregateFetchXml(...) | 34 | 84.52 | 72.34 |
OrderAggregateResult(...) | 8 | 80 | 66.67 |
ProcessAggregatesForSingleGroup(...) | 4 | 100 | 100 |
ProcessGroupedAggregate(...) | 7 | 100 | 100 |
Process(...) | 1 | 100 | 100 |
AggregateValues(...) | 4 | 100 | 100 |
AggregateValues(...) | 1 | 100 | 100 |
AggregateAliasedValues(...) | 2 | 100 | 100 |
AggregateAliasedValues(...) | 2 | 100 | 100 |
AggregateAliasedValues(...) | 21 | 61.54 | 60 |
AggregateAliasedValues(...) | 20 | 61.54 | 60 |
AggregateAliasedValues(...) | 17 | 43.48 | 30.77 |
AggregateAliasedValues(...) | 17 | 59.09 | 53.85 |
Process(...) | 3 | 100 | 100 |
Equals(...) | 1 | 100 | 100 |
GetHashCode(...) | 3 | 100 | 100 |
.ctor(...) | 1 | 100 | 100 |
System.IComparable.CompareTo(...) | 3 | 0 | 0 |
Equals(...) | 4 | 61.54 | 60 |
GetHashCode() | 2 | 100 | 100 |
FindGroupValue(...) | 2 | 100 | 100 |
FindGroupValue(...) | 19 | 58.82 | 58.33 |
GetDefaultWorkflowContext() | 3 | 100 | 100 |
ExecuteCodeActivity(...) | 1 | 100 | 100 |
ExecuteCodeActivity(...) | 2 | 0 | 0 |
ExecuteCodeActivity(...) | 5 | 74.07 | 57.14 |
GetRecordUniqueId(...) | 18 | 86.67 | 88.89 |
FakeRetrieve(...) | 1 | 100 | 100 |
FakeCreate(...) | 1 | 100 | 100 |
FakeUpdate(...) | 1 | 100 | 100 |
UpdateEntity(...) | 11 | 100 | 100 |
ResolveEntityReference(...) | 5 | 100 | 100 |
ResolveEntityReferenceByAlternateKeys(...) | 1 | 100 | 100 |
FakeDelete(...) | 1 | 100 | 100 |
DeleteEntity(...) | 9 | 92.31 | 92.31 |
EnsureEntityNameExistsInMetadata(...) | 4 | 83.33 | 85.71 |
AddEntityDefaultAttributes(...) | 6 | 100 | 77.78 |
ValidateEntity(...) | 4 | 81.82 | 85.71 |
CreateEntity(...) | 14 | 96 | 93.33 |
AddEntityWithDefaults(...) | 5 | 100 | 100 |
AddEntity(...) | 17 | 100 | 100 |
AttributeExistsInMetadata(...) | 9 | 79.17 | 76.92 |
AttributeExistsInInjectedMetadata(...) | 1 | 0 | 0 |
ConvertToUtc(...) | 1 | 100 | 100 |
.ctor() | 6 | 100 | 100 |
Initialize(...) | 5 | 100 | 100 |
Initialize(...) | 1 | 100 | 100 |
EnableProxyTypes(...) | 3 | 77.78 | 80 |
AddExecutionMock(...) | 2 | 100 | 100 |
RemoveExecutionMock() | 1 | 100 | 100 |
AddFakeMessageExecutor(...) | 2 | 80 | 66.67 |
RemoveFakeMessageExecutor() | 1 | 100 | 100 |
AddGenericFakeMessageExecutor(...) | 2 | 80 | 66.67 |
RemoveGenericFakeMessageExecutor(...) | 2 | 100 | 66.67 |
AddRelationship(...) | 1 | 100 | 100 |
RemoveRelationship(...) | 1 | 0 | 0 |
GetRelationship(...) | 2 | 100 | 100 |
AddAttributeMapping(...) | 5 | 71.43 | 55.56 |
GetOrganizationService() | 2 | 57.14 | 66.67 |
GetFakedOrganizationService() | 1 | 100 | 100 |
GetFakedOrganizationService(...) | 2 | 100 | 100 |
FakeExecute(...) | 1 | 100 | 100 |
FakeAssociate(...) | 1 | 100 | 100 |
FakeDisassociate(...) | 1 | 100 | 100 |
FakeRetrieveMultiple(...) | 1 | 100 | 100 |
GetFakedServiceEndpointNotificationService() | 2 | 100 | 100 |
InitializeMetadata(...) | 6 | 100 | 100 |
InitializeMetadata(...) | 1 | 100 | 100 |
InitializeMetadata(...) | 2 | 100 | 66.67 |
CreateMetadataQuery() | 2 | 100 | 100 |
GetEntityMetadataByName(...) | 2 | 100 | 100 |
SetEntityMetadata(...) | 2 | 80 | 66.67 |
GetAttributeMetadataFor(...) | 4 | 0 | 0 |
GetDefaultPluginContext() | 3 | 100 | 80 |
GetFakedPluginContext(...) | 1 | 100 | 100 |
PopulateExecutionContextPropertiesFromFakedContext(...) | 5 | 100 | 85.71 |
GetFakedExecutionContext(...) | 1 | 0 | 0 |
ExecutePluginWith(...) | 2 | 100 | 100 |
ExecutePluginWith(...) | 1 | 100 | 100 |
ExecutePluginWith(...) | 1 | 100 | 100 |
ExecutePluginWithConfigurations(...) | 3 | 100 | 100 |
ExecutePluginWithConfigurations(...) | 1 | 0 | 0 |
ExecutePluginWithTarget(...) | 1 | 0 | 0 |
ExecutePluginWithTarget(...) | 1 | 100 | 100 |
ExecutePluginWithTarget(...) | 1 | 100 | 100 |
ExecutePluginWithTargetReference(...) | 1 | 0 | 0 |
ExecutePluginWithTargetReference(...) | 1 | 0 | 0 |
ExecutePluginWithTargetAndPreEntityImages(...) | 1 | 100 | 100 |
ExecutePluginWithTargetAndPostEntityImages(...) | 1 | 100 | 100 |
ExecutePluginWithTargetAndInputParameters(...) | 1 | 0 | 0 |
GetFakedServiceProvider(...) | 1 | 100 | 100 |
GetFakeTracingService() | 1 | 100 | 100 |
FindReflectedType(...) | 5 | 29.41 | 66.67 |
FindReflectedType(...) | 5 | 31.58 | 66.67 |
FindAttributeTypeInInjectedMetadata(...) | 21 | 46.67 | 36.36 |
FindReflectedAttributeType(...) | 12 | 73.68 | 82.35 |
GetEarlyBoundTypeAttribute(...) | 2 | 100 | 100 |
CreateQuery(...) | 1 | 100 | 100 |
CreateQuery() | 4 | 100 | 71.43 |
CreateQuery(...) | 9 | 100 | 100 |
CreateQueryFromEntityName(...) | 1 | 0 | 0 |
TranslateLinkedEntityToLinq(...) | 16 | 85.45 | 76.19 |
EnsureUniqueLinkedEntityAlias(...) | 2 | 100 | 100 |
RetrieveFetchXmlNode(...) | 1 | 100 | 100 |
ParseFetchXml(...) | 1 | 100 | 100 |
TranslateFetchXmlToQueryExpression(...) | 1 | 100 | 100 |
TranslateFetchXmlDocumentToQueryExpression(...) | 11 | 94.44 | 90.91 |
TranslateQueryExpressionToLinq(...) | 10 | 100 | 93.33 |
TranslateConditionExpression(...) | 36 | 95.18 | 75.95 |
ValidateSupportedTypedExpression(...) | 3 | 91.67 | 100 |
GetAppropiateTypedValue(...) | 6 | 63.64 | 72.73 |
GetAppropiateTypeForValue(...) | 3 | 80 | 80 |
GetAppropiateTypedValueAndType(...) | 14 | 100 | 100 |
GetAppropiateCastExpressionBasedOnType(...) | 1 | 100 | 100 |
GetAppropiateCastExpressionBasedOnValueInherentType(...) | 9 | 92.86 | 90.91 |
GetAppropiateCastExpressionBasedOnAttributeTypeOrValue(...) | 15 | 96.15 | 91.30 |
GetAppropiateCastExpressionBasedOnString(...) | 3 | 80 | 80 |
GetAppropiateCastExpressionBasedOnStringAndType(...) | 3 | 71.43 | 66.67 |
GetAppropiateCastExpressionBasedOnDateTime(...) | 5 | 83.33 | 66.67 |
GetAppropiateCastExpressionDefault(...) | 1 | 100 | 100 |
GetAppropiateCastExpressionBasedGuid(...) | 1 | 100 | 100 |
GetAppropiateCastExpressionBasedOnEntityReference(...) | 3 | 100 | 100 |
GetAppropiateCastExpressionBasedOnDecimal(...) | 1 | 100 | 100 |
GetAppropiateCastExpressionBasedOnBoolean(...) | 1 | 100 | 100 |
GetAppropiateCastExpressionBasedOnInt(...) | 1 | 100 | 100 |
GetAppropiateCastExpressionBasedOnOptionSetValueCollection(...) | 1 | 100 | 100 |
TransformExpressionGetDateOnlyPart(...) | 1 | 100 | 100 |
TransformExpressionValueBasedOnOperator(...) | 3 | 100 | 100 |
TranslateConditionExpressionEqual(...) | 11 | 100 | 100 |
GetSingleConditionValue(...) | 7 | 92 | 100 |
TranslateConditionExpressionIn(...) | 7 | 100 | 100 |
TranslateConditionExpressionGreaterThanOrEqual(...) | 1 | 100 | 100 |
TranslateConditionExpressionGreaterThan(...) | 8 | 83.33 | 71.43 |
TranslateConditionExpressionGreaterThanString(...) | 3 | 100 | 100 |
TranslateConditionExpressionLessThanOrEqual(...) | 1 | 100 | 100 |
GetCompareToExpression(...) | 1 | 100 | 100 |
TranslateConditionExpressionLessThanString(...) | 3 | 100 | 100 |
TranslateConditionExpressionLessThan(...) | 8 | 91.67 | 85.71 |
TranslateConditionExpressionLast(...) | 8 | 100 | 61.54 |
TranslateConditionExpressionBetweenDates(...) | 14 | 100 | 75 |
TranslateConditionExpressionOlderThan(...) | 8 | 84 | 76.92 |
TranslateConditionExpressionBetween(...) | 1 | 100 | 100 |
TranslateConditionExpressionNull(...) | 1 | 100 | 100 |
TranslateConditionExpressionOlderThan(...) | 1 | 100 | 100 |
TranslateConditionExpressionEndsWith(...) | 2 | 100 | 100 |
GetToStringExpression(...) | 1 | 100 | 100 |
GetCaseInsensitiveExpression(...) | 1 | 100 | 100 |
TranslateConditionExpressionLike(...) | 6 | 100 | 100 |
TranslateConditionExpressionContains(...) | 2 | 100 | 100 |
TranslateMultipleConditionExpressions(...) | 13 | 100 | 94.12 |
TranslateMultipleFilterExpressions(...) | 4 | 100 | 100 |
TranslateLinkedEntityFilterExpressionToExpression(...) | 22 | 75.41 | 57.89 |
TranslateQueryExpressionFiltersToExpression(...) | 8 | 100 | 100 |
TranslateFilterExpressionToExpression(...) | 11 | 100 | 100 |
TranslateConditionExpressionNext(...) | 8 | 100 | 61.54 |
ValidateAliases(...) | 5 | 100 | 80 |
ValidateAliases(...) | 5 | 100 | 80 |
ValidateAliases(...) | 8 | 100 | 71.43 |
ValidateAliases(...) | 9 | 75 | 66.67 |
MatchByEntity(...) | 6 | 100 | 60 |
MatchByAlias(...) | 5 | 100 | 80 |
GetFakedEntityDataSourceRetrieverService() | 1 | 100 | 100 |
ConvertToHashSetOfInt(...) | 24 | 81.63 | 80 |
TranslateConditionExpressionContainValues(...) | 1 | 100 | 100 |
# | Line | Line coverage | ||
---|---|---|---|---|
1 | using FakeXrmEasy.Extensions.FetchXml; | |||
2 | using Microsoft.Xrm.Sdk; | |||
3 | using System; | |||
4 | using System.Collections.Generic; | |||
5 | using System.Linq; | |||
6 | using System.Xml.Linq; | |||
7 |
| |||
8 | namespace FakeXrmEasy | |||
9 | { | |||
10 | public partial class XrmFakedContext | |||
11 | { | |||
12 | internal static List<Entity> ProcessAggregateFetchXml(XrmFakedContext ctx, XDocument xmlDoc, List<Entity> result | |||
144 | 13 | { | ||
14 | // Validate that <all-attributes> is not present, | |||
15 | // that all attributes have groupby or aggregate, and an alias, | |||
16 | // and that there is exactly 1 groupby. | |||
144 | 17 | if (RetrieveFetchXmlNode(xmlDoc, "all-attributes") != null) | ||
0 | 18 | { | ||
0 | 19 | throw new Exception("Can't have <all-attributes /> present when using aggregate"); | ||
20 | } | |||
21 |
| |||
144 | 22 | var ns = xmlDoc.Root.Name.Namespace; | ||
23 |
| |||
144 | 24 | var entityName = RetrieveFetchXmlNode(xmlDoc, "entity")?.GetAttribute("name")?.Value; | ||
144 | 25 | if (string.IsNullOrEmpty(entityName)) | ||
0 | 26 | { | ||
0 | 27 | throw new Exception("Can't find entity name for aggregate query"); | ||
28 | } | |||
29 |
| |||
144 | 30 | var aggregates = new List<FetchAggregate>(); | ||
144 | 31 | var groups = new List<FetchGrouping>(); | ||
32 |
| |||
840 | 33 | foreach (var attr in xmlDoc.Descendants(ns + "attribute")) | ||
204 | 34 | { | ||
35 | //TODO: Find entity alias. Handle aliasedvalue in the query result. | |||
210 | 36 | var namespacedAlias = attr.Ancestors(ns + "link-entity").Select(x => x.GetAttribute("alias")?.Value != n | ||
204 | 37 | namespacedAlias.Add(attr.GetAttribute("alias")?.Value); | ||
204 | 38 | var alias = string.Join(".", namespacedAlias); | ||
204 | 39 | namespacedAlias.RemoveAt(namespacedAlias.Count - 1); | ||
204 | 40 | namespacedAlias.Add(attr.GetAttribute("name")?.Value); | ||
204 | 41 | var logicalName = string.Join(".", namespacedAlias); | ||
42 |
| |||
204 | 43 | if (string.IsNullOrEmpty("alias")) | ||
0 | 44 | { | ||
0 | 45 | throw new Exception("Missing alias for attribute in aggregate fetch xml"); | ||
46 | } | |||
204 | 47 | if (string.IsNullOrEmpty("name")) | ||
0 | 48 | { | ||
0 | 49 | throw new Exception("Missing name for attribute in aggregate fetch xml"); | ||
50 | } | |||
51 |
| |||
204 | 52 | if (attr.IsAttributeTrue("groupby")) | ||
60 | 53 | { | ||
60 | 54 | var dategrouping = attr.GetAttribute("dategrouping")?.Value; | ||
60 | 55 | if (dategrouping != null) | ||
42 | 56 | { | ||
57 | DateGroupType t; | |||
42 | 58 | if (!Enum.TryParse(dategrouping, true, out t)) | ||
0 | 59 | { | ||
0 | 60 | throw new Exception("Unknown dategrouping value '" + dategrouping + "'"); | ||
61 | } | |||
42 | 62 | groups.Add(new DateTimeGroup() | ||
42 | 63 | { | ||
42 | 64 | Type = t, | ||
42 | 65 | OutputAlias = alias, | ||
42 | 66 | Attribute = logicalName | ||
42 | 67 | }); | ||
42 | 68 | } | ||
69 | else | |||
18 | 70 | { | ||
18 | 71 | groups.Add(new SimpleValueGroup() | ||
18 | 72 | { | ||
18 | 73 | OutputAlias = alias, | ||
18 | 74 | Attribute = logicalName | ||
18 | 75 | }); | ||
18 | 76 | } | ||
60 | 77 | } | ||
78 | else | |||
144 | 79 | { | ||
144 | 80 | var agrFn = attr.GetAttribute("aggregate")?.Value; | ||
144 | 81 | if (string.IsNullOrEmpty(agrFn)) | ||
0 | 82 | { | ||
0 | 83 | throw new Exception("Attributes must have be aggregated or grouped by when using aggregation"); | ||
84 | } | |||
85 |
| |||
144 | 86 | FetchAggregate newAgr = null; | ||
144 | 87 | switch (agrFn?.ToLower()) | ||
88 | { | |||
89 | case "count": | |||
60 | 90 | newAgr = new CountAggregate(); | ||
60 | 91 | break; | ||
92 |
| |||
93 | case "countcolumn": | |||
12 | 94 | if (attr.IsAttributeTrue("distinct")) | ||
6 | 95 | { | ||
6 | 96 | newAgr = new CountDistinctAggregate(); | ||
6 | 97 | } | ||
98 | else | |||
6 | 99 | { | ||
6 | 100 | newAgr = new CountColumnAggregate(); | ||
6 | 101 | } | ||
12 | 102 | break; | ||
103 |
| |||
104 | case "min": | |||
18 | 105 | newAgr = new MinAggregate(); | ||
18 | 106 | break; | ||
107 |
| |||
108 | case "max": | |||
18 | 109 | newAgr = new MaxAggregate(); | ||
18 | 110 | break; | ||
111 |
| |||
112 | case "avg": | |||
12 | 113 | newAgr = new AvgAggregate(); | ||
12 | 114 | break; | ||
115 |
| |||
116 | case "sum": | |||
24 | 117 | newAgr = new SumAggregate(); | ||
24 | 118 | break; | ||
119 |
| |||
120 | default: | |||
0 | 121 | throw new Exception("Unknown aggregate function '" + agrFn + "'"); | ||
122 | } | |||
123 |
| |||
144 | 124 | newAgr.OutputAlias = alias; | ||
144 | 125 | newAgr.Attribute = logicalName; | ||
144 | 126 | aggregates.Add(newAgr); | ||
144 | 127 | } | ||
204 | 128 | } | ||
129 |
| |||
130 | List<Entity> aggregateResult; | |||
131 |
| |||
144 | 132 | if (groups.Any()) | ||
60 | 133 | { | ||
60 | 134 | aggregateResult = ProcessGroupedAggregate(entityName, resultOfQuery, aggregates, groups); | ||
60 | 135 | } | ||
136 | else | |||
84 | 137 | { | ||
84 | 138 | aggregateResult = new List<Entity>(); | ||
84 | 139 | var ent = ProcessAggregatesForSingleGroup(entityName, resultOfQuery, aggregates); | ||
84 | 140 | aggregateResult.Add(ent); | ||
84 | 141 | } | ||
142 |
| |||
144 | 143 | return OrderAggregateResult(xmlDoc, aggregateResult.AsQueryable()); | ||
144 | 144 | } | ||
145 |
| |||
146 | private static List<Entity> OrderAggregateResult(XDocument xmlDoc, IQueryable<Entity> result) | |||
144 | 147 | { | ||
144 | 148 | var ns = xmlDoc.Root.Name.Namespace; | ||
336 | 149 | foreach (var order in | ||
144 | 150 | xmlDoc.Root.Element(ns + "entity") | ||
144 | 151 | .Elements(ns + "order")) | ||
24 | 152 | { | ||
24 | 153 | var alias = order.GetAttribute("alias")?.Value; | ||
154 |
| |||
155 | // These error is also thrown by CRM | |||
24 | 156 | if (order.GetAttribute("attribute") != null) | ||
0 | 157 | { | ||
0 | 158 | throw new Exception("An attribute cannot be specified for an order clause for an aggregate Query. Us | ||
159 | } | |||
24 | 160 | if (string.IsNullOrEmpty("alias")) | ||
0 | 161 | { | ||
0 | 162 | throw new Exception("An alias is required for an order clause for an aggregate Query."); | ||
163 | } | |||
164 |
| |||
24 | 165 | if (order.IsAttributeTrue("descending")) | ||
12 | 166 | result = result.OrderByDescending(e => e.Attributes.ContainsKey(alias) ? e.Attributes[alias] : null, | ||
167 | else | |||
12 | 168 | result = result.OrderBy(e => e.Attributes.ContainsKey(alias) ? e.Attributes[alias] : null, new XrmOr | ||
24 | 169 | } | ||
170 |
| |||
144 | 171 | return result.ToList(); | ||
144 | 172 | } | ||
173 |
| |||
174 | private static Entity ProcessAggregatesForSingleGroup(string entityName, IEnumerable<Entity> entities, IList<Fet | |||
234 | 175 | { | ||
234 | 176 | var ent = new Entity(entityName); | ||
177 |
| |||
1170 | 178 | foreach (var agg in aggregates) | ||
234 | 179 | { | ||
234 | 180 | var val = agg.Process(entities); | ||
234 | 181 | if (val != null) | ||
222 | 182 | { | ||
222 | 183 | ent[agg.OutputAlias] = new AliasedValue(null, agg.Attribute, val); | ||
222 | 184 | } | ||
185 | else | |||
12 | 186 | { | ||
187 | //if the aggregate value cannot be calculated | |||
188 | //CRM still returns an alias | |||
12 | 189 | ent[agg.OutputAlias] = new AliasedValue(null, agg.Attribute, null); | ||
12 | 190 | } | ||
234 | 191 | } | ||
192 |
| |||
234 | 193 | return ent; | ||
234 | 194 | } | ||
195 |
| |||
196 | private static List<Entity> ProcessGroupedAggregate(string entityName, IList<Entity> resultOfQuery, IList<FetchA | |||
60 | 197 | { | ||
198 | // Group by the groupBy-attribute | |||
60 | 199 | var grouped = resultOfQuery.GroupBy(e => | ||
282 | 200 | { | ||
282 | 201 | return groups | ||
504 | 202 | .Select(g => g.Process(e)) | ||
282 | 203 | .ToArray(); | ||
282 | 204 | }, new ArrayComparer()); | ||
205 |
| |||
206 | // Perform aggregates in each group | |||
60 | 207 | var result = new List<Entity>(); | ||
480 | 208 | foreach (var g in grouped) | ||
150 | 209 | { | ||
150 | 210 | var firstInGroup = g.First(); | ||
211 |
| |||
212 | // Find the aggregates values in the group | |||
150 | 213 | var ent = ProcessAggregatesForSingleGroup(entityName, g, aggregates); | ||
214 |
| |||
215 | // Find the group values | |||
600 | 216 | for (var rule = 0; rule < groups.Count; ++rule) | ||
150 | 217 | { | ||
150 | 218 | if (g.Key[rule] != null) | ||
144 | 219 | { | ||
144 | 220 | object value = g.Key[rule]; | ||
144 | 221 | ent[groups[rule].OutputAlias] = new AliasedValue(null, groups[rule].Attribute, value is Comparab | ||
144 | 222 | } | ||
150 | 223 | } | ||
224 |
| |||
150 | 225 | result.Add(ent); | ||
150 | 226 | } | ||
227 |
| |||
60 | 228 | return result; | ||
60 | 229 | } | ||
230 |
| |||
231 | private abstract class FetchAggregate | |||
232 | { | |||
1536 | 233 | public string Attribute { get; set; } | ||
378 | 234 | public string OutputAlias { get; set; } | ||
235 |
| |||
236 | public object Process(IEnumerable<Entity> entities) | |||
234 | 237 | { | ||
234 | 238 | return AggregateValues(entities.Select(e => | ||
840 | 239 | e.Contains(Attribute) ? e[Attribute] : null | ||
234 | 240 | )); | ||
234 | 241 | } | ||
242 |
| |||
243 | protected abstract object AggregateValues(IEnumerable<object> values); | |||
244 | } | |||
245 |
| |||
246 | private abstract class AliasedAggregate : FetchAggregate | |||
247 | { | |||
248 | protected override object AggregateValues(IEnumerable<object> values) | |||
96 | 249 | { | ||
504 | 250 | var lst = values.Where(x => x != null); | ||
96 | 251 | bool alisedValue = lst.FirstOrDefault() is AliasedValue; | ||
96 | 252 | if (alisedValue) | ||
6 | 253 | { | ||
12 | 254 | lst = lst.Select(x => (x as AliasedValue)?.Value); | ||
6 | 255 | } | ||
256 |
| |||
96 | 257 | return AggregateAliasedValues(lst); | ||
96 | 258 | } | ||
259 |
| |||
260 | protected abstract object AggregateAliasedValues(IEnumerable<object> values); | |||
261 | } | |||
262 |
| |||
263 | private class CountAggregate : FetchAggregate | |||
264 | { | |||
265 | protected override object AggregateValues(IEnumerable<object> values) | |||
138 | 266 | { | ||
138 | 267 | return values.Count(); | ||
138 | 268 | } | ||
269 | } | |||
270 |
| |||
271 | private class CountColumnAggregate : AliasedAggregate | |||
272 | { | |||
273 | protected override object AggregateAliasedValues(IEnumerable<object> values) | |||
18 | 274 | { | ||
42 | 275 | return values.Where(x => x != null).Count(); | ||
18 | 276 | } | ||
277 | } | |||
278 |
| |||
279 | private class CountDistinctAggregate : AliasedAggregate | |||
280 | { | |||
281 | protected override object AggregateAliasedValues(IEnumerable<object> values) | |||
6 | 282 | { | ||
42 | 283 | return values.Where(x => x != null).Distinct().Count(); | ||
6 | 284 | } | ||
285 | } | |||
286 |
| |||
287 | private class MinAggregate : AliasedAggregate | |||
288 | { | |||
289 | protected override object AggregateAliasedValues(IEnumerable<object> values) | |||
18 | 290 | { | ||
96 | 291 | var lst = values.Where(x => x != null); | ||
18 | 292 | if (!lst.Any()) return null; | ||
293 |
| |||
36 | 294 | var firstValue = lst.Where(x => x != null).First(); | ||
18 | 295 | var valType = firstValue.GetType(); | ||
296 |
| |||
18 | 297 | if (valType == typeof(decimal) || valType == typeof(decimal?)) | ||
0 | 298 | { | ||
0 | 299 | return lst.Min(x => (decimal)x); | ||
300 | } | |||
301 |
| |||
18 | 302 | if (valType == typeof(Money)) | ||
0 | 303 | { | ||
0 | 304 | return new Money(lst.Min(x => (x as Money).Value)); | ||
305 | } | |||
306 |
| |||
18 | 307 | if (valType == typeof(int) || valType == typeof(int?)) | ||
6 | 308 | { | ||
18 | 309 | return lst.Min(x => (int)x); | ||
310 | } | |||
311 |
| |||
12 | 312 | if (valType == typeof(float) || valType == typeof(float?)) | ||
0 | 313 | { | ||
0 | 314 | return lst.Min(x => (float)x); | ||
315 | } | |||
316 |
| |||
12 | 317 | if (valType == typeof(double) || valType == typeof(double?)) | ||
0 | 318 | { | ||
0 | 319 | return lst.Min(x => (double)x); | ||
320 | } | |||
321 |
| |||
12 | 322 | if (valType == typeof(DateTime) || valType == typeof(DateTime?)) | ||
12 | 323 | { | ||
42 | 324 | return lst.Min(x => (DateTime)x); | ||
325 | } | |||
326 |
| |||
0 | 327 | throw new Exception("Unhndled property type '" + valType.FullName + "' in 'min' aggregate"); | ||
18 | 328 | } | ||
329 | } | |||
330 |
| |||
331 | private class MaxAggregate : AliasedAggregate | |||
332 | { | |||
333 | protected override object AggregateAliasedValues(IEnumerable<object> values) | |||
18 | 334 | { | ||
96 | 335 | var lst = values.Where(x => x != null); | ||
18 | 336 | if (!lst.Any()) return null; | ||
337 |
| |||
18 | 338 | var firstValue = lst.First(); | ||
18 | 339 | var valType = firstValue.GetType(); | ||
340 |
| |||
18 | 341 | if (valType == typeof(decimal) || valType == typeof(decimal?)) | ||
6 | 342 | { | ||
18 | 343 | return lst.Max(x => (decimal)x); | ||
344 | } | |||
345 |
| |||
12 | 346 | if (valType == typeof(Money)) | ||
0 | 347 | { | ||
0 | 348 | return new Money(lst.Max(x => (x as Money).Value)); | ||
349 | } | |||
350 |
| |||
12 | 351 | if (valType == typeof(int) || valType == typeof(int?)) | ||
0 | 352 | { | ||
0 | 353 | return lst.Max(x => (int)x); | ||
354 | } | |||
355 |
| |||
12 | 356 | if (valType == typeof(float) || valType == typeof(float?)) | ||
0 | 357 | { | ||
0 | 358 | return lst.Max(x => (float)x); | ||
359 | } | |||
360 |
| |||
12 | 361 | if (valType == typeof(double) || valType == typeof(double?)) | ||
0 | 362 | { | ||
0 | 363 | return lst.Max(x => (double)x); | ||
364 | } | |||
365 |
| |||
12 | 366 | if (valType == typeof(DateTime) || valType == typeof(DateTime?)) | ||
12 | 367 | { | ||
42 | 368 | return lst.Max(x => (DateTime)x); | ||
369 | } | |||
370 |
| |||
0 | 371 | throw new Exception("Unhndled property type '" + valType.FullName + "' in 'max' aggregate"); | ||
18 | 372 | } | ||
373 | } | |||
374 |
| |||
375 | private class AvgAggregate : AliasedAggregate | |||
376 | { | |||
377 | protected override object AggregateAliasedValues(IEnumerable<object> values) | |||
12 | 378 | { | ||
36 | 379 | var lst = values.Where(x => x != null); | ||
18 | 380 | if (!lst.Any()) return null; | ||
381 |
| |||
6 | 382 | var firstValue = lst.First(); | ||
6 | 383 | var valType = firstValue.GetType(); | ||
384 |
| |||
6 | 385 | if (valType == typeof(decimal) || valType == typeof(decimal?)) | ||
6 | 386 | { | ||
18 | 387 | return lst.Average(x => (decimal)x); | ||
388 | } | |||
389 |
| |||
0 | 390 | if (valType == typeof(Money)) | ||
0 | 391 | { | ||
0 | 392 | return new Money(lst.Average(x => (x as Money).Value)); | ||
393 | } | |||
394 |
| |||
0 | 395 | if (valType == typeof(int) || valType == typeof(int?)) | ||
0 | 396 | { | ||
0 | 397 | return lst.Average(x => (int)x); | ||
398 | } | |||
399 |
| |||
0 | 400 | if (valType == typeof(float) || valType == typeof(float?)) | ||
0 | 401 | { | ||
0 | 402 | return lst.Average(x => (float)x); | ||
403 | } | |||
404 |
| |||
0 | 405 | if (valType == typeof(double) || valType == typeof(double?)) | ||
0 | 406 | { | ||
0 | 407 | return lst.Average(x => (double)x); | ||
408 | } | |||
409 |
| |||
0 | 410 | throw new Exception("Unhndled property type '" + valType.FullName + "' in 'avg' aggregate"); | ||
12 | 411 | } | ||
412 | } | |||
413 |
| |||
414 | private class SumAggregate : AliasedAggregate | |||
415 | { | |||
416 | protected override object AggregateAliasedValues(IEnumerable<object> values) | |||
24 | 417 | { | ||
90 | 418 | var lst = values.ToList().Where(x => x != null); | ||
419 | // TODO: Check these cases in CRM proper | |||
30 | 420 | if (!lst.Any()) return null; | ||
421 |
| |||
18 | 422 | var valType = lst.First().GetType(); | ||
423 |
| |||
18 | 424 | if (valType == typeof(decimal) || valType == typeof(decimal?)) | ||
0 | 425 | { | ||
0 | 426 | return lst.Sum(x => x as decimal? ?? 0m); | ||
427 | } | |||
18 | 428 | if (valType == typeof(Money)) | ||
12 | 429 | { | ||
30 | 430 | return new Money(lst.Sum(x => (x as Money)?.Value ?? 0m)); | ||
431 | } | |||
432 |
| |||
6 | 433 | if (valType == typeof(int) || valType == typeof(int?)) | ||
6 | 434 | { | ||
18 | 435 | return lst.Sum(x => x as int? ?? 0); | ||
436 | } | |||
437 |
| |||
0 | 438 | if (valType == typeof(float) || valType == typeof(float?)) | ||
0 | 439 | { | ||
0 | 440 | return lst.Sum(x => x as float? ?? 0f); | ||
441 | } | |||
442 |
| |||
0 | 443 | if (valType == typeof(double) || valType == typeof(double?)) | ||
0 | 444 | { | ||
0 | 445 | return lst.Sum(x => x as double? ?? 0d); | ||
446 | } | |||
447 |
| |||
0 | 448 | throw new Exception("Unhndled property type '" + valType.FullName + "' in 'sum' aggregate"); | ||
24 | 449 | } | ||
450 | } | |||
451 |
| |||
452 | private abstract class FetchGrouping | |||
453 | { | |||
642 | 454 | public string Attribute { get; set; } | ||
204 | 455 | public string OutputAlias { get; set; } | ||
456 |
| |||
457 | public IComparable Process(Entity entity) | |||
222 | 458 | { | ||
222 | 459 | var attr = entity.Contains(Attribute) ? entity[Attribute] : null; | ||
222 | 460 | return FindGroupValue(attr); | ||
222 | 461 | } | ||
462 |
| |||
463 | public abstract IComparable FindGroupValue(object attributeValue); | |||
464 | } | |||
465 |
| |||
466 | /// <summary> | |||
467 | /// Used to compare array of objects, in order to group by a variable number of conditions. | |||
468 | /// </summary> | |||
469 | private class ArrayComparer : IEqualityComparer<IComparable[]> | |||
470 | { | |||
471 | public bool Equals(IComparable[] x, IComparable[] y) | |||
72 | 472 | { | ||
72 | 473 | return x.SequenceEqual(y); | ||
72 | 474 | } | ||
475 |
| |||
476 | public int GetHashCode(IComparable[] obj) | |||
222 | 477 | { | ||
222 | 478 | int result = 0; | ||
1110 | 479 | foreach (IComparable x in obj) | ||
222 | 480 | { | ||
222 | 481 | result ^= x == null ? 0 : x.GetHashCode(); | ||
222 | 482 | } | ||
222 | 483 | return result; | ||
222 | 484 | } | ||
485 | } | |||
486 |
| |||
487 | private class ComparableEntityReference : IComparable | |||
488 | { | |||
102 | 489 | public EntityReference entityReference { get; private set; } | ||
490 |
| |||
18 | 491 | public ComparableEntityReference(EntityReference entityReference) | ||
18 | 492 | { | ||
18 | 493 | this.entityReference = entityReference; | ||
18 | 494 | } | ||
495 |
| |||
496 | int IComparable.CompareTo(object obj) | |||
0 | 497 | { | ||
0 | 498 | return Equals(obj) ? 0 : 1; | ||
0 | 499 | } | ||
500 |
| |||
501 | public override bool Equals(object obj) | |||
6 | 502 | { | ||
503 | EntityReference other; | |||
6 | 504 | if (obj is EntityReference) | ||
0 | 505 | { | ||
0 | 506 | other = obj as EntityReference; | ||
0 | 507 | } | ||
6 | 508 | else if (obj is ComparableEntityReference) | ||
6 | 509 | { | ||
6 | 510 | other = (obj as ComparableEntityReference).entityReference; | ||
6 | 511 | } | ||
512 | else | |||
0 | 513 | { | ||
0 | 514 | return false; | ||
515 | } | |||
6 | 516 | return entityReference.Id == other.Id && entityReference.LogicalName == other.LogicalName; | ||
6 | 517 | } | ||
518 |
| |||
519 | public override int GetHashCode() | |||
18 | 520 | { | ||
18 | 521 | return (entityReference.LogicalName == null ? 0 : entityReference.LogicalName.GetHashCode()) ^ entityRef | ||
18 | 522 | } | ||
523 | } | |||
524 |
| |||
525 | private class SimpleValueGroup : FetchGrouping | |||
526 | { | |||
527 | public override IComparable FindGroupValue(object attributeValue) | |||
54 | 528 | { | ||
54 | 529 | if (attributeValue is EntityReference) | ||
18 | 530 | { | ||
18 | 531 | return new ComparableEntityReference(attributeValue as EntityReference) as IComparable; | ||
532 | } | |||
533 | else | |||
36 | 534 | { | ||
36 | 535 | return attributeValue as IComparable; | ||
536 | } | |||
54 | 537 | } | ||
538 | } | |||
539 |
| |||
540 | private enum DateGroupType | |||
541 | { | |||
542 | DateTime, | |||
543 | Day, | |||
544 | Week, | |||
545 | Month, | |||
546 | Quarter, | |||
547 | Year | |||
548 | } | |||
549 |
| |||
550 | private class DateTimeGroup : FetchGrouping | |||
551 | { | |||
210 | 552 | public DateGroupType Type { get; set; } | ||
553 |
| |||
554 | public override IComparable FindGroupValue(object attributeValue) | |||
168 | 555 | { | ||
168 | 556 | if (attributeValue == null) return null; | ||
557 |
| |||
168 | 558 | if (!(attributeValue is DateTime || attributeValue is DateTime?)) | ||
0 | 559 | { | ||
0 | 560 | throw new Exception("Can only do date grouping of DateTime values"); | ||
561 | } | |||
562 |
| |||
168 | 563 | var d = attributeValue as DateTime?; | ||
564 |
| |||
168 | 565 | switch (Type) | ||
566 | { | |||
567 | case DateGroupType.DateTime: | |||
0 | 568 | return d; | ||
569 |
| |||
570 | case DateGroupType.Day: | |||
24 | 571 | return d?.Day; | ||
572 |
| |||
573 | case DateGroupType.Week: | |||
0 | 574 | var cal = System.Globalization.DateTimeFormatInfo.InvariantInfo; | ||
0 | 575 | return cal.Calendar.GetWeekOfYear(d.Value, cal.CalendarWeekRule, cal.FirstDayOfWeek); | ||
576 |
| |||
577 | case DateGroupType.Month: | |||
96 | 578 | return d?.Month; | ||
579 |
| |||
580 | case DateGroupType.Quarter: | |||
24 | 581 | return (d?.Month + 2) / 3; | ||
582 |
| |||
583 | case DateGroupType.Year: | |||
24 | 584 | return d?.Year; | ||
585 |
| |||
586 | default: | |||
0 | 587 | throw new Exception("Unhandled date group type"); | ||
588 | } | |||
168 | 589 | } | ||
590 | } | |||
591 | } | |||
592 | } |
# | Line | Line coverage | ||
---|---|---|---|---|
1 | using FakeItEasy; | |||
2 | using Microsoft.Xrm.Sdk; | |||
3 | using Microsoft.Xrm.Sdk.Workflow; | |||
4 | using System; | |||
5 | using System.Activities; | |||
6 | using System.Collections.Generic; | |||
7 |
| |||
8 | namespace FakeXrmEasy | |||
9 | { | |||
10 | public partial class XrmFakedContext : IXrmContext | |||
11 | { | |||
12 | public XrmFakedWorkflowContext GetDefaultWorkflowContext() | |||
42 | 13 | { | ||
42 | 14 | var userId = CallerId?.Id ?? Guid.NewGuid(); | ||
42 | 15 | Guid businessUnitId = BusinessUnitId?.Id ?? Guid.NewGuid(); | ||
16 |
| |||
42 | 17 | return new XrmFakedWorkflowContext | ||
42 | 18 | { | ||
42 | 19 | Depth = 1, | ||
42 | 20 | IsExecutingOffline = false, | ||
42 | 21 | MessageName = "Create", | ||
42 | 22 | UserId = userId, | ||
42 | 23 | BusinessUnitId = businessUnitId, | ||
42 | 24 | InitiatingUserId = userId, | ||
42 | 25 | InputParameters = new ParameterCollection(), | ||
42 | 26 | OutputParameters = new ParameterCollection(), | ||
42 | 27 | SharedVariables = new ParameterCollection(), | ||
42 | 28 | PreEntityImages = new EntityImageCollection(), | ||
42 | 29 | PostEntityImages = new EntityImageCollection() | ||
42 | 30 | }; | ||
42 | 31 | } | ||
32 |
| |||
33 | /// <summary> | |||
34 | /// Executes a code activity against this context | |||
35 | /// </summary> | |||
36 | /// <typeparam name="T"></typeparam> | |||
37 | public IDictionary<string, object> ExecuteCodeActivity<T>(Dictionary<string, object> inputs, T instance = null) | |||
38 | where T : CodeActivity, new() | |||
30 | 39 | { | ||
30 | 40 | var wfContext = GetDefaultWorkflowContext(); | ||
30 | 41 | return this.ExecuteCodeActivity(wfContext, inputs, instance); | ||
30 | 42 | } | ||
43 |
| |||
44 | public IDictionary<string, object> ExecuteCodeActivity<T>(Entity primaryEntity, Dictionary<string, object> input | |||
45 | where T : CodeActivity, new() | |||
0 | 46 | { | ||
0 | 47 | var wfContext = GetDefaultWorkflowContext(); | ||
0 | 48 | wfContext.PrimaryEntityId = primaryEntity.Id; | ||
0 | 49 | wfContext.PrimaryEntityName = primaryEntity.LogicalName; | ||
50 |
| |||
0 | 51 | if (inputs == null) | ||
0 | 52 | { | ||
0 | 53 | inputs = new Dictionary<string, object>(); | ||
0 | 54 | } | ||
55 |
| |||
0 | 56 | return this.ExecuteCodeActivity(wfContext, inputs, instance); | ||
0 | 57 | } | ||
58 |
| |||
59 | /// <summary> | |||
60 | /// Executes a code activity passing the primary entity | |||
61 | /// </summary> | |||
62 | /// <typeparam name="T"></typeparam> | |||
63 | public IDictionary<string, object> ExecuteCodeActivity<T>(XrmFakedWorkflowContext wfContext, Dictionary<string, | |||
64 | where T : CodeActivity, new() | |||
36 | 65 | { | ||
36 | 66 | var debugText = ""; | ||
67 | try | |||
36 | 68 | { | ||
36 | 69 | debugText = "Creating instance..." + Environment.NewLine; | ||
36 | 70 | if (instance == null) | ||
24 | 71 | { | ||
24 | 72 | instance = new T(); | ||
24 | 73 | } | ||
36 | 74 | var invoker = new WorkflowInvoker(instance); | ||
36 | 75 | debugText += "Invoker created" + Environment.NewLine; | ||
36 | 76 | debugText += "Adding extensions..." + Environment.NewLine; | ||
72 | 77 | invoker.Extensions.Add<ITracingService>(() => TracingService); | ||
72 | 78 | invoker.Extensions.Add<IWorkflowContext>(() => wfContext); | ||
36 | 79 | invoker.Extensions.Add(() => | ||
72 | 80 | { | ||
72 | 81 | var fakedServiceFactory = A.Fake<IOrganizationServiceFactory>(); | ||
108 | 82 | A.CallTo(() => fakedServiceFactory.CreateOrganizationService(A<Guid?>._)).ReturnsLazily((Guid? g) => | ||
72 | 83 | return fakedServiceFactory; | ||
72 | 84 | }); | ||
72 | 85 | invoker.Extensions.Add<IServiceEndpointNotificationService>(() => GetFakedServiceEndpointNotificationSer | ||
86 |
| |||
36 | 87 | debugText += "Adding extensions...ok." + Environment.NewLine; | ||
36 | 88 | debugText += "Invoking activity..." + Environment.NewLine; | ||
89 |
| |||
36 | 90 | if (inputs == null) | ||
0 | 91 | { | ||
0 | 92 | inputs = new Dictionary<string, object>(); | ||
0 | 93 | } | ||
94 |
| |||
36 | 95 | return invoker.Invoke(inputs); | ||
96 | } | |||
0 | 97 | catch (TypeLoadException exception) | ||
0 | 98 | { | ||
0 | 99 | var typeName = exception.TypeName != null ? exception.TypeName : "(null)"; | ||
0 | 100 | throw new TypeLoadException($"When loading type: {typeName}.{exception.Message}in domain directory: {App | ||
101 | } | |||
36 | 102 | } | ||
103 | } | |||
104 | } |
# | Line | Line coverage | ||
---|---|---|---|---|
1 | using FakeItEasy; | |||
2 | using FakeXrmEasy.Extensions; | |||
3 | using Microsoft.Xrm.Sdk; | |||
4 | using Microsoft.Xrm.Sdk.Messages; | |||
5 | using Microsoft.Xrm.Sdk.Query; | |||
6 | using System; | |||
7 | using System.Collections.Generic; | |||
8 | using System.Linq; | |||
9 | using System.Reflection; | |||
10 | using System.ServiceModel; | |||
11 |
| |||
12 | namespace FakeXrmEasy | |||
13 | { | |||
14 | public partial class XrmFakedContext : IXrmContext | |||
15 | { | |||
16 | protected const int EntityActiveStateCode = 0; | |||
17 | protected const int EntityInactiveStateCode = 1; | |||
18 |
| |||
97274 | 19 | public bool ValidateReferences { get; set; } | ||
20 |
| |||
21 | #region CRUD | |||
22 | public Guid GetRecordUniqueId(EntityReference record, bool validate = true) | |||
1129 | 23 | { | ||
1129 | 24 | if (string.IsNullOrWhiteSpace(record.LogicalName)) | ||
24 | 25 | { | ||
24 | 26 | throw new InvalidOperationException("The entity logical name must not be null or empty."); | ||
27 | } | |||
28 |
| |||
29 | // Don't fail with invalid operation exception, if no record of this entity exists, but entity is known | |||
1105 | 30 | if (!Data.ContainsKey(record.LogicalName) && !EntityMetadata.ContainsKey(record.LogicalName)) | ||
27 | 31 | { | ||
27 | 32 | if (ProxyTypesAssembly == null) | ||
6 | 33 | { | ||
6 | 34 | throw new InvalidOperationException($"The entity logical name {record.LogicalName} is not valid."); | ||
35 | } | |||
36 |
| |||
42 | 37 | if (!ProxyTypesAssembly.GetTypes().Any(type => FindReflectedType(record.LogicalName) != null)) | ||
0 | 38 | { | ||
0 | 39 | throw new InvalidOperationException($"The entity logical name {record.LogicalName} is not valid."); | ||
40 | } | |||
21 | 41 | } | ||
42 |
| |||
43 | #if !FAKE_XRM_EASY && !FAKE_XRM_EASY_2013 && !FAKE_XRM_EASY_2015 | |||
580 | 44 | if (record.Id == Guid.Empty && record.HasKeyAttributes()) | ||
33 | 45 | { | ||
33 | 46 | if (EntityMetadata.ContainsKey(record.LogicalName)) | ||
30 | 47 | { | ||
30 | 48 | var entityMetadata = EntityMetadata[record.LogicalName]; | ||
123 | 49 | foreach (var key in entityMetadata.Keys) | ||
30 | 50 | { | ||
60 | 51 | if (record.KeyAttributes.Keys.Count == key.KeyAttributes.Length && key.KeyAttributes.All(x => re | ||
30 | 52 | { | ||
30 | 53 | if (Data.ContainsKey(record.LogicalName)) | ||
27 | 54 | { | ||
93 | 55 | var matchedRecord = Data[record.LogicalName].Values.SingleOrDefault(x => record.KeyAttri | ||
27 | 56 | if (matchedRecord != null) | ||
27 | 57 | { | ||
27 | 58 | return matchedRecord.Id; | ||
59 | } | |||
0 | 60 | } | ||
3 | 61 | if (validate) | ||
0 | 62 | { | ||
0 | 63 | new FaultException<OrganizationServiceFault>(new OrganizationServiceFault(), $"{record.L | ||
0 | 64 | } | ||
3 | 65 | } | ||
3 | 66 | } | ||
3 | 67 | } | ||
6 | 68 | if (validate) | ||
3 | 69 | { | ||
3 | 70 | throw new InvalidOperationException($"The requested key attributes do not exist for the entity {reco | ||
71 | } | |||
3 | 72 | } | ||
73 | #endif | |||
74 | /* | |||
75 | if (validate && record.Id == Guid.Empty) | |||
76 | { | |||
77 | throw new InvalidOperationException("The id must not be empty."); | |||
78 | } | |||
79 | */ | |||
80 |
| |||
1069 | 81 | return record.Id; | ||
1096 | 82 | } | ||
83 |
| |||
84 | /// <summary> | |||
85 | /// A fake retrieve method that will query the FakedContext to retrieve the specified | |||
86 | /// entity and Guid, or null, if the entity was not found | |||
87 | /// </summary> | |||
88 | /// <param name="context">The faked context</param> | |||
89 | /// <param name="fakedService">The faked service where the Retrieve method will be faked</param> | |||
90 | /// <returns></returns> | |||
91 | protected static void FakeRetrieve(XrmFakedContext context, IOrganizationService fakedService) | |||
3203 | 92 | { | ||
3203 | 93 | A.CallTo(() => fakedService.Retrieve(A<string>._, A<Guid>._, A<ColumnSet>._)) | ||
3203 | 94 | .ReturnsLazily((string entityName, Guid id, ColumnSet columnSet) => | ||
3885 | 95 | { | ||
3885 | 96 | RetrieveRequest retrieveRequest = new RetrieveRequest() | ||
3885 | 97 | { | ||
3885 | 98 | Target = new EntityReference() { LogicalName = entityName, Id = id }, | ||
3885 | 99 | ColumnSet = columnSet | ||
3885 | 100 | }; | ||
3885 | 101 | var executor = context.FakeMessageExecutors[typeof(RetrieveRequest)]; | ||
3203 | 102 |
| ||
3885 | 103 | RetrieveResponse retrieveResponse = (RetrieveResponse)executor.Execute(retrieveRequest, context); | ||
3203 | 104 |
| ||
3819 | 105 | return retrieveResponse.Entity; | ||
3819 | 106 | }); | ||
3203 | 107 | } | ||
108 | /// <summary> | |||
109 | /// Fakes the Create message | |||
110 | /// </summary> | |||
111 | /// <param name="context"></param> | |||
112 | /// <param name="fakedService"></param> | |||
113 | protected static void FakeCreate(XrmFakedContext context, IOrganizationService fakedService) | |||
3203 | 114 | { | ||
3203 | 115 | A.CallTo(() => fakedService.Create(A<Entity>._)) | ||
3203 | 116 | .ReturnsLazily((Entity e) => | ||
4467 | 117 | { | ||
4467 | 118 | return context.CreateEntity(e); | ||
4407 | 119 | }); | ||
3203 | 120 | } | ||
121 |
| |||
122 | protected static void FakeUpdate(XrmFakedContext context, IOrganizationService fakedService) | |||
3203 | 123 | { | ||
3203 | 124 | A.CallTo(() => fakedService.Update(A<Entity>._)) | ||
3203 | 125 | .Invokes((Entity e) => | ||
3539 | 126 | { | ||
3539 | 127 | context.UpdateEntity(e); | ||
3503 | 128 | }); | ||
3203 | 129 | } | ||
130 |
| |||
131 | protected void UpdateEntity(Entity e) | |||
336 | 132 | { | ||
336 | 133 | if (e == null) | ||
6 | 134 | { | ||
6 | 135 | throw new InvalidOperationException("The entity must not be null"); | ||
136 | } | |||
330 | 137 | e = e.Clone(e.GetType()); | ||
330 | 138 | var reference = e.ToEntityReferenceWithKeyAttributes(); | ||
330 | 139 | e.Id = GetRecordUniqueId(reference); | ||
140 |
| |||
141 | // Update specific validations: The entity record must exist in the context | |||
324 | 142 | if (Data.ContainsKey(e.LogicalName) && | ||
324 | 143 | Data[e.LogicalName].ContainsKey(e.Id)) | ||
306 | 144 | { | ||
306 | 145 | if (this.UsePipelineSimulation) | ||
24 | 146 | { | ||
24 | 147 | ExecutePipelineStage("Update", ProcessingStepStage.Preoperation, ProcessingStepMode.Synchronous, e); | ||
24 | 148 | } | ||
149 |
| |||
150 | // Add as many attributes to the entity as the ones received (this will keep existing ones) | |||
306 | 151 | var cachedEntity = Data[e.LogicalName][e.Id]; | ||
4210 | 152 | foreach (var sAttributeName in e.Attributes.Keys.ToList()) | ||
1649 | 153 | { | ||
1649 | 154 | var attribute = e[sAttributeName]; | ||
1649 | 155 | if (attribute == null) | ||
6 | 156 | { | ||
6 | 157 | cachedEntity.Attributes.Remove(sAttributeName); | ||
6 | 158 | } | ||
1643 | 159 | else if (attribute is DateTime) | ||
323 | 160 | { | ||
323 | 161 | cachedEntity[sAttributeName] = ConvertToUtc((DateTime)e[sAttributeName]); | ||
323 | 162 | } | ||
163 | else | |||
1320 | 164 | { | ||
1320 | 165 | if (attribute is EntityReference && ValidateReferences) | ||
51 | 166 | { | ||
51 | 167 | var target = (EntityReference)e[sAttributeName]; | ||
51 | 168 | attribute = ResolveEntityReference(target); | ||
45 | 169 | } | ||
1314 | 170 | cachedEntity[sAttributeName] = attribute; | ||
1314 | 171 | } | ||
1643 | 172 | } | ||
173 |
| |||
174 | // Update ModifiedOn | |||
300 | 175 | cachedEntity["modifiedon"] = DateTime.UtcNow; | ||
300 | 176 | cachedEntity["modifiedby"] = CallerId; | ||
177 |
| |||
300 | 178 | if (this.UsePipelineSimulation) | ||
24 | 179 | { | ||
24 | 180 | ExecutePipelineStage("Update", ProcessingStepStage.Postoperation, ProcessingStepMode.Synchronous, e) | ||
181 |
| |||
24 | 182 | var clone = e.Clone(e.GetType()); | ||
24 | 183 | ExecutePipelineStage("Update", ProcessingStepStage.Postoperation, ProcessingStepMode.Asynchronous, c | ||
24 | 184 | } | ||
300 | 185 | } | ||
186 | else | |||
18 | 187 | { | ||
188 | // The entity record was not found, return a CRM-ish update error message | |||
18 | 189 | throw new FaultException<OrganizationServiceFault>(new OrganizationServiceFault(), $"{e.LogicalName} wit | ||
190 | } | |||
300 | 191 | } | ||
192 |
| |||
193 | protected EntityReference ResolveEntityReference(EntityReference er) | |||
225 | 194 | { | ||
225 | 195 | if (!Data.ContainsKey(er.LogicalName) || !Data[er.LogicalName].ContainsKey(er.Id)) | ||
24 | 196 | { | ||
24 | 197 | if (er.Id == Guid.Empty && er.HasKeyAttributes()) | ||
12 | 198 | { | ||
12 | 199 | return ResolveEntityReferenceByAlternateKeys(er); | ||
200 | } | |||
201 | else | |||
12 | 202 | { | ||
12 | 203 | throw new FaultException<OrganizationServiceFault>(new OrganizationServiceFault(), $"{er.LogicalName | ||
204 | } | |||
205 | } | |||
201 | 206 | return er; | ||
213 | 207 | } | ||
208 |
| |||
209 | protected EntityReference ResolveEntityReferenceByAlternateKeys(EntityReference er) | |||
12 | 210 | { | ||
12 | 211 | var resolvedId = GetRecordUniqueId(er); | ||
212 |
| |||
12 | 213 | return new EntityReference() | ||
12 | 214 | { | ||
12 | 215 | LogicalName = er.LogicalName, | ||
12 | 216 | Id = resolvedId | ||
12 | 217 | }; | ||
12 | 218 | } | ||
219 | /// <summary> | |||
220 | /// Fakes the delete method. Very similar to the Retrieve one | |||
221 | /// </summary> | |||
222 | /// <param name="context"></param> | |||
223 | /// <param name="fakedService"></param> | |||
224 | protected static void FakeDelete(XrmFakedContext context, IOrganizationService fakedService) | |||
3203 | 225 | { | ||
3203 | 226 | A.CallTo(() => fakedService.Delete(A<string>._, A<Guid>._)) | ||
3203 | 227 | .Invokes((string entityName, Guid id) => | ||
3347 | 228 | { | ||
3347 | 229 | if (string.IsNullOrWhiteSpace(entityName)) | ||
3221 | 230 | { | ||
3221 | 231 | throw new InvalidOperationException("The entity logical name must not be null or empty."); | ||
3203 | 232 | } | ||
3203 | 233 |
| ||
3329 | 234 | if (id == Guid.Empty) | ||
3209 | 235 | { | ||
3209 | 236 | throw new InvalidOperationException("The id must not be empty."); | ||
3203 | 237 | } | ||
3203 | 238 |
| ||
3323 | 239 | var entityReference = new EntityReference(entityName, id); | ||
3203 | 240 |
| ||
3323 | 241 | context.DeleteEntity(entityReference); | ||
3305 | 242 | }); | ||
3203 | 243 | } | ||
244 |
| |||
245 | protected void DeleteEntity(EntityReference er) | |||
120 | 246 | { | ||
247 | // Don't fail with invalid operation exception, if no record of this entity exists, but entity is known | |||
120 | 248 | if (!this.Data.ContainsKey(er.LogicalName)) | ||
12 | 249 | { | ||
12 | 250 | if (this.ProxyTypesAssembly == null) | ||
6 | 251 | { | ||
6 | 252 | throw new InvalidOperationException($"The entity logical name {er.LogicalName} is not valid."); | ||
253 | } | |||
254 |
| |||
12 | 255 | if (!this.ProxyTypesAssembly.GetTypes().Any(type => this.FindReflectedType(er.LogicalName) != null)) | ||
0 | 256 | { | ||
0 | 257 | throw new InvalidOperationException($"The entity logical name {er.LogicalName} is not valid."); | ||
258 | } | |||
6 | 259 | } | ||
260 |
| |||
261 | // Entity logical name exists, so , check if the requested entity exists | |||
114 | 262 | if (this.Data.ContainsKey(er.LogicalName) && this.Data[er.LogicalName] != null && | ||
114 | 263 | this.Data[er.LogicalName].ContainsKey(er.Id)) | ||
102 | 264 | { | ||
102 | 265 | if (this.UsePipelineSimulation) | ||
18 | 266 | { | ||
18 | 267 | ExecutePipelineStage("Delete", ProcessingStepStage.Preoperation, ProcessingStepMode.Synchronous, er) | ||
18 | 268 | } | ||
269 |
| |||
270 | // Entity found => return only the subset of columns specified or all of them | |||
102 | 271 | this.Data[er.LogicalName].Remove(er.Id); | ||
272 |
| |||
102 | 273 | if (this.UsePipelineSimulation) | ||
18 | 274 | { | ||
18 | 275 | ExecutePipelineStage("Delete", ProcessingStepStage.Postoperation, ProcessingStepMode.Synchronous, er | ||
18 | 276 | ExecutePipelineStage("Delete", ProcessingStepStage.Postoperation, ProcessingStepMode.Asynchronous, e | ||
18 | 277 | } | ||
102 | 278 | } | ||
279 | else | |||
12 | 280 | { | ||
281 | // Entity not found in the context => throw not found exception | |||
282 | // The entity record was not found, return a CRM-ish update error message | |||
12 | 283 | throw new FaultException<OrganizationServiceFault>(new OrganizationServiceFault(), $"{er.LogicalName} wi | ||
284 | } | |||
102 | 285 | } | ||
286 | #endregion | |||
287 |
| |||
288 | #region Other protected methods | |||
289 | protected void EnsureEntityNameExistsInMetadata(string sEntityName) | |||
6119 | 290 | { | ||
6599 | 291 | if (Relationships.Values.Any(value => new[] { value.Entity1LogicalName, value.Entity2LogicalName, value.Inte | ||
456 | 292 | { | ||
456 | 293 | return; | ||
294 | } | |||
295 |
| |||
296 | // Entity metadata is checked differently when we are using a ProxyTypesAssembly => we can infer that from t | |||
5663 | 297 | if (ProxyTypesAssembly != null) | ||
4124 | 298 | { | ||
4124 | 299 | var subClassType = FindReflectedType(sEntityName); | ||
4124 | 300 | if (subClassType == null) | ||
0 | 301 | { | ||
0 | 302 | throw new Exception($"Entity {sEntityName} does not exist in the metadata cache"); | ||
303 | } | |||
4124 | 304 | } | ||
305 | //else if (!Data.ContainsKey(sEntityName)) | |||
306 | //{ | |||
307 | // //No Proxy Types Assembly | |||
308 | // throw new Exception(string.Format("Entity {0} does not exist in the metadata cache", sEntityName)); | |||
309 | //}; | |||
6119 | 310 | } | ||
311 |
| |||
312 | protected void AddEntityDefaultAttributes(Entity e) | |||
28494 | 313 | { | ||
314 | // Add createdon, modifiedon, createdby, modifiedby properties | |||
28494 | 315 | if (CallerId == null) | ||
2980 | 316 | { | ||
2980 | 317 | CallerId = new EntityReference("systemuser", Guid.NewGuid()); // Create a new instance by default | ||
2980 | 318 | if (ValidateReferences) | ||
33 | 319 | { | ||
33 | 320 | if (!Data.ContainsKey("systemuser")) | ||
33 | 321 | { | ||
33 | 322 | Data.Add("systemuser", new Dictionary<Guid, Entity>()); | ||
33 | 323 | } | ||
33 | 324 | if (!Data["systemuser"].ContainsKey(CallerId.Id)) | ||
33 | 325 | { | ||
33 | 326 | Data["systemuser"].Add(CallerId.Id, new Entity("systemuser") { Id = CallerId.Id }); | ||
33 | 327 | } | ||
33 | 328 | } | ||
329 |
| |||
2980 | 330 | } | ||
331 |
| |||
28494 | 332 | var isManyToManyRelationshipEntity = e.LogicalName != null && this.Relationships.ContainsKey(e.LogicalName); | ||
333 |
| |||
28494 | 334 | EntityInitializerService.Initialize(e, CallerId.Id, this, isManyToManyRelationshipEntity); | ||
28494 | 335 | } | ||
336 |
| |||
337 | protected void ValidateEntity(Entity e) | |||
29782 | 338 | { | ||
29782 | 339 | if (e == null) | ||
0 | 340 | { | ||
0 | 341 | throw new InvalidOperationException("The entity must not be null"); | ||
342 | } | |||
343 |
| |||
344 | // Validate the entity | |||
29782 | 345 | if (string.IsNullOrWhiteSpace(e.LogicalName)) | ||
12 | 346 | { | ||
12 | 347 | throw new InvalidOperationException("The LogicalName property must not be empty"); | ||
348 | } | |||
349 |
| |||
29770 | 350 | if (e.Id == Guid.Empty) | ||
6 | 351 | { | ||
6 | 352 | throw new InvalidOperationException("The Id property must not be empty"); | ||
353 | } | |||
29764 | 354 | } | ||
355 |
| |||
356 | protected internal Guid CreateEntity(Entity e) | |||
1294 | 357 | { | ||
1294 | 358 | if (e == null) | ||
6 | 359 | { | ||
6 | 360 | throw new InvalidOperationException("The entity must not be null"); | ||
361 | } | |||
362 |
| |||
1288 | 363 | var clone = e.Clone(e.GetType()); | ||
364 |
| |||
1288 | 365 | if (clone.Id == Guid.Empty) | ||
994 | 366 | { | ||
994 | 367 | clone.Id = Guid.NewGuid(); // Add default guid if none present | ||
994 | 368 | } | ||
369 |
| |||
370 | // Hack for Dynamic Entities where the Id property doesn't populate the "entitynameid" primary key | |||
1288 | 371 | var primaryKeyAttribute = $"{e.LogicalName}id"; | ||
1288 | 372 | if (!clone.Attributes.ContainsKey(primaryKeyAttribute)) | ||
784 | 373 | { | ||
784 | 374 | clone[primaryKeyAttribute] = clone.Id; | ||
784 | 375 | } | ||
376 |
| |||
1288 | 377 | ValidateEntity(clone); | ||
378 |
| |||
379 | // Create specific validations | |||
1282 | 380 | if (clone.Id != Guid.Empty && Data.ContainsKey(clone.LogicalName) && | ||
1282 | 381 | Data[clone.LogicalName].ContainsKey(clone.Id)) | ||
24 | 382 | { | ||
24 | 383 | throw new InvalidOperationException($"There is already a record of entity {clone.LogicalName} with id {c | ||
384 | } | |||
385 |
| |||
386 | // Create specific validations | |||
1258 | 387 | if (clone.Attributes.ContainsKey("statecode")) | ||
6 | 388 | { | ||
6 | 389 | throw new InvalidOperationException($"When creating an entity with logical name '{clone.LogicalName}', o | ||
390 | } | |||
391 |
| |||
1252 | 392 | AddEntityWithDefaults(clone, false, this.UsePipelineSimulation); | ||
393 |
| |||
1240 | 394 | if (e.RelatedEntities.Count > 0) | ||
18 | 395 | { | ||
84 | 396 | foreach (var relationshipSet in e.RelatedEntities) | ||
18 | 397 | { | ||
18 | 398 | var relationship = relationshipSet.Key; | ||
399 |
| |||
18 | 400 | var entityReferenceCollection = new EntityReferenceCollection(); | ||
401 |
| |||
114 | 402 | foreach (var relatedEntity in relationshipSet.Value.Entities) | ||
30 | 403 | { | ||
30 | 404 | var relatedId = CreateEntity(relatedEntity); | ||
30 | 405 | entityReferenceCollection.Add(new EntityReference(relatedEntity.LogicalName, relatedId)); | ||
30 | 406 | } | ||
407 |
| |||
18 | 408 | if (FakeMessageExecutors.ContainsKey(typeof(AssociateRequest))) | ||
18 | 409 | { | ||
18 | 410 | var request = new AssociateRequest | ||
18 | 411 | { | ||
18 | 412 | Target = clone.ToEntityReference(), | ||
18 | 413 | Relationship = relationship, | ||
18 | 414 | RelatedEntities = entityReferenceCollection | ||
18 | 415 | }; | ||
18 | 416 | FakeMessageExecutors[typeof(AssociateRequest)].Execute(request, this); | ||
12 | 417 | } | ||
418 | else | |||
0 | 419 | { | ||
0 | 420 | throw PullRequestException.NotImplementedOrganizationRequest(typeof(AssociateRequest)); | ||
421 | } | |||
12 | 422 | } | ||
12 | 423 | } | ||
424 |
| |||
1234 | 425 | return clone.Id; | ||
1234 | 426 | } | ||
427 |
| |||
428 | protected internal void AddEntityWithDefaults(Entity e, bool clone = false, bool usePluginPipeline = false) | |||
28494 | 429 | { | ||
430 | // Create the entity with defaults | |||
28494 | 431 | AddEntityDefaultAttributes(e); | ||
432 |
| |||
28494 | 433 | if (usePluginPipeline) | ||
42 | 434 | { | ||
42 | 435 | ExecutePipelineStage("Create", ProcessingStepStage.Preoperation, ProcessingStepMode.Synchronous, e); | ||
42 | 436 | } | ||
437 |
| |||
438 | // Store | |||
28494 | 439 | AddEntity(clone ? e.Clone(e.GetType()) : e); | ||
440 |
| |||
28470 | 441 | if (usePluginPipeline) | ||
42 | 442 | { | ||
42 | 443 | ExecutePipelineStage("Create", ProcessingStepStage.Postoperation, ProcessingStepMode.Synchronous, e); | ||
42 | 444 | ExecutePipelineStage("Create", ProcessingStepStage.Postoperation, ProcessingStepMode.Asynchronous, e); | ||
42 | 445 | } | ||
28470 | 446 | } | ||
447 |
| |||
448 | protected internal void AddEntity(Entity e) | |||
28494 | 449 | { | ||
450 | //Automatically detect proxy types assembly if an early bound type was used. | |||
28494 | 451 | if (ProxyTypesAssembly == null && | ||
28494 | 452 | e.GetType().IsSubclassOf(typeof(Entity))) | ||
1438 | 453 | { | ||
1438 | 454 | ProxyTypesAssembly = Assembly.GetAssembly(e.GetType()); | ||
1438 | 455 | } | ||
456 |
| |||
28494 | 457 | ValidateEntity(e); //Entity must have a logical name and an Id | ||
458 |
| |||
532218 | 459 | foreach (var sAttributeName in e.Attributes.Keys.ToList()) | ||
223389 | 460 | { | ||
223389 | 461 | var attribute = e[sAttributeName]; | ||
223389 | 462 | if (attribute is DateTime) | ||
57964 | 463 | { | ||
57964 | 464 | e[sAttributeName] = ConvertToUtc((DateTime)e[sAttributeName]); | ||
57964 | 465 | } | ||
223389 | 466 | if (attribute is EntityReference && ValidateReferences) | ||
174 | 467 | { | ||
174 | 468 | var target = (EntityReference)e[sAttributeName]; | ||
174 | 469 | e[sAttributeName] = ResolveEntityReference(target); | ||
168 | 470 | } | ||
223383 | 471 | } | ||
472 |
| |||
473 | //Add the entity collection | |||
28476 | 474 | if (!Data.ContainsKey(e.LogicalName)) | ||
4921 | 475 | { | ||
4921 | 476 | Data.Add(e.LogicalName, new Dictionary<Guid, Entity>()); | ||
4921 | 477 | } | ||
478 |
| |||
28476 | 479 | if (Data[e.LogicalName].ContainsKey(e.Id)) | ||
6 | 480 | { | ||
6 | 481 | Data[e.LogicalName][e.Id] = e; | ||
6 | 482 | } | ||
483 | else | |||
28470 | 484 | { | ||
28470 | 485 | Data[e.LogicalName].Add(e.Id, e); | ||
28470 | 486 | } | ||
487 |
| |||
488 | //Update metadata for that entity | |||
28476 | 489 | if (!AttributeMetadataNames.ContainsKey(e.LogicalName)) | ||
4921 | 490 | AttributeMetadataNames.Add(e.LogicalName, new Dictionary<string, string>()); | ||
491 |
| |||
492 | //Update attribute metadata | |||
28476 | 493 | if (ProxyTypesAssembly != null) | ||
6404 | 494 | { | ||
495 | //If the context is using a proxy types assembly then we can just guess the metadata from the generated | |||
6404 | 496 | var type = FindReflectedType(e.LogicalName); | ||
6404 | 497 | if (type != null) | ||
6398 | 498 | { | ||
6398 | 499 | var props = type.GetProperties(); | ||
3221870 | 500 | foreach (var p in props) | ||
1601338 | 501 | { | ||
1601338 | 502 | if (!AttributeMetadataNames[e.LogicalName].ContainsKey(p.Name)) | ||
769860 | 503 | AttributeMetadataNames[e.LogicalName].Add(p.Name, p.Name); | ||
1601338 | 504 | } | ||
6398 | 505 | } | ||
506 | else | |||
6 | 507 | throw new Exception(string.Format("Couldnt find reflected type for {0}", e.LogicalName)); | ||
508 |
| |||
6398 | 509 | } | ||
510 | else | |||
22072 | 511 | { | ||
512 | //If dynamic entities are being used, then the only way of guessing if a property exists is just by chec | |||
513 | //if the entity has the attribute in the dictionary | |||
408142 | 514 | foreach (var attKey in e.Attributes.Keys) | ||
170963 | 515 | { | ||
170963 | 516 | if (!AttributeMetadataNames[e.LogicalName].ContainsKey(attKey)) | ||
15837 | 517 | AttributeMetadataNames[e.LogicalName].Add(attKey, attKey); | ||
170963 | 518 | } | ||
22072 | 519 | } | ||
520 |
| |||
28470 | 521 | } | ||
522 |
| |||
523 | protected internal bool AttributeExistsInMetadata(string sEntityName, string sAttributeName) | |||
6384 | 524 | { | ||
6696 | 525 | var relationships = this.Relationships.Values.Where(value => new[] { value.Entity1LogicalName, value.Entity2 | ||
6660 | 526 | if (relationships.Any(e => e.Entity1Attribute == sAttributeName || e.Entity2Attribute == sAttributeName)) | ||
102 | 527 | { | ||
102 | 528 | return true; | ||
529 | } | |||
530 |
| |||
531 | //Early bound types | |||
6282 | 532 | if (ProxyTypesAssembly != null) | ||
5286 | 533 | { | ||
534 | //Check if attribute exists in the early bound type | |||
5286 | 535 | var earlyBoundType = FindReflectedType(sEntityName); | ||
5286 | 536 | if (earlyBoundType != null) | ||
5286 | 537 | { | ||
538 | //Get that type properties | |||
5286 | 539 | var attributeFound = earlyBoundType | ||
5286 | 540 | .GetProperties() | ||
490899 | 541 | .Where(pi => pi.GetCustomAttributes(typeof(AttributeLogicalNameAttribute), true).Length > 0) | ||
485552 | 542 | .Where(pi => (pi.GetCustomAttributes(typeof(AttributeLogicalNameAttribute), true)[0] as Attribut | ||
5286 | 543 | .FirstOrDefault(); | ||
544 |
| |||
5286 | 545 | if (attributeFound != null) | ||
5262 | 546 | return true; | ||
547 |
| |||
24 | 548 | if (attributeFound == null && EntityMetadata.ContainsKey(sEntityName)) | ||
0 | 549 | { | ||
550 | //Try with metadata | |||
0 | 551 | return AttributeExistsInInjectedMetadata(sEntityName, sAttributeName); | ||
552 | } | |||
553 | else | |||
24 | 554 | { | ||
24 | 555 | return false; | ||
556 | } | |||
557 | } | |||
558 | //Try with metadata | |||
0 | 559 | return false; | ||
560 | } | |||
561 |
| |||
996 | 562 | if (EntityMetadata.ContainsKey(sEntityName)) | ||
0 | 563 | { | ||
564 | //Try with metadata | |||
0 | 565 | return AttributeExistsInInjectedMetadata(sEntityName, sAttributeName); | ||
566 | } | |||
567 |
| |||
568 | //Dynamic entities and not entity metadata injected for entity => just return true if not found | |||
996 | 569 | return true; | ||
6384 | 570 | } | ||
571 |
| |||
572 | protected internal bool AttributeExistsInInjectedMetadata(string sEntityName, string sAttributeName) | |||
0 | 573 | { | ||
0 | 574 | var attributeInMetadata = FindAttributeTypeInInjectedMetadata(sEntityName, sAttributeName); | ||
0 | 575 | return attributeInMetadata != null; | ||
0 | 576 | } | ||
577 |
| |||
578 | protected internal DateTime ConvertToUtc(DateTime attribute) | |||
58287 | 579 | { | ||
58287 | 580 | return DateTime.SpecifyKind(attribute, DateTimeKind.Utc); | ||
58287 | 581 | } | ||
582 | #endregion | |||
583 | } | |||
584 | } |
# | Line | Line coverage | ||
---|---|---|---|---|
1 | using FakeItEasy; | |||
2 | using FakeXrmEasy.FakeMessageExecutors; | |||
3 | using FakeXrmEasy.Permissions; | |||
4 | using FakeXrmEasy.Services; | |||
5 | using Microsoft.Xrm.Sdk; | |||
6 | using Microsoft.Xrm.Sdk.Messages; | |||
7 | using Microsoft.Xrm.Sdk.Metadata; | |||
8 | using Microsoft.Xrm.Sdk.Query; | |||
9 | using System; | |||
10 | using System.Collections.Generic; | |||
11 | using System.Linq; | |||
12 | using System.Reflection; | |||
13 |
| |||
14 | namespace FakeXrmEasy | |||
15 | { | |||
16 | /// <summary> | |||
17 | /// A fake context that stores In-Memory entites indexed by logical name and then Entity records, simulating | |||
18 | /// how entities are persisted in Tables (with the logical name) and then the records themselves | |||
19 | /// where the Primary Key is the Guid | |||
20 | /// </summary> | |||
21 | public partial class XrmFakedContext : IXrmContext | |||
22 | { | |||
11813 | 23 | protected internal IOrganizationService Service { get; set; } | ||
24 |
| |||
25 | private IServiceEndpointNotificationService _serviceEndpointNotificationService; | |||
26 |
| |||
4463 | 27 | private readonly Lazy<XrmFakedTracingService> _tracingService = new Lazy<XrmFakedTracingService>(() => new XrmFa | ||
28 |
| |||
29 | /// <summary> | |||
30 | /// All proxy type assemblies available on mocked database. | |||
31 | /// </summary> | |||
141365 | 32 | private List<Assembly> ProxyTypesAssemblies { get; set; } | ||
33 |
| |||
271 | 34 | protected internal XrmFakedTracingService TracingService => _tracingService.Value; | ||
35 |
| |||
5332 | 36 | protected internal bool Initialised { get; set; } | ||
37 |
| |||
114532 | 38 | public Dictionary<string, Dictionary<Guid, Entity>> Data { get; set; } | ||
39 |
| |||
40 | /// <summary> | |||
41 | /// Specify which assembly is used to search for early-bound proxy | |||
42 | /// types when used within simulated CRM context. | |||
43 | /// | |||
44 | /// If you want to specify multiple different assemblies for early-bound | |||
45 | /// proxy types please use <see cref="EnableProxyTypes(Assembly)"/> | |||
46 | /// instead. | |||
47 | /// </summary> | |||
48 | public Assembly ProxyTypesAssembly | |||
49 | { | |||
50 | get | |||
102785 | 51 | { | ||
52 | // TODO What we should do when ProxyTypesAssemblies contains multiple assemblies? One shouldn't throw ex | |||
102785 | 53 | return ProxyTypesAssemblies.FirstOrDefault(); | ||
102785 | 54 | } | ||
55 | set | |||
2321 | 56 | { | ||
2321 | 57 | ProxyTypesAssemblies = new List<Assembly>(); | ||
2321 | 58 | if (value != null) | ||
2321 | 59 | { | ||
2321 | 60 | ProxyTypesAssemblies.Add(value); | ||
2321 | 61 | } | ||
2321 | 62 | } | ||
63 | } | |||
64 |
| |||
65 | /// <summary> | |||
66 | /// Sets the user to assign the CreatedBy and ModifiedBy properties when entities are added to the context. | |||
67 | /// All requests will be executed on behalf of this user | |||
68 | /// </summary> | |||
60752 | 69 | public EntityReference CallerId { get; set; } | ||
70 |
| |||
283 | 71 | public EntityReference BusinessUnitId { get; set; } | ||
72 |
| |||
73 | public delegate OrganizationResponse ServiceRequestExecution(OrganizationRequest req); | |||
74 |
| |||
75 | /// <summary> | |||
76 | /// Probably should be replaced by FakeMessageExecutors, more generic, which can use custom interfaces rather th | |||
77 | /// </summary> | |||
6286 | 78 | private Dictionary<Type, ServiceRequestExecution> ExecutionMocks { get; set; } | ||
79 |
| |||
11120 | 80 | private Dictionary<Type, IFakeMessageExecutor> FakeMessageExecutors { get; set; } | ||
81 |
| |||
4336 | 82 | private Dictionary<string, IFakeMessageExecutor> GenericFakeMessageExecutors { get; set; } | ||
83 |
| |||
46035 | 84 | private Dictionary<string, XrmFakedRelationship> Relationships { get; set; } | ||
85 |
| |||
86 |
| |||
32770 | 87 | public IEntityInitializerService EntityInitializerService { get; set; } | ||
4559 | 88 | public IAccessRightsRepository AccessRightsRepository { get; set; } | ||
89 |
| |||
20317 | 90 | public int MaxRetrieveCount { get; set; } | ||
91 |
| |||
32770 | 92 | public EntityInitializationLevel InitializationLevel { get; set; } | ||
93 |
| |||
94 | [Obsolete("FakeXrmEasy v1.x is deprecated and will stop receiving updates soon. Please start planning your upgra | |||
4270 | 95 | public XrmFakedContext() | ||
4270 | 96 | { | ||
4270 | 97 | MaxRetrieveCount = 5000; | ||
98 |
| |||
4270 | 99 | AttributeMetadataNames = new Dictionary<string, Dictionary<string, string>>(); | ||
4270 | 100 | Data = new Dictionary<string, Dictionary<Guid, Entity>>(); | ||
4270 | 101 | ExecutionMocks = new Dictionary<Type, ServiceRequestExecution>(); | ||
4270 | 102 | OptionSetValuesMetadata = new Dictionary<string, OptionSetMetadata>(); | ||
4270 | 103 | StatusAttributeMetadata = new Dictionary<string, StatusAttributeMetadata>(); | ||
104 |
| |||
4270 | 105 | FakeMessageExecutors = Assembly.GetExecutingAssembly() | ||
4270 | 106 | .GetTypes() | ||
873811 | 107 | .Where(t => t.GetInterfaces().Contains(typeof(IFakeMessageExecutor))) | ||
202400 | 108 | .Select(t => Activator.CreateInstance(t) as IFakeMessageExecutor) | ||
400530 | 109 | .ToDictionary(t => t.GetResponsibleRequestType(), t => t); | ||
110 |
| |||
4270 | 111 | GenericFakeMessageExecutors = new Dictionary<string, IFakeMessageExecutor>(); | ||
112 |
| |||
4270 | 113 | Relationships = new Dictionary<string, XrmFakedRelationship>(); | ||
114 |
| |||
4270 | 115 | EntityInitializerService = new DefaultEntityInitializerService(); | ||
116 |
| |||
4270 | 117 | AccessRightsRepository = new AccessRightsRepository(); | ||
118 |
| |||
4270 | 119 | SystemTimeZone = TimeZoneInfo.Local; | ||
4270 | 120 | DateBehaviour = DefaultDateBehaviour(); | ||
121 |
| |||
4270 | 122 | EntityMetadata = new Dictionary<string, EntityMetadata>(); | ||
123 |
| |||
4270 | 124 | UsePipelineSimulation = false; | ||
125 |
| |||
4270 | 126 | InitializationLevel = EntityInitializationLevel.Default; | ||
127 |
| |||
4270 | 128 | ProxyTypesAssemblies = new List<Assembly>(); | ||
4270 | 129 | } | ||
130 |
| |||
131 | /// <summary> | |||
132 | /// Initializes the context with the provided entities | |||
133 | /// </summary> | |||
134 | /// <param name="entities"></param> | |||
135 | [Obsolete("FakeXrmEasy v1.x is deprecated and will stop receiving updates soon. Please start planning your upgra | |||
136 | public virtual void Initialize(IEnumerable<Entity> entities) | |||
2678 | 137 | { | ||
2678 | 138 | if (Initialised) | ||
6 | 139 | { | ||
6 | 140 | throw new Exception("Initialize should be called only once per unit test execution and XrmFakedContext i | ||
141 | } | |||
142 |
| |||
2672 | 143 | if (entities == null) | ||
6 | 144 | { | ||
6 | 145 | throw new InvalidOperationException("The entities parameter must be not null"); | ||
146 | } | |||
147 |
| |||
61798 | 148 | foreach (var e in entities) | ||
26906 | 149 | { | ||
26906 | 150 | AddEntityWithDefaults(e, true); | ||
26894 | 151 | } | ||
152 |
| |||
2654 | 153 | Initialised = true; | ||
2654 | 154 | } | ||
155 |
| |||
156 | public void Initialize(Entity e) | |||
111 | 157 | { | ||
111 | 158 | this.Initialize(new List<Entity>() { e }); | ||
111 | 159 | } | ||
160 |
| |||
161 | /// <summary> | |||
162 | /// Enables support for the early-cound types exposed in a specified assembly. | |||
163 | /// </summary> | |||
164 | /// <param name="assembly"> | |||
165 | /// An assembly containing early-bound entity types. | |||
166 | /// </param> | |||
167 | /// <remarks> | |||
168 | /// See issue #334 on GitHub. This has quite similar idea as is on SDK method | |||
169 | /// https://docs.microsoft.com/en-us/dotnet/api/microsoft.xrm.sdk.client.organizationserviceproxy.enableproxytyp | |||
170 | /// </remarks> | |||
171 | public void EnableProxyTypes(Assembly assembly) | |||
24 | 172 | { | ||
24 | 173 | if (assembly == null) | ||
0 | 174 | { | ||
0 | 175 | throw new ArgumentNullException(nameof(assembly)); | ||
176 | } | |||
177 |
| |||
24 | 178 | if (ProxyTypesAssemblies.Contains(assembly)) | ||
6 | 179 | { | ||
6 | 180 | throw new InvalidOperationException($"Proxy types assembly { assembly.GetName().Name } is already enable | ||
181 | } | |||
182 |
| |||
18 | 183 | ProxyTypesAssemblies.Add(assembly); | ||
18 | 184 | } | ||
185 |
| |||
186 | public void AddExecutionMock<T>(ServiceRequestExecution mock) where T : OrganizationRequest | |||
18 | 187 | { | ||
18 | 188 | if (!ExecutionMocks.ContainsKey(typeof(T))) | ||
12 | 189 | ExecutionMocks.Add(typeof(T), mock); | ||
190 | else | |||
6 | 191 | ExecutionMocks[typeof(T)] = mock; | ||
18 | 192 | } | ||
193 |
| |||
194 | public void RemoveExecutionMock<T>() where T : OrganizationRequest | |||
12 | 195 | { | ||
12 | 196 | ExecutionMocks.Remove(typeof(T)); | ||
12 | 197 | } | ||
198 |
| |||
199 | public void AddFakeMessageExecutor<T>(IFakeMessageExecutor executor) where T : OrganizationRequest | |||
30 | 200 | { | ||
30 | 201 | if (!FakeMessageExecutors.ContainsKey(typeof(T))) | ||
0 | 202 | FakeMessageExecutors.Add(typeof(T), executor); | ||
203 | else | |||
30 | 204 | FakeMessageExecutors[typeof(T)] = executor; | ||
30 | 205 | } | ||
206 |
| |||
207 | public void RemoveFakeMessageExecutor<T>() where T : OrganizationRequest | |||
6 | 208 | { | ||
6 | 209 | FakeMessageExecutors.Remove(typeof(T)); | ||
6 | 210 | } | ||
211 |
| |||
212 | public void AddGenericFakeMessageExecutor(string message, IFakeMessageExecutor executor) | |||
12 | 213 | { | ||
12 | 214 | if (!GenericFakeMessageExecutors.ContainsKey(message)) | ||
12 | 215 | GenericFakeMessageExecutors.Add(message, executor); | ||
216 | else | |||
0 | 217 | GenericFakeMessageExecutors[message] = executor; | ||
12 | 218 | } | ||
219 |
| |||
220 | public void RemoveGenericFakeMessageExecutor(string message) | |||
6 | 221 | { | ||
6 | 222 | if (GenericFakeMessageExecutors.ContainsKey(message)) | ||
6 | 223 | GenericFakeMessageExecutors.Remove(message); | ||
6 | 224 | } | ||
225 |
| |||
226 | public void AddRelationship(string schemaname, XrmFakedRelationship relationship) | |||
198 | 227 | { | ||
198 | 228 | Relationships.Add(schemaname, relationship); | ||
198 | 229 | } | ||
230 |
| |||
231 | public void RemoveRelationship(string schemaname) | |||
0 | 232 | { | ||
0 | 233 | Relationships.Remove(schemaname); | ||
0 | 234 | } | ||
235 |
| |||
236 | public XrmFakedRelationship GetRelationship(string schemaName) | |||
294 | 237 | { | ||
294 | 238 | if (Relationships.ContainsKey(schemaName)) | ||
282 | 239 | { | ||
282 | 240 | return Relationships[schemaName]; | ||
241 | } | |||
242 |
| |||
12 | 243 | return null; | ||
294 | 244 | } | ||
245 |
| |||
246 | public void AddAttributeMapping(string sourceEntityName, string sourceAttributeName, string targetEntityName, st | |||
18 | 247 | { | ||
18 | 248 | if (string.IsNullOrWhiteSpace(sourceEntityName)) | ||
0 | 249 | throw new ArgumentNullException("sourceEntityName"); | ||
18 | 250 | if (string.IsNullOrWhiteSpace(sourceAttributeName)) | ||
0 | 251 | throw new ArgumentNullException("sourceAttributeName"); | ||
18 | 252 | if (string.IsNullOrWhiteSpace(targetEntityName)) | ||
0 | 253 | throw new ArgumentNullException("targetEntityName"); | ||
18 | 254 | if (string.IsNullOrWhiteSpace(targetAttributeName)) | ||
0 | 255 | throw new ArgumentNullException("targetAttributeName"); | ||
256 |
| |||
18 | 257 | var entityMap = new Entity | ||
18 | 258 | { | ||
18 | 259 | LogicalName = "entitymap", | ||
18 | 260 | Id = Guid.NewGuid(), | ||
18 | 261 | ["targetentityname"] = targetEntityName, | ||
18 | 262 | ["sourceentityname"] = sourceEntityName | ||
18 | 263 | }; | ||
264 |
| |||
18 | 265 | var attributeMap = new Entity | ||
18 | 266 | { | ||
18 | 267 | LogicalName = "attributemap", | ||
18 | 268 | Id = Guid.NewGuid(), | ||
18 | 269 | ["entitymapid"] = new EntityReference("entitymap", entityMap.Id), | ||
18 | 270 | ["targetattributename"] = targetAttributeName, | ||
18 | 271 | ["sourceattributename"] = sourceAttributeName | ||
18 | 272 | }; | ||
273 |
| |||
18 | 274 | AddEntityWithDefaults(entityMap); | ||
18 | 275 | AddEntityWithDefaults(attributeMap); | ||
18 | 276 | } | ||
277 |
| |||
278 | public virtual IOrganizationService GetOrganizationService() | |||
2350 | 279 | { | ||
2350 | 280 | if (this is XrmRealContext) | ||
0 | 281 | { | ||
0 | 282 | Service = GetOrganizationService(); | ||
0 | 283 | return Service; | ||
284 | } | |||
2350 | 285 | return GetFakedOrganizationService(this); | ||
2350 | 286 | } | ||
287 |
| |||
288 | /// <summary> | |||
289 | /// Deprecated. Use GetOrganizationService instead | |||
290 | /// </summary> | |||
291 | /// <returns></returns> | |||
292 | [Obsolete("Use GetOrganizationService instead")] | |||
293 | public IOrganizationService GetFakedOrganizationService() | |||
1685 | 294 | { | ||
1685 | 295 | return GetFakedOrganizationService(this); | ||
1685 | 296 | } | ||
297 |
| |||
298 | protected IOrganizationService GetFakedOrganizationService(XrmFakedContext context) | |||
4035 | 299 | { | ||
4035 | 300 | if (context.Service != null) | ||
832 | 301 | { | ||
832 | 302 | return context.Service; | ||
303 | } | |||
304 |
| |||
3203 | 305 | var fakedService = A.Fake<IOrganizationService>(); | ||
306 |
| |||
307 | //Fake CRUD methods | |||
3203 | 308 | FakeRetrieve(context, fakedService); | ||
3203 | 309 | FakeCreate(context, fakedService); | ||
3203 | 310 | FakeUpdate(context, fakedService); | ||
3203 | 311 | FakeDelete(context, fakedService); | ||
312 |
| |||
313 | //Fake / Intercept Retrieve Multiple Requests | |||
3203 | 314 | FakeRetrieveMultiple(context, fakedService); | ||
315 |
| |||
316 | //Fake / Intercept other requests | |||
3203 | 317 | FakeExecute(context, fakedService); | ||
3203 | 318 | FakeAssociate(context, fakedService); | ||
3203 | 319 | FakeDisassociate(context, fakedService); | ||
3203 | 320 | context.Service = fakedService; | ||
321 |
| |||
3203 | 322 | return context.Service; | ||
4035 | 323 | } | ||
324 |
| |||
325 | /// <summary> | |||
326 | /// Fakes the Execute method of the organization service. | |||
327 | /// Not all the OrganizationRequest are going to be implemented, so stay tunned on updates! | |||
328 | /// </summary> | |||
329 | /// <param name="context"></param> | |||
330 | /// <param name="fakedService"></param> | |||
331 | public static void FakeExecute(XrmFakedContext context, IOrganizationService fakedService) | |||
3203 | 332 | { | ||
3203 | 333 | OrganizationResponse response = null; | ||
3203 | 334 | Func<OrganizationRequest, OrganizationResponse> execute = (req) => | ||
5159 | 335 | { | ||
5159 | 336 | if (context.ExecutionMocks.ContainsKey(req.GetType())) | ||
3215 | 337 | return context.ExecutionMocks[req.GetType()].Invoke(req); | ||
3203 | 338 |
| ||
5147 | 339 | if (context.FakeMessageExecutors.ContainsKey(req.GetType()) | ||
5147 | 340 | && context.FakeMessageExecutors[req.GetType()].CanExecute(req)) | ||
5129 | 341 | return context.FakeMessageExecutors[req.GetType()].Execute(req, context); | ||
3203 | 342 |
| ||
3221 | 343 | if (req.GetType() == typeof(OrganizationRequest) | ||
3221 | 344 | && context.GenericFakeMessageExecutors.ContainsKey(req.RequestName)) | ||
3215 | 345 | return context.GenericFakeMessageExecutors[req.RequestName].Execute(req, context); | ||
3203 | 346 |
| ||
3209 | 347 | throw PullRequestException.NotImplementedOrganizationRequest(req.GetType()); | ||
4891 | 348 | }; | ||
349 |
| |||
3203 | 350 | A.CallTo(() => fakedService.Execute(A<OrganizationRequest>._)) | ||
5159 | 351 | .Invokes((OrganizationRequest req) => response = execute(req)) | ||
4891 | 352 | .ReturnsLazily((OrganizationRequest req) => response); | ||
3203 | 353 | } | ||
354 |
| |||
355 | public static void FakeAssociate(XrmFakedContext context, IOrganizationService fakedService) | |||
3203 | 356 | { | ||
3203 | 357 | A.CallTo(() => fakedService.Associate(A<string>._, A<Guid>._, A<Relationship>._, A<EntityReferenceCollection | ||
3203 | 358 | .Invokes((string entityName, Guid entityId, Relationship relationship, EntityReferenceCollection entityC | ||
3311 | 359 | { | ||
3311 | 360 | if (context.FakeMessageExecutors.ContainsKey(typeof(AssociateRequest))) | ||
3311 | 361 | { | ||
3311 | 362 | var request = new AssociateRequest() | ||
3311 | 363 | { | ||
3311 | 364 | Target = new EntityReference() { Id = entityId, LogicalName = entityName }, | ||
3311 | 365 | Relationship = relationship, | ||
3311 | 366 | RelatedEntities = entityCollection | ||
3311 | 367 | }; | ||
3311 | 368 | context.FakeMessageExecutors[typeof(AssociateRequest)].Execute(request, context); | ||
3311 | 369 | } | ||
3203 | 370 | else | ||
3203 | 371 | throw PullRequestException.NotImplementedOrganizationRequest(typeof(AssociateRequest)); | ||
3311 | 372 | }); | ||
3203 | 373 | } | ||
374 |
| |||
375 | public static void FakeDisassociate(XrmFakedContext context, IOrganizationService fakedService) | |||
3203 | 376 | { | ||
3203 | 377 | A.CallTo(() => fakedService.Disassociate(A<string>._, A<Guid>._, A<Relationship>._, A<EntityReferenceCollect | ||
3203 | 378 | .Invokes((string entityName, Guid entityId, Relationship relationship, EntityReferenceCollection entityC | ||
3221 | 379 | { | ||
3221 | 380 | if (context.FakeMessageExecutors.ContainsKey(typeof(DisassociateRequest))) | ||
3221 | 381 | { | ||
3221 | 382 | var request = new DisassociateRequest() | ||
3221 | 383 | { | ||
3221 | 384 | Target = new EntityReference() { Id = entityId, LogicalName = entityName }, | ||
3221 | 385 | Relationship = relationship, | ||
3221 | 386 | RelatedEntities = entityCollection | ||
3221 | 387 | }; | ||
3221 | 388 | context.FakeMessageExecutors[typeof(DisassociateRequest)].Execute(request, context); | ||
3221 | 389 | } | ||
3203 | 390 | else | ||
3203 | 391 | throw PullRequestException.NotImplementedOrganizationRequest(typeof(DisassociateRequest)); | ||
3221 | 392 | }); | ||
3203 | 393 | } | ||
394 |
| |||
395 | public static void FakeRetrieveMultiple(XrmFakedContext context, IOrganizationService fakedService) | |||
3203 | 396 | { | ||
3203 | 397 | EntityCollection entities = null; | ||
3203 | 398 | Func<QueryBase, EntityCollection> retriveMultiple = (QueryBase req) => | ||
4769 | 399 | { | ||
4769 | 400 | var request = new RetrieveMultipleRequest { Query = req }; | ||
3203 | 401 |
| ||
4769 | 402 | var executor = new RetrieveMultipleRequestExecutor(); | ||
4769 | 403 | var response = executor.Execute(request, context) as RetrieveMultipleResponse; | ||
3203 | 404 |
| ||
4737 | 405 | return response.EntityCollection; | ||
4737 | 406 | }; | ||
407 |
| |||
408 | //refactored from RetrieveMultipleExecutor | |||
3203 | 409 | A.CallTo(() => fakedService.RetrieveMultiple(A<QueryBase>._)) | ||
4769 | 410 | .Invokes((QueryBase req) => entities = retriveMultiple(req)) | ||
4737 | 411 | .ReturnsLazily((QueryBase req) => entities); | ||
3203 | 412 | } | ||
413 |
| |||
414 | public IServiceEndpointNotificationService GetFakedServiceEndpointNotificationService() | |||
48 | 415 | { | ||
48 | 416 | return _serviceEndpointNotificationService ?? | ||
48 | 417 | (_serviceEndpointNotificationService = A.Fake<IServiceEndpointNotificationService>()); | ||
48 | 418 | } | ||
419 | #if FAKE_XRM_EASY_9 | |||
420 | public IEntityDataSourceRetrieverService GetFakedEntityDataSourceRetrieverService() | |||
1 | 421 | { | ||
1 | 422 | var service = A.Fake<IEntityDataSourceRetrieverService>(); | ||
1 | 423 | A.CallTo(() => service.RetrieveEntityDataSource()) | ||
2 | 424 | .ReturnsLazily(() => EntityDataSourceRetriever); | ||
1 | 425 | return service; | ||
1 | 426 | } | ||
427 | #endif | |||
428 | } | |||
429 | } |
# | Line | Line coverage | ||
---|---|---|---|---|
1 | using FakeXrmEasy.Metadata; | |||
2 | using System; | |||
3 | using System.Collections.Generic; | |||
4 |
| |||
5 | namespace FakeXrmEasy | |||
6 | { | |||
7 | public partial class XrmFakedContext : IXrmContext | |||
8 | { | |||
4270 | 9 | public TimeZoneInfo SystemTimeZone { get; set; } | ||
10 |
| |||
12 | 11 | public FiscalYearSettings FiscalYearSettings { get; set; } | ||
12 |
| |||
27704 | 13 | public Dictionary<string, Dictionary<string, DateTimeAttributeBehavior>> DateBehaviour { get; set; } | ||
14 |
| |||
15 | private static Dictionary<string, Dictionary<string, DateTimeAttributeBehavior>> DefaultDateBehaviour() | |||
4270 | 16 | { | ||
17 | #if FAKE_XRM_EASY || FAKE_XRM_EASY_2013 | |||
1364 | 18 | return new Dictionary<string, Dictionary<string, DateTimeAttributeBehavior>>(); | ||
19 | #else | |||
2906 | 20 | return new Dictionary<string, Dictionary<string, DateTimeAttributeBehavior>> | ||
2906 | 21 | { | ||
2906 | 22 | { | ||
2906 | 23 | "contact", new Dictionary<string, DateTimeAttributeBehavior> | ||
2906 | 24 | { | ||
2906 | 25 | { "anniversary", DateTimeAttributeBehavior.DateOnly }, | ||
2906 | 26 | { "birthdate", DateTimeAttributeBehavior.DateOnly } | ||
2906 | 27 | } | ||
2906 | 28 | }, | ||
2906 | 29 | { | ||
2906 | 30 | "invoice", new Dictionary<string, DateTimeAttributeBehavior> | ||
2906 | 31 | { | ||
2906 | 32 | { "duedate", DateTimeAttributeBehavior.DateOnly } | ||
2906 | 33 | } | ||
2906 | 34 | }, | ||
2906 | 35 | { | ||
2906 | 36 | "lead", new Dictionary<string, DateTimeAttributeBehavior> | ||
2906 | 37 | { | ||
2906 | 38 | { "estimatedclosedate", DateTimeAttributeBehavior.DateOnly } | ||
2906 | 39 | } | ||
2906 | 40 | }, | ||
2906 | 41 | { | ||
2906 | 42 | "opportunity", new Dictionary<string, DateTimeAttributeBehavior> | ||
2906 | 43 | { | ||
2906 | 44 | { "actualclosedate", DateTimeAttributeBehavior.DateOnly }, | ||
2906 | 45 | { "estimatedclosedate", DateTimeAttributeBehavior.DateOnly }, | ||
2906 | 46 | { "finaldecisiondate", DateTimeAttributeBehavior.DateOnly } | ||
2906 | 47 | } | ||
2906 | 48 | }, | ||
2906 | 49 | { | ||
2906 | 50 | "product", new Dictionary<string, DateTimeAttributeBehavior> | ||
2906 | 51 | { | ||
2906 | 52 | { "validfromdate", DateTimeAttributeBehavior.DateOnly }, | ||
2906 | 53 | { "validtodate", DateTimeAttributeBehavior.DateOnly } | ||
2906 | 54 | } | ||
2906 | 55 | }, | ||
2906 | 56 | { | ||
2906 | 57 | "quote", new Dictionary<string, DateTimeAttributeBehavior> | ||
2906 | 58 | { | ||
2906 | 59 | { "closedon", DateTimeAttributeBehavior.DateOnly }, | ||
2906 | 60 | { "dueby", DateTimeAttributeBehavior.DateOnly } | ||
2906 | 61 | } | ||
2906 | 62 | } | ||
2906 | 63 | }; | ||
64 | #endif | |||
4270 | 65 | } | ||
66 | } | |||
67 | } |
# | Line | Line coverage | ||
---|---|---|---|---|
1 | using Microsoft.Xrm.Sdk.Metadata; | |||
2 | using System; | |||
3 | using System.Collections.Generic; | |||
4 | using System.Linq; | |||
5 | using FakeXrmEasy.Extensions; | |||
6 | using System.Reflection; | |||
7 | using Microsoft.Xrm.Sdk.Client; | |||
8 | using Microsoft.Xrm.Sdk; | |||
9 | using FakeXrmEasy.Metadata; | |||
10 |
| |||
11 | namespace FakeXrmEasy | |||
12 | { | |||
13 | public partial class XrmFakedContext : IXrmContext | |||
14 | { | |||
15 | /// <summary> | |||
16 | /// Stores some minimal metadata info if dynamic entities are used and no injected metadata was used | |||
17 | /// </summary> | |||
2598564 | 18 | protected internal Dictionary<string, Dictionary<string, string>> AttributeMetadataNames { get; set; } | ||
19 |
| |||
20 | /// <summary> | |||
21 | /// Stores fake global option set metadata | |||
22 | /// </summary> | |||
4444 | 23 | public Dictionary<string, OptionSetMetadata> OptionSetValuesMetadata { get; set; } | ||
24 |
| |||
25 | /// <summary> | |||
26 | /// Stores fake global status values metadata | |||
27 | /// </summary> | |||
4300 | 28 | public Dictionary<string, StatusAttributeMetadata> StatusAttributeMetadata { get; set; } | ||
29 |
| |||
30 | /// <summary> | |||
31 | /// Stores fake entity metadata | |||
32 | /// </summary> | |||
180083 | 33 | protected internal Dictionary<string, EntityMetadata> EntityMetadata { get; set; } | ||
34 |
| |||
35 |
| |||
36 | public void InitializeMetadata(IEnumerable<EntityMetadata> entityMetadataList) | |||
164 | 37 | { | ||
164 | 38 | if (entityMetadataList == null) | ||
6 | 39 | { | ||
6 | 40 | throw new Exception("Entity metadata parameter can not be null"); | ||
41 | } | |||
42 |
| |||
43 | // this.EntityMetadata = new Dictionary<string, EntityMetadata>(); | |||
6618 | 44 | foreach (var eMetadata in entityMetadataList) | ||
3081 | 45 | { | ||
3081 | 46 | if (string.IsNullOrWhiteSpace(eMetadata.LogicalName)) | ||
12 | 47 | { | ||
12 | 48 | throw new Exception("An entity metadata record must have a LogicalName property."); | ||
49 | } | |||
50 |
| |||
3069 | 51 | if (EntityMetadata.ContainsKey(eMetadata.LogicalName)) | ||
6 | 52 | { | ||
6 | 53 | throw new Exception("An entity metadata record with the same logical name was previously added. "); | ||
54 | } | |||
3063 | 55 | EntityMetadata.Add(eMetadata.LogicalName, eMetadata.Copy()); | ||
3063 | 56 | } | ||
140 | 57 | } | ||
58 |
| |||
59 | public void InitializeMetadata(EntityMetadata entityMetadata) | |||
98 | 60 | { | ||
98 | 61 | this.InitializeMetadata(new List<EntityMetadata>() { entityMetadata }); | ||
98 | 62 | } | ||
63 |
| |||
64 | public void InitializeMetadata(Assembly earlyBoundEntitiesAssembly) | |||
12 | 65 | { | ||
12 | 66 | IEnumerable<EntityMetadata> entityMetadatas = MetadataGenerator.FromEarlyBoundEntities(earlyBoundEntitiesAss | ||
12 | 67 | if (entityMetadatas.Any()) | ||
12 | 68 | { | ||
12 | 69 | this.InitializeMetadata(entityMetadatas); | ||
12 | 70 | } | ||
12 | 71 | } | ||
72 |
| |||
73 | public IQueryable<EntityMetadata> CreateMetadataQuery() | |||
30 | 74 | { | ||
30 | 75 | return this.EntityMetadata.Values | ||
1519 | 76 | .Select(em => em.Copy()) | ||
30 | 77 | .ToList() | ||
30 | 78 | .AsQueryable(); | ||
30 | 79 | } | ||
80 |
| |||
81 | public EntityMetadata GetEntityMetadataByName(string sLogicalName) | |||
90 | 82 | { | ||
90 | 83 | if (EntityMetadata.ContainsKey(sLogicalName)) | ||
78 | 84 | return EntityMetadata[sLogicalName].Copy(); | ||
85 |
| |||
12 | 86 | return null; | ||
90 | 87 | } | ||
88 |
| |||
89 | public void SetEntityMetadata(EntityMetadata em) | |||
30 | 90 | { | ||
30 | 91 | if (this.EntityMetadata.ContainsKey(em.LogicalName)) | ||
30 | 92 | this.EntityMetadata[em.LogicalName] = em.Copy(); | ||
93 | else | |||
0 | 94 | this.EntityMetadata.Add(em.LogicalName, em.Copy()); | ||
30 | 95 | } | ||
96 |
| |||
97 | public AttributeMetadata GetAttributeMetadataFor(string sEntityName, string sAttributeName, Type attributeType) | |||
0 | 98 | { | ||
0 | 99 | if (EntityMetadata.ContainsKey(sEntityName)) | ||
0 | 100 | { | ||
0 | 101 | var entityMetadata = GetEntityMetadataByName(sEntityName); | ||
0 | 102 | var attribute = entityMetadata.Attributes | ||
0 | 103 | .Where(a => a.LogicalName.Equals(sAttributeName)) | ||
0 | 104 | .FirstOrDefault(); | ||
105 |
| |||
0 | 106 | if (attribute != null) | ||
0 | 107 | return attribute; | ||
0 | 108 | } | ||
109 |
| |||
0 | 110 | if (attributeType == typeof(string)) | ||
0 | 111 | { | ||
0 | 112 | return new StringAttributeMetadata(sAttributeName); | ||
113 | } | |||
114 | //Default | |||
0 | 115 | return new StringAttributeMetadata(sAttributeName); | ||
0 | 116 | } | ||
117 |
| |||
118 | } | |||
119 | } |
# | Line | Line coverage | ||
---|---|---|---|---|
1 | using System; | |||
2 | using System.Collections.Generic; | |||
3 | using System.Linq; | |||
4 | using System.Reflection; | |||
5 | using Microsoft.Xrm.Sdk; | |||
6 | using Microsoft.Xrm.Sdk.Query; | |||
7 |
| |||
8 | namespace FakeXrmEasy | |||
9 | { | |||
10 | public partial class XrmFakedContext : IXrmContext | |||
11 | { | |||
6416 | 12 | public bool UsePipelineSimulation { get; set; } | ||
13 |
| |||
14 | /// <summary> | |||
15 | /// Registers the <typeparamref name="TPlugin"/> as a SDK Message Processing Step for the Entity <typeparamref n | |||
16 | /// </summary> | |||
17 | /// <typeparam name="TPlugin">The plugin to register the step for.</typeparam> | |||
18 | /// <typeparam name="TEntity">The entity to filter this step for.</typeparam> | |||
19 | /// <param name="message">The message that should trigger the execution of plugin.</param> | |||
20 | /// <param name="stage">The stage when the plugin should be executed.</param> | |||
21 | /// <param name="mode">The mode in which the plugin should be executed.</param> | |||
22 | /// <param name="rank">The order in which this plugin should be executed in comparison to other plugins register | |||
23 | /// <param name="filteringAttributes">When not one of these attributes is present in the execution context, the | |||
24 | public void RegisterPluginStep<TPlugin, TEntity>(string message, ProcessingStepStage stage = ProcessingStepStage | |||
25 | where TPlugin : IPlugin | |||
26 | where TEntity : Entity, new() | |||
66 | 27 | { | ||
66 | 28 | var entity = new TEntity(); | ||
66 | 29 | var entityTypeCode = (int)entity.GetType().GetField("EntityTypeCode").GetValue(entity); | ||
30 |
| |||
66 | 31 | RegisterPluginStep<TPlugin>(message, stage, mode, rank, filteringAttributes, entityTypeCode); | ||
66 | 32 | } | ||
33 |
| |||
34 | /// <summary> | |||
35 | /// Registers the <typeparamref name="TPlugin"/> as a SDK Message Processing Step. | |||
36 | /// </summary> | |||
37 | /// <typeparam name="TPlugin">The plugin to register the step for.</typeparam> | |||
38 | /// <param name="message">The message that should trigger the execution of plugin.</param> | |||
39 | /// <param name="stage">The stage when the plugin should be executed.</param> | |||
40 | /// <param name="mode">The mode in which the plugin should be executed.</param> | |||
41 | /// <param name="rank">The order in which this plugin should be executed in comparison to other plugins register | |||
42 | /// <param name="filteringAttributes">When not one of these attributes is present in the execution context, the | |||
43 | /// <param name="primaryEntityTypeCode">The entity type code to filter this step for.</param> | |||
44 | public void RegisterPluginStep<TPlugin>(string message, ProcessingStepStage stage = ProcessingStepStage.Postoper | |||
45 | where TPlugin : IPlugin | |||
78 | 46 | { | ||
47 | // Message | |||
78 | 48 | var sdkMessage = this.CreateQuery("sdkmessage").FirstOrDefault(sm => string.Equals(sm.GetAttributeValue<stri | ||
78 | 49 | if (sdkMessage == null) | ||
78 | 50 | { | ||
78 | 51 | sdkMessage = new Entity("sdkmessage") | ||
78 | 52 | { | ||
78 | 53 | Id = Guid.NewGuid(), | ||
78 | 54 | ["name"] = message | ||
78 | 55 | }; | ||
78 | 56 | this.AddEntityWithDefaults(sdkMessage); | ||
78 | 57 | } | ||
58 |
| |||
59 | // Plugin Type | |||
78 | 60 | var type = typeof(TPlugin); | ||
78 | 61 | var assemblyName = type.Assembly.GetName(); | ||
62 |
| |||
78 | 63 | var pluginType = this.CreateQuery("plugintype").FirstOrDefault(pt => string.Equals(pt.GetAttributeValue<stri | ||
78 | 64 | if (pluginType == null) | ||
78 | 65 | { | ||
78 | 66 | pluginType = new Entity("plugintype") | ||
78 | 67 | { | ||
78 | 68 | Id = Guid.NewGuid(), | ||
78 | 69 | ["name"] = type.FullName, | ||
78 | 70 | ["typename"] = type.FullName, | ||
78 | 71 | ["assemblyname"] = assemblyName.Name, | ||
78 | 72 | ["major"] = assemblyName.Version.Major, | ||
78 | 73 | ["minor"] = assemblyName.Version.Minor, | ||
78 | 74 | ["version"] = assemblyName.Version.ToString(), | ||
78 | 75 | }; | ||
78 | 76 | this.AddEntityWithDefaults(pluginType); | ||
78 | 77 | } | ||
78 |
| |||
79 | // Filter | |||
78 | 80 | Entity sdkFilter = null; | ||
78 | 81 | if (primaryEntityTypeCode.HasValue) | ||
66 | 82 | { | ||
66 | 83 | sdkFilter = new Entity("sdkmessagefilter") | ||
66 | 84 | { | ||
66 | 85 | Id = Guid.NewGuid(), | ||
66 | 86 | ["primaryobjecttypecode"] = primaryEntityTypeCode | ||
66 | 87 | }; | ||
66 | 88 | this.AddEntityWithDefaults(sdkFilter); | ||
66 | 89 | } | ||
90 |
| |||
91 | // Message Step | |||
78 | 92 | var sdkMessageProcessingStep = new Entity("sdkmessageprocessingstep") | ||
78 | 93 | { | ||
78 | 94 | Id = Guid.NewGuid(), | ||
78 | 95 | ["eventhandler"] = pluginType.ToEntityReference(), | ||
78 | 96 | ["sdkmessageid"] = sdkMessage.ToEntityReference(), | ||
78 | 97 | ["sdkmessagefilterid"] = sdkFilter?.ToEntityReference(), | ||
78 | 98 | ["filteringattributes"] = filteringAttributes != null ? string.Join(",", filteringAttributes) : null, | ||
78 | 99 | ["mode"] = new OptionSetValue((int)mode), | ||
78 | 100 | ["stage"] = new OptionSetValue((int)stage), | ||
78 | 101 | ["rank"] = rank | ||
78 | 102 | }; | ||
78 | 103 | this.AddEntityWithDefaults(sdkMessageProcessingStep); | ||
78 | 104 | } | ||
105 |
| |||
106 | private void ExecutePipelineStage(string method, ProcessingStepStage stage, ProcessingStepMode mode, Entity enti | |||
198 | 107 | { | ||
198 | 108 | var plugins = GetStepsForStage(method, stage, mode, entity); | ||
109 |
| |||
198 | 110 | ExecutePipelinePlugins(plugins, entity); | ||
198 | 111 | } | ||
112 |
| |||
113 | private void ExecutePipelineStage(string method, ProcessingStepStage stage, ProcessingStepMode mode, EntityRefer | |||
54 | 114 | { | ||
54 | 115 | var entityType = FindReflectedType(entityReference.LogicalName); | ||
54 | 116 | if (entityType == null) | ||
0 | 117 | { | ||
0 | 118 | return; | ||
119 | } | |||
120 |
| |||
54 | 121 | var plugins = GetStepsForStage(method, stage, mode, (Entity)Activator.CreateInstance(entityType)); | ||
122 |
| |||
54 | 123 | ExecutePipelinePlugins(plugins, entityReference); | ||
54 | 124 | } | ||
125 |
| |||
126 | private void ExecutePipelinePlugins(IEnumerable<Entity> plugins, object target) | |||
252 | 127 | { | ||
900 | 128 | foreach (var plugin in plugins) | ||
72 | 129 | { | ||
72 | 130 | var pluginMethod = GetPluginMethod(plugin); | ||
131 |
| |||
72 | 132 | var pluginContext = this.GetDefaultPluginContext(); | ||
72 | 133 | pluginContext.Mode = plugin.GetAttributeValue<OptionSetValue>("mode").Value; | ||
72 | 134 | pluginContext.Stage = plugin.GetAttributeValue<OptionSetValue>("stage").Value; | ||
72 | 135 | pluginContext.MessageName = (string)plugin.GetAttributeValue<AliasedValue>("sdkmessage.name").Value; | ||
72 | 136 | pluginContext.InputParameters = new ParameterCollection | ||
72 | 137 | { | ||
72 | 138 | { "Target", target } | ||
72 | 139 | }; | ||
72 | 140 | pluginContext.OutputParameters = new ParameterCollection(); | ||
72 | 141 | pluginContext.PreEntityImages = new EntityImageCollection(); | ||
72 | 142 | pluginContext.PostEntityImages = new EntityImageCollection(); | ||
143 |
| |||
72 | 144 | pluginMethod.Invoke(this, new object[] { pluginContext }); | ||
72 | 145 | } | ||
252 | 146 | } | ||
147 |
| |||
148 | private static MethodInfo GetPluginMethod(Entity pluginEntity) | |||
72 | 149 | { | ||
72 | 150 | var assemblyName = (string)pluginEntity.GetAttributeValue<AliasedValue>("plugintype.assemblyname").Value; | ||
72 | 151 | var assembly = AppDomain.CurrentDomain.Load(assemblyName); | ||
152 |
| |||
72 | 153 | var pluginTypeName = (string)pluginEntity.GetAttributeValue<AliasedValue>("plugintype.typename").Value; | ||
72 | 154 | var pluginType = assembly.GetType(pluginTypeName); | ||
155 |
| |||
72 | 156 | var methodInfo = typeof(XrmFakedContext).GetMethod("ExecutePluginWith", new[] { typeof(XrmFakedPluginExecuti | ||
72 | 157 | var pluginMethod = methodInfo.MakeGenericMethod(pluginType); | ||
158 |
| |||
72 | 159 | return pluginMethod; | ||
72 | 160 | } | ||
161 |
| |||
162 | private IEnumerable<Entity> GetStepsForStage(string method, ProcessingStepStage stage, ProcessingStepMode mode, | |||
252 | 163 | { | ||
252 | 164 | var query = new QueryExpression("sdkmessageprocessingstep") | ||
252 | 165 | { | ||
252 | 166 | ColumnSet = new ColumnSet("configuration", "filteringattributes", "stage", "mode"), | ||
252 | 167 | Criteria = | ||
252 | 168 | { | ||
252 | 169 | Conditions = | ||
252 | 170 | { | ||
252 | 171 | new ConditionExpression("stage", ConditionOperator.Equal, (int)stage), | ||
252 | 172 | new ConditionExpression("mode", ConditionOperator.Equal, (int)mode) | ||
252 | 173 | } | ||
252 | 174 | }, | ||
252 | 175 | Orders = | ||
252 | 176 | { | ||
252 | 177 | new OrderExpression("rank", OrderType.Ascending) | ||
252 | 178 | }, | ||
252 | 179 | LinkEntities = | ||
252 | 180 | { | ||
252 | 181 | new LinkEntity("sdkmessageprocessingstep", "sdkmessagefilter", "sdkmessagefilterid", "sdkmessagefilt | ||
252 | 182 | { | ||
252 | 183 | EntityAlias = "sdkmessagefilter", | ||
252 | 184 | Columns = new ColumnSet("primaryobjecttypecode") | ||
252 | 185 | }, | ||
252 | 186 | new LinkEntity("sdkmessageprocessingstep", "sdkmessage", "sdkmessageid", "sdkmessageid", JoinOperato | ||
252 | 187 | { | ||
252 | 188 | EntityAlias = "sdkmessage", | ||
252 | 189 | Columns = new ColumnSet("name"), | ||
252 | 190 | LinkCriteria = | ||
252 | 191 | { | ||
252 | 192 | Conditions = | ||
252 | 193 | { | ||
252 | 194 | new ConditionExpression("name", ConditionOperator.Equal, method) | ||
252 | 195 | } | ||
252 | 196 | } | ||
252 | 197 | }, | ||
252 | 198 | new LinkEntity("sdkmessageprocessingstep", "plugintype", "eventhandler", "plugintypeid", JoinOperato | ||
252 | 199 | { | ||
252 | 200 | EntityAlias = "plugintype", | ||
252 | 201 | Columns = new ColumnSet("assemblyname", "typename") | ||
252 | 202 | } | ||
252 | 203 | } | ||
252 | 204 | }; | ||
205 |
| |||
252 | 206 | var entityTypeCode = (int?)entity.GetType().GetField("EntityTypeCode")?.GetValue(entity); | ||
207 |
| |||
252 | 208 | var plugins = this.Service.RetrieveMultiple(query).Entities.AsEnumerable(); | ||
252 | 209 | plugins = plugins.Where(p => | ||
330 | 210 | { | ||
330 | 211 | var primaryObjectTypeCode = p.GetAttributeValue<AliasedValue>("sdkmessagefilter.primaryobjecttypecode"); | ||
252 | 212 |
| ||
330 | 213 | return primaryObjectTypeCode == null || entityTypeCode.HasValue && (int)primaryObjectTypeCode.Value == e | ||
330 | 214 | }); | ||
215 |
| |||
216 | // Todo: Filter on attributes | |||
217 |
| |||
252 | 218 | return plugins; | ||
252 | 219 | } | ||
220 | } | |||
221 | } |
# | Line | Line coverage | ||
---|---|---|---|---|
1 | using FakeItEasy; | |||
2 | using Microsoft.Xrm.Sdk; | |||
3 | using System; | |||
4 | using System.Linq; | |||
5 |
| |||
6 | namespace FakeXrmEasy | |||
7 | { | |||
8 | public partial class XrmFakedContext : IXrmContext | |||
9 | { | |||
10 | /// <summary> | |||
11 | /// Returns a plugin context with default properties one can override | |||
12 | /// </summary> | |||
13 | /// <returns></returns> | |||
14 | public XrmFakedPluginExecutionContext GetDefaultPluginContext() | |||
229 | 15 | { | ||
229 | 16 | var userId = CallerId?.Id ?? Guid.NewGuid(); | ||
229 | 17 | Guid businessUnitId = BusinessUnitId?.Id ?? Guid.NewGuid(); | ||
18 |
| |||
229 | 19 | return new XrmFakedPluginExecutionContext | ||
229 | 20 | { | ||
229 | 21 | Depth = 1, | ||
229 | 22 | IsExecutingOffline = false, | ||
229 | 23 | MessageName = "Create", | ||
229 | 24 | UserId = userId, | ||
229 | 25 | BusinessUnitId = businessUnitId, | ||
229 | 26 | InitiatingUserId = userId, | ||
229 | 27 | InputParameters = new ParameterCollection(), | ||
229 | 28 | OutputParameters = new ParameterCollection(), | ||
229 | 29 | SharedVariables = new ParameterCollection(), | ||
229 | 30 | PreEntityImages = new EntityImageCollection(), | ||
229 | 31 | PostEntityImages = new EntityImageCollection(), | ||
229 | 32 | IsolationMode = 1 | ||
229 | 33 | }; | ||
229 | 34 | } | ||
35 |
| |||
36 | protected IPluginExecutionContext GetFakedPluginContext(XrmFakedPluginExecutionContext ctx) | |||
235 | 37 | { | ||
235 | 38 | var context = A.Fake<IPluginExecutionContext>(); | ||
39 |
| |||
235 | 40 | PopulateExecutionContextPropertiesFromFakedContext(context, ctx); | ||
41 |
| |||
235 | 42 | A.CallTo(() => context.ParentContext).ReturnsLazily(() => ctx.ParentContext); | ||
313 | 43 | A.CallTo(() => context.Stage).ReturnsLazily(() => ctx.Stage); | ||
44 |
| |||
235 | 45 | return context; | ||
235 | 46 | } | ||
47 |
| |||
48 | protected void PopulateExecutionContextPropertiesFromFakedContext(IExecutionContext context, XrmFakedPluginExecu | |||
235 | 49 | { | ||
235 | 50 | var newUserId = Guid.NewGuid(); | ||
51 |
| |||
241 | 52 | A.CallTo(() => context.Depth).ReturnsLazily(() => ctx.Depth <= 0 ? 1 : ctx.Depth); | ||
235 | 53 | A.CallTo(() => context.IsExecutingOffline).ReturnsLazily(() => ctx.IsExecutingOffline); | ||
476 | 54 | A.CallTo(() => context.InputParameters).ReturnsLazily(() => ctx.InputParameters); | ||
260 | 55 | A.CallTo(() => context.OutputParameters).ReturnsLazily(() => ctx.OutputParameters); | ||
247 | 56 | A.CallTo(() => context.PreEntityImages).ReturnsLazily(() => ctx.PreEntityImages); | ||
247 | 57 | A.CallTo(() => context.PostEntityImages).ReturnsLazily(() => ctx.PostEntityImages); | ||
343 | 58 | A.CallTo(() => context.MessageName).ReturnsLazily(() => ctx.MessageName); | ||
289 | 59 | A.CallTo(() => context.Mode).ReturnsLazily(() => ctx.Mode); | ||
241 | 60 | A.CallTo(() => context.OrganizationName).ReturnsLazily(() => ctx.OrganizationName); | ||
235 | 61 | A.CallTo(() => context.OrganizationId).ReturnsLazily(() => ctx.OrganizationId); | ||
235 | 62 | A.CallTo(() => context.OwningExtension).ReturnsLazily(() => ctx.OwningExtension); | ||
259 | 63 | A.CallTo(() => context.InitiatingUserId).ReturnsLazily(() => ctx.InitiatingUserId == Guid.Empty ? newUserId | ||
320 | 64 | A.CallTo(() => context.UserId).ReturnsLazily(() => ctx.UserId == Guid.Empty ? newUserId : ctx.UserId); | ||
235 | 65 | A.CallTo(() => context.PrimaryEntityId).ReturnsLazily(() => ctx.PrimaryEntityId); | ||
241 | 66 | A.CallTo(() => context.PrimaryEntityName).ReturnsLazily(() => ctx.PrimaryEntityName); | ||
235 | 67 | A.CallTo(() => context.SecondaryEntityName).ReturnsLazily(() => ctx.SecondaryEntityName); | ||
241 | 68 | A.CallTo(() => context.SharedVariables).ReturnsLazily(() => ctx.SharedVariables); | ||
235 | 69 | A.CallTo(() => context.BusinessUnitId).ReturnsLazily(() => ctx.BusinessUnitId); | ||
235 | 70 | A.CallTo(() => context.CorrelationId).ReturnsLazily(() => ctx.CorrelationId); | ||
235 | 71 | A.CallTo(() => context.OperationCreatedOn).ReturnsLazily(() => ctx.OperationCreatedOn); | ||
235 | 72 | A.CallTo(() => context.IsolationMode).ReturnsLazily(() => ctx.IsolationMode); | ||
235 | 73 | A.CallTo(() => context.IsInTransaction).ReturnsLazily(() => ctx.IsInTransaction); | ||
74 |
| |||
75 |
| |||
76 | // Create message will pass an Entity as the target but this is not always true | |||
77 | // For instance, a Delete request will receive an EntityReference | |||
235 | 78 | if (ctx.InputParameters != null && ctx.InputParameters.ContainsKey("Target")) | ||
186 | 79 | { | ||
186 | 80 | if (ctx.InputParameters["Target"] is Entity) | ||
168 | 81 | { | ||
168 | 82 | var target = (Entity)ctx.InputParameters["Target"]; | ||
168 | 83 | A.CallTo(() => context.PrimaryEntityId).ReturnsLazily(() => target.Id); | ||
198 | 84 | A.CallTo(() => context.PrimaryEntityName).ReturnsLazily(() => target.LogicalName); | ||
168 | 85 | } | ||
18 | 86 | else if (ctx.InputParameters["Target"] is EntityReference) | ||
18 | 87 | { | ||
18 | 88 | var target = (EntityReference)ctx.InputParameters["Target"]; | ||
18 | 89 | A.CallTo(() => context.PrimaryEntityId).ReturnsLazily(() => target.Id); | ||
18 | 90 | A.CallTo(() => context.PrimaryEntityName).ReturnsLazily(() => target.LogicalName); | ||
18 | 91 | } | ||
186 | 92 | } | ||
235 | 93 | } | ||
94 |
| |||
95 | protected IExecutionContext GetFakedExecutionContext(XrmFakedPluginExecutionContext ctx) | |||
0 | 96 | { | ||
0 | 97 | var context = A.Fake<IExecutionContext>(); | ||
98 |
| |||
0 | 99 | PopulateExecutionContextPropertiesFromFakedContext(context, ctx); | ||
100 |
| |||
0 | 101 | return context; | ||
0 | 102 | } | ||
103 |
| |||
104 | /// <summary> | |||
105 | /// Executes a plugin passing a custom context. This is useful whenever we need to mock more complex plugin cont | |||
106 | /// </summary> | |||
107 | /// <typeparam name="T">Must be a plugin</typeparam> | |||
108 | /// <param name="ctx"></param> | |||
109 | /// <returns></returns> | |||
110 | public IPlugin ExecutePluginWith<T>(XrmFakedPluginExecutionContext ctx = null) | |||
111 | where T : IPlugin, new() | |||
150 | 112 | { | ||
150 | 113 | if (ctx == null) | ||
24 | 114 | { | ||
24 | 115 | ctx = GetDefaultPluginContext(); | ||
24 | 116 | } | ||
117 |
| |||
150 | 118 | return this.ExecutePluginWith(ctx, new T()); | ||
150 | 119 | } | ||
120 |
| |||
121 | /// <summary> | |||
122 | /// Executes a plugin passing a custom context. This is useful whenever we need to mock more complex plugin cont | |||
123 | /// </summary> | |||
124 | /// <param name="ctx"></param> | |||
125 | /// <param name="instance"></param> | |||
126 | /// <returns></returns> | |||
127 | public IPlugin ExecutePluginWith(XrmFakedPluginExecutionContext ctx, IPlugin instance) | |||
211 | 128 | { | ||
211 | 129 | var fakedServiceProvider = GetFakedServiceProvider(ctx); | ||
130 |
| |||
211 | 131 | var fakedPlugin = A.Fake<IPlugin>(); | ||
211 | 132 | A.CallTo(() => fakedPlugin.Execute(A<IServiceProvider>._)) | ||
211 | 133 | .Invokes((IServiceProvider provider) => | ||
422 | 134 | { | ||
422 | 135 | var plugin = instance; | ||
422 | 136 | plugin.Execute(fakedServiceProvider); | ||
416 | 137 | }); | ||
138 |
| |||
211 | 139 | fakedPlugin.Execute(fakedServiceProvider); //Execute the plugin | ||
205 | 140 | return fakedPlugin; | ||
205 | 141 | } | ||
142 |
| |||
143 | public IPlugin ExecutePluginWith<T>(ParameterCollection inputParameters, ParameterCollection outputParameters, E | |||
144 | where T : IPlugin, new() | |||
24 | 145 | { | ||
24 | 146 | var ctx = GetDefaultPluginContext(); | ||
24 | 147 | ctx.InputParameters.AddRange(inputParameters); | ||
24 | 148 | ctx.OutputParameters.AddRange(outputParameters); | ||
24 | 149 | ctx.PreEntityImages.AddRange(preEntityImages); | ||
24 | 150 | ctx.PostEntityImages.AddRange(postEntityImages); | ||
151 |
| |||
24 | 152 | var fakedServiceProvider = GetFakedServiceProvider(ctx); | ||
153 |
| |||
24 | 154 | var fakedPlugin = A.Fake<IPlugin>(); | ||
24 | 155 | A.CallTo(() => fakedPlugin.Execute(A<IServiceProvider>._)) | ||
24 | 156 | .Invokes((IServiceProvider provider) => | ||
48 | 157 | { | ||
48 | 158 | var plugin = new T(); | ||
48 | 159 | plugin.Execute(fakedServiceProvider); | ||
48 | 160 | }); | ||
161 |
| |||
24 | 162 | fakedPlugin.Execute(fakedServiceProvider); //Execute the plugin | ||
24 | 163 | return fakedPlugin; | ||
24 | 164 | } | ||
165 |
| |||
166 | public IPlugin ExecutePluginWithConfigurations<T>(XrmFakedPluginExecutionContext plugCtx, string unsecureConfigu | |||
167 | where T : class, IPlugin | |||
31 | 168 | { | ||
31 | 169 | var pluginType = typeof(T); | ||
31 | 170 | var constructors = pluginType.GetConstructors().ToList(); | ||
171 |
| |||
106 | 172 | if (!constructors.Any(c => c.GetParameters().Length == 2 && c.GetParameters().All(param => param.ParameterTy | ||
12 | 173 | { | ||
12 | 174 | throw new ArgumentException("The plugin you are trying to execute does not specify a constructor for pas | ||
175 | } | |||
176 |
| |||
19 | 177 | var pluginInstance = (T)Activator.CreateInstance(typeof(T), unsecureConfiguration, secureConfiguration); | ||
178 |
| |||
19 | 179 | return this.ExecutePluginWith(plugCtx, pluginInstance); | ||
19 | 180 | } | ||
181 |
| |||
182 | [Obsolete("Use ExecutePluginWith(XrmFakedPluginExecutionContext ctx, IPlugin instance).")] | |||
183 | public IPlugin ExecutePluginWithConfigurations<T>(XrmFakedPluginExecutionContext plugCtx, T instance, string uns | |||
184 | where T : class, IPlugin | |||
0 | 185 | { | ||
0 | 186 | var fakedServiceProvider = GetFakedServiceProvider(plugCtx); | ||
187 |
| |||
0 | 188 | var fakedPlugin = A.Fake<IPlugin>(); | ||
189 |
| |||
0 | 190 | A.CallTo(() => fakedPlugin.Execute(A<IServiceProvider>._)) | ||
0 | 191 | .Invokes((IServiceProvider provider) => | ||
0 | 192 | { | ||
0 | 193 | var pluginType = typeof(T); | ||
0 | 194 | var constructors = pluginType.GetConstructors(); | ||
0 | 195 |
| ||
0 | 196 | if (!constructors.Any(c => c.GetParameters().Length == 2 && c.GetParameters().All(param => param.Par | ||
0 | 197 | { | ||
0 | 198 | throw new ArgumentException("The plugin you are trying to execute does not specify a constructor | ||
0 | 199 | } | ||
0 | 200 |
| ||
0 | 201 | var plugin = instance; | ||
0 | 202 | plugin.Execute(fakedServiceProvider); | ||
0 | 203 | }); | ||
204 |
| |||
0 | 205 | fakedPlugin.Execute(fakedServiceProvider); //Execute the plugin | ||
0 | 206 | return fakedPlugin; | ||
0 | 207 | } | ||
208 |
| |||
209 | public IPlugin ExecutePluginWithTarget<T>(XrmFakedPluginExecutionContext ctx, Entity target, string messageName | |||
210 | where T : IPlugin, new() | |||
0 | 211 | { | ||
0 | 212 | ctx.InputParameters.Add("Target", target); | ||
0 | 213 | ctx.MessageName = messageName; | ||
0 | 214 | ctx.Stage = stage; | ||
215 |
| |||
0 | 216 | return this.ExecutePluginWith<T>(ctx); | ||
0 | 217 | } | ||
218 |
| |||
219 | /// <summary> | |||
220 | /// Executes the plugin of type T against the faked context for an entity target | |||
221 | /// and returns the faked plugin | |||
222 | /// </summary> | |||
223 | /// <typeparam name="T"></typeparam> | |||
224 | /// <param name="target">The entity to execute the plug-in for.</param> | |||
225 | /// <param name="messageName">Sets the message name.</param> | |||
226 | /// <param name="stage">Sets the stage.</param> | |||
227 | /// <returns></returns> | |||
228 | public IPlugin ExecutePluginWithTarget<T>(Entity target, string messageName = "Create", int stage = 40) | |||
229 | where T : IPlugin, new() | |||
36 | 230 | { | ||
36 | 231 | return this.ExecutePluginWithTarget(new T(), target, messageName, stage); | ||
30 | 232 | } | ||
233 |
| |||
234 | /// <summary> | |||
235 | /// Executes the plugin of type T against the faked context for an entity target | |||
236 | /// and returns the faked plugin | |||
237 | /// </summary> | |||
238 | /// <param name="instance"></param> | |||
239 | /// <param name="target">The entity to execute the plug-in for.</param> | |||
240 | /// <param name="messageName">Sets the message name.</param> | |||
241 | /// <param name="stage">Sets the stage.</param> | |||
242 | /// <returns></returns> | |||
243 | public IPlugin ExecutePluginWithTarget(IPlugin instance, Entity target, string messageName = "Create", int stage | |||
36 | 244 | { | ||
36 | 245 | var ctx = GetDefaultPluginContext(); | ||
246 |
| |||
247 | // Add the target entity to the InputParameters | |||
36 | 248 | ctx.InputParameters.Add("Target", target); | ||
36 | 249 | ctx.MessageName = messageName; | ||
36 | 250 | ctx.Stage = stage; | ||
251 |
| |||
36 | 252 | return this.ExecutePluginWith(ctx, instance); | ||
30 | 253 | } | ||
254 |
| |||
255 | /// <summary> | |||
256 | /// Executes the plugin of type T against the faked context for an entity reference target | |||
257 | /// and returns the faked plugin | |||
258 | /// </summary> | |||
259 | /// <typeparam name="T"></typeparam> | |||
260 | /// <param name="target">The entity reference to execute the plug-in for.</param> | |||
261 | /// <param name="messageName">Sets the message name.</param> | |||
262 | /// <param name="stage">Sets the stage.</param> | |||
263 | /// <returns></returns> | |||
264 | public IPlugin ExecutePluginWithTargetReference<T>(EntityReference target, string messageName = "Delete", int st | |||
265 | where T : IPlugin, new() | |||
0 | 266 | { | ||
0 | 267 | return this.ExecutePluginWithTargetReference(new T(), target, messageName, stage); | ||
0 | 268 | } | ||
269 |
| |||
270 | /// <summary> | |||
271 | /// Executes the plugin of type T against the faked context for an entity reference target | |||
272 | /// and returns the faked plugin | |||
273 | /// </summary> | |||
274 | /// <param name="instance"></param> | |||
275 | /// <param name="target">The entity reference to execute the plug-in for.</param> | |||
276 | /// <param name="messageName">Sets the message name.</param> | |||
277 | /// <param name="stage">Sets the stage.</param> | |||
278 | /// <returns></returns> | |||
279 | public IPlugin ExecutePluginWithTargetReference(IPlugin instance, EntityReference target, string messageName = " | |||
0 | 280 | { | ||
0 | 281 | var ctx = GetDefaultPluginContext(); | ||
282 | // Add the target entity to the InputParameters | |||
0 | 283 | ctx.InputParameters.Add("Target", target); | ||
0 | 284 | ctx.MessageName = messageName; | ||
0 | 285 | ctx.Stage = stage; | ||
286 |
| |||
0 | 287 | return this.ExecutePluginWith(ctx, instance); | ||
0 | 288 | } | ||
289 |
| |||
290 | /// <summary> | |||
291 | /// Returns a faked plugin with a target and the specified pre entity images | |||
292 | /// </summary> | |||
293 | /// <typeparam name="T"></typeparam> | |||
294 | /// <returns></returns> | |||
295 | [Obsolete] | |||
296 | public IPlugin ExecutePluginWithTargetAndPreEntityImages<T>(object target, EntityImageCollection preEntityImages | |||
297 | where T : IPlugin, new() | |||
6 | 298 | { | ||
6 | 299 | var ctx = GetDefaultPluginContext(); | ||
300 | // Add the target entity to the InputParameters | |||
6 | 301 | ctx.InputParameters.Add("Target", target); | ||
6 | 302 | ctx.PreEntityImages.AddRange(preEntityImages); | ||
6 | 303 | ctx.MessageName = messageName; | ||
6 | 304 | ctx.Stage = stage; | ||
305 |
| |||
6 | 306 | return this.ExecutePluginWith<T>(ctx); | ||
6 | 307 | } | ||
308 |
| |||
309 | /// <summary> | |||
310 | /// Returns a faked plugin with a target and the specified post entity images | |||
311 | /// </summary> | |||
312 | /// <typeparam name="T"></typeparam> | |||
313 | /// <returns></returns> | |||
314 | [Obsolete] | |||
315 | public IPlugin ExecutePluginWithTargetAndPostEntityImages<T>(object target, EntityImageCollection postEntityImag | |||
316 | where T : IPlugin, new() | |||
6 | 317 | { | ||
6 | 318 | var ctx = GetDefaultPluginContext(); | ||
319 | // Add the target entity to the InputParameters | |||
6 | 320 | ctx.InputParameters.Add("Target", target); | ||
6 | 321 | ctx.PostEntityImages.AddRange(postEntityImages); | ||
6 | 322 | ctx.MessageName = messageName; | ||
6 | 323 | ctx.Stage = stage; | ||
324 |
| |||
6 | 325 | return this.ExecutePluginWith<T>(ctx); | ||
6 | 326 | } | ||
327 |
| |||
328 | [Obsolete] | |||
329 | public IPlugin ExecutePluginWithTargetAndInputParameters<T>(Entity target, ParameterCollection inputParameters, | |||
330 | where T : IPlugin, new() | |||
0 | 331 | { | ||
0 | 332 | var ctx = GetDefaultPluginContext(); | ||
333 |
| |||
0 | 334 | ctx.InputParameters.AddRange(inputParameters); | ||
335 |
| |||
0 | 336 | return this.ExecutePluginWithTarget<T>(ctx, target, messageName, stage); | ||
0 | 337 | } | ||
338 |
| |||
339 | protected IServiceProvider GetFakedServiceProvider(XrmFakedPluginExecutionContext plugCtx) | |||
235 | 340 | { | ||
235 | 341 | var fakedServiceProvider = A.Fake<IServiceProvider>(); | ||
342 |
| |||
235 | 343 | A.CallTo(() => fakedServiceProvider.GetService(A<Type>._)) | ||
235 | 344 | .ReturnsLazily((Type t) => | ||
707 | 345 | { | ||
707 | 346 | if (t == typeof(IOrganizationService)) | ||
247 | 347 | { | ||
235 | 348 | //Return faked or real organization service | ||
247 | 349 | return GetOrganizationService(); | ||
235 | 350 | } | ||
235 | 351 |
| ||
695 | 352 | if (t == typeof(ITracingService)) | ||
386 | 353 | { | ||
386 | 354 | return TracingService; | ||
235 | 355 | } | ||
235 | 356 |
| ||
544 | 357 | if (t == typeof(IPluginExecutionContext)) | ||
470 | 358 | { | ||
470 | 359 | return GetFakedPluginContext(plugCtx); | ||
235 | 360 | } | ||
235 | 361 |
| ||
309 | 362 | if (t == typeof(IExecutionContext)) | ||
235 | 363 | { | ||
235 | 364 | return GetFakedExecutionContext(plugCtx); | ||
235 | 365 | } | ||
235 | 366 |
| ||
309 | 367 | if (t == typeof(IOrganizationServiceFactory)) | ||
302 | 368 | { | ||
302 | 369 | var fakedServiceFactory = A.Fake<IOrganizationServiceFactory>(); | ||
369 | 370 | A.CallTo(() => fakedServiceFactory.CreateOrganizationService(A<Guid?>._)).ReturnsLazily((Guid? g) | ||
302 | 371 | return fakedServiceFactory; | ||
235 | 372 | } | ||
235 | 373 |
| ||
242 | 374 | if (t == typeof(IServiceEndpointNotificationService)) | ||
241 | 375 | { | ||
241 | 376 | return GetFakedServiceEndpointNotificationService(); | ||
235 | 377 | } | ||
235 | 378 | #if FAKE_XRM_EASY_9 | ||
236 | 379 | if (t == typeof(IEntityDataSourceRetrieverService)) | ||
236 | 380 | { | ||
236 | 381 | return GetFakedEntityDataSourceRetrieverService(); | ||
235 | 382 | } | ||
235 | 383 | #endif | ||
235 | 384 | throw new PullRequestException("The specified service type is not supported"); | ||
707 | 385 | }); | ||
386 |
| |||
235 | 387 | return fakedServiceProvider; | ||
235 | 388 | } | ||
389 |
| |||
390 | #if FAKE_XRM_EASY_9 | |||
2 | 391 | public Entity EntityDataSourceRetriever { get; set; } | ||
392 | #endif | |||
393 |
| |||
394 | public XrmFakedTracingService GetFakeTracingService() | |||
84 | 395 | { | ||
84 | 396 | return TracingService; | ||
84 | 397 | } | ||
398 | } | |||
399 | } |
# | Line | Line coverage | ||
---|---|---|---|---|
1 | using System; | |||
2 | using System.Collections; | |||
3 | using System.Collections.Generic; | |||
4 | using System.Globalization; | |||
5 | using System.Linq; | |||
6 | using System.Linq.Expressions; | |||
7 | using System.Reflection; | |||
8 | using System.ServiceModel; | |||
9 | using System.Text.RegularExpressions; | |||
10 | using System.Xml.Linq; | |||
11 | using FakeXrmEasy.Extensions; | |||
12 | using FakeXrmEasy.Extensions.FetchXml; | |||
13 | using FakeXrmEasy.Models; | |||
14 | using Microsoft.Xrm.Sdk; | |||
15 | using Microsoft.Xrm.Sdk.Client; | |||
16 | using Microsoft.Xrm.Sdk.Query; | |||
17 |
| |||
18 | namespace FakeXrmEasy | |||
19 | { | |||
20 | public partial class XrmFakedContext : IXrmContext | |||
21 | { | |||
22 | protected internal Type FindReflectedType(string logicalName) | |||
29626 | 23 | { | ||
29626 | 24 | var types = | ||
84498 | 25 | ProxyTypesAssemblies.Select(a => FindReflectedType(logicalName, a)) | ||
84498 | 26 | .Where(t => t != null); | ||
27 |
| |||
29626 | 28 | if (types.Count() > 1) | ||
0 | 29 | { | ||
0 | 30 | var errorMsg = $"Type { logicalName } is defined in multiple assemblies: "; | ||
0 | 31 | foreach (var type in types) | ||
0 | 32 | { | ||
0 | 33 | errorMsg += type.Assembly | ||
0 | 34 | .GetName() | ||
0 | 35 | .Name + "; "; | ||
0 | 36 | } | ||
0 | 37 | var lastIndex = errorMsg.LastIndexOf("; "); | ||
0 | 38 | errorMsg = errorMsg.Substring(0, lastIndex) + "."; | ||
0 | 39 | throw new InvalidOperationException(errorMsg); | ||
40 | } | |||
41 |
| |||
29626 | 42 | return types.SingleOrDefault(); | ||
29626 | 43 | } | ||
44 |
| |||
45 | /// <summary> | |||
46 | /// Finds reflected type for given entity from given assembly. | |||
47 | /// </summary> | |||
48 | /// <param name="logicalName"> | |||
49 | /// Entity logical name which type is searched from given | |||
50 | /// <paramref name="assembly"/>. | |||
51 | /// </param> | |||
52 | /// <param name="assembly"> | |||
53 | /// Assembly where early-bound type is searched for given | |||
54 | /// <paramref name="logicalName"/>. | |||
55 | /// </param> | |||
56 | /// <returns> | |||
57 | /// Early-bound type of <paramref name="logicalName"/> if it's found | |||
58 | /// from <paramref name="assembly"/>. Otherwise null is returned. | |||
59 | /// </returns> | |||
60 | private static Type FindReflectedType(string logicalName, | |||
61 | Assembly assembly) | |||
54872 | 62 | { | ||
63 | try | |||
54872 | 64 | { | ||
54872 | 65 | if (assembly == null) | ||
0 | 66 | { | ||
0 | 67 | throw new ArgumentNullException(nameof(assembly)); | ||
68 | } | |||
69 |
| |||
70 | /* This wasn't building within the CI FAKE build script... | |||
71 | var subClassType = assembly.GetTypes() | |||
72 | .Where(t => typeof(Entity).IsAssignableFrom(t)) | |||
73 | .Where(t => t.GetCustomAttributes<EntityLogicalNameAttribute>(true).Any()) | |||
74 | .FirstOrDefault(t => t.GetCustomAttributes<EntityLogicalNameAttribute>(true).First().LogicalName | |||
75 |
| |||
76 | */ | |||
54872 | 77 | var subClassType = assembly.GetTypes() | ||
7149198 | 78 | .Where(t => typeof(Entity).IsAssignableFrom(t)) | ||
4329622 | 79 | .Where(t => t.GetCustomAttributes(typeof(EntityLogicalNameAttribute), true).Length > 0) | ||
4329622 | 80 | .Where(t => ((EntityLogicalNameAttribute)t.GetCustomAttributes(typeof(EntityLogicalNameAttribute | ||
54872 | 81 | .FirstOrDefault(); | ||
82 |
| |||
54872 | 83 | return subClassType; | ||
84 | } | |||
0 | 85 | catch (ReflectionTypeLoadException exception) | ||
0 | 86 | { | ||
87 | // now look at ex.LoaderExceptions - this is an Exception[], so: | |||
0 | 88 | var s = ""; | ||
0 | 89 | foreach (var innerException in exception.LoaderExceptions) | ||
0 | 90 | { | ||
91 | // write details of "inner", in particular inner.Message | |||
0 | 92 | s += innerException.Message + " "; | ||
0 | 93 | } | ||
94 |
| |||
0 | 95 | throw new Exception("XrmFakedContext.FindReflectedType: " + s); | ||
96 | } | |||
54872 | 97 | } | ||
98 |
| |||
99 | protected internal Type FindAttributeTypeInInjectedMetadata(string sEntityName, string sAttributeName) | |||
14 | 100 | { | ||
14 | 101 | if (!EntityMetadata.ContainsKey(sEntityName)) | ||
6 | 102 | return null; | ||
103 |
| |||
8 | 104 | if (EntityMetadata[sEntityName].Attributes == null) | ||
0 | 105 | return null; | ||
106 |
| |||
8 | 107 | var attribute = EntityMetadata[sEntityName].Attributes | ||
16 | 108 | .Where(a => a.LogicalName == sAttributeName) | ||
8 | 109 | .FirstOrDefault(); | ||
110 |
| |||
8 | 111 | if (attribute == null) | ||
0 | 112 | return null; | ||
113 |
| |||
8 | 114 | if (attribute.AttributeType == null) | ||
0 | 115 | return null; | ||
116 |
| |||
8 | 117 | switch (attribute.AttributeType.Value) | ||
118 | { | |||
119 | case Microsoft.Xrm.Sdk.Metadata.AttributeTypeCode.BigInt: | |||
0 | 120 | return typeof(long); | ||
121 |
| |||
122 | case Microsoft.Xrm.Sdk.Metadata.AttributeTypeCode.Integer: | |||
0 | 123 | return typeof(int); | ||
124 |
| |||
125 | case Microsoft.Xrm.Sdk.Metadata.AttributeTypeCode.Boolean: | |||
0 | 126 | return typeof(bool); | ||
127 |
| |||
128 | case Microsoft.Xrm.Sdk.Metadata.AttributeTypeCode.CalendarRules: | |||
0 | 129 | throw new Exception("CalendarRules: Type not yet supported"); | ||
130 |
| |||
131 | case Microsoft.Xrm.Sdk.Metadata.AttributeTypeCode.Lookup: | |||
132 | case Microsoft.Xrm.Sdk.Metadata.AttributeTypeCode.Customer: | |||
133 | case Microsoft.Xrm.Sdk.Metadata.AttributeTypeCode.Owner: | |||
0 | 134 | return typeof(EntityReference); | ||
135 |
| |||
136 | case Microsoft.Xrm.Sdk.Metadata.AttributeTypeCode.DateTime: | |||
0 | 137 | return typeof(DateTime); | ||
138 |
| |||
139 | case Microsoft.Xrm.Sdk.Metadata.AttributeTypeCode.Decimal: | |||
0 | 140 | return typeof(decimal); | ||
141 |
| |||
142 | case Microsoft.Xrm.Sdk.Metadata.AttributeTypeCode.Double: | |||
0 | 143 | return typeof(double); | ||
144 |
| |||
145 | case Microsoft.Xrm.Sdk.Metadata.AttributeTypeCode.EntityName: | |||
146 | case Microsoft.Xrm.Sdk.Metadata.AttributeTypeCode.Memo: | |||
147 | case Microsoft.Xrm.Sdk.Metadata.AttributeTypeCode.String: | |||
6 | 148 | return typeof(string); | ||
149 |
| |||
150 | case Microsoft.Xrm.Sdk.Metadata.AttributeTypeCode.Money: | |||
0 | 151 | return typeof(Money); | ||
152 |
| |||
153 | case Microsoft.Xrm.Sdk.Metadata.AttributeTypeCode.PartyList: | |||
0 | 154 | return typeof(EntityReferenceCollection); | ||
155 |
| |||
156 | case Microsoft.Xrm.Sdk.Metadata.AttributeTypeCode.Picklist: | |||
157 | case Microsoft.Xrm.Sdk.Metadata.AttributeTypeCode.State: | |||
158 | case Microsoft.Xrm.Sdk.Metadata.AttributeTypeCode.Status: | |||
1 | 159 | return typeof(OptionSetValue); | ||
160 |
| |||
161 | case Microsoft.Xrm.Sdk.Metadata.AttributeTypeCode.Uniqueidentifier: | |||
0 | 162 | return typeof(Guid); | ||
163 |
| |||
164 | case Microsoft.Xrm.Sdk.Metadata.AttributeTypeCode.Virtual: | |||
165 | #if FAKE_XRM_EASY_9 | |||
1 | 166 | if (attribute.AttributeTypeName.Value == "MultiSelectPicklistType") | ||
1 | 167 | { | ||
1 | 168 | return typeof(OptionSetValueCollection); | ||
169 | } | |||
170 | #endif | |||
0 | 171 | throw new Exception("Virtual: Type not yet supported"); | ||
172 |
| |||
173 | default: | |||
0 | 174 | return typeof(string); | ||
175 |
| |||
176 | } | |||
177 |
| |||
14 | 178 | } | ||
179 | protected internal Type FindReflectedAttributeType(Type earlyBoundType, string sEntityName, string attributeName | |||
3198 | 180 | { | ||
181 | //Get that type properties | |||
3198 | 182 | var attributeInfo = GetEarlyBoundTypeAttribute(earlyBoundType, attributeName); | ||
3198 | 183 | if (attributeInfo == null && attributeName.EndsWith("name")) | ||
6 | 184 | { | ||
185 | // Special case for referencing the name of a EntityReference | |||
6 | 186 | attributeName = attributeName.Substring(0, attributeName.Length - 4); | ||
6 | 187 | attributeInfo = GetEarlyBoundTypeAttribute(earlyBoundType, attributeName); | ||
188 |
| |||
6 | 189 | if (attributeInfo.PropertyType != typeof(EntityReference)) | ||
0 | 190 | { | ||
191 | // Don't mess up if other attributes follow this naming pattern | |||
0 | 192 | attributeInfo = null; | ||
0 | 193 | } | ||
6 | 194 | } | ||
195 |
| |||
3198 | 196 | if (attributeInfo == null || attributeInfo.PropertyType.FullName == null) | ||
14 | 197 | { | ||
198 | //Try with metadata | |||
14 | 199 | var injectedType = FindAttributeTypeInInjectedMetadata(sEntityName, attributeName); | ||
200 |
| |||
14 | 201 | if (injectedType == null) | ||
6 | 202 | { | ||
6 | 203 | throw new Exception($"XrmFakedContext.FindReflectedAttributeType: Attribute {attributeName} not foun | ||
204 | } | |||
205 |
| |||
8 | 206 | return injectedType; | ||
207 | } | |||
208 |
| |||
3184 | 209 | if (attributeInfo.PropertyType.FullName.EndsWith("Enum") || attributeInfo.PropertyType.BaseType.FullName.End | ||
0 | 210 | { | ||
0 | 211 | return typeof(int); | ||
212 | } | |||
213 |
| |||
3184 | 214 | if (!attributeInfo.PropertyType.FullName.StartsWith("System.")) | ||
879 | 215 | { | ||
216 | try | |||
879 | 217 | { | ||
879 | 218 | var instance = Activator.CreateInstance(attributeInfo.PropertyType); | ||
879 | 219 | if (instance is Entity) | ||
0 | 220 | { | ||
0 | 221 | return typeof(EntityReference); | ||
222 | } | |||
879 | 223 | } | ||
0 | 224 | catch | ||
0 | 225 | { | ||
226 | // ignored | |||
0 | 227 | } | ||
879 | 228 | } | ||
229 | #if FAKE_XRM_EASY_2015 || FAKE_XRM_EASY_2016 || FAKE_XRM_EASY_365 || FAKE_XRM_EASY_9 | |||
1568 | 230 | else if (attributeInfo.PropertyType.FullName.StartsWith("System.Nullable")) | ||
936 | 231 | { | ||
936 | 232 | return attributeInfo.PropertyType.GenericTypeArguments[0]; | ||
233 | } | |||
234 | #endif | |||
235 |
| |||
2248 | 236 | return attributeInfo.PropertyType; | ||
3192 | 237 | } | ||
238 |
| |||
239 | private static PropertyInfo GetEarlyBoundTypeAttribute(Type earlyBoundType, string attributeName) | |||
3589 | 240 | { | ||
3589 | 241 | var attributeInfo = earlyBoundType.GetProperties() | ||
231512 | 242 | .Where(pi => pi.GetCustomAttributes(typeof(AttributeLogicalNameAttribute), true).Length > 0) | ||
224655 | 243 | .Where(pi => (pi.GetCustomAttributes(typeof(AttributeLogicalNameAttribute), true)[0] as AttributeLogical | ||
3589 | 244 | .FirstOrDefault(); | ||
245 |
| |||
3589 | 246 | return attributeInfo; | ||
3589 | 247 | } | ||
248 |
| |||
249 | public IQueryable<Entity> CreateQuery(string entityLogicalName) | |||
1540 | 250 | { | ||
1540 | 251 | return this.CreateQuery<Entity>(entityLogicalName); | ||
1534 | 252 | } | ||
253 |
| |||
254 | public IQueryable<T> CreateQuery<T>() | |||
255 | where T : Entity | |||
261 | 256 | { | ||
261 | 257 | var typeParameter = typeof(T); | ||
258 |
| |||
261 | 259 | if (ProxyTypesAssembly == null) | ||
6 | 260 | { | ||
261 | //Try to guess proxy types assembly | |||
6 | 262 | var assembly = Assembly.GetAssembly(typeof(T)); | ||
6 | 263 | if (assembly != null) | ||
6 | 264 | { | ||
6 | 265 | ProxyTypesAssembly = assembly; | ||
6 | 266 | } | ||
6 | 267 | } | ||
268 |
| |||
261 | 269 | var logicalName = ""; | ||
270 |
| |||
261 | 271 | if (typeParameter.GetCustomAttributes(typeof(EntityLogicalNameAttribute), true).Length > 0) | ||
261 | 272 | { | ||
261 | 273 | logicalName = (typeParameter.GetCustomAttributes(typeof(EntityLogicalNameAttribute), true)[0] as EntityL | ||
261 | 274 | } | ||
275 |
| |||
261 | 276 | return this.CreateQuery<T>(logicalName); | ||
261 | 277 | } | ||
278 |
| |||
279 | protected IQueryable<T> CreateQuery<T>(string entityLogicalName) | |||
280 | where T : Entity | |||
5953 | 281 | { | ||
5953 | 282 | var subClassType = FindReflectedType(entityLogicalName); | ||
5953 | 283 | if (subClassType == null && !(typeof(T) == typeof(Entity)) || (typeof(T) == typeof(Entity) && string.IsNullO | ||
6 | 284 | { | ||
6 | 285 | throw new Exception($"The type {entityLogicalName} was not found"); | ||
286 | } | |||
287 |
| |||
5947 | 288 | var lst = new List<T>(); | ||
5947 | 289 | if (!Data.ContainsKey(entityLogicalName)) | ||
458 | 290 | { | ||
458 | 291 | return lst.AsQueryable(); //Empty list | ||
292 | } | |||
293 |
| |||
145251 | 294 | foreach (var e in Data[entityLogicalName].Values) | ||
64392 | 295 | { | ||
64392 | 296 | if (subClassType != null) | ||
23553 | 297 | { | ||
23553 | 298 | var cloned = e.Clone(subClassType); | ||
23553 | 299 | lst.Add((T)cloned); | ||
23553 | 300 | } | ||
301 | else | |||
40839 | 302 | lst.Add((T)e.Clone()); | ||
64392 | 303 | } | ||
304 |
| |||
5489 | 305 | return lst.AsQueryable(); | ||
5947 | 306 | } | ||
307 |
| |||
308 | public IQueryable<Entity> CreateQueryFromEntityName(string entityName) | |||
0 | 309 | { | ||
0 | 310 | return Data[entityName].Values.AsQueryable(); | ||
0 | 311 | } | ||
312 |
| |||
313 | public static IQueryable<Entity> TranslateLinkedEntityToLinq(XrmFakedContext context, LinkEntity le, IQueryable< | |||
1593 | 314 | { | ||
1593 | 315 | if (!string.IsNullOrEmpty(le.EntityAlias)) | ||
1593 | 316 | { | ||
1593 | 317 | if (!Regex.IsMatch(le.EntityAlias, "^[A-Za-z_](\\w|\\.)*$", RegexOptions.ECMAScript)) | ||
0 | 318 | { | ||
0 | 319 | FakeOrganizationServiceFault.Throw(ErrorCodes.QueryBuilderInvalid_Alias, $"Invalid character specified f | ||
0 | 320 | } | ||
1593 | 321 | } | ||
322 |
| |||
1593 | 323 | var leAlias = string.IsNullOrWhiteSpace(le.EntityAlias) ? le.LinkToEntityName : le.EntityAlias; | ||
1593 | 324 | context.EnsureEntityNameExistsInMetadata(le.LinkFromEntityName != linkFromAlias ? le.LinkFromEntityName : li | ||
1593 | 325 | context.EnsureEntityNameExistsInMetadata(le.LinkToEntityName); | ||
326 |
| |||
1593 | 327 | if (!context.AttributeExistsInMetadata(le.LinkToEntityName, le.LinkToAttributeName)) | ||
12 | 328 | { | ||
12 | 329 | FakeOrganizationServiceFault.Throw(ErrorCodes.QueryBuilderNoAttribute, string.Format("The attribute {0} | ||
0 | 330 | } | ||
331 |
| |||
1581 | 332 | IQueryable<Entity> inner = null; | ||
1581 | 333 | if (le.JoinOperator == JoinOperator.LeftOuter) | ||
362 | 334 | { | ||
335 | //inner = context.CreateQuery<Entity>(le.LinkToEntityName); | |||
336 |
| |||
337 |
| |||
338 | //filters are applied in the inner query and then ignored during filter evaluation | |||
362 | 339 | var outerQueryExpression = new QueryExpression() | ||
362 | 340 | { | ||
362 | 341 | EntityName = le.LinkToEntityName, | ||
362 | 342 | Criteria = le.LinkCriteria, | ||
362 | 343 | ColumnSet = new ColumnSet(true) | ||
362 | 344 | }; | ||
345 |
| |||
362 | 346 | var outerQuery = TranslateQueryExpressionToLinq(context, outerQueryExpression); | ||
362 | 347 | inner = outerQuery; | ||
348 |
| |||
362 | 349 | } | ||
350 | else | |||
1219 | 351 | { | ||
352 | //Filters are applied after joins | |||
1219 | 353 | inner = context.CreateQuery<Entity>(le.LinkToEntityName); | ||
1219 | 354 | } | ||
355 |
| |||
356 | //if (!le.Columns.AllColumns && le.Columns.Columns.Count == 0) | |||
357 | //{ | |||
358 | // le.Columns.AllColumns = true; //Add all columns in the joined entity, otherwise we can't filter by r | |||
359 | //} | |||
360 |
| |||
1581 | 361 | if (string.IsNullOrWhiteSpace(linkFromAlias)) | ||
1467 | 362 | { | ||
1467 | 363 | linkFromAlias = le.LinkFromAttributeName; | ||
1467 | 364 | } | ||
365 | else | |||
114 | 366 | { | ||
114 | 367 | linkFromAlias += "." + le.LinkFromAttributeName; | ||
114 | 368 | } | ||
369 |
| |||
1581 | 370 | switch (le.JoinOperator) | ||
371 | { | |||
372 | case JoinOperator.Inner: | |||
373 | case JoinOperator.Natural: | |||
1219 | 374 | query = query.Join(inner, | ||
1219 | 375 | outerKey => outerKey.KeySelector(linkFromAlias, context), | ||
1219 | 376 | innerKey => innerKey.KeySelector(le.LinkToAttributeName, context), | ||
1219 | 377 | (outerEl, innerEl) => outerEl.Clone(outerEl.GetType(), context).JoinAttributes(inner | ||
378 |
| |||
1219 | 379 | break; | ||
380 | case JoinOperator.LeftOuter: | |||
362 | 381 | query = query.GroupJoin(inner, | ||
362 | 382 | outerKey => outerKey.KeySelector(linkFromAlias, context), | ||
362 | 383 | innerKey => innerKey.KeySelector(le.LinkToAttributeName, context), | ||
362 | 384 | (outerEl, innerElemsCol) => new { outerEl, innerElemsCol }) | ||
362 | 385 | .SelectMany(x => x.innerElemsCol.DefaultIfEmpty() | ||
362 | 386 | , (x, y) => x.outerEl | ||
362 | 387 | .JoinAttributes(y, new ColumnSet(true), leAl | ||
388 |
| |||
389 |
| |||
362 | 390 | break; | ||
391 | default: //This shouldn't be reached as there are only 3 types of Join... | |||
0 | 392 | throw new PullRequestException(string.Format("The join operator {0} is currently not supported. Feel | ||
393 |
| |||
394 | } | |||
395 |
| |||
396 | // Process nested linked entities recursively | |||
4971 | 397 | foreach (var nestedLinkedEntity in le.LinkEntities) | ||
114 | 398 | { | ||
114 | 399 | if (string.IsNullOrWhiteSpace(le.EntityAlias)) | ||
0 | 400 | { | ||
0 | 401 | le.EntityAlias = le.LinkToEntityName; | ||
0 | 402 | } | ||
403 |
| |||
114 | 404 | if (string.IsNullOrWhiteSpace(nestedLinkedEntity.EntityAlias)) | ||
54 | 405 | { | ||
54 | 406 | nestedLinkedEntity.EntityAlias = EnsureUniqueLinkedEntityAlias(linkedEntities, nestedLinkedEntity.Li | ||
54 | 407 | } | ||
408 |
| |||
114 | 409 | query = TranslateLinkedEntityToLinq(context, nestedLinkedEntity, query, le.Columns, linkedEntities, le.E | ||
114 | 410 | } | ||
411 |
| |||
1581 | 412 | return query; | ||
1581 | 413 | } | ||
414 |
| |||
415 | private static string EnsureUniqueLinkedEntityAlias(IDictionary<string, int> linkedEntities, string entityName) | |||
368 | 416 | { | ||
368 | 417 | if (linkedEntities.ContainsKey(entityName)) | ||
30 | 418 | { | ||
30 | 419 | linkedEntities[entityName]++; | ||
30 | 420 | } | ||
421 | else | |||
338 | 422 | { | ||
338 | 423 | linkedEntities[entityName] = 1; | ||
338 | 424 | } | ||
425 |
| |||
368 | 426 | return $"{entityName}{linkedEntities[entityName]}"; | ||
368 | 427 | } | ||
428 |
| |||
429 |
| |||
430 | protected static XElement RetrieveFetchXmlNode(XDocument xlDoc, string sName) | |||
1556 | 431 | { | ||
4926 | 432 | return xlDoc.Descendants().Where(e => e.Name.LocalName.Equals(sName)).FirstOrDefault(); | ||
1556 | 433 | } | ||
434 |
| |||
435 | public static XDocument ParseFetchXml(string fetchXml) | |||
1316 | 436 | { | ||
437 | try | |||
1316 | 438 | { | ||
1316 | 439 | return XDocument.Parse(fetchXml); | ||
440 | } | |||
6 | 441 | catch (Exception ex) | ||
6 | 442 | { | ||
6 | 443 | throw new Exception(string.Format("FetchXml must be a valid XML document: {0}", ex.ToString())); | ||
444 | } | |||
1310 | 445 | } | ||
446 |
| |||
447 | public static QueryExpression TranslateFetchXmlToQueryExpression(XrmFakedContext context, string fetchXml) | |||
643 | 448 | { | ||
643 | 449 | return TranslateFetchXmlDocumentToQueryExpression(context, ParseFetchXml(fetchXml)); | ||
577 | 450 | } | ||
451 |
| |||
452 | public static QueryExpression TranslateFetchXmlDocumentToQueryExpression(XrmFakedContext context, XDocument xlDo | |||
1310 | 453 | { | ||
454 | //Validate nodes | |||
9629 | 455 | if (!xlDoc.Descendants().All(el => el.IsFetchXmlNodeValid())) | ||
36 | 456 | throw new Exception("At least some node is not valid"); | ||
457 |
| |||
458 | //Root node | |||
1268 | 459 | if (!xlDoc.Root.Name.LocalName.Equals("fetch")) | ||
0 | 460 | { | ||
0 | 461 | throw new Exception("Root node must be fetch"); | ||
462 | } | |||
463 |
| |||
1268 | 464 | var entityNode = RetrieveFetchXmlNode(xlDoc, "entity"); | ||
1268 | 465 | var query = new QueryExpression(entityNode.GetAttribute("name").Value); | ||
466 |
| |||
1268 | 467 | query.ColumnSet = xlDoc.ToColumnSet(); | ||
468 |
| |||
469 | // Ordering is done after grouping/aggregation | |||
1268 | 470 | if (!xlDoc.IsAggregateFetchXml()) | ||
1124 | 471 | { | ||
1124 | 472 | var orders = xlDoc.ToOrderExpressionList(); | ||
3992 | 473 | foreach (var order in orders) | ||
310 | 474 | { | ||
310 | 475 | query.AddOrder(order.AttributeName, order.OrderType); | ||
310 | 476 | } | ||
1124 | 477 | } | ||
478 |
| |||
1268 | 479 | query.Distinct = xlDoc.IsDistincFetchXml(); | ||
480 |
| |||
1268 | 481 | query.Criteria = xlDoc.ToCriteria(context); | ||
482 |
| |||
1238 | 483 | query.TopCount = xlDoc.ToTopCount(); | ||
484 |
| |||
1238 | 485 | query.PageInfo.Count = xlDoc.ToCount() ?? 0; | ||
1232 | 486 | query.PageInfo.PageNumber = xlDoc.ToPageNumber() ?? 1; | ||
1232 | 487 | query.PageInfo.ReturnTotalRecordCount = xlDoc.ToReturnTotalRecordCount(); | ||
488 |
| |||
1232 | 489 | var linkedEntities = xlDoc.ToLinkEntities(context); | ||
4024 | 490 | foreach (var le in linkedEntities) | ||
164 | 491 | { | ||
164 | 492 | query.LinkEntities.Add(le); | ||
164 | 493 | } | ||
494 |
| |||
1232 | 495 | return query; | ||
1232 | 496 | } | ||
497 |
| |||
498 | public static IQueryable<Entity> TranslateQueryExpressionToLinq(XrmFakedContext context, QueryExpression qe) | |||
2939 | 499 | { | ||
2945 | 500 | if (qe == null) return null; | ||
501 |
| |||
502 | //Start form the root entity and build a LINQ query to execute the query against the In-Memory context: | |||
2933 | 503 | context.EnsureEntityNameExistsInMetadata(qe.EntityName); | ||
504 |
| |||
2933 | 505 | IQueryable<Entity> query = null; | ||
506 |
| |||
2933 | 507 | query = context.CreateQuery<Entity>(qe.EntityName); | ||
508 |
| |||
2933 | 509 | var linkedEntities = new Dictionary<string, int>(); | ||
510 |
| |||
511 | #if !FAKE_XRM_EASY | |||
2473 | 512 | ValidateAliases(qe, context); | ||
513 | #endif | |||
514 |
| |||
515 | // Add as many Joins as linked entities | |||
11745 | 516 | foreach (var le in qe.LinkEntities) | ||
1479 | 517 | { | ||
1479 | 518 | if (string.IsNullOrWhiteSpace(le.EntityAlias)) | ||
314 | 519 | { | ||
314 | 520 | le.EntityAlias = EnsureUniqueLinkedEntityAlias(linkedEntities, le.LinkToEntityName); | ||
314 | 521 | } | ||
522 |
| |||
1479 | 523 | query = TranslateLinkedEntityToLinq(context, le, query, qe.ColumnSet, linkedEntities); | ||
1467 | 524 | } | ||
525 |
| |||
526 | // Compose the expression tree that represents the parameter to the predicate. | |||
2921 | 527 | ParameterExpression entity = Expression.Parameter(typeof(Entity)); | ||
2921 | 528 | var expTreeBody = TranslateQueryExpressionFiltersToExpression(context, qe, entity); | ||
2888 | 529 | Expression<Func<Entity, bool>> lambda = Expression.Lambda<Func<Entity, bool>>(expTreeBody, entity); | ||
2888 | 530 | query = query.Where(lambda); | ||
531 |
| |||
532 | //Sort results | |||
2888 | 533 | if (qe.Orders != null) | ||
2888 | 534 | { | ||
2888 | 535 | if (qe.Orders.Count > 0) | ||
500 | 536 | { | ||
500 | 537 | IOrderedQueryable<Entity> orderedQuery = null; | ||
538 |
| |||
500 | 539 | var order = qe.Orders[0]; | ||
500 | 540 | if (order.OrderType == OrderType.Ascending) | ||
464 | 541 | orderedQuery = query.OrderBy(e => e.Attributes.ContainsKey(order.AttributeName) ? e[order.Attrib | ||
542 | else | |||
36 | 543 | orderedQuery = query.OrderByDescending(e => e.Attributes.ContainsKey(order.AttributeName) ? e[or | ||
544 |
| |||
545 | //Subsequent orders should use ThenBy and ThenByDescending | |||
1048 | 546 | for (var i = 1; i < qe.Orders.Count; i++) | ||
24 | 547 | { | ||
24 | 548 | var thenOrder = qe.Orders[i]; | ||
24 | 549 | if (thenOrder.OrderType == OrderType.Ascending) | ||
12 | 550 | orderedQuery = orderedQuery.ThenBy(e => e.Attributes.ContainsKey(thenOrder.AttributeName) ? | ||
551 | else | |||
12 | 552 | orderedQuery = orderedQuery.ThenByDescending(e => e[thenOrder.AttributeName], new XrmOrderBy | ||
24 | 553 | } | ||
554 |
| |||
500 | 555 | query = orderedQuery; | ||
500 | 556 | } | ||
2888 | 557 | } | ||
558 |
| |||
559 | //Project the attributes in the root column set (must be applied after the where and order clauses, not bef | |||
2888 | 560 | query = query.Select(x => x.Clone(x.GetType(), context).ProjectAttributes(qe, context)); | ||
561 |
| |||
2888 | 562 | return query; | ||
2894 | 563 | } | ||
564 |
| |||
565 | #if !FAKE_XRM_EASY | |||
566 | protected static void ValidateAliases(QueryExpression qe, XrmFakedContext context) | |||
2473 | 567 | { | ||
2473 | 568 | if (qe.Criteria != null) | ||
2303 | 569 | ValidateAliases(qe, context, qe.Criteria); | ||
2473 | 570 | if (qe.LinkEntities != null) | ||
9899 | 571 | foreach (var link in qe.LinkEntities) | ||
1240 | 572 | { | ||
1240 | 573 | ValidateAliases(qe, context, link); | ||
1240 | 574 | } | ||
2473 | 575 | } | ||
576 |
| |||
577 | protected static void ValidateAliases(QueryExpression qe, XrmFakedContext context, LinkEntity link) | |||
1335 | 578 | { | ||
1335 | 579 | if (link.LinkCriteria != null) | ||
1250 | 580 | ValidateAliases(qe, context, link.LinkCriteria); | ||
1335 | 581 | if (link.LinkEntities != null) | ||
4195 | 582 | foreach (var innerLink in link.LinkEntities) | ||
95 | 583 | { | ||
95 | 584 | ValidateAliases(qe, context, innerLink); | ||
95 | 585 | } | ||
1335 | 586 | } | ||
587 |
| |||
588 | protected static void ValidateAliases(QueryExpression qe, XrmFakedContext context, FilterExpression filter) | |||
3783 | 589 | { | ||
3783 | 590 | if (filter.Filters != null) | ||
11809 | 591 | foreach (var innerFilter in filter.Filters) | ||
230 | 592 | { | ||
230 | 593 | ValidateAliases(qe, context, innerFilter); | ||
230 | 594 | } | ||
595 |
| |||
3783 | 596 | if (filter.Conditions != null) | ||
16459 | 597 | foreach (var condition in filter.Conditions) | ||
2555 | 598 | { | ||
2555 | 599 | if (!string.IsNullOrEmpty(condition.EntityName)) | ||
100 | 600 | { | ||
100 | 601 | ValidateAliases(qe, context, condition); | ||
100 | 602 | } | ||
2555 | 603 | } | ||
3783 | 604 | } | ||
605 |
| |||
606 | protected static void ValidateAliases(QueryExpression qe, XrmFakedContext context, ConditionExpression condition | |||
100 | 607 | { | ||
100 | 608 | var matches = qe.LinkEntities != null ? MatchByAlias(qe, context, condition, qe.LinkEntities) : 0; | ||
100 | 609 | if (matches > 1) | ||
0 | 610 | { | ||
0 | 611 | throw new FaultException<OrganizationServiceFault>(new OrganizationServiceFault(), $"Table {condition.En | ||
612 | } | |||
100 | 613 | else if (matches == 0) | ||
15 | 614 | { | ||
30 | 615 | if (qe.LinkEntities != null) matches = MatchByEntity(qe, context, condition, qe.LinkEntities); | ||
15 | 616 | if (matches > 1) | ||
0 | 617 | { | ||
0 | 618 | throw new FaultException<OrganizationServiceFault>(new OrganizationServiceFault(), $"There's more th | ||
619 | } | |||
15 | 620 | else if (matches == 0) | ||
5 | 621 | { | ||
10 | 622 | if (condition.EntityName == qe.EntityName) return; | ||
0 | 623 | throw new FaultException<OrganizationServiceFault>(new OrganizationServiceFault(), $"LinkEntity with | ||
624 | } | |||
10 | 625 | condition.EntityName += "1"; | ||
10 | 626 | } | ||
100 | 627 | } | ||
628 |
| |||
629 | protected static int MatchByEntity(QueryExpression qe, XrmFakedContext context, ConditionExpression condition, D | |||
25 | 630 | { | ||
25 | 631 | var matches = 0; | ||
95 | 632 | foreach (var link in linkEntities) | ||
10 | 633 | { | ||
10 | 634 | if (string.IsNullOrEmpty(link.EntityAlias) && condition.EntityName == link.LinkToEntityName) | ||
10 | 635 | { | ||
10 | 636 | matches += 1; | ||
10 | 637 | } | ||
20 | 638 | if (link.LinkEntities != null) matches += MatchByEntity(qe, context, condition, link.LinkEntities); | ||
10 | 639 | } | ||
25 | 640 | return matches; | ||
25 | 641 | } | ||
642 |
| |||
643 | protected static int MatchByAlias(QueryExpression qe, XrmFakedContext context, ConditionExpression condition, Da | |||
235 | 644 | { | ||
235 | 645 | var matches = 0; | ||
975 | 646 | foreach (var link in linkEntities) | ||
135 | 647 | { | ||
135 | 648 | if (link.EntityAlias == condition.EntityName) | ||
85 | 649 | { | ||
85 | 650 | matches += 1; | ||
85 | 651 | } | ||
270 | 652 | if (link.LinkEntities != null) matches += MatchByAlias(qe, context, condition, link.LinkEntities); | ||
135 | 653 | } | ||
235 | 654 | return matches; | ||
235 | 655 | } | ||
656 | #endif | |||
657 |
| |||
658 | protected static Expression TranslateConditionExpression(QueryExpression qe, XrmFakedContext context, TypedCondi | |||
3020 | 659 | { | ||
3020 | 660 | Expression attributesProperty = Expression.Property( | ||
3020 | 661 | entity, | ||
3020 | 662 | "Attributes" | ||
3020 | 663 | ); | ||
664 |
| |||
665 |
| |||
666 | //If the attribute comes from a joined entity, then we need to access the attribute from the join | |||
667 | //But the entity name attribute only exists >= 2013 | |||
668 | #if FAKE_XRM_EASY_2013 || FAKE_XRM_EASY_2015 || FAKE_XRM_EASY_2016 || FAKE_XRM_EASY_365 || FAKE_XRM_EASY_9 | |||
2549 | 669 | string attributeName = ""; | ||
670 |
| |||
671 | //Do not prepend the entity name if the EntityLogicalName is the same as the QueryExpression main logical na | |||
672 |
| |||
2549 | 673 | if (!string.IsNullOrWhiteSpace(c.CondExpression.EntityName) && !c.CondExpression.EntityName.Equals(qe.Entity | ||
95 | 674 | { | ||
95 | 675 | attributeName = c.CondExpression.EntityName + "." + c.CondExpression.AttributeName; | ||
95 | 676 | } | ||
677 | else | |||
2454 | 678 | attributeName = c.CondExpression.AttributeName; | ||
679 |
| |||
2549 | 680 | Expression containsAttributeExpression = Expression.Call( | ||
2549 | 681 | attributesProperty, | ||
2549 | 682 | typeof(AttributeCollection).GetMethod("ContainsKey", new Type[] { typeof(string) }), | ||
2549 | 683 | Expression.Constant(attributeName) | ||
2549 | 684 | ); | ||
685 |
| |||
2549 | 686 | Expression getAttributeValueExpr = Expression.Property( | ||
2549 | 687 | attributesProperty, "Item", | ||
2549 | 688 | Expression.Constant(attributeName, typeof(string)) | ||
2549 | 689 | ); | ||
690 | #else | |||
471 | 691 | Expression containsAttributeExpression = Expression.Call( | ||
471 | 692 | attributesProperty, | ||
471 | 693 | typeof(AttributeCollection).GetMethod("ContainsKey", new Type[] { typeof(string) }), | ||
471 | 694 | Expression.Constant(c.CondExpression.AttributeName) | ||
471 | 695 | ); | ||
696 |
| |||
471 | 697 | Expression getAttributeValueExpr = Expression.Property( | ||
471 | 698 | attributesProperty, "Item", | ||
471 | 699 | Expression.Constant(c.CondExpression.AttributeName, typeof(string)) | ||
471 | 700 | ); | ||
701 | #endif | |||
702 |
| |||
703 |
| |||
704 |
| |||
3020 | 705 | Expression getNonBasicValueExpr = getAttributeValueExpr; | ||
706 |
| |||
3020 | 707 | Expression operatorExpression = null; | ||
708 |
| |||
3020 | 709 | switch (c.CondExpression.Operator) | ||
710 | { | |||
711 | case ConditionOperator.Equal: | |||
712 | case ConditionOperator.Today: | |||
713 | case ConditionOperator.Yesterday: | |||
714 | case ConditionOperator.Tomorrow: | |||
715 | case ConditionOperator.EqualUserId: | |||
2269 | 716 | operatorExpression = TranslateConditionExpressionEqual(context, c, getNonBasicValueExpr, containsAtt | ||
2265 | 717 | break; | ||
718 |
| |||
719 | case ConditionOperator.NotEqualUserId: | |||
6 | 720 | operatorExpression = Expression.Not(TranslateConditionExpressionEqual(context, c, getNonBasicValueEx | ||
6 | 721 | break; | ||
722 |
| |||
723 | case ConditionOperator.EqualBusinessId: | |||
6 | 724 | operatorExpression = TranslateConditionExpressionEqual(context, c, getNonBasicValueExpr, containsAtt | ||
6 | 725 | break; | ||
726 |
| |||
727 | case ConditionOperator.NotEqualBusinessId: | |||
0 | 728 | operatorExpression = Expression.Not(TranslateConditionExpressionEqual(context, c, getNonBasicValueEx | ||
0 | 729 | break; | ||
730 |
| |||
731 | case ConditionOperator.BeginsWith: | |||
732 | case ConditionOperator.Like: | |||
66 | 733 | operatorExpression = TranslateConditionExpressionLike(c, getNonBasicValueExpr, containsAttributeExpr | ||
66 | 734 | break; | ||
735 |
| |||
736 | case ConditionOperator.EndsWith: | |||
18 | 737 | operatorExpression = TranslateConditionExpressionEndsWith(c, getNonBasicValueExpr, containsAttribute | ||
18 | 738 | break; | ||
739 |
| |||
740 | case ConditionOperator.Contains: | |||
24 | 741 | operatorExpression = TranslateConditionExpressionContains(c, getNonBasicValueExpr, containsAttribute | ||
24 | 742 | break; | ||
743 |
| |||
744 | case ConditionOperator.NotEqual: | |||
20 | 745 | operatorExpression = Expression.Not(TranslateConditionExpressionEqual(context, c, getNonBasicValueEx | ||
20 | 746 | break; | ||
747 |
| |||
748 | case ConditionOperator.DoesNotBeginWith: | |||
749 | case ConditionOperator.DoesNotEndWith: | |||
750 | case ConditionOperator.NotLike: | |||
751 | case ConditionOperator.DoesNotContain: | |||
12 | 752 | operatorExpression = Expression.Not(TranslateConditionExpressionLike(c, getNonBasicValueExpr, contai | ||
12 | 753 | break; | ||
754 |
| |||
755 | case ConditionOperator.Null: | |||
71 | 756 | operatorExpression = TranslateConditionExpressionNull(c, getNonBasicValueExpr, containsAttributeExpr | ||
71 | 757 | break; | ||
758 |
| |||
759 | case ConditionOperator.NotNull: | |||
108 | 760 | operatorExpression = Expression.Not(TranslateConditionExpressionNull(c, getNonBasicValueExpr, contai | ||
108 | 761 | break; | ||
762 |
| |||
763 | case ConditionOperator.GreaterThan: | |||
24 | 764 | operatorExpression = TranslateConditionExpressionGreaterThan(c, getNonBasicValueExpr, containsAttrib | ||
24 | 765 | break; | ||
766 |
| |||
767 | case ConditionOperator.GreaterEqual: | |||
38 | 768 | operatorExpression = TranslateConditionExpressionGreaterThanOrEqual(context, c, getNonBasicValueExpr | ||
38 | 769 | break; | ||
770 |
| |||
771 | case ConditionOperator.LessThan: | |||
36 | 772 | operatorExpression = TranslateConditionExpressionLessThan(c, getNonBasicValueExpr, containsAttribute | ||
36 | 773 | break; | ||
774 |
| |||
775 | case ConditionOperator.LessEqual: | |||
28 | 776 | operatorExpression = TranslateConditionExpressionLessThanOrEqual(context, c, getNonBasicValueExpr, c | ||
28 | 777 | break; | ||
778 |
| |||
779 | case ConditionOperator.In: | |||
36 | 780 | operatorExpression = TranslateConditionExpressionIn(c, getNonBasicValueExpr, containsAttributeExpres | ||
34 | 781 | break; | ||
782 |
| |||
783 | case ConditionOperator.NotIn: | |||
2 | 784 | operatorExpression = Expression.Not(TranslateConditionExpressionIn(c, getNonBasicValueExpr, contains | ||
2 | 785 | break; | ||
786 |
| |||
787 | case ConditionOperator.On: | |||
12 | 788 | operatorExpression = TranslateConditionExpressionEqual(context, c, getNonBasicValueExpr, containsAtt | ||
12 | 789 | break; | ||
790 |
| |||
791 | case ConditionOperator.NotOn: | |||
0 | 792 | operatorExpression = Expression.Not(TranslateConditionExpressionEqual(context, c, getNonBasicValueEx | ||
0 | 793 | break; | ||
794 |
| |||
795 | case ConditionOperator.OnOrAfter: | |||
12 | 796 | operatorExpression = Expression.Or( | ||
12 | 797 | TranslateConditionExpressionEqual(context, c, getNonBasicValueExpr, containsAttributeExpr | ||
12 | 798 | TranslateConditionExpressionGreaterThan(c, getNonBasicValueExpr, containsAttributeExpress | ||
12 | 799 | break; | ||
800 | case ConditionOperator.LastXHours: | |||
801 | case ConditionOperator.LastXDays: | |||
802 | case ConditionOperator.Last7Days: | |||
803 | case ConditionOperator.LastXWeeks: | |||
804 | case ConditionOperator.LastXMonths: | |||
805 | case ConditionOperator.LastXYears: | |||
36 | 806 | operatorExpression = TranslateConditionExpressionLast(c, getNonBasicValueExpr, containsAttributeExpr | ||
36 | 807 | break; | ||
808 |
| |||
809 | case ConditionOperator.OnOrBefore: | |||
18 | 810 | operatorExpression = Expression.Or( | ||
18 | 811 | TranslateConditionExpressionEqual(context, c, getNonBasicValueExpr, containsAttributeExp | ||
18 | 812 | TranslateConditionExpressionLessThan(c, getNonBasicValueExpr, containsAttributeExpressio | ||
18 | 813 | break; | ||
814 |
| |||
815 | case ConditionOperator.Between: | |||
12 | 816 | if (c.CondExpression.Values.Count != 2) | ||
6 | 817 | { | ||
6 | 818 | throw new Exception("Between operator requires exactly 2 values."); | ||
819 | } | |||
6 | 820 | operatorExpression = TranslateConditionExpressionBetween(c, getNonBasicValueExpr, containsAttributeE | ||
6 | 821 | break; | ||
822 |
| |||
823 | case ConditionOperator.NotBetween: | |||
12 | 824 | if (c.CondExpression.Values.Count != 2) | ||
6 | 825 | { | ||
6 | 826 | throw new Exception("Not-Between operator requires exactly 2 values."); | ||
827 | } | |||
6 | 828 | operatorExpression = Expression.Not(TranslateConditionExpressionBetween(c, getNonBasicValueExpr, con | ||
6 | 829 | break; | ||
830 | #if !FAKE_XRM_EASY && !FAKE_XRM_EASY_2013 | |||
831 | case ConditionOperator.OlderThanXMinutes: | |||
832 | case ConditionOperator.OlderThanXHours: | |||
833 | case ConditionOperator.OlderThanXDays: | |||
834 | case ConditionOperator.OlderThanXWeeks: | |||
835 | case ConditionOperator.OlderThanXYears: | |||
836 | #endif | |||
837 | case ConditionOperator.OlderThanXMonths: | |||
38 | 838 | operatorExpression = TranslateConditionExpressionOlderThan(c, getNonBasicValueExpr, containsAttribut | ||
38 | 839 | break; | ||
840 |
| |||
841 | case ConditionOperator.NextXHours: | |||
842 | case ConditionOperator.NextXDays: | |||
843 | case ConditionOperator.Next7Days: | |||
844 | case ConditionOperator.NextXWeeks: | |||
845 | case ConditionOperator.NextXMonths: | |||
846 | case ConditionOperator.NextXYears: | |||
36 | 847 | operatorExpression = TranslateConditionExpressionNext(c, getNonBasicValueExpr, containsAttributeExpr | ||
36 | 848 | break; | ||
849 | case ConditionOperator.ThisYear: | |||
850 | case ConditionOperator.LastYear: | |||
851 | case ConditionOperator.NextYear: | |||
852 | case ConditionOperator.ThisMonth: | |||
853 | case ConditionOperator.LastMonth: | |||
854 | case ConditionOperator.NextMonth: | |||
855 | case ConditionOperator.LastWeek: | |||
856 | case ConditionOperator.ThisWeek: | |||
857 | case ConditionOperator.NextWeek: | |||
858 | case ConditionOperator.InFiscalYear: | |||
60 | 859 | operatorExpression = TranslateConditionExpressionBetweenDates(c, getNonBasicValueExpr, containsAttri | ||
60 | 860 | break; | ||
861 | #if FAKE_XRM_EASY_9 | |||
862 | case ConditionOperator.ContainValues: | |||
11 | 863 | operatorExpression = TranslateConditionExpressionContainValues(c, getNonBasicValueExpr, containsAttr | ||
9 | 864 | break; | ||
865 |
| |||
866 | case ConditionOperator.DoesNotContainValues: | |||
3 | 867 | operatorExpression = Expression.Not(TranslateConditionExpressionContainValues(c, getNonBasicValueExp | ||
3 | 868 | break; | ||
869 | #endif | |||
870 |
| |||
871 | default: | |||
6 | 872 | throw new PullRequestException(string.Format("Operator {0} not yet implemented for condition express | ||
873 |
| |||
874 |
| |||
875 | } | |||
876 |
| |||
2994 | 877 | if (c.IsOuter) | ||
44 | 878 | { | ||
879 | //If outer join, filter is optional, only if there was a value | |||
44 | 880 | return Expression.Constant(true); | ||
881 | } | |||
882 | else | |||
2950 | 883 | return operatorExpression; | ||
884 |
| |||
2994 | 885 | } | ||
886 |
| |||
887 | private static void ValidateSupportedTypedExpression(TypedConditionExpression typedExpression) | |||
3021 | 888 | { | ||
3021 | 889 | Expression validateOperatorTypeExpression = Expression.Empty(); | ||
3021 | 890 | ConditionOperator[] supportedOperators = (ConditionOperator[])Enum.GetValues(typeof(ConditionOperator)); | ||
891 |
| |||
892 | #if FAKE_XRM_EASY_9 | |||
543 | 893 | if (typedExpression.AttributeType == typeof(OptionSetValueCollection)) | ||
39 | 894 | { | ||
39 | 895 | supportedOperators = new[] | ||
39 | 896 | { | ||
39 | 897 | ConditionOperator.ContainValues, | ||
39 | 898 | ConditionOperator.DoesNotContainValues, | ||
39 | 899 | ConditionOperator.Equal, | ||
39 | 900 | ConditionOperator.NotEqual, | ||
39 | 901 | ConditionOperator.NotNull, | ||
39 | 902 | ConditionOperator.Null, | ||
39 | 903 | ConditionOperator.In, | ||
39 | 904 | ConditionOperator.NotIn, | ||
39 | 905 | }; | ||
39 | 906 | } | ||
907 | #endif | |||
908 |
| |||
3021 | 909 | if (!supportedOperators.Contains(typedExpression.CondExpression.Operator)) | ||
1 | 910 | { | ||
1 | 911 | FakeOrganizationServiceFault.Throw(ErrorCodes.InvalidOperatorCode, "The operator is not valid or it is n | ||
0 | 912 | } | ||
3020 | 913 | } | ||
914 |
| |||
915 | protected static Expression GetAppropiateTypedValue(object value) | |||
591 | 916 | { | ||
917 | //Basic types conversions | |||
918 | //Special case => datetime is sent as a string | |||
591 | 919 | if (value is string) | ||
168 | 920 | { | ||
921 | DateTime dtDateTimeConversion; | |||
168 | 922 | if (DateTime.TryParse(value.ToString(), CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, | ||
0 | 923 | { | ||
0 | 924 | return Expression.Constant(dtDateTimeConversion, typeof(DateTime)); | ||
925 | } | |||
926 | else | |||
168 | 927 | { | ||
168 | 928 | return GetCaseInsensitiveExpression(Expression.Constant(value, typeof(string))); | ||
929 | } | |||
930 | } | |||
423 | 931 | else if (value is EntityReference) | ||
12 | 932 | { | ||
12 | 933 | var cast = (value as EntityReference).Id; | ||
12 | 934 | return Expression.Constant(cast); | ||
935 | } | |||
411 | 936 | else if (value is OptionSetValue) | ||
0 | 937 | { | ||
0 | 938 | var cast = (value as OptionSetValue).Value; | ||
0 | 939 | return Expression.Constant(cast); | ||
940 | } | |||
411 | 941 | else if (value is Money) | ||
0 | 942 | { | ||
0 | 943 | var cast = (value as Money).Value; | ||
0 | 944 | return Expression.Constant(cast); | ||
945 | } | |||
411 | 946 | return Expression.Constant(value); | ||
591 | 947 | } | ||
948 |
| |||
949 | protected static Type GetAppropiateTypeForValue(object value) | |||
114 | 950 | { | ||
951 | //Basic types conversions | |||
952 | //Special case => datetime is sent as a string | |||
114 | 953 | if (value is string) | ||
6 | 954 | { | ||
955 | DateTime dtDateTimeConversion; | |||
6 | 956 | if (DateTime.TryParse(value.ToString(), CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, | ||
0 | 957 | { | ||
0 | 958 | return typeof(DateTime); | ||
959 | } | |||
960 | else | |||
6 | 961 | { | ||
6 | 962 | return typeof(string); | ||
963 | } | |||
964 | } | |||
965 | else | |||
108 | 966 | return value.GetType(); | ||
114 | 967 | } | ||
968 |
| |||
969 | protected static Expression GetAppropiateTypedValueAndType(object value, Type attributeType) | |||
2942 | 970 | { | ||
2942 | 971 | if (attributeType == null) | ||
591 | 972 | return GetAppropiateTypedValue(value); | ||
973 |
| |||
2351 | 974 | if (Nullable.GetUnderlyingType(attributeType) != null) | ||
312 | 975 | { | ||
312 | 976 | attributeType = Nullable.GetUnderlyingType(attributeType); | ||
312 | 977 | } | ||
978 |
| |||
979 | //Basic types conversions | |||
980 | //Special case => datetime is sent as a string | |||
2351 | 981 | if (value is string) | ||
635 | 982 | { | ||
983 | int iValue; | |||
984 |
| |||
985 | DateTime dtDateTimeConversion; | |||
986 | Guid id; | |||
635 | 987 | if (attributeType.IsDateTime() //Only convert to DateTime if the attribute's type was DateTime | ||
635 | 988 | && DateTime.TryParse(value.ToString(), CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversa | ||
6 | 989 | { | ||
6 | 990 | return Expression.Constant(dtDateTimeConversion, typeof(DateTime)); | ||
991 | } | |||
629 | 992 | else if (attributeType.IsOptionSet() && int.TryParse(value.ToString(), out iValue)) | ||
6 | 993 | { | ||
6 | 994 | return Expression.Constant(iValue, typeof(int)); | ||
995 | } | |||
623 | 996 | else if ((attributeType == typeof(EntityReference) || attributeType == typeof(Guid)) && Guid.TryParse((s | ||
18 | 997 | { | ||
18 | 998 | return Expression.Constant(id); | ||
999 | } | |||
1000 | else | |||
605 | 1001 | { | ||
605 | 1002 | return GetCaseInsensitiveExpression(Expression.Constant(value, typeof(string))); | ||
1003 | } | |||
1004 | } | |||
1716 | 1005 | else if (value is EntityReference) | ||
23 | 1006 | { | ||
23 | 1007 | var cast = (value as EntityReference).Id; | ||
23 | 1008 | return Expression.Constant(cast); | ||
1009 | } | |||
1693 | 1010 | else if (value is OptionSetValue) | ||
46 | 1011 | { | ||
46 | 1012 | var cast = (value as OptionSetValue).Value; | ||
46 | 1013 | return Expression.Constant(cast); | ||
1014 | } | |||
1647 | 1015 | else if (value is Money) | ||
6 | 1016 | { | ||
6 | 1017 | var cast = (value as Money).Value; | ||
6 | 1018 | return Expression.Constant(cast); | ||
1019 | } | |||
1641 | 1020 | return Expression.Constant(value); | ||
2942 | 1021 | } | ||
1022 |
| |||
1023 | protected static Expression GetAppropiateCastExpressionBasedOnType(Type t, Expression input, object value) | |||
3096 | 1024 | { | ||
3096 | 1025 | var typedExpression = GetAppropiateCastExpressionBasedOnAttributeTypeOrValue(input, value, t); | ||
1026 |
| |||
1027 | //Now, any value (entity reference, string, int, etc,... could be wrapped in an AliasedValue object | |||
1028 | //So let's add this | |||
3096 | 1029 | var getValueFromAliasedValueExp = Expression.Call(Expression.Convert(input, typeof(AliasedValue)), | ||
3096 | 1030 | typeof(AliasedValue).GetMethod("get_Value")); | ||
1031 |
| |||
3096 | 1032 | var exp = Expression.Condition(Expression.TypeIs(input, typeof(AliasedValue)), | ||
3096 | 1033 | GetAppropiateCastExpressionBasedOnAttributeTypeOrValue(getValueFromAliasedValueExp, value, t), | ||
3096 | 1034 | typedExpression //Not an aliased value | ||
3096 | 1035 | ); | ||
1036 |
| |||
3096 | 1037 | return exp; | ||
3096 | 1038 | } | ||
1039 |
| |||
1040 | //protected static Expression GetAppropiateCastExpressionBasedOnValue(XrmFakedContext context, Expression input, | |||
1041 | //{ | |||
1042 | // var typedExpression = GetAppropiateCastExpressionBasedOnAttributeTypeOrValue(context, input, value, sEntit | |||
1043 |
| |||
1044 | // //Now, any value (entity reference, string, int, etc,... could be wrapped in an AliasedValue object | |||
1045 | // //So let's add this | |||
1046 | // var getValueFromAliasedValueExp = Expression.Call(Expression.Convert(input, typeof(AliasedValue)), | |||
1047 | // typeof(AliasedValue).GetMethod("get_Value")); | |||
1048 |
| |||
1049 | // var exp = Expression.Condition(Expression.TypeIs(input, typeof(AliasedValue)), | |||
1050 | // GetAppropiateCastExpressionBasedOnAttributeTypeOrValue(context, getValueFromAliasedValueExp, value | |||
1051 | // typedExpression //Not an aliased value | |||
1052 | // ); | |||
1053 |
| |||
1054 | // return exp; | |||
1055 | //} | |||
1056 |
| |||
1057 | protected static Expression GetAppropiateCastExpressionBasedOnValueInherentType(Expression input, object value) | |||
1230 | 1058 | { | ||
1230 | 1059 | if (value is Guid || value is EntityReference) | ||
454 | 1060 | return GetAppropiateCastExpressionBasedGuid(input); //Could be compared against an EntityReference | ||
776 | 1061 | if (value is int || value is OptionSetValue) | ||
192 | 1062 | return GetAppropiateCastExpressionBasedOnInt(input); //Could be compared against an OptionSet | ||
584 | 1063 | if (value is decimal || value is Money) | ||
0 | 1064 | return GetAppropiateCastExpressionBasedOnDecimal(input); //Could be compared against a Money | ||
584 | 1065 | if (value is bool) | ||
104 | 1066 | return GetAppropiateCastExpressionBasedOnBoolean(input); //Could be a BooleanManagedProperty | ||
480 | 1067 | if (value is string) | ||
384 | 1068 | { | ||
384 | 1069 | return GetAppropiateCastExpressionBasedOnString(input, value); | ||
1070 | } | |||
96 | 1071 | return GetAppropiateCastExpressionDefault(input, value); //any other type | ||
1230 | 1072 | } | ||
1073 |
| |||
1074 | protected static Expression GetAppropiateCastExpressionBasedOnAttributeTypeOrValue(Expression input, object valu | |||
6192 | 1075 | { | ||
6192 | 1076 | if (attributeType != null) | ||
4962 | 1077 | { | ||
4962 | 1078 | if (Nullable.GetUnderlyingType(attributeType) != null) | ||
624 | 1079 | { | ||
624 | 1080 | attributeType = Nullable.GetUnderlyingType(attributeType); | ||
624 | 1081 | } | ||
1082 | #if FAKE_XRM_EASY || FAKE_XRM_EASY_2013 || FAKE_XRM_EASY_2015 | |||
2420 | 1083 | if (attributeType == typeof(Microsoft.Xrm.Client.CrmEntityReference)) | ||
0 | 1084 | return GetAppropiateCastExpressionBasedGuid(input); | ||
1085 | #endif | |||
4962 | 1086 | if (attributeType == typeof(Guid)) | ||
780 | 1087 | return GetAppropiateCastExpressionBasedGuid(input); | ||
4182 | 1088 | if (attributeType == typeof(EntityReference)) | ||
454 | 1089 | return GetAppropiateCastExpressionBasedOnEntityReference(input, value); | ||
3728 | 1090 | if (attributeType == typeof(int) || attributeType == typeof(Nullable<int>) || attributeType.IsOptionSet( | ||
1150 | 1091 | return GetAppropiateCastExpressionBasedOnInt(input); | ||
2578 | 1092 | if (attributeType == typeof(decimal) || attributeType == typeof(Money)) | ||
36 | 1093 | return GetAppropiateCastExpressionBasedOnDecimal(input); | ||
2542 | 1094 | if (attributeType == typeof(bool) || attributeType == typeof(BooleanManagedProperty)) | ||
72 | 1095 | return GetAppropiateCastExpressionBasedOnBoolean(input); | ||
2470 | 1096 | if (attributeType == typeof(string)) | ||
1390 | 1097 | return GetAppropiateCastExpressionBasedOnStringAndType(input, value, attributeType); | ||
1080 | 1098 | if (attributeType.IsDateTime()) | ||
916 | 1099 | return GetAppropiateCastExpressionBasedOnDateTime(input, value); | ||
1100 | #if FAKE_XRM_EASY_9 | |||
84 | 1101 | if (attributeType.IsOptionSetValueCollection()) | ||
68 | 1102 | return GetAppropiateCastExpressionBasedOnOptionSetValueCollection(input); | ||
1103 | #endif | |||
1104 |
| |||
96 | 1105 | return GetAppropiateCastExpressionDefault(input, value); //any other type | ||
1106 | } | |||
1107 |
| |||
1230 | 1108 | return GetAppropiateCastExpressionBasedOnValueInherentType(input, value); //Dynamic entities | ||
6192 | 1109 | } | ||
1110 | protected static Expression GetAppropiateCastExpressionBasedOnString(Expression input, object value) | |||
384 | 1111 | { | ||
384 | 1112 | var defaultStringExpression = GetCaseInsensitiveExpression(GetAppropiateCastExpressionDefault(input, value)) | ||
1113 |
| |||
1114 | DateTime dtDateTimeConversion; | |||
384 | 1115 | if (DateTime.TryParse(value.ToString(), out dtDateTimeConversion)) | ||
0 | 1116 | { | ||
0 | 1117 | return Expression.Convert(input, typeof(DateTime)); | ||
1118 | } | |||
1119 |
| |||
1120 | int iValue; | |||
384 | 1121 | if (int.TryParse(value.ToString(), out iValue)) | ||
52 | 1122 | { | ||
52 | 1123 | return Expression.Condition(Expression.TypeIs(input, typeof(OptionSetValue)), | ||
52 | 1124 | GetToStringExpression<Int32>(GetAppropiateCastExpressionBasedOnInt(input)), | ||
52 | 1125 | defaultStringExpression | ||
52 | 1126 | ); | ||
1127 | } | |||
1128 |
| |||
332 | 1129 | return defaultStringExpression; | ||
384 | 1130 | } | ||
1131 |
| |||
1132 | protected static Expression GetAppropiateCastExpressionBasedOnStringAndType(Expression input, object value, Type | |||
1390 | 1133 | { | ||
1390 | 1134 | var defaultStringExpression = GetCaseInsensitiveExpression(GetAppropiateCastExpressionDefault(input, value)) | ||
1135 |
| |||
1136 | int iValue; | |||
1390 | 1137 | if (attributeType.IsOptionSet() && int.TryParse(value.ToString(), out iValue)) | ||
0 | 1138 | { | ||
0 | 1139 | return Expression.Condition(Expression.TypeIs(input, typeof(OptionSetValue)), | ||
0 | 1140 | GetToStringExpression<Int32>(GetAppropiateCastExpressionBasedOnInt(input)), | ||
0 | 1141 | defaultStringExpression | ||
0 | 1142 | ); | ||
1143 | } | |||
1144 |
| |||
1390 | 1145 | return defaultStringExpression; | ||
1390 | 1146 | } | ||
1147 |
| |||
1148 | protected static Expression GetAppropiateCastExpressionBasedOnDateTime(Expression input, object value) | |||
916 | 1149 | { | ||
1150 | // Convert to DateTime if string | |||
1151 | DateTime _; | |||
916 | 1152 | if (value is DateTime || value is string && DateTime.TryParse(value.ToString(), out _)) | ||
916 | 1153 | { | ||
916 | 1154 | return Expression.Convert(input, typeof(DateTime)); | ||
1155 | } | |||
1156 |
| |||
0 | 1157 | return input; // return directly | ||
916 | 1158 | } | ||
1159 |
| |||
1160 | protected static Expression GetAppropiateCastExpressionDefault(Expression input, object value) | |||
1966 | 1161 | { | ||
1966 | 1162 | return Expression.Convert(input, value.GetType()); //Default type conversion | ||
1966 | 1163 | } | ||
1164 | protected static Expression GetAppropiateCastExpressionBasedGuid(Expression input) | |||
1234 | 1165 | { | ||
1234 | 1166 | var getIdFromEntityReferenceExpr = Expression.Call(Expression.TypeAs(input, typeof(EntityReference)), | ||
1234 | 1167 | typeof(EntityReference).GetMethod("get_Id")); | ||
1168 |
| |||
1234 | 1169 | return Expression.Condition( | ||
1234 | 1170 | Expression.TypeIs(input, typeof(EntityReference)), //If input is an entity reference, compare the Guid | ||
1234 | 1171 | Expression.Convert( | ||
1234 | 1172 | getIdFromEntityReferenceExpr, | ||
1234 | 1173 | typeof(Guid)), | ||
1234 | 1174 | Expression.Condition(Expression.TypeIs(input, typeof(Guid)), //If any other case, then just compare it | ||
1234 | 1175 | Expression.Convert(input, typeof(Guid)), | ||
1234 | 1176 | Expression.Constant(Guid.Empty, typeof(Guid)))); | ||
1234 | 1177 | } | ||
1178 |
| |||
1179 | protected static Expression GetAppropiateCastExpressionBasedOnEntityReference(Expression input, object value) | |||
454 | 1180 | { | ||
1181 | Guid guid; | |||
454 | 1182 | if (value is string && !Guid.TryParse((string)value, out guid)) | ||
12 | 1183 | { | ||
12 | 1184 | var getNameFromEntityReferenceExpr = Expression.Call(Expression.TypeAs(input, typeof(EntityReference)), | ||
12 | 1185 | typeof(EntityReference).GetMethod("get_Name")); | ||
1186 |
| |||
12 | 1187 | return GetCaseInsensitiveExpression(Expression.Condition(Expression.TypeIs(input, typeof(EntityReference | ||
12 | 1188 | Expression.Convert(getNameFromEntityReferenceExpr, typeof(string)), | ||
12 | 1189 | Expression.Constant(string.Empty, typeof(string)))); | ||
1190 | } | |||
1191 |
| |||
442 | 1192 | var getIdFromEntityReferenceExpr = Expression.Call(Expression.TypeAs(input, typeof(EntityReference)), | ||
442 | 1193 | typeof(EntityReference).GetMethod("get_Id")); | ||
1194 |
| |||
442 | 1195 | return Expression.Condition( | ||
442 | 1196 | Expression.TypeIs(input, typeof(EntityReference)), //If input is an entity reference, compare the Guid | ||
442 | 1197 | Expression.Convert( | ||
442 | 1198 | getIdFromEntityReferenceExpr, | ||
442 | 1199 | typeof(Guid)), | ||
442 | 1200 | Expression.Condition(Expression.TypeIs(input, typeof(Guid)), //If any other case, then just compare it | ||
442 | 1201 | Expression.Convert(input, typeof(Guid)), | ||
442 | 1202 | Expression.Constant(Guid.Empty, typeof(Guid)))); | ||
1203 |
| |||
454 | 1204 | } | ||
1205 |
| |||
1206 | protected static Expression GetAppropiateCastExpressionBasedOnDecimal(Expression input) | |||
36 | 1207 | { | ||
36 | 1208 | return Expression.Condition( | ||
36 | 1209 | Expression.TypeIs(input, typeof(Money)), | ||
36 | 1210 | Expression.Convert( | ||
36 | 1211 | Expression.Call(Expression.TypeAs(input, typeof(Money)), | ||
36 | 1212 | typeof(Money).GetMethod("get_Value")), | ||
36 | 1213 | typeof(decimal)), | ||
36 | 1214 | Expression.Condition(Expression.TypeIs(input, typeof(decimal)), | ||
36 | 1215 | Expression.Convert(input, typeof(decimal)), | ||
36 | 1216 | Expression.Constant(0.0M))); | ||
1217 |
| |||
36 | 1218 | } | ||
1219 |
| |||
1220 | protected static Expression GetAppropiateCastExpressionBasedOnBoolean(Expression input) | |||
176 | 1221 | { | ||
176 | 1222 | return Expression.Condition( | ||
176 | 1223 | Expression.TypeIs(input, typeof(BooleanManagedProperty)), | ||
176 | 1224 | Expression.Convert( | ||
176 | 1225 | Expression.Call(Expression.TypeAs(input, typeof(BooleanManagedProperty)), | ||
176 | 1226 | typeof(BooleanManagedProperty).GetMethod("get_Value")), | ||
176 | 1227 | typeof(bool)), | ||
176 | 1228 | Expression.Condition(Expression.TypeIs(input, typeof(bool)), | ||
176 | 1229 | Expression.Convert(input, typeof(bool)), | ||
176 | 1230 | Expression.Constant(false))); | ||
1231 |
| |||
176 | 1232 | } | ||
1233 |
| |||
1234 | protected static Expression GetAppropiateCastExpressionBasedOnInt(Expression input) | |||
1394 | 1235 | { | ||
1394 | 1236 | return Expression.Condition( | ||
1394 | 1237 | Expression.TypeIs(input, typeof(OptionSetValue)), | ||
1394 | 1238 | Expression.Convert( | ||
1394 | 1239 | Expression.Call(Expression.TypeAs(input, typeof(OptionSetValue)), | ||
1394 | 1240 | typeof(OptionSetValue).GetMethod("get_Value")), | ||
1394 | 1241 | typeof(int)), | ||
1394 | 1242 | Expression.Convert(input, typeof(int))); | ||
1394 | 1243 | } | ||
1244 |
| |||
1245 | protected static Expression GetAppropiateCastExpressionBasedOnOptionSetValueCollection(Expression input) | |||
68 | 1246 | { | ||
68 | 1247 | return Expression.Call(typeof(XrmFakedContext).GetMethod("ConvertToHashSetOfInt"), input, Expression.Constan | ||
68 | 1248 | } | ||
1249 |
| |||
1250 | #if FAKE_XRM_EASY_9 | |||
1251 | public static HashSet<int> ConvertToHashSetOfInt(object input, bool isOptionSetValueCollectionAccepted) | |||
144 | 1252 | { | ||
144 | 1253 | var set = new HashSet<int>(); | ||
1254 |
| |||
144 | 1255 | var faultReason = $"The formatter threw an exception while trying to deserialize the message: There was an e | ||
144 | 1256 | $" http://schemas.microsoft.com/xrm/2011/Contracts/Services:query. The InnerException message was 'Error | ||
144 | 1257 | $"'http://schemas.microsoft.com/2003/10/Serialization/Arrays:anyType' contains data from a type that map | ||
144 | 1258 | $"'http://schemas.microsoft.com/xrm/2011/Contracts:{input?.GetType()}'. The deserializer has no knowledg | ||
144 | 1259 | $"Consider changing the implementation of the ResolveName method on your DataContractResolver to return | ||
144 | 1260 | $"'{input?.GetType()}' and namespace 'http://schemas.microsoft.com/xrm/2011/Contracts'.'. Please see In | ||
1261 |
| |||
144 | 1262 | if (input is int) | ||
4 | 1263 | { | ||
4 | 1264 | set.Add((int)input); | ||
4 | 1265 | } | ||
140 | 1266 | else if (input is string) | ||
1 | 1267 | { | ||
1 | 1268 | set.Add(int.Parse(input as string)); | ||
1 | 1269 | } | ||
139 | 1270 | else if (input is int[]) | ||
0 | 1271 | { | ||
0 | 1272 | set.UnionWith(input as int[]); | ||
0 | 1273 | } | ||
139 | 1274 | else if (input is string[]) | ||
0 | 1275 | { | ||
0 | 1276 | set.UnionWith((input as string[]).Select(s => int.Parse(s))); | ||
0 | 1277 | } | ||
139 | 1278 | else if (input is DataCollection<object>) | ||
28 | 1279 | { | ||
28 | 1280 | var collection = input as DataCollection<object>; | ||
1281 |
| |||
62 | 1282 | if (collection.All(o => o is int)) | ||
7 | 1283 | { | ||
7 | 1284 | set.UnionWith(collection.Cast<int>()); | ||
7 | 1285 | } | ||
49 | 1286 | else if (collection.All(o => o is string)) | ||
9 | 1287 | { | ||
25 | 1288 | set.UnionWith(collection.Select(o => int.Parse(o as string))); | ||
9 | 1289 | } | ||
12 | 1290 | else if (collection.Count == 1 && collection[0] is int[]) | ||
8 | 1291 | { | ||
8 | 1292 | set.UnionWith(collection[0] as int[]); | ||
8 | 1293 | } | ||
4 | 1294 | else if (collection.Count == 1 && collection[0] is string[]) | ||
0 | 1295 | { | ||
0 | 1296 | set.UnionWith((collection[0] as string[]).Select(s => int.Parse(s))); | ||
0 | 1297 | } | ||
1298 | else | |||
4 | 1299 | { | ||
4 | 1300 | throw new FaultException(new FaultReason(faultReason)); | ||
1301 | } | |||
24 | 1302 | } | ||
111 | 1303 | else if (isOptionSetValueCollectionAccepted && input is OptionSetValueCollection) | ||
110 | 1304 | { | ||
326 | 1305 | set.UnionWith((input as OptionSetValueCollection).Select(osv => osv.Value)); | ||
110 | 1306 | } | ||
1307 | else | |||
1 | 1308 | { | ||
1 | 1309 | throw new FaultException(new FaultReason(faultReason)); | ||
1310 | } | |||
1311 |
| |||
139 | 1312 | return set; | ||
139 | 1313 | } | ||
1314 | #endif | |||
1315 |
| |||
1316 | protected static Expression TransformExpressionGetDateOnlyPart(Expression input) | |||
180 | 1317 | { | ||
180 | 1318 | return Expression.Call(input, typeof(DateTime).GetMethod("get_Date")); | ||
180 | 1319 | } | ||
1320 |
| |||
1321 | protected static Expression TransformExpressionValueBasedOnOperator(ConditionOperator op, Expression input) | |||
5058 | 1322 | { | ||
5058 | 1323 | switch (op) | ||
1324 | { | |||
1325 | case ConditionOperator.Today: | |||
1326 | case ConditionOperator.Yesterday: | |||
1327 | case ConditionOperator.Tomorrow: | |||
1328 | case ConditionOperator.On: | |||
1329 | case ConditionOperator.OnOrAfter: | |||
1330 | case ConditionOperator.OnOrBefore: | |||
180 | 1331 | return TransformExpressionGetDateOnlyPart(input); | ||
1332 |
| |||
1333 | default: | |||
4878 | 1334 | return input; //No transformation | ||
1335 | } | |||
5058 | 1336 | } | ||
1337 |
| |||
1338 | protected static Expression TranslateConditionExpressionEqual(XrmFakedContext context, TypedConditionExpression | |||
2409 | 1339 | { | ||
1340 |
| |||
2409 | 1341 | BinaryExpression expOrValues = Expression.Or(Expression.Constant(false), Expression.Constant(false)); | ||
1342 |
| |||
2409 | 1343 | object unaryOperatorValue = null; | ||
1344 |
| |||
2409 | 1345 | switch (c.CondExpression.Operator) | ||
1346 | { | |||
1347 | case ConditionOperator.Today: | |||
12 | 1348 | unaryOperatorValue = DateTime.Today; | ||
12 | 1349 | break; | ||
1350 | case ConditionOperator.Yesterday: | |||
12 | 1351 | unaryOperatorValue = DateTime.Today.AddDays(-1); | ||
12 | 1352 | break; | ||
1353 | case ConditionOperator.Tomorrow: | |||
12 | 1354 | unaryOperatorValue = DateTime.Today.AddDays(1); | ||
12 | 1355 | break; | ||
1356 | case ConditionOperator.EqualUserId: | |||
1357 | case ConditionOperator.NotEqualUserId: | |||
12 | 1358 | unaryOperatorValue = context.CallerId.Id; | ||
12 | 1359 | break; | ||
1360 |
| |||
1361 | case ConditionOperator.EqualBusinessId: | |||
1362 | case ConditionOperator.NotEqualBusinessId: | |||
6 | 1363 | unaryOperatorValue = context.BusinessUnitId.Id; | ||
6 | 1364 | break; | ||
1365 | } | |||
1366 |
| |||
2409 | 1367 | if (unaryOperatorValue != null) | ||
54 | 1368 | { | ||
1369 | //c.Values empty in this case | |||
54 | 1370 | var leftHandSideExpression = GetAppropiateCastExpressionBasedOnType(c.AttributeType, getAttributeValueEx | ||
54 | 1371 | var transformedExpression = TransformExpressionValueBasedOnOperator(c.CondExpression.Operator, leftHandS | ||
1372 |
| |||
54 | 1373 | expOrValues = Expression.Equal(transformedExpression, | ||
54 | 1374 | GetAppropiateTypedValueAndType(unaryOperatorValue, c.AttributeType)); | ||
54 | 1375 | } | ||
1376 | #if FAKE_XRM_EASY_9 | |||
405 | 1377 | else if (c.AttributeType == typeof(OptionSetValueCollection)) | ||
9 | 1378 | { | ||
9 | 1379 | var conditionValue = GetSingleConditionValue(c); | ||
1380 |
| |||
6 | 1381 | var leftHandSideExpression = GetAppropiateCastExpressionBasedOnType(c.AttributeType, getAttributeValueEx | ||
6 | 1382 | var rightHandSideExpression = Expression.Constant(ConvertToHashSetOfInt(conditionValue, isOptionSetValue | ||
1383 |
| |||
5 | 1384 | expOrValues = Expression.Equal( | ||
5 | 1385 | Expression.Call(leftHandSideExpression, typeof(HashSet<int>).GetMethod("SetEquals"), rightHandSideEx | ||
5 | 1386 | Expression.Constant(true)); | ||
5 | 1387 | } | ||
1388 | #endif | |||
1389 | else | |||
2346 | 1390 | { | ||
11730 | 1391 | foreach (object value in c.CondExpression.Values) | ||
2346 | 1392 | { | ||
2346 | 1393 | var leftHandSideExpression = GetAppropiateCastExpressionBasedOnType(c.AttributeType, getAttributeVal | ||
2346 | 1394 | var transformedExpression = TransformExpressionValueBasedOnOperator(c.CondExpression.Operator, leftH | ||
1395 |
| |||
2346 | 1396 | expOrValues = Expression.Or(expOrValues, Expression.Equal( | ||
2346 | 1397 | transformedExpression, | ||
2346 | 1398 | TransformExpressionValueBasedOnOperator(c.CondExpression.Operator, GetAppropiateTypedVal | ||
1399 |
| |||
1400 |
| |||
2346 | 1401 | } | ||
2346 | 1402 | } | ||
1403 |
| |||
2405 | 1404 | return Expression.AndAlso( | ||
2405 | 1405 | containsAttributeExpr, | ||
2405 | 1406 | Expression.AndAlso(Expression.NotEqual(getAttributeValueExpr, Expression.Constant(null)), | ||
2405 | 1407 | expOrValues)); | ||
2405 | 1408 | } | ||
1409 |
| |||
1410 | private static object GetSingleConditionValue(TypedConditionExpression c) | |||
9 | 1411 | { | ||
9 | 1412 | if (c.CondExpression.Values.Count != 1) | ||
1 | 1413 | { | ||
1 | 1414 | FakeOrganizationServiceFault.Throw(ErrorCodes.InvalidArgument, $"The {c.CondExpression.Operator} require | ||
0 | 1415 | } | ||
1416 |
| |||
8 | 1417 | var conditionValue = c.CondExpression.Values.Single(); | ||
1418 |
| |||
8 | 1419 | if (!(conditionValue is string) && conditionValue is IEnumerable) | ||
4 | 1420 | { | ||
4 | 1421 | var conditionValueEnumerable = conditionValue as IEnumerable; | ||
4 | 1422 | var count = 0; | ||
1423 |
| |||
26 | 1424 | foreach (var obj in conditionValueEnumerable) | ||
7 | 1425 | { | ||
7 | 1426 | count++; | ||
7 | 1427 | conditionValue = obj; | ||
7 | 1428 | } | ||
1429 |
| |||
4 | 1430 | if (count != 1) | ||
2 | 1431 | { | ||
2 | 1432 | FakeOrganizationServiceFault.Throw(ErrorCodes.InvalidArgument, $"The {c.CondExpression.Operator} req | ||
0 | 1433 | } | ||
2 | 1434 | } | ||
1435 |
| |||
6 | 1436 | return conditionValue; | ||
6 | 1437 | } | ||
1438 |
| |||
1439 | protected static Expression TranslateConditionExpressionIn(TypedConditionExpression tc, Expression getAttributeV | |||
38 | 1440 | { | ||
38 | 1441 | var c = tc.CondExpression; | ||
1442 |
| |||
38 | 1443 | BinaryExpression expOrValues = Expression.Or(Expression.Constant(false), Expression.Constant(false)); | ||
1444 |
| |||
1445 | #if FAKE_XRM_EASY_9 | |||
18 | 1446 | if (tc.AttributeType == typeof(OptionSetValueCollection)) | ||
14 | 1447 | { | ||
14 | 1448 | var leftHandSideExpression = GetAppropiateCastExpressionBasedOnType(tc.AttributeType, getAttributeValueE | ||
14 | 1449 | var rightHandSideExpression = Expression.Constant(ConvertToHashSetOfInt(c.Values, isOptionSetValueCollec | ||
1450 |
| |||
12 | 1451 | expOrValues = Expression.Equal( | ||
12 | 1452 | Expression.Call(leftHandSideExpression, typeof(HashSet<int>).GetMethod("SetEquals"), rightHandSideEx | ||
12 | 1453 | Expression.Constant(true)); | ||
12 | 1454 | } | ||
1455 | else | |||
1456 | #endif | |||
24 | 1457 | { | ||
144 | 1458 | foreach (object value in c.Values) | ||
36 | 1459 | { | ||
36 | 1460 | if (value is Array) | ||
12 | 1461 | { | ||
108 | 1462 | foreach (var a in ((Array)value)) | ||
36 | 1463 | { | ||
36 | 1464 | expOrValues = Expression.Or(expOrValues, Expression.Equal( | ||
36 | 1465 | GetAppropiateCastExpressionBasedOnType(tc.AttributeType, getAttributeValueExpr, a), | ||
36 | 1466 | GetAppropiateTypedValueAndType(a, tc.AttributeType))); | ||
36 | 1467 | } | ||
12 | 1468 | } | ||
1469 | else | |||
24 | 1470 | { | ||
24 | 1471 | expOrValues = Expression.Or(expOrValues, Expression.Equal( | ||
24 | 1472 | GetAppropiateCastExpressionBasedOnType(tc.AttributeType, getAttributeValueExpr, valu | ||
24 | 1473 | GetAppropiateTypedValueAndType(value, tc.AttributeType))); | ||
24 | 1474 | } | ||
36 | 1475 | } | ||
24 | 1476 | } | ||
1477 |
| |||
36 | 1478 | return Expression.AndAlso( | ||
36 | 1479 | containsAttributeExpr, | ||
36 | 1480 | Expression.AndAlso(Expression.NotEqual(getAttributeValueExpr, Expression.Constant(null)), | ||
36 | 1481 | expOrValues)); | ||
36 | 1482 | } | ||
1483 |
| |||
1484 | //protected static Expression TranslateConditionExpressionOn(ConditionExpression c, Expression getAttributeValue | |||
1485 | //{ | |||
1486 | // BinaryExpression expOrValues = Expression.Or(Expression.Constant(false), Expression.Constant(false)); | |||
1487 | // foreach (object value in c.Values) | |||
1488 | // { | |||
1489 |
| |||
1490 | // expOrValues = Expression.Or(expOrValues, Expression.Equal( | |||
1491 | // GetAppropiateCastExpressionBasedOnValue(getAttributeValueExpr, value), | |||
1492 | // GetAppropiateTypedValue(value))); | |||
1493 |
| |||
1494 |
| |||
1495 | // } | |||
1496 | // return Expression.AndAlso( | |||
1497 | // containsAttributeExpr, | |||
1498 | // Expression.AndAlso(Expression.NotEqual(getAttributeValueExpr, Expression.Constant(null)), | |||
1499 | // expOrValues)); | |||
1500 | //} | |||
1501 |
| |||
1502 | protected static Expression TranslateConditionExpressionGreaterThanOrEqual(XrmFakedContext context, TypedConditi | |||
38 | 1503 | { | ||
1504 | //var c = tc.CondExpression; | |||
1505 |
| |||
38 | 1506 | return Expression.Or( | ||
38 | 1507 | TranslateConditionExpressionEqual(context, tc, getAttributeValueExpr, containsAttributeE | ||
38 | 1508 | TranslateConditionExpressionGreaterThan(tc, getAttributeValueExpr, containsAttributeExpr | ||
1509 |
| |||
38 | 1510 | } | ||
1511 | protected static Expression TranslateConditionExpressionGreaterThan(TypedConditionExpression tc, Expression getA | |||
74 | 1512 | { | ||
74 | 1513 | var c = tc.CondExpression; | ||
1514 |
| |||
148 | 1515 | if (c.Values.Count(v => v != null) != 1) | ||
0 | 1516 | { | ||
0 | 1517 | throw new FaultException(new FaultReason($"The ConditonOperator.{c.Operator} requires 1 value/s, not {c. | ||
1518 | } | |||
1519 |
| |||
74 | 1520 | if (tc.AttributeType == typeof(string)) | ||
18 | 1521 | { | ||
18 | 1522 | return TranslateConditionExpressionGreaterThanString(tc, getAttributeValueExpr, containsAttributeExpr); | ||
1523 | } | |||
56 | 1524 | else if (GetAppropiateTypeForValue(c.Values[0]) == typeof(string)) | ||
0 | 1525 | { | ||
0 | 1526 | return TranslateConditionExpressionGreaterThanString(tc, getAttributeValueExpr, containsAttributeExpr); | ||
1527 | } | |||
1528 | else | |||
56 | 1529 | { | ||
56 | 1530 | BinaryExpression expOrValues = Expression.Or(Expression.Constant(false), Expression.Constant(false)); | ||
280 | 1531 | foreach (object value in c.Values) | ||
56 | 1532 | { | ||
56 | 1533 | var leftHandSideExpression = GetAppropiateCastExpressionBasedOnType(tc.AttributeType, getAttributeVa | ||
56 | 1534 | var transformedExpression = TransformExpressionValueBasedOnOperator(tc.CondExpression.Operator, left | ||
1535 |
| |||
56 | 1536 | expOrValues = Expression.Or(expOrValues, | ||
56 | 1537 | Expression.GreaterThan( | ||
56 | 1538 | transformedExpression, | ||
56 | 1539 | TransformExpressionValueBasedOnOperator(tc.CondExpression.Operator, GetAppropiateTypedVa | ||
56 | 1540 | } | ||
56 | 1541 | return Expression.AndAlso( | ||
56 | 1542 | containsAttributeExpr, | ||
56 | 1543 | Expression.AndAlso(Expression.NotEqual(getAttributeValueExpr, Expression.Constant(null)) | ||
56 | 1544 | expOrValues)); | ||
1545 | } | |||
1546 |
| |||
74 | 1547 | } | ||
1548 |
| |||
1549 | protected static Expression TranslateConditionExpressionGreaterThanString(TypedConditionExpression tc, Expressio | |||
18 | 1550 | { | ||
18 | 1551 | var c = tc.CondExpression; | ||
1552 |
| |||
18 | 1553 | BinaryExpression expOrValues = Expression.Or(Expression.Constant(false), Expression.Constant(false)); | ||
90 | 1554 | foreach (object value in c.Values) | ||
18 | 1555 | { | ||
18 | 1556 | var leftHandSideExpression = GetAppropiateCastExpressionBasedOnType(tc.AttributeType, getAttributeValueE | ||
18 | 1557 | var transformedExpression = TransformExpressionValueBasedOnOperator(tc.CondExpression.Operator, leftHand | ||
1558 |
| |||
18 | 1559 | var left = transformedExpression; | ||
18 | 1560 | var right = TransformExpressionValueBasedOnOperator(tc.CondExpression.Operator, GetAppropiateTypedValueA | ||
1561 |
| |||
18 | 1562 | var methodCallExpr = GetCompareToExpression<string>(left, right); | ||
1563 |
| |||
18 | 1564 | expOrValues = Expression.Or(expOrValues, | ||
18 | 1565 | Expression.GreaterThan( | ||
18 | 1566 | methodCallExpr, | ||
18 | 1567 | Expression.Constant(0))); | ||
18 | 1568 | } | ||
18 | 1569 | return Expression.AndAlso( | ||
18 | 1570 | containsAttributeExpr, | ||
18 | 1571 | Expression.AndAlso(Expression.NotEqual(getAttributeValueExpr, Expression.Constant(null)), | ||
18 | 1572 | expOrValues)); | ||
18 | 1573 | } | ||
1574 |
| |||
1575 | protected static Expression TranslateConditionExpressionLessThanOrEqual(XrmFakedContext context, TypedConditionE | |||
28 | 1576 | { | ||
1577 | //var c = tc.CondExpression; | |||
1578 |
| |||
28 | 1579 | return Expression.Or( | ||
28 | 1580 | TranslateConditionExpressionEqual(context, tc, getAttributeValueExpr, containsAttributeE | ||
28 | 1581 | TranslateConditionExpressionLessThan(tc, getAttributeValueExpr, containsAttributeExpr)); | ||
1582 |
| |||
28 | 1583 | } | ||
1584 |
| |||
1585 | protected static Expression GetCompareToExpression<T>(Expression left, Expression right) | |||
48 | 1586 | { | ||
48 | 1587 | return Expression.Call(left, typeof(T).GetMethod("CompareTo", new Type[] { typeof(string) }), new[] { right | ||
48 | 1588 | } | ||
1589 |
| |||
1590 | protected static Expression TranslateConditionExpressionLessThanString(TypedConditionExpression tc, Expression g | |||
30 | 1591 | { | ||
30 | 1592 | var c = tc.CondExpression; | ||
1593 |
| |||
30 | 1594 | BinaryExpression expOrValues = Expression.Or(Expression.Constant(false), Expression.Constant(false)); | ||
150 | 1595 | foreach (object value in c.Values) | ||
30 | 1596 | { | ||
30 | 1597 | var leftHandSideExpression = GetAppropiateCastExpressionBasedOnType(tc.AttributeType, getAttributeValueE | ||
30 | 1598 | var transformedLeftHandSideExpression = TransformExpressionValueBasedOnOperator(tc.CondExpression.Operat | ||
1599 |
| |||
30 | 1600 | var rightHandSideExpression = TransformExpressionValueBasedOnOperator(tc.CondExpression.Operator, GetApp | ||
1601 |
| |||
1602 | //var compareToMethodCall = Expression.Call(transformedLeftHandSideExpression, typeof(string).GetMethod( | |||
30 | 1603 | var compareToMethodCall = GetCompareToExpression<string>(transformedLeftHandSideExpression, rightHandSid | ||
1604 |
| |||
30 | 1605 | expOrValues = Expression.Or(expOrValues, | ||
30 | 1606 | Expression.LessThan(compareToMethodCall, Expression.Constant(0))); | ||
30 | 1607 | } | ||
30 | 1608 | return Expression.AndAlso( | ||
30 | 1609 | containsAttributeExpr, | ||
30 | 1610 | Expression.AndAlso(Expression.NotEqual(getAttributeValueExpr, Expression.Constant(null)), | ||
30 | 1611 | expOrValues)); | ||
30 | 1612 | } | ||
1613 |
| |||
1614 | protected static Expression TranslateConditionExpressionLessThan(TypedConditionExpression tc, Expression getAttr | |||
82 | 1615 | { | ||
82 | 1616 | var c = tc.CondExpression; | ||
1617 |
| |||
164 | 1618 | if (c.Values.Count(v => v != null) != 1) | ||
0 | 1619 | { | ||
0 | 1620 | throw new FaultException(new FaultReason($"The ConditonOperator.{c.Operator} requires 1 value/s, not {c. | ||
1621 | } | |||
1622 |
| |||
82 | 1623 | if (tc.AttributeType == typeof(string)) | ||
24 | 1624 | { | ||
24 | 1625 | return TranslateConditionExpressionLessThanString(tc, getAttributeValueExpr, containsAttributeExpr); | ||
1626 | } | |||
58 | 1627 | else if (GetAppropiateTypeForValue(c.Values[0]) == typeof(string)) | ||
6 | 1628 | { | ||
6 | 1629 | return TranslateConditionExpressionLessThanString(tc, getAttributeValueExpr, containsAttributeExpr); | ||
1630 | } | |||
1631 | else | |||
52 | 1632 | { | ||
52 | 1633 | BinaryExpression expOrValues = Expression.Or(Expression.Constant(false), Expression.Constant(false)); | ||
260 | 1634 | foreach (object value in c.Values) | ||
52 | 1635 | { | ||
52 | 1636 | var leftHandSideExpression = GetAppropiateCastExpressionBasedOnType(tc.AttributeType, getAttributeVa | ||
52 | 1637 | var transformedExpression = TransformExpressionValueBasedOnOperator(tc.CondExpression.Operator, left | ||
1638 |
| |||
52 | 1639 | expOrValues = Expression.Or(expOrValues, | ||
52 | 1640 | Expression.LessThan( | ||
52 | 1641 | transformedExpression, | ||
52 | 1642 | TransformExpressionValueBasedOnOperator(tc.CondExpression.Operator, GetAppropiateTypedVa | ||
52 | 1643 | } | ||
52 | 1644 | return Expression.AndAlso( | ||
52 | 1645 | containsAttributeExpr, | ||
52 | 1646 | Expression.AndAlso(Expression.NotEqual(getAttributeValueExpr, Expression.Constant(null)) | ||
52 | 1647 | expOrValues)); | ||
1648 | } | |||
1649 |
| |||
82 | 1650 | } | ||
1651 |
| |||
1652 | protected static Expression TranslateConditionExpressionLast(TypedConditionExpression tc, Expression getAttribut | |||
36 | 1653 | { | ||
36 | 1654 | var c = tc.CondExpression; | ||
1655 |
| |||
36 | 1656 | var beforeDateTime = default(DateTime); | ||
36 | 1657 | var currentDateTime = DateTime.UtcNow; | ||
36 | 1658 | switch (c.Operator) | ||
1659 | { | |||
1660 | case ConditionOperator.LastXHours: | |||
6 | 1661 | beforeDateTime = currentDateTime.AddHours(-(int)c.Values[0]); | ||
6 | 1662 | break; | ||
1663 | case ConditionOperator.LastXDays: | |||
6 | 1664 | beforeDateTime = currentDateTime.AddDays(-(int)c.Values[0]); | ||
6 | 1665 | break; | ||
1666 | case ConditionOperator.Last7Days: | |||
6 | 1667 | beforeDateTime = currentDateTime.AddDays(-7); | ||
6 | 1668 | break; | ||
1669 | case ConditionOperator.LastXWeeks: | |||
6 | 1670 | beforeDateTime = currentDateTime.AddDays(-7 * (int)c.Values[0]); | ||
6 | 1671 | break; | ||
1672 | case ConditionOperator.LastXMonths: | |||
6 | 1673 | beforeDateTime = currentDateTime.AddMonths(-(int)c.Values[0]); | ||
6 | 1674 | break; | ||
1675 | case ConditionOperator.LastXYears: | |||
6 | 1676 | beforeDateTime = currentDateTime.AddYears(-(int)c.Values[0]); | ||
6 | 1677 | break; | ||
1678 | } | |||
1679 |
| |||
36 | 1680 | c.Values.Clear(); | ||
36 | 1681 | c.Values.Add(beforeDateTime); | ||
36 | 1682 | c.Values.Add(currentDateTime); | ||
1683 |
| |||
36 | 1684 | return TranslateConditionExpressionBetween(tc, getAttributeValueExpr, containsAttributeExpr); | ||
36 | 1685 | } | ||
1686 |
| |||
1687 | /// <summary> | |||
1688 | /// Takes a condition expression which needs translating into a 'between two dates' expression and works out the | |||
1689 | /// </summary> | |||
1690 | protected static Expression TranslateConditionExpressionBetweenDates(TypedConditionExpression tc, Expression get | |||
60 | 1691 | { | ||
60 | 1692 | var c = tc.CondExpression; | ||
1693 |
| |||
60 | 1694 | DateTime? fromDate = null; | ||
60 | 1695 | DateTime? toDate = null; | ||
1696 |
| |||
60 | 1697 | var today = DateTime.Today; | ||
60 | 1698 | var thisYear = today.Year; | ||
60 | 1699 | var thisMonth = today.Month; | ||
1700 |
| |||
1701 |
| |||
60 | 1702 | switch (c.Operator) | ||
1703 | { | |||
1704 | case ConditionOperator.ThisYear: // From first day of this year to last day of this year | |||
6 | 1705 | fromDate = new DateTime(thisYear, 1, 1); | ||
6 | 1706 | toDate = new DateTime(thisYear, 12, 31); | ||
6 | 1707 | break; | ||
1708 | case ConditionOperator.LastYear: // From first day of last year to last day of last year | |||
6 | 1709 | fromDate = new DateTime(thisYear - 1, 1, 1); | ||
6 | 1710 | toDate = new DateTime(thisYear - 1, 12, 31); | ||
6 | 1711 | break; | ||
1712 | case ConditionOperator.NextYear: // From first day of next year to last day of next year | |||
6 | 1713 | fromDate = new DateTime(thisYear + 1, 1, 1); | ||
6 | 1714 | toDate = new DateTime(thisYear + 1, 12, 31); | ||
6 | 1715 | break; | ||
1716 | case ConditionOperator.ThisMonth: // From first day of this month to last day of this month | |||
6 | 1717 | fromDate = new DateTime(thisYear, thisMonth, 1); | ||
1718 | // Last day of this month: Add one month to the first of this month, and then remove one day | |||
6 | 1719 | toDate = new DateTime(thisYear, thisMonth, 1).AddMonths(1).AddDays(-1); | ||
6 | 1720 | break; | ||
1721 | case ConditionOperator.LastMonth: // From first day of last month to last day of last month | |||
6 | 1722 | fromDate = new DateTime(thisYear, thisMonth, 1).AddMonths(-1); | ||
1723 | // Last day of last month: One day before the first of this month | |||
6 | 1724 | toDate = new DateTime(thisYear, thisMonth, 1).AddDays(-1); | ||
6 | 1725 | break; | ||
1726 | case ConditionOperator.NextMonth: // From first day of next month to last day of next month | |||
6 | 1727 | fromDate = new DateTime(thisYear, thisMonth, 1).AddMonths(1); | ||
1728 | // LAst day of Next Month: Add two months to the first of this month, and then go back one day | |||
6 | 1729 | toDate = new DateTime(thisYear, thisMonth, 1).AddMonths(2).AddDays(-1); | ||
6 | 1730 | break; | ||
1731 | case ConditionOperator.ThisWeek: | |||
6 | 1732 | fromDate = today.ToFirstDayOfDeltaWeek(); | ||
6 | 1733 | toDate = today.ToLastDayOfDeltaWeek().AddDays(1); | ||
6 | 1734 | break; | ||
1735 | case ConditionOperator.LastWeek: | |||
6 | 1736 | fromDate = today.ToFirstDayOfDeltaWeek(-1); | ||
6 | 1737 | toDate = today.ToLastDayOfDeltaWeek(-1).AddDays(1); | ||
6 | 1738 | break; | ||
1739 | case ConditionOperator.NextWeek: | |||
6 | 1740 | fromDate = today.ToFirstDayOfDeltaWeek(1); | ||
6 | 1741 | toDate = today.ToLastDayOfDeltaWeek(1).AddDays(1); | ||
6 | 1742 | break; | ||
1743 | case ConditionOperator.InFiscalYear: | |||
6 | 1744 | var fiscalYear = (int)c.Values[0]; | ||
6 | 1745 | c.Values.Clear(); | ||
6 | 1746 | var fiscalYearDate = context.FiscalYearSettings?.StartDate ?? new DateTime(fiscalYear, 4, 1); | ||
6 | 1747 | fromDate = fiscalYearDate; | ||
6 | 1748 | toDate = fiscalYearDate.AddYears(1).AddDays(-1); | ||
6 | 1749 | break; | ||
1750 | } | |||
1751 |
| |||
60 | 1752 | c.Values.Add(fromDate); | ||
60 | 1753 | c.Values.Add(toDate); | ||
1754 |
| |||
60 | 1755 | return TranslateConditionExpressionBetween(tc, getAttributeValueExpr, containsAttributeExpr); | ||
60 | 1756 | } | ||
1757 |
| |||
1758 |
| |||
1759 | protected static Expression TranslateConditionExpressionOlderThan(TypedConditionExpression tc, Expression getAtt | |||
38 | 1760 | { | ||
38 | 1761 | var c = tc.CondExpression; | ||
1762 |
| |||
38 | 1763 | var valueToAdd = 0; | ||
1764 |
| |||
38 | 1765 | if (!int.TryParse(c.Values[0].ToString(), out valueToAdd)) | ||
0 | 1766 | { | ||
0 | 1767 | throw new Exception(c.Operator + " requires an integer value in the ConditionExpression."); | ||
1768 | } | |||
1769 |
| |||
38 | 1770 | if (valueToAdd <= 0) | ||
0 | 1771 | { | ||
0 | 1772 | throw new Exception(c.Operator + " requires a value greater than 0."); | ||
1773 | } | |||
1774 |
| |||
38 | 1775 | DateTime toDate = default(DateTime); | ||
1776 |
| |||
38 | 1777 | switch (c.Operator) | ||
1778 | { | |||
1779 | case ConditionOperator.OlderThanXMonths: | |||
18 | 1780 | toDate = DateTime.UtcNow.AddMonths(-valueToAdd); | ||
18 | 1781 | break; | ||
1782 | #if !FAKE_XRM_EASY && !FAKE_XRM_EASY_2013 | |||
1783 | case ConditionOperator.OlderThanXMinutes: | |||
4 | 1784 | toDate = DateTime.UtcNow.AddMinutes(-valueToAdd); | ||
4 | 1785 | break; | ||
1786 | case ConditionOperator.OlderThanXHours: | |||
4 | 1787 | toDate = DateTime.UtcNow.AddHours(-valueToAdd); | ||
4 | 1788 | break; | ||
1789 | case ConditionOperator.OlderThanXDays: | |||
4 | 1790 | toDate = DateTime.UtcNow.AddDays(-valueToAdd); | ||
4 | 1791 | break; | ||
1792 | case ConditionOperator.OlderThanXWeeks: | |||
4 | 1793 | toDate = DateTime.UtcNow.AddDays(-7 * valueToAdd); | ||
4 | 1794 | break; | ||
1795 | case ConditionOperator.OlderThanXYears: | |||
4 | 1796 | toDate = DateTime.UtcNow.AddYears(-valueToAdd); | ||
4 | 1797 | break; | ||
1798 | #endif | |||
1799 | } | |||
1800 |
| |||
38 | 1801 | return TranslateConditionExpressionOlderThan(tc, getAttributeValueExpr, containsAttributeExpr, toDate); | ||
38 | 1802 | } | ||
1803 |
| |||
1804 |
| |||
1805 | protected static Expression TranslateConditionExpressionBetween(TypedConditionExpression tc, Expression getAttri | |||
144 | 1806 | { | ||
144 | 1807 | var c = tc.CondExpression; | ||
1808 |
| |||
1809 | object value1, value2; | |||
144 | 1810 | value1 = c.Values[0]; | ||
144 | 1811 | value2 = c.Values[1]; | ||
1812 |
| |||
1813 | //Between the range... | |||
144 | 1814 | var exp = Expression.And( | ||
144 | 1815 | Expression.GreaterThanOrEqual( | ||
144 | 1816 | GetAppropiateCastExpressionBasedOnType(tc.AttributeType, getAttributeValueExpr, value1), | ||
144 | 1817 | GetAppropiateTypedValueAndType(value1, tc.AttributeType)), | ||
144 | 1818 |
| ||
144 | 1819 | Expression.LessThanOrEqual( | ||
144 | 1820 | GetAppropiateCastExpressionBasedOnType(tc.AttributeType, getAttributeValueExpr, value2), | ||
144 | 1821 | GetAppropiateTypedValueAndType(value2, tc.AttributeType))); | ||
1822 |
| |||
1823 |
| |||
1824 | //and... attribute exists too | |||
144 | 1825 | return Expression.AndAlso( | ||
144 | 1826 | containsAttributeExpr, | ||
144 | 1827 | Expression.AndAlso(Expression.NotEqual(getAttributeValueExpr, Expression.Constant(null)), | ||
144 | 1828 | exp)); | ||
144 | 1829 | } | ||
1830 |
| |||
1831 | protected static Expression TranslateConditionExpressionNull(TypedConditionExpression tc, Expression getAttribut | |||
179 | 1832 | { | ||
179 | 1833 | var c = tc.CondExpression; | ||
1834 |
| |||
179 | 1835 | return Expression.Or(Expression.AndAlso( | ||
179 | 1836 | containsAttributeExpr, | ||
179 | 1837 | Expression.Equal( | ||
179 | 1838 | getAttributeValueExpr, | ||
179 | 1839 | Expression.Constant(null))), //Attribute is null | ||
179 | 1840 | Expression.AndAlso( | ||
179 | 1841 | Expression.Not(containsAttributeExpr), | ||
179 | 1842 | Expression.Constant(true))); //Or attribute is not defined (null) | ||
179 | 1843 | } | ||
1844 |
| |||
1845 | protected static Expression TranslateConditionExpressionOlderThan(TypedConditionExpression tc, Expression getAtt | |||
38 | 1846 | { | ||
38 | 1847 | var lessThanExpression = Expression.LessThan( | ||
38 | 1848 | GetAppropiateCastExpressionBasedOnType(tc.AttributeType, getAttributeValueExpr, olderThanDat | ||
38 | 1849 | GetAppropiateTypedValueAndType(olderThanDate, tc.AttributeType)); | ||
1850 |
| |||
38 | 1851 | return Expression.AndAlso(containsAttributeExpr, | ||
38 | 1852 | Expression.AndAlso(Expression.NotEqual(getAttributeValueExpr, Expression.Constant(null)), | ||
38 | 1853 | lessThanExpression)); | ||
38 | 1854 | } | ||
1855 |
| |||
1856 | protected static Expression TranslateConditionExpressionEndsWith(TypedConditionExpression tc, Expression getAttr | |||
18 | 1857 | { | ||
18 | 1858 | var c = tc.CondExpression; | ||
1859 |
| |||
1860 | //Append a ´%´at the end of each condition value | |||
36 | 1861 | var computedCondition = new ConditionExpression(c.AttributeName, c.Operator, c.Values.Select(x => "%" + x.To | ||
18 | 1862 | var typedComputedCondition = new TypedConditionExpression(computedCondition); | ||
18 | 1863 | typedComputedCondition.AttributeType = tc.AttributeType; | ||
1864 |
| |||
18 | 1865 | return TranslateConditionExpressionLike(typedComputedCondition, getAttributeValueExpr, containsAttributeExpr | ||
18 | 1866 | } | ||
1867 |
| |||
1868 | protected static Expression GetToStringExpression<T>(Expression e) | |||
52 | 1869 | { | ||
52 | 1870 | return Expression.Call(e, typeof(T).GetMethod("ToString", new Type[] { })); | ||
52 | 1871 | } | ||
1872 | protected static Expression GetCaseInsensitiveExpression(Expression e) | |||
2679 | 1873 | { | ||
2679 | 1874 | return Expression.Call(e, | ||
2679 | 1875 | typeof(string).GetMethod("ToLowerInvariant", new Type[] { })); | ||
2679 | 1876 | } | ||
1877 |
| |||
1878 | protected static Expression TranslateConditionExpressionLike(TypedConditionExpression tc, Expression getAttribut | |||
120 | 1879 | { | ||
120 | 1880 | var c = tc.CondExpression; | ||
1881 |
| |||
120 | 1882 | BinaryExpression expOrValues = Expression.Or(Expression.Constant(false), Expression.Constant(false)); | ||
120 | 1883 | Expression convertedValueToStr = Expression.Convert(GetAppropiateCastExpressionBasedOnType(tc.AttributeType, | ||
1884 |
| |||
120 | 1885 | Expression convertedValueToStrAndToLower = GetCaseInsensitiveExpression(convertedValueToStr); | ||
1886 |
| |||
120 | 1887 | string sLikeOperator = "%"; | ||
600 | 1888 | foreach (object value in c.Values) | ||
120 | 1889 | { | ||
120 | 1890 | var strValue = value.ToString(); | ||
120 | 1891 | string sMethod = ""; | ||
1892 |
| |||
120 | 1893 | if (strValue.EndsWith(sLikeOperator) && strValue.StartsWith(sLikeOperator)) | ||
36 | 1894 | sMethod = "Contains"; | ||
1895 |
| |||
84 | 1896 | else if (strValue.StartsWith(sLikeOperator)) | ||
18 | 1897 | sMethod = "EndsWith"; | ||
1898 |
| |||
1899 | else | |||
66 | 1900 | sMethod = "StartsWith"; | ||
1901 |
| |||
120 | 1902 | expOrValues = Expression.Or(expOrValues, Expression.Call( | ||
120 | 1903 | convertedValueToStrAndToLower, | ||
120 | 1904 | typeof(string).GetMethod(sMethod, new Type[] { typeof(string) }), | ||
120 | 1905 | Expression.Constant(value.ToString().ToLowerInvariant().Replace("%", "")) //Linq2CRM adds the percen | ||
120 | 1906 | )); | ||
120 | 1907 | } | ||
1908 |
| |||
120 | 1909 | return Expression.AndAlso( | ||
120 | 1910 | containsAttributeExpr, | ||
120 | 1911 | expOrValues); | ||
120 | 1912 | } | ||
1913 |
| |||
1914 | protected static Expression TranslateConditionExpressionContains(TypedConditionExpression tc, Expression getAttr | |||
24 | 1915 | { | ||
24 | 1916 | var c = tc.CondExpression; | ||
1917 |
| |||
1918 | //Append a ´%´at the end of each condition value | |||
48 | 1919 | var computedCondition = new ConditionExpression(c.AttributeName, c.Operator, c.Values.Select(x => "%" + x.To | ||
24 | 1920 | var computedTypedCondition = new TypedConditionExpression(computedCondition); | ||
24 | 1921 | computedTypedCondition.AttributeType = tc.AttributeType; | ||
1922 |
| |||
24 | 1923 | return TranslateConditionExpressionLike(computedTypedCondition, getAttributeValueExpr, containsAttributeExpr | ||
1924 |
| |||
24 | 1925 | } | ||
1926 |
| |||
1927 | protected static BinaryExpression TranslateMultipleConditionExpressions(QueryExpression qe, XrmFakedContext cont | |||
2322 | 1928 | { | ||
2322 | 1929 | BinaryExpression binaryExpression = null; //Default initialisation depending on logical operator | ||
2322 | 1930 | if (op == LogicalOperator.And) | ||
2286 | 1931 | binaryExpression = Expression.And(Expression.Constant(true), Expression.Constant(true)); | ||
1932 | else | |||
36 | 1933 | binaryExpression = Expression.Or(Expression.Constant(false), Expression.Constant(false)); | ||
1934 |
| |||
12987 | 1935 | foreach (var c in conditions) | ||
3027 | 1936 | { | ||
3027 | 1937 | var cEntityName = sEntityName; | ||
1938 | //Create a new typed expression | |||
3027 | 1939 | var typedExpression = new TypedConditionExpression(c); | ||
3027 | 1940 | typedExpression.IsOuter = bIsOuter; | ||
1941 |
| |||
3027 | 1942 | string sAttributeName = c.AttributeName; | ||
1943 |
| |||
1944 | //Find the attribute type if using early bound entities | |||
3027 | 1945 | if (context.ProxyTypesAssembly != null) | ||
2371 | 1946 | { | ||
1947 |
| |||
1948 | #if FAKE_XRM_EASY_2013 || FAKE_XRM_EASY_2015 || FAKE_XRM_EASY_2016 || FAKE_XRM_EASY_365 || FAKE_XRM_EASY_9 | |||
1995 | 1949 | if (c.EntityName != null) | ||
75 | 1950 | cEntityName = qe.GetEntityNameFromAlias(c.EntityName); | ||
1951 | else | |||
1920 | 1952 | { | ||
1920 | 1953 | if (c.AttributeName.IndexOf(".") >= 0) | ||
305 | 1954 | { | ||
305 | 1955 | var alias = c.AttributeName.Split('.')[0]; | ||
305 | 1956 | cEntityName = qe.GetEntityNameFromAlias(alias); | ||
305 | 1957 | sAttributeName = c.AttributeName.Split('.')[1]; | ||
305 | 1958 | } | ||
1920 | 1959 | } | ||
1960 |
| |||
1961 | #else | |||
1962 | //CRM 2011 | |||
448 | 1963 | if (c.AttributeName.IndexOf(".") >= 0) { | ||
72 | 1964 | var alias = c.AttributeName.Split('.')[0]; | ||
72 | 1965 | cEntityName = qe.GetEntityNameFromAlias(alias); | ||
72 | 1966 | sAttributeName = c.AttributeName.Split('.')[1]; | ||
72 | 1967 | } | ||
1968 | #endif | |||
1969 |
| |||
2371 | 1970 | var earlyBoundType = context.FindReflectedType(cEntityName); | ||
2371 | 1971 | if (earlyBoundType != null) | ||
2365 | 1972 | { | ||
2365 | 1973 | typedExpression.AttributeType = context.FindReflectedAttributeType(earlyBoundType, cEntityName, | ||
1974 |
| |||
1975 | // Special case when filtering on the name of a Lookup | |||
2359 | 1976 | if (typedExpression.AttributeType == typeof(EntityReference) && sAttributeName.EndsWith("name")) | ||
6 | 1977 | { | ||
6 | 1978 | var realAttributeName = c.AttributeName.Substring(0, c.AttributeName.Length - 4); | ||
1979 |
| |||
6 | 1980 | if (GetEarlyBoundTypeAttribute(earlyBoundType, sAttributeName) == null && GetEarlyBoundTypeA | ||
6 | 1981 | { | ||
1982 | // Need to make Lookups work against the real attribute, not the "name" suffixed attribu | |||
6 | 1983 | c.AttributeName = realAttributeName; | ||
6 | 1984 | } | ||
6 | 1985 | } | ||
2359 | 1986 | } | ||
2365 | 1987 | } | ||
1988 |
| |||
3021 | 1989 | ValidateSupportedTypedExpression(typedExpression); | ||
1990 |
| |||
1991 | //Build a binary expression | |||
3020 | 1992 | if (op == LogicalOperator.And) | ||
2954 | 1993 | { | ||
2954 | 1994 | binaryExpression = Expression.And(binaryExpression, TranslateConditionExpression(qe, context, typedE | ||
2928 | 1995 | } | ||
1996 | else | |||
66 | 1997 | binaryExpression = Expression.Or(binaryExpression, TranslateConditionExpression(qe, context, typedEx | ||
2994 | 1998 | } | ||
1999 |
| |||
2289 | 2000 | return binaryExpression; | ||
2289 | 2001 | } | ||
2002 |
| |||
2003 | protected static BinaryExpression TranslateMultipleFilterExpressions(QueryExpression qe, XrmFakedContext context | |||
240 | 2004 | { | ||
240 | 2005 | BinaryExpression binaryExpression = null; | ||
240 | 2006 | if (op == LogicalOperator.And) | ||
216 | 2007 | binaryExpression = Expression.And(Expression.Constant(true), Expression.Constant(true)); | ||
2008 | else | |||
24 | 2009 | binaryExpression = Expression.Or(Expression.Constant(false), Expression.Constant(false)); | ||
2010 |
| |||
1260 | 2011 | foreach (var f in filters) | ||
270 | 2012 | { | ||
270 | 2013 | var thisFilterLambda = TranslateFilterExpressionToExpression(qe, context, sEntityName, f, entity, bIsOut | ||
2014 |
| |||
2015 | //Build a binary expression | |||
270 | 2016 | if (op == LogicalOperator.And) | ||
228 | 2017 | { | ||
228 | 2018 | binaryExpression = Expression.And(binaryExpression, thisFilterLambda); | ||
228 | 2019 | } | ||
2020 | else | |||
42 | 2021 | binaryExpression = Expression.Or(binaryExpression, thisFilterLambda); | ||
270 | 2022 | } | ||
2023 |
| |||
240 | 2024 | return binaryExpression; | ||
240 | 2025 | } | ||
2026 |
| |||
2027 | protected static List<Expression> TranslateLinkedEntityFilterExpressionToExpression(QueryExpression qe, XrmFaked | |||
1581 | 2028 | { | ||
2029 | //In CRM 2011, condition expressions are at the LinkEntity level without an entity name | |||
2030 | //From CRM 2013, condition expressions were moved to outside the LinkEntity object at the QueryExpression le | |||
2031 | //with an EntityName alias attribute | |||
2032 |
| |||
2033 | //If we reach this point, it means we are translating filters at the Link Entity level (2011), | |||
2034 | //Therefore we need to prepend the alias attribute because the code to generate attributes for Joins (JoinAt | |||
1581 | 2035 | var linkedEntitiesQueryExpressions = new List<Expression>(); | ||
2036 |
| |||
1581 | 2037 | if (le.LinkCriteria != null) | ||
1482 | 2038 | { | ||
1482 | 2039 | var earlyBoundType = context.FindReflectedType(le.LinkToEntityName); | ||
1482 | 2040 | var attributeMetadata = context.AttributeMetadataNames.ContainsKey(le.LinkToEntityName) ? context.Attrib | ||
2041 |
| |||
5404 | 2042 | foreach (var ce in le.LinkCriteria.Conditions) | ||
479 | 2043 | { | ||
479 | 2044 | if (earlyBoundType != null) | ||
367 | 2045 | { | ||
367 | 2046 | var attributeInfo = GetEarlyBoundTypeAttribute(earlyBoundType, ce.AttributeName); | ||
367 | 2047 | if (attributeInfo == null && ce.AttributeName.EndsWith("name")) | ||
0 | 2048 | { | ||
2049 | // Special case for referencing the name of a EntityReference | |||
0 | 2050 | var sAttributeName = ce.AttributeName.Substring(0, ce.AttributeName.Length - 4); | ||
0 | 2051 | attributeInfo = GetEarlyBoundTypeAttribute(earlyBoundType, sAttributeName); | ||
2052 |
| |||
0 | 2053 | if (attributeInfo.PropertyType == typeof(EntityReference)) | ||
0 | 2054 | { | ||
2055 | // Don't mess up if other attributes follow this naming pattern | |||
0 | 2056 | ce.AttributeName = sAttributeName; | ||
0 | 2057 | } | ||
0 | 2058 | } | ||
367 | 2059 | } | ||
112 | 2060 | else if (attributeMetadata != null && !attributeMetadata.ContainsKey(ce.AttributeName) && ce.Attribu | ||
0 | 2061 | { | ||
2062 | // Special case for referencing the name of a EntityReference | |||
0 | 2063 | var sAttributeName = ce.AttributeName.Substring(0, ce.AttributeName.Length - 4); | ||
0 | 2064 | if (attributeMetadata.ContainsKey(sAttributeName)) | ||
0 | 2065 | { | ||
0 | 2066 | ce.AttributeName = sAttributeName; | ||
0 | 2067 | } | ||
0 | 2068 | } | ||
2069 |
| |||
479 | 2070 | var entityAlias = !string.IsNullOrEmpty(le.EntityAlias) ? le.EntityAlias : le.LinkToEntityName; | ||
479 | 2071 | ce.AttributeName = entityAlias + "." + ce.AttributeName; | ||
479 | 2072 | } | ||
2073 |
| |||
4490 | 2074 | foreach (var fe in le.LinkCriteria.Filters) | ||
22 | 2075 | { | ||
86 | 2076 | foreach (var ce in fe.Conditions) | ||
10 | 2077 | { | ||
10 | 2078 | var entityAlias = !string.IsNullOrEmpty(le.EntityAlias) ? le.EntityAlias : le.LinkToEntityName; | ||
10 | 2079 | ce.AttributeName = entityAlias + "." + ce.AttributeName; | ||
10 | 2080 | } | ||
22 | 2081 | } | ||
1482 | 2082 | } | ||
2083 |
| |||
2084 | //Translate this specific Link Criteria | |||
1581 | 2085 | linkedEntitiesQueryExpressions.Add(TranslateFilterExpressionToExpression(qe, context, le.LinkToEntityName, l | ||
2086 |
| |||
2087 | //Processed nested linked entities | |||
4971 | 2088 | foreach (var nestedLinkedEntity in le.LinkEntities) | ||
114 | 2089 | { | ||
114 | 2090 | var listOfExpressions = TranslateLinkedEntityFilterExpressionToExpression(qe, context, nestedLinkedEntit | ||
114 | 2091 | linkedEntitiesQueryExpressions.AddRange(listOfExpressions); | ||
114 | 2092 | } | ||
2093 |
| |||
1581 | 2094 | return linkedEntitiesQueryExpressions; | ||
1581 | 2095 | } | ||
2096 |
| |||
2097 | protected static Expression TranslateQueryExpressionFiltersToExpression(XrmFakedContext context, QueryExpression | |||
2921 | 2098 | { | ||
2921 | 2099 | var linkedEntitiesQueryExpressions = new List<Expression>(); | ||
11697 | 2100 | foreach (var le in qe.LinkEntities) | ||
1467 | 2101 | { | ||
1467 | 2102 | var listOfExpressions = TranslateLinkedEntityFilterExpressionToExpression(qe, context, le, entity); | ||
1467 | 2103 | linkedEntitiesQueryExpressions.AddRange(listOfExpressions); | ||
1467 | 2104 | } | ||
2105 |
| |||
2921 | 2106 | if (linkedEntitiesQueryExpressions.Count > 0 && qe.Criteria != null) | ||
849 | 2107 | { | ||
2108 | //Return the and of the two | |||
849 | 2109 | Expression andExpression = Expression.Constant(true); | ||
5625 | 2110 | foreach (var e in linkedEntitiesQueryExpressions) | ||
1539 | 2111 | { | ||
1539 | 2112 | andExpression = Expression.And(e, andExpression); | ||
2113 |
| |||
1539 | 2114 | } | ||
849 | 2115 | var feExpression = TranslateFilterExpressionToExpression(qe, context, qe.EntityName, qe.Criteria, entity | ||
849 | 2116 | return Expression.And(andExpression, feExpression); | ||
2117 | } | |||
2072 | 2118 | else if (linkedEntitiesQueryExpressions.Count > 0) | ||
24 | 2119 | { | ||
2120 | //Linked entity expressions only | |||
24 | 2121 | Expression andExpression = Expression.Constant(true); | ||
156 | 2122 | foreach (var e in linkedEntitiesQueryExpressions) | ||
42 | 2123 | { | ||
42 | 2124 | andExpression = Expression.And(e, andExpression); | ||
2125 |
| |||
42 | 2126 | } | ||
24 | 2127 | return andExpression; | ||
2128 | } | |||
2129 | else | |||
2048 | 2130 | { | ||
2131 | //Criteria only | |||
2048 | 2132 | return TranslateFilterExpressionToExpression(qe, context, qe.EntityName, qe.Criteria, entity, false); | ||
2133 | } | |||
2888 | 2134 | } | ||
2135 | protected static Expression TranslateFilterExpressionToExpression(QueryExpression qe, XrmFakedContext context, s | |||
4748 | 2136 | { | ||
5027 | 2137 | if (fe == null) return Expression.Constant(true); | ||
2138 |
| |||
4469 | 2139 | BinaryExpression conditionsLambda = null; | ||
4469 | 2140 | BinaryExpression filtersLambda = null; | ||
4469 | 2141 | if (fe.Conditions != null && fe.Conditions.Count > 0) | ||
2322 | 2142 | { | ||
2322 | 2143 | conditionsLambda = TranslateMultipleConditionExpressions(qe, context, sEntityName, fe.Conditions.ToList( | ||
2289 | 2144 | } | ||
2145 |
| |||
2146 | //Process nested filters recursively | |||
4436 | 2147 | if (fe.Filters != null && fe.Filters.Count > 0) | ||
240 | 2148 | { | ||
240 | 2149 | filtersLambda = TranslateMultipleFilterExpressions(qe, context, sEntityName, fe.Filters.ToList(), fe.Fil | ||
240 | 2150 | } | ||
2151 |
| |||
4436 | 2152 | if (conditionsLambda != null && filtersLambda != null) | ||
42 | 2153 | { | ||
2154 | //Satisfy both | |||
42 | 2155 | if (fe.FilterOperator == LogicalOperator.And) | ||
36 | 2156 | { | ||
36 | 2157 | return Expression.And(conditionsLambda, filtersLambda); | ||
2158 | } | |||
2159 | else | |||
6 | 2160 | { | ||
6 | 2161 | return Expression.Or(conditionsLambda, filtersLambda); | ||
2162 | } | |||
2163 | } | |||
4394 | 2164 | else if (conditionsLambda != null) | ||
2247 | 2165 | return conditionsLambda; | ||
2147 | 2166 | else if (filtersLambda != null) | ||
198 | 2167 | return filtersLambda; | ||
2168 |
| |||
1949 | 2169 | return Expression.Constant(true); //Satisfy filter if there are no conditions nor filters | ||
4715 | 2170 | } | ||
2171 | protected static Expression TranslateConditionExpressionNext(TypedConditionExpression tc, Expression getAttribut | |||
36 | 2172 | { | ||
36 | 2173 | var c = tc.CondExpression; | ||
2174 |
| |||
36 | 2175 | var nextDateTime = default(DateTime); | ||
36 | 2176 | var currentDateTime = DateTime.UtcNow; | ||
36 | 2177 | switch (c.Operator) | ||
2178 | { | |||
2179 | case ConditionOperator.NextXHours: | |||
6 | 2180 | nextDateTime = currentDateTime.AddHours((int)c.Values[0]); | ||
6 | 2181 | break; | ||
2182 | case ConditionOperator.NextXDays: | |||
6 | 2183 | nextDateTime = currentDateTime.AddDays((int)c.Values[0]); | ||
6 | 2184 | break; | ||
2185 | case ConditionOperator.Next7Days: | |||
6 | 2186 | nextDateTime = currentDateTime.AddDays(7); | ||
6 | 2187 | break; | ||
2188 | case ConditionOperator.NextXWeeks: | |||
6 | 2189 | nextDateTime = currentDateTime.AddDays(7 * (int)c.Values[0]); | ||
6 | 2190 | break; | ||
2191 | case ConditionOperator.NextXMonths: | |||
6 | 2192 | nextDateTime = currentDateTime.AddMonths((int)c.Values[0]); | ||
6 | 2193 | break; | ||
2194 | case ConditionOperator.NextXYears: | |||
6 | 2195 | nextDateTime = currentDateTime.AddYears((int)c.Values[0]); | ||
6 | 2196 | break; | ||
2197 | } | |||
2198 |
| |||
36 | 2199 | c.Values.Clear(); | ||
36 | 2200 | c.Values.Add(currentDateTime); | ||
36 | 2201 | c.Values.Add(nextDateTime); | ||
2202 |
| |||
2203 |
| |||
36 | 2204 | return TranslateConditionExpressionBetween(tc, getAttributeValueExpr, containsAttributeExpr); | ||
36 | 2205 | } | ||
2206 |
| |||
2207 | #if FAKE_XRM_EASY_9 | |||
2208 | protected static Expression TranslateConditionExpressionContainValues(TypedConditionExpression tc, Expression ge | |||
14 | 2209 | { | ||
14 | 2210 | var leftHandSideExpression = GetAppropiateCastExpressionBasedOnType(tc.AttributeType, getAttributeValueExpr, | ||
14 | 2211 | var rightHandSideExpression = Expression.Constant(ConvertToHashSetOfInt(tc.CondExpression.Values, isOptionSe | ||
2212 |
| |||
12 | 2213 | return Expression.AndAlso( | ||
12 | 2214 | containsAttributeExpr, | ||
12 | 2215 | Expression.AndAlso( | ||
12 | 2216 | Expression.NotEqual(getAttributeValueExpr, Expression.Constant(null)), | ||
12 | 2217 | Expression.Equal( | ||
12 | 2218 | Expression.Call(leftHandSideExpression, typeof(HashSet<int>).GetMethod("Overlaps"), right | ||
12 | 2219 | Expression.Constant(true)))); | ||
12 | 2220 | } | ||
2221 | #endif | |||
2222 | } | |||
2223 | } |