/
Validations.cs
202 lines (190 loc) · 9.94 KB
/
Validations.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
using Cinteros.Xrm.FetchXmlBuilder.Controls;
using Microsoft.Xrm.Sdk.Metadata;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace Cinteros.Xrm.FetchXmlBuilder.AppCode
{
internal static class Validations
{
internal static ControlValidationResult GetWarning(TreeNode node, FetchXmlBuilder fxb)
{
if (!fxb.settings.ShowValidation)
{
return null;
}
var name = node.Value("name");
var attribute = node.Value("attribute");
var alias = node.Value("alias");
var parententity = node.LocalEntityName();
switch (node.Name)
{
case "fetch":
break;
case "entity":
if (string.IsNullOrWhiteSpace(name))
{
return new ControlValidationResult(ControlValidationLevel.Warning, "Entity Name must be included.");
}
break;
case "link-entity":
if (string.IsNullOrWhiteSpace(name) ||
string.IsNullOrWhiteSpace(node.Value("to")) ||
string.IsNullOrWhiteSpace(node.Value("from")))
{
return new ControlValidationResult(ControlValidationLevel.Warning, "Link-Entity must include Name, To, From.");
}
if (node.Value("intersect") != "true" &&
fxb.GetAttribute(name, node.Value("from")) is AttributeMetadata fromAttr && fromAttr.IsPrimaryId == false)
{
return new ControlValidationResult(ControlValidationLevel.Info, "Links to records that aren't parents may cause paging issues.", "https://markcarrington.dev/2021/02/23/msdyn365-internals-paging-gotchas/#multiple_linked_entities");
}
break;
case "attribute":
if (string.IsNullOrWhiteSpace(name))
{
return new ControlValidationResult(ControlValidationLevel.Warning, "Attribute Name must be included.");
}
if (fxb.entities != null)
{
if (fxb.GetAttribute(parententity, name) is AttributeMetadata metaatt)
{
if (metaatt.IsValidForGrid.Value == false && metaatt.IsPrimaryId.Value != true)
{
return new ControlValidationResult(ControlValidationLevel.Warning, $"Attribute '{name}' has 'IsValidForGrid=false'.");
}
}
else
{
return new ControlValidationResult(ControlValidationLevel.Warning, $"Attribute '{name}' is not in the table '{parententity}'.");
}
}
if (node.IsFetchAggregate())
{
if (string.IsNullOrWhiteSpace(alias))
{
return new ControlValidationResult(ControlValidationLevel.Warning, "Aggregate should always have an Alias.", "https://docs.microsoft.com/en-us/powerapps/developer/data-platform/use-fetchxml-aggregation#about-aggregation");
}
if (node.Value("groupby") == "true")
{
if (!HasSortOnAttribute(node))
{
return new ControlValidationResult(ControlValidationLevel.Info, "Aggregate queries should be sorted by all grouped attributes for correct paging.", "https://markcarrington.dev/2022/01/13/fetchxml-aggregate-queries-lookup-fields-and-paging/");
}
if (fxb.GetAttribute(parententity, name) is LookupAttributeMetadata)
{
return new ControlValidationResult(ControlValidationLevel.Info, "Grouping by lookup columns can give inconsistent results across multiple pages.", "https://markcarrington.dev/2022/01/13/fetchxml-aggregate-queries-lookup-fields-and-paging/");
}
}
}
else
{
if (!string.IsNullOrWhiteSpace(alias))
{
return new ControlValidationResult(ControlValidationLevel.Info, "Alias is not recommended for not Aggregate queries.");
}
if (node.IsFetchDistinct() && !HasSortOnAttribute(node))
{
return new ControlValidationResult(ControlValidationLevel.Info, "Distinct queries should be sorted by all attributes for correct paging.", "https://markcarrington.dev/2020/12/08/dataverse-paging-with-distinct/");
}
}
break;
case "filter":
if (node.Nodes.Count == 0)
{
return new ControlValidationResult(ControlValidationLevel.Info, "Filter shound have at least one Condition.");
}
break;
case "condition":
if (string.IsNullOrWhiteSpace(attribute))
{
return new ControlValidationResult(ControlValidationLevel.Warning, "Attribute must be included.");
}
var entityname = node.Value("entityname");
if (!string.IsNullOrWhiteSpace(entityname) && !node.LocalEntityIsRoot())
{
return new ControlValidationResult(ControlValidationLevel.Error, "Cannot enter Entity for Link-Entity condition.");
}
if (string.IsNullOrWhiteSpace(entityname) && fxb.entities != null)
{
if (fxb.GetAttribute(parententity, attribute) is AttributeMetadata metaatt)
{
if (metaatt.IsValidForGrid.Value == false)
{
// return new ControlValidationResult(ControlValidationLevel.Error, $"Attribute '{attribute}' has 'IsValidForGrid=false'.");
}
}
else
{
return new ControlValidationResult(ControlValidationLevel.Warning, $"Attribute '{attribute}' is not in the table '{parententity}'.");
}
}
break;
case "value":
if (string.IsNullOrWhiteSpace(node.Value("#text")))
{
return new ControlValidationResult(ControlValidationLevel.Warning, "Value should be added.");
}
break;
case "order":
if (string.IsNullOrWhiteSpace(attribute) && string.IsNullOrWhiteSpace(alias))
{
return new ControlValidationResult(ControlValidationLevel.Warning, "Order Name must be included.");
}
if (node.Parent.Name == "link-entity")
{
return new ControlValidationResult(ControlValidationLevel.Info, "Sorting on a link-entity triggers legacy paging.", "https://docs.microsoft.com/en-us/powerapps/developer/data-platform/org-service/paging-behaviors-and-ordering#ordering-and-multiple-table-queries");
}
if (fxb.entities != null)
{
if (fxb.GetAttribute(parententity, attribute) is AttributeMetadata metaatt)
{
}
else
{
return new ControlValidationResult(ControlValidationLevel.Warning, $"Order Attribute '{attribute}' is not in the table '{parententity}'.");
}
}
if (node.IsFetchAggregate() && !string.IsNullOrWhiteSpace(alias))
{
var attr = node.Parent.Nodes.OfType<TreeNode>()
.Where(n => n.Name == "attribute" && n.Value("alias") == alias)
.FirstOrDefault();
if (attr != null &&
attr.Value("groupby") == "true" &&
fxb.GetAttribute(parententity, attr.Value("name")) is LookupAttributeMetadata)
{
return new ControlValidationResult(ControlValidationLevel.Info, "Sorting on a grouped lookup column may cause paging problems.", "https://markcarrington.dev/2022/01/13/fetchxml-aggregate-queries-lookup-fields-and-paging/");
}
}
break;
}
return null;
}
private static bool HasSortOnAttribute(TreeNode node)
{
var attrName = node.Value("name");
var attrAlias = node.Value("alias");
foreach (var sort in node.Parent.Nodes.OfType<TreeNode>().Where(c => c.Name == "order"))
{
var sortAttribute = sort.Value("attribute");
var sortAlias = sort.Value("alias");
if (string.IsNullOrWhiteSpace(attrAlias))
{
if (attrName == sortAttribute)
{
return true;
}
}
else
{
if (attrAlias == sortAlias)
{
return true;
}
}
}
return false;
}
}
}