/
breeze.getEntityGraph.js
314 lines (289 loc) · 11 KB
/
breeze.getEntityGraph.js
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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
//#region Copyright, Version, and Description
/*
* Copyright 2015 IdeaBlade, Inc. All Rights Reserved.
* Use, reproduction, distribution, and modification of this code is subject to the terms and
* conditions of the IdeaBlade Breeze license, available at http://www.breezejs.com/license
*
* Author: Ward Bell
* Version: 0.9.4
* --------------------------------------------------------------------------------
* Adds getEntityGraph method to Breeze EntityManager and EntityManager prototype
* Source:
* https://github.com/Breeze/breeze.js.labs/blob/master/breeze.getEntityGraph.js
*
* Depends on Breeze which it patches
*
* For discussion, see:
* http://www.breezejs.com/documentation/getentitygraph
*
* For example usage, see:
* https://github.com/Breeze/breeze.js.samples/tree/master/net/DocCode/DocCode/tests/getEntityGraphTests.js
*/
//#endregion
(function (definition) {
if (typeof breeze === "object") {
definition(breeze);
} else if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
// CommonJS or Node
var b = require('breeze-client');
definition(b);
} else if (typeof define === "function" && define["amd"]) {
// Requirejs / AMD
define(['breeze-client'], definition);
} else {
throw new Error("Can't find breeze");
}
}(function (breeze) {
'use strict';
var EntityManager = breeze.EntityManager;
var proto = EntityManager.prototype;
if (!EntityManager.getEntityGraph) {
/**
Get related entities of root entity (or root entities) as specified by expand.
@example
var graph = breeze.EntityManager.getEntityGraph(customer, 'Orders.OrderDetails');
// graph will be the customer, all of its orders and their details even if deleted.
@method getEntityGraph
@param roots {Entity|Array of Entity} The root entity or root entities.
@param expand {String|Array of String|Object} an expand string, a query expand clause, or array of string paths
@return {Array of Entity} root entities and their related entities, including deleted entities. Duplicates are removed and entity order is indeterminate.
**/
EntityManager.getEntityGraph = getEntityGraphCore;
}
if (!proto.getEntityGraph) {
/**
Execute query locally and return both the query results and their related entities as specified by the optional expand parameter or the query's expand clause.
@example
var query = breeze.EntityQuery.from('Customers')
.where('CompanyName', 'startsWith', 'Alfred')
.expand('Orders.OrderDetails');
var graph = manager.getEntityGraph(query);
// graph will be the 'Alfred' customers, their orders and their details even if deleted.
@method getEntityGraph
@param query {EntityQuery} A query to be executed against the manager's local cache.
@param [expand] {String|Array of String|Object} an expand string, a query expand clause, or array of string paths
@return {Array of Entity} local queried root entities and their related entities, including deleted entities. Duplicates are removed and entity order is indeterminate.
**/
/**
Get related entities of root entity (or root entities) as specified by expand.
@example
var graph = manager.getEntityGraph(customer, 'Orders.OrderDetails');
// graph will be the customer, all of its orders and their details even if deleted.
@method getEntityGraph
@param roots {Entity|Array of Entity} The root entity or root entities.
@param expand {String|Array of String|Object} an expand string, a query expand clause, or array of string paths
@return {Array of Entity} root entities and their related entities, including deleted entities. Duplicates are removed and entity order is indeterminate.
**/
proto.getEntityGraph = getEntityGraph;
}
function getEntityGraph(roots, expand) {
if (roots instanceof breeze.EntityQuery) {
var newRoots = this.executeQueryLocally(roots);
return getEntityGraphCore(newRoots, expand || roots.expandClause);
} else {
return getEntityGraphCore(roots, expand);
}
}
function getEntityGraphCore(roots, expand) {
var entityGroupMap, graph = [], rootType;
roots = Array.isArray(roots) ? roots : [roots];
addToGraph(roots); // removes dups & nulls
roots = graph.slice(); // copy of de-duped roots
if (roots.length) {
getRootInfo();
getExpand();
buildGraph();
}
return graph;
function addToGraph(entities) {
entities.forEach(function (entity) {
if (entity && graph.indexOf(entity) < 0) {
graph.push(entity);
}
});
}
function getRootInfo() {
var compatTypes;
roots.forEach(function (root, ix) {
var aspect;
if (!root || !(aspect = root.entityAspect)) {
throw getRootErr(ix, 'is not an entity');
}
if (aspect.entityState === breeze.EntityState.Detached) {
throw getRootErr(ix, 'is a detached entity');
}
var em = aspect.entityManager;
if (entityGroupMap) {
if (entityGroupMap !== em._entityGroupMap) {
throw getRootErr(ix, "has a different 'EntityManager' than other roots");
}
} else {
entityGroupMap = em._entityGroupMap;
}
getRootType(root, ix);
});
function getRootErr(ix, msg) {
return new Error("'getEntityGraph' root[" + ix + "] " + msg);
};
function getRootType(root, ix) {
var thisType = root.entityType;
if (!rootType) {
rootType = thisType;
return;
} else if (rootType === thisType) {
return;
}
// Types differs. Look for closest common base type
// does thisType derive from current rootType?
var baseType = rootType;
do {
compatTypes = compatTypes || baseType.getSelfAndSubtypes();
if (compatTypes.indexOf(thisType) > -1) {
rootType = baseType;
return;
}
baseType = baseType.baseEntityType;
compatTypes = null;
} while (baseType);
// does current rootType derives from thisType?
baseType = thisType;
do {
compatTypes = baseType.getSelfAndSubtypes();
if (compatTypes.indexOf(rootType) > -1) {
rootType = baseType;
return;
}
baseType = baseType.baseEntityType;
} while (baseType)
throw getRootErr(ix, "is not EntityType-compatible with other roots");
}
}
function getExpand() {
try {
if (!expand) {
expand = [];
} else if (typeof expand === 'string') {
// tricky because Breeze expandClause not exposed publically
expand = new breeze.EntityQuery().expand(expand).expandClause;
}
if (expand.propertyPaths) { // expand clause
expand = expand.propertyPaths;
} else if (Array.isArray(expand)) {
if (!expand.every(function (elem) { return typeof elem === 'string'; })) {
throw '';
}
} else {
throw '';
}
} catch (_) {
throw new Error(
"expand must be an expand string, array of string paths, or a query expand clause");
}
}
function buildGraph() {
if (expand && expand.length) {
var fns = expand.map(makePathFn);
fns.forEach(function (fn) { fn(roots); });
}
}
// Make function to get entities along a single expand path
// such as 'Orders.OrderDetails.Product'
function makePathFn(path) {
var fns = [],
segments = path.split('.'),
type = rootType;
for (var i = 0, slen = segments.length; i < slen; i++) {
var f = makePathSegmentFn(type, segments[i]);
type = f.navType;
fns.push(f);
}
return function pathFn(entities) {
for (var j = 0, flen = fns.length; j < flen; j++) {
var elen = entities.length;
if (elen === 0) { return; } // nothing left to explore
// fn to get related entities for this path segment
var fn = fns[j];
// get entities related by this path segment
var related = [];
for (var k = 0; k < elen; k++) {
related = related.concat(fn(entities[k]));
}
addToGraph(related);
if (j >= flen - 1) { return; } // no more path segments
// reset entities to deduped related entities
entities = [];
for (var l = 0, rlen = related.length; l < rlen; l++) {
var r = related[l];
if (entities.indexOf(r) < 0) { entities.push(r); }
}
}
};
}
// Make function to get entities along a single expand path segment
// such as the 'OrderDetails' in the 'Orders.OrderDetails.Product' path
function makePathSegmentFn(baseType, segment) {
var baseTypeName, fn = undefined, navType;
try {
baseTypeName = baseType.name;
var nav = baseType.getNavigationProperty(segment);
var fkName = nav.foreignKeyNames[0];
if (!nav) {
throw new Error(segment + " is not a navigation property of " + baseTypeName);
}
navType = nav.entityType;
// add derived types
var navTypes = navType.getSelfAndSubtypes();
var grps = []; // non-empty groups for these types
navTypes.forEach(function (t) {
var grp = entityGroupMap[t.name];
if (grp && grp._entities.length > 0) {
grps.push(grp);
}
});
var grpCount = grps.length;
if (grpCount === 0) {
// no related entities in cache
fn = function () { return []; };
} else if (fkName) {
fn = function (entity) {
var val = null;
try {
var keyValue = entity.getProperty(fkName);
for (var i = 0; i < grpCount; i += 1) {
val = grps[i]._entities[grps[i]._indexMap[keyValue]];
if (val) { break; }
}
} catch (e) { rethrow(e); }
return val;
};
} else {
fkName = nav.inverse ?
nav.inverse.foreignKeyNames[0] :
nav.invForeignKeyNames[0];
if (!fkName) { throw new Error("No inverse keys"); }
fn = function (entity) {
var vals = [];
try {
var keyValue = entity.entityAspect.getKey().values[0];
grps.forEach(function (grp) {
vals = vals.concat(grp._entities.filter(function (en) {
return en && en.getProperty(fkName) === keyValue;
}));
});
} catch (e) { rethrow(e); }
return vals;
};
}
fn.navType = navType;
fn.path = segment;
} catch (err) { rethrow(err); }
return fn;
function rethrow(e) {
var typeName = baseTypeName || baseType;
var error = new Error("'getEntityGraph' can't expand '" + segment + "' for " + typeName);
error.innerError = e;
throw error;
}
}
}
}));