UNPKG

beetle.js

Version:

Beetle is a data manager for Javascript. The goal is to be able to work with data as easy as Entity Framework and LINQ.

10,571 lines • 493 kB
/**
 * Beetle module.
 * Using UMD pattern.
 * @module beetle
 */
(function (root, factory) {
    var deps = {
        jQuery: root.$,
        angularjs: root.angular,
        ko: root.ko,
        Q: root.Q
    };

    if (typeof exports === "object") {
        var node;
        if (typeof window == 'undefined') {
            try {
                var http = require("http");
                var https = require("https");
                node = { http: http, https: https };
            } catch (e) { }
        }
        var angular;
        try {
            var aCore = require("@angular/core");
            var aHttp = require("@angular/http");

            var http = aCore.ReflectiveInjector.resolveAndCreate([
                aHttp.Http, aHttp.BrowserXhr,
                { provide: aHttp.ConnectionBackend, useClass: aHttp.XHRBackend },
                { provide: aHttp.RequestOptions, useClass: aHttp.BaseRequestOptions },
                { provide: aHttp.ResponseOptions, useClass: aHttp.BaseResponseOptions },
                { provide: aHttp.XSRFStrategy, useValue: new aHttp.CookieXSRFStrategy() }
            ]).get(aHttp.Http);
            angular = { http: http, Request: aHttp.Request, Headers: aHttp.Headers };
        } catch (e) { }

        module.exports = factory(root, deps.jQuery, deps.angularjs, deps.ko, deps.Q, node, angular);
        return module.exports;
    }
    else if (typeof define === "function" && define.amd) {
        var modules = [];
        for (var p in deps) {
            if (require.specified(p)) {
                modules.push(p);
            }
        }

        define(modules, function () {
            for (var i = 0; i < arguments.length; i++) {
                var mdl = arguments[i];
                if (mdl) deps[modules[i]] = mdl;
            }

            root.beetle = factory(root, deps.jQuery, deps.angularjs, deps.ko, deps.Q);
        });
    }
    else {
        root.beetle = factory(root, deps.jQuery, deps.angularjs, deps.ko, deps.Q);
        return root.beetle;
    }
})(this, function (root, $, angularjs, ko, Q, node, angular) {
    "use strict";

    /**
     * Helper functions. We are trying not to use ECMA 5, so we polyfill some methods.
     * @namespace helper
     */
    var helper = {
        /**
         * Creates an assert instance to work with, a shortcut.
         * @example
         * helper.assertPrm(prm, 'prm').isArray().check()
         * @param {any} value - The value of parameter.
         * @param {string} name - The name of the parameter.
         * @returns {Assert} Assert instance.
         */
        assertPrm: function (value, name) {
            return new Assert(value, name);
        },
        /**
         * Combines first object's properties with second object's properties on a new object.
         * @param {Object} obj1 - The first object.
         * @param {Object} obj2 - The second object.
         * @returns {Object} New object containing all properties from both objects.
         */
        combine: function (obj1, obj2) {
            if (obj1 == obj2) return obj1;
            var obj;
            if (obj1 != null) {
                obj = {};
                for (var p1 in obj1) {
                    obj[p1] = obj1[p1];
                }
            }
            if (obj2 != null) {
                obj = obj || {};
                for (var p2 in obj2) {
                    var v1 = obj[p2];
                    var v2 = obj2[p2];
                    var v = Assert.isTypeOf(v1, 'object') && Assert.isTypeOf(v2, 'object') ? helper.combine(v1, v2) : v2;
                    obj[p2] = v;
                }
            }
            return obj;
        },
        /**
         * Extends objMain with objExt's properties.
         * @param {Object} objMain - The main object.
         * @param {Object} objExt - Object to extend with.
         * @returns {Object} objMain is returned.
         */
        extend: function (objMain, objExt) {
            if (objMain != null && objExt != null) {
                for (var p in objExt) {
                    if (!objMain.hasOwnProperty(p))
                        objMain[p] = objExt[p];
                }
            }
            return objMain;
        },
        /**
         * Checks if the given two are equal. if parameters are both objects, recursively controls their properties too.
         * @param {Object} obj1 - The first object.
         * @param {Object} obj2 - The second object.
         * @returns {boolean} True when two objects are equal, otherwise false.
         */
        objEquals: function (obj1, obj2) {
            if (obj1 == obj2)
                return true;

            if (obj1 == null || obj2 == null)
                return false;

            if (Assert.isObject(obj1) && Assert.isObject(obj2)) {
                var count1 = 0;
                var count2 = 0;
                for (var p in obj1) {
                    if (!(p in obj2)) return false;
                    if (helper.getValue(obj1, p) != helper.getValue(obj2, p)) return false;
                    count1++;
                }
                for (var p2 in obj2) count2++;
                return count1 == count2;
            }

            return false;
        },
        /**
         * Returns string case option for current operation context.
         * @param {StringOptions} options - String options for the context.
         * @returns {boolean} True when given options' isCaseSensitive is true (or null and global options' isCaseSensitive is true), otherwise false.
         */
        isCaseSensitive: function (options) {
            var isCaseSensitive = options && options.isCaseSensitive;
            return isCaseSensitive == null ? settings.isCaseSensitive : isCaseSensitive;
        },
        /**
         * Returns whitespace ignore option for current operation context.
         * @param {StringOptions} options - String options for the context.
         * @returns {boolean} True when given options' ignoreWhiteSpaces is true (or null and global options' ignoreWhiteSpaces is true), otherwise false.
         */
        ignoreWhiteSpaces: function (options) {
            var ignoreWhiteSpaces = options && options.ignoreWhiteSpaces;
            return ignoreWhiteSpaces == null ? settings.ignoreWhiteSpaces : ignoreWhiteSpaces;
        },
        /**
         * Applies current operation context string options to given parameter.
         * @param {StringOptions} options - String options for the context.
         * @returns {string} Modified string (using the options).
         */
        handleStrOptions: function (str, options) {
            if (str == null || typeof str !== "string") return str;
            if (!helper.isCaseSensitive(options)) {
                str = str.replace("İ", "i");
                str = str.toLowerCase();
                str = str.replace("ı", "i");
            }
            return helper.ignoreWhiteSpaces(options) ? str.trim() : str;
        },
        /**
         * Compares two objects. Uses given options when necessary.
         * @param {Object} obj1 - The first object.
         * @param {Object} obj2 - The second object.
         * @param {bool} isStrict - Use strict comparing (===).
         * @param {StringOptions} options - String options for the context.
         * @returns {boolean} True when two values are equal (options will be used for string values).
         */
        equals: function (obj1, obj2, isStrict, options) {
            if (typeof obj1 === 'string' && typeof obj2 === 'string') {
                obj1 = helper.handleStrOptions(obj1, options);
                obj2 = helper.handleStrOptions(obj2, options);
            }

            if (obj1 != null && obj2 != null && core.dataTypes.date.isValid(obj1) && core.dataTypes.date.isValid(obj2)) {
                obj1 = obj1.valueOf();
                obj2 = obj2.valueOf();
            }

            return isStrict ? obj1 === obj2 : obj1 == obj2;
        },
        /**
         * Format string using given arguments. %1 and {1} format can be used for placeholders.
         * @param {string} string - String to format.
         * @param {...string} params - Values to replace.
         * @returns {string} Formatted string.
         */
        formatString: function (string, params) {
            var args = arguments;
            var pattern1 = RegExp("%([0-" + (arguments.length - 1) + "])", "g");
            var pattern2 = RegExp("{([0-" + (arguments.length - 2) + "])}", "g");
            return string
                .replace(pattern1, function (match, index) {
                    return args[Number(index) + 1] || '';
                })
                .replace(pattern2, function (match, index) {
                    return args[Number(index) + 1] || '';
                });
        },
        /**
         * Finds the index of the given item in the array.
         * @param {any[]} array - Array to search.
         * @param {any} item - Item to find.
         * @param {number=} index - Start index.
         * @returns {number} Found index. If the item could not be found returns '-1'.
         */
        indexOf: function (array, item, index) {
            for (var i = index || 0; i < array.length; i++)
                if (array[i] === item) return i;
            return -1;
        },
        /**
         * Calls given callback with item and current index parameters for each item in the array.
         * @param {any[]} array - Array to iterate.
         * @param {forEachCallback} callback - Method to call for each item.
         */
        forEach: function (array, callback) {
            for (var i = 0; i < array.length; i++) {
                var obj = array[i];
                callback.call(null, obj, i);
            }
        },
        /**
         * Iterate objects properties and skips ones starting with '$'.
         * @param {Object} object - Object to iterate.
         * @param {forEachPropertyCallback} callback - Method to call for each property.
         */
        forEachProperty: function (object, callback) {
            for (var p in object) {
                if (p[0] == '$') continue;
                var v = object[p];
                if (Assert.isFunction(v)) continue;
                callback(p, v);
            }
        },
        /**
         * Finds given item in the array.
         * When property is given, looks item's property value, otherwise compares item's itself.
         * @param {any[]} array - Array to search.
         * @param {any} value - Value to find.
         * @param {string=} property - Property to look for the value.
         * @returns {any} When value is found; if property is provided, the array item containing the given value, otherwise value itself. When not found, null.
         */
        findInArray: function (array, value, property) {
            for (var i = 0; i < array.length; i++)
                if (property) {
                    if (array[i][property] === value) return array[i];
                } else if (array[i] === value) return value;
            return null;
        },
        /**
         * Copies array items that match the given conditions to another array and returns the new array.
         * @param {any[]} array - The array to filter.
         * @param {string|predicateFunction} predicate - A function to test each element for a condition (can be string expression).
         * @returns {any[]} New array with filtered items.
         */
        filterArray: function (array, predicate) {
            var retVal = [];
            for (var i = 0; i < array.length; i++) {
                var item = array[i];
                if (predicate(item) === true)
                    retVal.push(item);
            }
            return retVal;
        },
        /**
         * Removes the item from given array.
         * @param {any[]} array - The array to remove item from.
         * @param {any} item - Item to remove.
         * @param {string=} property - Property to look for the value.
         * @returns {number[]} Removed item indexes.
         */
        removeFromArray: function (array, item, property) {
            var indexes = [];
            this.forEach(array, function (current, index) {
                if (property) {
                    if (current[property] === item) indexes.push(index);
                } else if (current === item) indexes.push(index);
            });
            for (var i = indexes.length - 1; i >= 0; i--)
                array.splice(indexes[i], 1);
            return indexes.length;
        },
        /**
         * Creates a new array with the results of calling the provided function on every element in the given array.
         * @param {any[]} array - The array to map.
         * @param {mapCallback} callback - Function that produces new element.
         * @returns {any[]} New array with mapped values.
         */
        mapArray: function (array, callback) {
            var retVal = [];
            for (var i = 0; i < array.length; i++) {
                var item = array[i];
                retVal.push(callback.call(item, item, i));
            }
            return retVal;
        },
        /**
         * Creates a GUID string with "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" format.
         * @returns {string} Newly generated GUID.
         */
        createGuid: function () {

            function s4() {
                return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
            }

            return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
                s4() + '-' + s4() + s4() + s4();
        },
        /**
         * Creates string representation of given function with arrow syntax.
         * @param {function} func - The function.
         * @returns {string} Arrow style code for given function.
         */
        funcToLambda: function (func) {
            helper.assertPrm(func, "func").isFunction().check();

            var f = func.toString().replace(/function(.*?){/g, "$1=>").replace(/{|}|;|return /g, "").trim();

            var l1 = f.indexOf("=>");
            var p = f.substr(0, l1).trim();
            f = f.substr(l1 + 2).trim();

            var xs = f.split(",");
            var s = [];
            for (var i = 0; i < xs.length; i++) {
                var x = xs[i];
                var ps = x.split(":");
                if (ps.length > 1) {
                    s.push(ps[1].trim() + " as " + ps[0].trim());
                }
                else s.push(x.trim());
            }
            f = s.join(", ");

            return p ? (p + " => " + f) : f;
        },
        /**
         * Finds and returns function name. Works for ES6 classes too.
         * @param {function} func - The function (or class).
         * @returns {string} Name of the given function.
         */
        getFuncName: function (func) {
            var funcNameRegex = /function (.*?)[\s|\(]|class (.*?)\s|\{/;
            var results = funcNameRegex.exec(func.toString());
            return results[1] || results[2];
        },
        /**
         * Implements prototypal inheritance. Uses a "Function" instance to avoid unnecessary instantiations.
         * @param {function} derivedClass - Deriving type.
         * @param {function} baseClass - Base type.
         */
        inherit: function (derivedClass, baseClass) {
            var f = new Function();
            f.prototype = baseClass.prototype;

            derivedClass.prototype = new f();
            derivedClass.prototype.constructor = derivedClass;
            derivedClass.baseClass = baseClass.prototype;
        },
        /**
         * Reads property of value, used when we are not sure if property is observable.
         * @param {Object} object - Deriving type.
         * @param {string} property - Property path. Can be a chain like "address.city.name".
         * @returns {any} Value of property (undefined when a property cannot be found).
         */
        getValue: function (object, propertyPath) {
            if (object == null) return undefined;
            var op = settings.getObservableProvider();
            // split propertyPath path and read every items value
            var paths = propertyPath.trim().split('.');
            var retVal = object;
            for (var i = 0; i < paths.length; i++) {
                retVal = getValue(retVal, paths[i]);
                if (retVal == null) break;
            }
            return retVal;

            function getValue(o, p) {
                // get tracker
                var tracker = o.$tracker;
                // if o is an entity, get value from its tracker
                if (tracker)
                    return tracker.getValue(p);
                if (op.isObservable(o, p)) // if o's p is observable get value from default observable provider
                    return op.getValue(o, p);
                var value = o[p];
                return value; // if its not observable get value of p
            }
        },
        /**
         * Gets localized value for given name using "settings.localizeFunc" function.
         * @param {string} resourceName - Resource name.
         * @param {string} altValue - Alternative value to use when resource cannot be found.
         * @returns {string} Value for the given resource name.
         */
        getResourceValue: function (resourceName, altValue) {
            var localizeFunc = settings.getLocalizeFunction();
            return (localizeFunc && resourceName && localizeFunc(resourceName)) || altValue;
        },
        /**
         * Creates validation error object using given parameters.
         * @param {any} entity - Entity containing invalid value.
         * @param {any} value - Invalid value itself.
         * @param {string} property - Property containing invalid value.
         * @param {string} message - Validation message.
         * @param {Validator} validator - Validator instance.
         * @returns {Object} Validation error object.
         */
        createValidationError: function (entity, value, property, message, validator) {
            return { message: message, entity: entity, validator: validator, value: value, property: property };
        },
        /**
         * Creates error object by formatting provided message and populates with given object's values.
         * @param {string} message - Error message.
         * @param {string[]} arg1 - Message format arguments.
         * @param {Object=} arg2 - Extra informations, properties will be attached to error object.
         * @returns {Error} Error object.
         */
        createError: function (message, arg1, arg2) {
            var args = null, obj = arg2;
            if (Assert.isArray(arg1)) args = arg1;
            else if (Assert.isObject(arg1)) obj = arg1;

            if (args && args.length > 0) {
                args.splice(0, 0, message);
                message = helper.formatString.apply(null, args);
            }
            var retVal = new Error(message);
            if (obj) {
                for (var p in obj)
                    retVal[p] = obj[p];
            }
            events.error.notify(retVal);
            return retVal;
        },
        /**
         * Updates foreign keys of given navigation property with new values.
         * @param {any} entity - The entity.
         * @param {NavigationProperty} navProperty - The navigation property.
         * @param {Object} newValue - Value of the navigation property.
         */
        setForeignKeys: function (entity, navProperty, newValue) {
            for (var i = 0; i < navProperty.foreignKeyNames.length; i++) {
                // We get each related foreign key for this navigation property.
                var fk = navProperty.foreignKeyNames[i];
                var tracker = entity.$tracker;
                if (newValue) {
                    // When foreign key is built with more than one column, we presume foreign key-primary key order is same
                    // Example:
                    //  When we create an association between Product and Supplier using Name and Location fields
                    //  we presume Supplier's corresponding primary keys are in exactly same order.
                    var k = newValue.$tracker.entityType.keys[i];
                    var v = newValue.$tracker.getValue(k.name);
                    tracker.setValue(fk, v);
                } else {
                    var fkp = helper.findInArray(tracker.entityType.dataProperties, fk, 'name');
                    tracker.setValue(fk, fkp.getDefaultValue());
                }
            }
        },
        /**
         * Creates an array and overrides methods to provide callbacks on array changes.
         * @param {any[]} initial - Initial values for the array.
         * @param {Object} object - Owner object of the array.
         * @param {NavigationProperty} property - Navigation property metadata.
         * @param {arrayChangeCallback} after - Array change callback.
         * @returns {TrackableArray} Trackable array, an array with change events.
         */
        createTrackableArray: function (initial, object, property, after) {
            var array = initial || [];
            array.object = object;
            array.property = property;
            array.after = after;

            array.changing = new core.Event(property + "ArrayChanging", array);
            array.changed = new core.Event(property + "ArrayChanged", array);

            array.pop = function () {
                var items = [this[this.length - 1]];
                // call base method
                this.changing.notify({ added: [], removed: items });
                var retVal = Array.prototype.pop.call(this);
                this.after(this.object, this.property, this, items, null);
                this.changed.notify({ added: [], removed: items });
                return retVal;
            };

            array.push = function (items) {
                this.changing.notify({ added: arguments, removed: [] });
                beforeAdd(arguments, this);
                var retVal = Array.prototype.push.apply(this, arguments);
                this.after(this.object, this.property, this, null, arguments);
                this.changed.notify({ added: arguments, removed: [] });
                return retVal;
            };

            array.unshift = function (items) {
                this.changing.notify({ added: arguments, removed: [] });
                beforeAdd(arguments, this);
                var retVal = Array.prototype.unshift.apply(this, arguments);
                this.after(this.object, this.property, this, null, arguments);
                this.changed.notify({ added: arguments, removed: [] });
                return retVal;
            };

            array.shift = function () {
                var items = [this[0]];
                this.changing.notify({ added: [], removed: items });
                var retVal = Array.prototype.shift.call(this);
                this.after(this.object, this.property, this, items, null);
                this.changed.notify({ added: [], removed: items });
                return retVal;
            };

            array.splice = function (start, count) {
                count = count || this.length;
                var addedItems = null;
                if (arguments.length > 2)
                    addedItems = Array.prototype.slice.call(arguments).slice(2); // convert arguments to array then slice
                var removedItems = this.slice(start, start + count);
                this.changing.notify({ added: addedItems, removed: removedItems });
                if (addedItems)
                    beforeAdd(addedItems, this);
                var retVal = Array.prototype.splice.apply(this, arguments);
                this.after(this.object, this.property, this, removedItems, addedItems);
                this.changed.notify({ added: addedItems, removed: removedItems });
                return retVal;
            };

            array.remove = function (items) {
                var removed = [];
                this.changing.notify({ added: [], removed: arguments });
                var that = this;
                helper.forEach(arguments, function (item) {
                    var index = helper.indexOf(that, item);
                    if (index >= 0) {
                        Array.prototype.splice.call(that, index, 1);
                        removed.push(item);
                    }
                });
                this.after(this.object, this.property, this, removed, null);
                this.changed.notify({ added: [], removed: removed });
                return removed;
            };

            /**
             * Loads the navigation property using EntityManager.
             * @param {string[]} expands - Expand navigations to apply when loading navigation property.
             * @param {Object} resourceName - Resource name to query entities.
             * @param {queryOptions} options - Query options.
             * @param {successCallback=} successCallback - Success callback function.
             * @param {errorCallback=} errorCallback - Error callback function.
             * @returns {Promise} A Promise when available, otherwise return value of the AjaxProvider.
             */
            array.load = function (expands, resourceName, options, successCallback, errorCallback) {
                return this.object.$tracker.loadNavigationProperty(this.property.name, expands, resourceName, options, successCallback, errorCallback);
            };

            function beforeAdd(added, instance) {
                var o = instance.object;
                var p = instance.property;
                if (p) {
                    var handleUnmappedProperties;
                    if (o.$tracker && o.$tracker.manager)
                        handleUnmappedProperties = o.$tracker.manager.handleUnmappedProperties;
                    if (handleUnmappedProperties == null) handleUnmappedProperties = settings.handleUnmappedProperties;

                    if (Assert.isInstanceOf(p, metadata.NavigationProperty))
                        helper.forEach(added, function (a) { p.checkAssign(a); });
                    else if (Assert.isInstanceOf(p, metadata.DataProperty))
                        helper.forEach(added, function (a, i) { added[i] = p.handle(a); });
                    else if (handleUnmappedProperties === true)
                        helper.forEach(added, function (a, i) { added[i] = core.dataTypes.handle(a); });
                }
            }

            return array;
        },
        /**
         * Creates a new array with the results of evaluating provided expression on every element in the given array.
         * @param {any[]} array - Array to run projection on.
         * @param {string} exp - Projection expression.
         * @param {QueryContext} queryContext - Query execution context.
         * @returns {any[]} Projected new object.
         */
        runSelectExp: function (array, exp, queryContext) {
            if (array.length == 0) return array;

            exp = libs.jsep(exp);
            var exps = exp.type === 'Compound' ? exp.body : [exp];
            var projector = helper.jsepToProjector(exps, queryContext);
            return helper.mapArray(array, projector);
        },
        /**
        * Converts parsed javascript expression (jsep) to OData format query string.
        * @param {Object} exp - Jsep expression (tokenized).
        * @param {QueryContext} queryContext - Query execution context.
        * @param {Object=} firstExp - First evaluated expression (for internal use only).
        * @returns {string} OData query string.
        */
        jsepToODataQuery: function (exp, queryContext, firstExp) {
            firstExp = firstExp || exp;
            if (!queryContext) queryContext = { aliases: [] };
            else if (!queryContext.aliases) queryContext.aliases = [];
            if (exp.type == 'LogicalExpression' || exp.type == 'BinaryExpression') {
                if (exp.operator == '=>') {
                    if (exp != firstExp)
                        throw helper.createError(i18N.odataDoesNotSupportAlias);
                    queryContext.aliases.push({ alias: exp.left.name, value: null });
                    var r = helper.jsepToODataQuery(exp.right, queryContext, firstExp);
                    if (exp != firstExp)
                        queryContext.aliases.pop();
                    return r;
                }
                var op = enums.langOperators.find(exp.operator).oData;
                if (!op) throw helper.createError(i18N.operatorNotSupportedForOData, [exp.operator], { expression: exp });
                return '(' + helper.jsepToODataQuery(exp.left, queryContext, firstExp) + ' ' + op + ' ' + helper.jsepToODataQuery(exp.right, queryContext, firstExp) + ')';
            }
            else if (exp.type == 'UnaryExpression')
                return exp.operator + helper.jsepToODataQuery(exp.argument, queryContext, firstExp);
            else if (exp.type == 'Identifier') {
                var n = exp.name;
                var val = undefined;
                var isPrm = false;
                if (n[0] == '@') {
                    isPrm = true;
                    n = n.slice(1);
                }

                if (queryContext.expVarContext && queryContext.expVarContext[n] !== undefined)
                    val = queryContext.expVarContext[n];
                else if (queryContext.varContext && queryContext.varContext[n] !== undefined)
                    val = queryContext.varContext[n];

                if (val !== undefined)
                    return core.dataTypes.toODataValue(val);
                if (isPrm)
                    throw helper.createError(i18N.unknownParameter, [n], { expression: exp, queryContext: queryContext });

                var a = helper.findInArray(queryContext.aliases, n, 'alias');
                if (a) return a.value;

                return n;
            }
            else if (exp.type == 'Literal')
                return core.dataTypes.toODataValue(exp.value);
            else if (exp.type == 'MemberExpression') {
                if (queryContext.currentAlias && exp.object.name == queryContext.currentAlias.alias)
                    return exp.property.name;
                else {
                    var ali = helper.findInArray(queryContext.aliases, exp.object.name, 'alias'), o;
                    if (ali) o = ali.value;
                    else o = helper.jsepToODataQuery(exp.object, queryContext, firstExp);

                    if (o && o[exp.property.name] !== undefined)
                        return core.dataTypes.toODataValue(o[exp.property.name]);
                    return o ? o + '/' + exp.property.name : exp.property.name;
                }
            }
            else if (exp.type == 'Compound') {
                var sts = [];
                for (var i = 0; i < exp.body.length; i++) {
                    var st = exp.body[i];
                    var s = helper.jsepToODataQuery(st, queryContext);
                    var ls = s.toLowerCase();
                    if (ls == 'desc' || ls == 'asc') {
                        if (sts.length == 0)
                            throw helper.createError(i18N.invalidStatement, { expression: exp, statement: st });
                        sts[sts.length - 1] += ' ' + s;
                    }
                    else if (ls == 'as') {
                        if (sts.length == 0 || exp.body.length < i + 1)
                            throw helper.createError(i18N.invalidStatement, { expression: exp, statement: st });
                        sts[sts.length - 1] += ' as ' + exp.body[i + 1].name;
                        i++;
                    }
                    else sts.push(s);
                }
                return sts.join(', ');
            }
            else if (exp.type == 'CallExpression') {
                var argList = exp.arguments, args = [], alias = null;
                if (argList.length == 1 && argList[0] && argList[0].type == 'BinaryExpression' && argList[0].operator == '=>') {
                    alias = { alias: argList[0].left.name };
                    alias.value = alias.alias;
                    argList = [argList[0].right];
                }
                if (alias) {
                    queryContext.currentAlias = alias;
                    queryContext.aliases.push(alias);
                }
                for (var j = 0; j < argList.length; j++) {
                    var arg = argList[j];
                    if (arg != null)
                        args.push(helper.jsepToODataQuery(arg, queryContext, firstExp));
                }
                var funcName;
                if (exp.callee.type == 'MemberExpression') {
                    args.splice(0, 0, helper.jsepToODataQuery(exp.callee.object, queryContext, firstExp));
                    funcName = exp.callee.property.name;
                }
                else funcName = exp.callee.name;
                var func = querying.queryFuncs.getFunc(funcName);
                if (func.needsAlias == true) {
                    if (alias)
                        args.splice(0, 0, alias);
                    else throw helper.createError(i18N.functionNeedsAlias, [funcName], { expression: exp });
                }
                var retVal = func.toODataFunction.apply(func, args);
                if (alias) {
                    queryContext.currentAlias = null;
                    queryContext.aliases.pop();
                }
                return retVal;
            }
            throw helper.createError(i18N.unknownExpression, { expression: exp });
        },
        /**
        * Converts parsed javascript expression (jsep) to Beetle format query string.
        * @param {Object} exp - Jsep expression (tokenized).
        * @param {QueryContext} queryContext - Query execution context.
        * @param {Object=} firstExp - First evaluated expression (for internal use only).
        * @returns {string} OData query string.
        */
        jsepToBeetleQuery: function (exp, queryContext, firstExp) {
            firstExp = firstExp || exp;
            if (!queryContext) queryContext = { aliases: [] };
            else if (!queryContext.aliases) queryContext.aliases = [];
            if (exp.type === 'LogicalExpression' || exp.type == 'BinaryExpression') {
                if (exp.operator == '=>') {
                    queryContext.aliases.push({ alias: exp.left.name, value: 'it' });
                    var r = helper.jsepToBeetleQuery(exp.right, queryContext, firstExp);
                    if (exp != firstExp)
                        queryContext.aliases.pop();
                    return r;
                }
                var op = enums.langOperators.find(exp.operator).code;
                return '(' + helper.jsepToBeetleQuery(exp.left, queryContext, firstExp) + ' ' + op + ' ' + helper.jsepToBeetleQuery(exp.right, queryContext, firstExp) + ')';
            }
            else if (exp.type === 'UnaryExpression')
                return exp.operator + helper.jsepToBeetleQuery(exp.argument, queryContext, firstExp);
            else if (exp.type === 'Identifier') {
                var n = exp.name;
                var val = undefined;
                var isPrm = false;
                if (n[0] == '@') {
                    isPrm = true;
                    n = n.slice(1);
                }

                if (queryContext.expVarContext && queryContext.expVarContext[n] !== undefined)
                    val = queryContext.expVarContext[n];
                else if (queryContext.varContext && queryContext.varContext[n] !== undefined)
                    val = queryContext.varContext[n];

                if (val !== undefined)
                    return core.dataTypes.toBeetleValue(val);
                if (isPrm)
                    throw helper.createError(i18N.unknownParameter, [n], { expression: exp, queryContext: queryContext });

                var a = helper.findInArray(queryContext.aliases, n, 'alias');
                if (a) return a.value;

                return n;
            }
            else if (exp.type === 'Literal')
                return core.dataTypes.toBeetleValue(exp.value);
            else if (exp.type === 'MemberExpression') {
                if (queryContext.currentAlias && exp.object.name == queryContext.currentAlias.alias)
                    return exp.property.name;
                else {
                    var ali = helper.findInArray(queryContext.aliases, exp.object.name, 'alias'), o;
                    if (ali) o = ali.value;
                    else o = helper.jsepToBeetleQuery(exp.object, queryContext, firstExp);

                    if (o[exp.property.name] !== undefined)
                        return core.dataTypes.toBeetleValue(o[exp.property.name]);
                    return o + '.' + exp.property.name;
                }
            }
            else if (exp.type === 'Compound') {
                var sts = [];
                for (var i = 0; i < exp.body.length; i++) {
                    var st = exp.body[i];
                    var s = helper.jsepToBeetleQuery(st, queryContext);
                    var ls = s.toLowerCase();
                    if (ls == 'desc' || ls == 'asc') {
                        if (sts.length == 0)
                            throw helper.createError(i18N.invalidStatement, { expression: exp, statement: st });
                        sts[sts.length - 1] += ' ' + s;
                    }
                    else if (ls == 'as') {
                        if (sts.length == 0 || exp.body.length < i + 1)
                            throw helper.createError(i18N.invalidStatement, { expression: exp, statement: st });
                        sts[sts.length - 1] += ' as ' + exp.body[i + 1].name;
                        i++;
                    }
                    else sts.push(s);
                }
                return sts.join(', ');
            }
            else if (exp.type === 'CallExpression') {
                var argList = exp.arguments, args = [], alias = null;
                if (argList.length == 1 && argList[0] && argList[0].type == 'BinaryExpression' && argList[0].operator == '=>') {
                    alias = { alias: argList[0].left.name };
                    alias.value = alias.alias;
                    argList = [argList[0].right];
                }
                if (alias) {
                    queryContext.currentAlias = alias;
                    queryContext.aliases.push(alias);
                }
                for (var j = 0; j < argList.length; j++) {
                    var arg = argList[j];
                    if (arg != null)
                        args.push(helper.jsepToBeetleQuery(arg, queryContext, firstExp));
                }
                var funcName;
                if (exp.callee.type == 'MemberExpression') {
                    args.splice(0, 0, helper.jsepToBeetleQuery(exp.callee.object, queryContext, firstExp));
                    funcName = exp.callee.property.name;
                }
                else funcName = exp.callee.name;
                var func = querying.queryFuncs.getFunc(funcName);
                var retVal = func.toBeetleFunction.apply(func, args);
                if (alias) {
                    queryContext.currentAlias = null;
                    queryContext.aliases.pop();
                }
                return retVal;
            }
            throw helper.createError(i18N.unknownExpression, { expression: exp });
        },
        /**
         * Converts parsed javascript expression (jsep) to Javascript function (not using evil "eval").
         * @param {Object} exp - Jsep expression (tokenized).
         * @param {QueryContext} queryContext - Query execution context.
         * @returns {Function} Javascript function.
         */
        jsepToFunction: function (exp, queryContext) {
            return function (value) {
                if (!queryContext) queryContext = { aliases: [] };
                else if (!queryContext.aliases) queryContext.aliases = [];
                if (queryContext.currentAlias)
                    queryContext.currentAlias.value = value;
                if (exp.type === undefined) return value;
                else if (exp.type == 'LogicalExpression' || exp.type == 'BinaryExpression') {
                    if (exp.operator == '=>') {
                        if (queryContext.currentAlias)
                            queryContext.aliases.push(queryContext.currentAlias);
                        queryContext.currentAlias = { alias: exp.left.name };
                        var r = helper.jsepToFunction(exp.right, queryContext)(value);
                        queryContext.currentAlias = queryContext.aliases.pop();
                        return r;
                    }
                    var op = enums.langOperators.find(exp.operator);
                    var varContext = queryContext.varContext;
                    var arg1 = function () { return helper.jsepToFunction(exp.left, queryContext)(value); };
                    var arg2 = function () { return helper.jsepToFunction(exp.right, queryContext)(value); };
                    return op.asFunc.call(varContext, arg1, arg2);
                }
                else if (exp.type == 'UnaryExpression') {
                    var arg = function () { return helper.jsepToFunction(exp.argument, queryContext)(value); };
                    var uop = enums.langOperators.find(exp.operator);
                    return uop.asFunc.call(varContext, arg);
                }
                else if (exp.type == 'Identifier') {
                    var n = exp.name;
                    if (n == 'null') return null;
                    if (n == 'true') return true;
                    if (n == 'false') return false;
                    if (n[0] == '@') {
                        var val = undefined;
                        var varName = n.slice(1);
                        if (queryContext.expVarContext && queryContext.expVarContext[varName] !== undefined)
                            val = queryContext.expVarContext[varName];
                        else if (queryContext.varContext)
                            val = queryContext.varContext[varName];
                        if (val === undefined) throw helper.createError(i18N.unknownParameter, [n], { expression: exp, queryContext: queryContext });
                        return val;
                    }
                    if (queryContext.currentAlias && queryContext.currentAlias.alias == n)
                        return value;
                    var a = helper.findInArray(queryContext.aliases, n, 'alias');
                    if (a) return a.value;
                    var v = helper.getValue(value, n);
                    if (v === undefined) return root[n];
                    return v;
                }
                else if (exp.type == 'Literal')
                    return exp.value;
                else if (exp.type == 'MemberExpression') {
                    if (exp.object.name) {
                        if (queryContext.currentAlias && exp.object.name == queryContext.currentAlias.alias)
                            return helper.getValue(value, exp.property.name);
                        var ali = helper.findInArray(queryContext.aliases, exp.object.name, 'alias');
                        if (ali) return helper.getValue(ali.value, exp.property.name);
                    }
                    return helper.getValue(helper.jsepToFunction(exp.object, queryContext)(value), exp.property.name);
                }
                else if (exp.type == 'CallExpression') {
                    var argList = exp.arguments, args = [], alias = null;
                    if (argList.length == 1 && argList[0] && argList[0].type == 'BinaryExpression' && argList[0].operator == '=>') {
                        alias = argList[0].left.name;
                        argList = [argList[0].right];
                    }
                    if (alias) {
                        if (queryContext.currentAlias)
                            queryContext.aliases.push(queryContext.currentAlias);
                        queryContext.currentAlias = { alias: alias };
                    }
                    var funcName;
                    helper.forEach(argList, function (farg) {
                        if (farg != null)
                            args.push(helper.jsepToFunction(farg, queryContext));
                    });

                    funcName = exp.callee.type == 'MemberExpression' ? exp.callee.property.name : exp.callee.name;

                    var retVal;
                    var func = querying.queryFuncs.getFunc(funcName, false);
                    if (func) {
                        if (exp.callee.type == 'MemberExpression')
                            args.splice(0, 0, helper.jsepToFunction(exp.callee.object, queryContext));
                        args.splice(0, 0, value);

                        retVal = func.impl.apply(queryContext, args);
                    }
                    else {
                        if (funcName[0] == '@') {
                            var varFuncName = funcName.slice(1);
                            if (queryContext.expVarContext && queryContext.expVarContext[varFuncName])
                                func = queryContext.expVarContext[varFuncName];
                            else if (queryContext.varContext && queryContext.varContext[varFuncName])
                                func = queryContext.varContext[varFuncName];
                            else throw helper.createError(i18N.unknownParameter, [varFuncName], { expression: exp, queryContext: queryContext });
                        } else {
                            var obj;
                            if (exp.callee.type == 'MemberExpression')
                                obj = helper.jsepToFunction(exp.callee.object, queryContext)(value);
                            else
                                obj = root;

                            if (obj == null || (func = obj[funcName]) == null)
                                throw helper.createError(i18N.unknownFunction, [funcName]);
                        }

                        args = helper.mapArray(args, function () { return this(value); });
                        retVal = func.apply(obj, args);
                    }

                    if (alias)
                        queryContext.currentAlias = queryContext.aliases.pop();
                    return retVal;
                }
                else throw helper.createError(i18N.unknownExpression, { expression: exp });
            };
        },
        /**
         * Converts parsed javascript expression (jsep) to Javascript projection function (not using evil "eval").
         * @param {Object} exp - Jsep expression (tokenized).
         * @param {QueryContext} queryContext - Query execution context.
         * @returns {Function} Javascript projector function.
         */
        jsepToProjector: function (exps, queryContext) {
            function getPropertyPath(e) {
                switch (e.type) {
                    case 'Identifier':
                        return e.name;
                    case 'MemberExpression':
                        return getPropertyPath(e.object) + '-' + e.property.name;
                }
            }

            var projectExps = [];
            if (!Assert.isArray(exps)) exps = [exps];
            for (var i = 0; i < exps.length; i++) {
                var e = exps[i];
                // list expression property names and value evaluators
                var propertyName = getPropertyPath(e);
                if (exps.length > i + 2 && exps[i + 1].name && exps[i + 1].name.toLowerCase() == 'as') {
                    i = i + 2;
                    var pExp = exps[i];
                    if (pExp.type != 'Identifier')
                        throw helper.createError(i18N.invalidPropertyAlias, { expressions: exps, aliasExpression: pExp });
                    propertyName = pExp.name;
                }
                if (exps.length > 1 && !propertyName)
                    throw helper.createError(i18N.projectionsMustHaveAlias, { expressions: exps, expression: e });

                projectExps.push({ p: propertyName, func: helper.jsepToFunction(e, queryContext) });
            }

            return function (item) {
                var projection = {};
                for (var k = 0; k < projectExps.length; k++) {
                    var pe = projectExps[k];
                    var value = pe.func(item);
                    if (exps.length == 1) return value;
                    projection[pe.p] = value;
                }
                return projection;
            };
        }
    };

    /**
     * Assertion methods. Two different usage possible, static methods and instance methods.
     * Static methods returns true or false. Instance methods can be chained and they collect errors in an array.
     * Check method throws error if there are any.
     * @class
     */
    var Assert = (function () {

        /** @constructor
         * @param {any} value - Value to check.
         * @param {string=} name - Property name representing the value (will be used in error messages).
         */
        var ctor = function (value, name) {
            this.value = value;
            this.name = name;
            this.errors = [];
        };
        var proto = ctor.prototype;

        /** Checks if value is not null or undefined. */
        proto.hasValue = function () {
            ctor.hasValue(this.value, this.errors, this.name);
            return this;
        };
        /** Checks if value is object. */
        proto.isObject = function () {
            ctor.isObject(this.value, this.errors, this.name);
            return this;
        };
        /** Checks if value is function. */
        proto.isFunction = function () {
            ctor.isFunction(this.value, this.errors, this.name);
            return this;
        };
        /** Checks if value is a non-empty string. */
        proto.isNotEmptyString = function () {
            ctor.isNotEmptyString(this.value, this.errors, this.name);
            return this;
        };
        /**
         * Checks if value is an object of given type.
         * @param {string} typeName - Name of the javascript type.
         */
        proto.isTypeOf = function (typeName) {
            ctor.isTypeOf(this.value, typeName, this.errors, this.name);
            return this;
        };
        /** Checks if value is array. */
        proto.isArray = function () {
            ctor.isArray(this.value, this.errors, this.name);
            return this;
        };
        /**
         * Checks if value is an symbol of given enum.
         * @param {Enum} enumType - Type of the enum.
         */
        proto.isEnum = function (enumType) {
            ctor.isEnum(this.value, enumType, this.errors, this.name);
            return this;
        };
        /**
         * Checks if value is an instance of given type.
         * @param {any} type - Javascript function or class to check.
         */
        proto.isInstanceOf = function (type) {
            ctor.isInstanceOf(this.value, type, this.errors, this.name);
            return this;
        };

        /** If previous checks created any error, joins them with a new line and throws an Error. */
        proto.check = function () {
            if (this.errors.length > 0)
                throw helper.createError(this.errors.join('\n'), { name: this.name, value: this.value });
        };

        /**
         * Checks if value is not null.
         * @param {any} value - Value to check.
         * @param {string[]=} errors - Previously generated error messages for the value.
         * @param {string=} name - Property name representing the value.
         */
        ctor.hasValue = function (value, errors, name) {
            if (value == null) {
                if (errors) errors.push(helper.formatString(i18N.valueCannotBeNull, name));
                return false;
            }
            return true;
        };
        /**
         * Checks if value is object.
         * @param {any} value - Value to check.
         * @param {string[]=} errors - Previously generated error messages for the value.
         * @param {string=} name - Property name representing the value.
         */
        ctor.isObject = function (value, errors, name) {
            if (value == null || !core.dataTypes.object.isValid(value)) {
                if (errors) errors.push(helper.formatString(i18N.typeError, name, 'object'));
                return false;
            }
            return true;
        };
        /**
         * Checks if value is function.
         * @param {any} value - Value to check.
         * @param {string[]=} errors - Previously generated error messages for the value.
         * @param {string=} name - Property name representing the value.
         */
        ctor.isFunction = function (value, errors, name) {
            if (value == null || !core.dataTypes.func.isValid(value)) {
                if (errors) errors.push(helper.formatString(i18N.typeError, name, 'function'));
                return false;
            }
            return true;
        };
        /**
         * Checks if value is a non-empty string.
         * @param {any} value - Value to check.
         * @param {string[]=} errors - Previously generated error messages for the value.
         * @param {string=} name - Property name representing the value.
         */
        ctor.isNotEmptyString = function (value, errors, name) {
            if (value == null || value === '' || !ctor.isTypeOf(value, 'string', errors)) {
                if (errors) errors.push(helper.formatString(i18N.cannotBeEmptyString, name));
                return false;
            }
            return true;
        };
        /**
         * Checks if value is an object of given type.
         * @param {any} value - Value to check.
         * @param {string} typeName - Name of the javascript type.
         * @param {string[]=} errors - Previously generated error messages for the value.
         * @param {string=} name - Property name representing the value.
         */
        ctor.isTypeOf = function (value, typeName, errors, name) {
            if (!ctor.hasValue(value)) return false;
            var type = core.dataTypes.byName(typeName);
            if (!type.isValid(value)) {
                if (errors) errors.push(helper.formatString(i18N.typeMismatch, name, typeName, type, value));
                return false;
            }
            return true;
        };
        /**
         * Checks if value is array.
         * @param {any} value - Value to check.
         * @param {string[]=} errors - Previously generated error messages for the value.
         * @param {string=} name - Property name representing the value.
         */
        ctor.isArray = function (value, errors, name) {
            if (value == null || !core.dataTypes.array.isValid(value)) {
                if (errors) errors.push(helper.formatString(i18N.typeError, name, 'array'));
                return false;
            }
            return true;
        };
        /**
         * Checks if value is an symbol of given enum.
         * @param {any} value - Value to check.
         * @param {Enum} enumType - Type of the enum.
         * @param {string[]=} errors - Previously generated error messages for the value.
         * @param {string=} name - Property name representing the value.
         */
        ctor.isEnum = function (value, enumType, errors, name) {
            if (!enumType.contains(value)) {
                if (errors) errors.push(helper.formatString(i18N.invalidEnumValue, [enumType, name], value));
                return false;
            }
            return true;
        };
        /**
         * Checks if value is instance of given type.
         * @param {any} value - Value to check.
         * @param {any} type - Javascript function or class to check.
         * @param {string[]=} errors - Previously generated error messages for the value.
         * @param {string=} name - Property name representing the value.
         */
        ctor.isInstanceOf = function (value, type, errors, name) {
            if (value == null) {
                if (errors) errors.push(i18N.cannotCheckInstanceOnNull);
                return false;
            }
            if (!(value instanceof type)) {
                if (errors) errors.push(helper.formatString(i18N.instanceError, name, type));
                return false;
            }
            return true;
        };

        return ctor;
    })();

    /**
     * 3rd party libraries and snippets.
     * @namespace
     */
    var libs = (function () {
        var expose = {};

        // A simple enum implementation for JavaScript
        // https://github.com/rauschma/enums
        expose.Enum = (function () {

            function copyOwnFrom(target, source) {
                for (var p in source)
                    target[p] = source[p];
                return target;
            }

            function symbol(name, props) {
                this.name = name;
                if (props)
                    copyOwnFrom(this, props);
            }

            /** We don’t want the mutable Object.prototype in the prototype chain */
            symbol.prototype = {};
            symbol.prototype.constructor = symbol;
			/**
			 * Without Object.prototype in the prototype chain, we need toString()
			 * in order to display symbols.
			 */
            symbol.prototype.toString = function () {
                return this.name;
            };

            /**
             * 3rd party code. Modified to work with old browsers.
             * For details: http://www.2ality.com/2011/10/enums.html
             */
            var x = function (obj) {
                var self = this;
                if (arguments.length === 1 && obj !== null && typeof obj === "object") {
                    for (var p in obj)
                        self[p] = new symbol(p, obj[p]);
                } else {
                    helper.forEach(arguments, function (name) {
                        self[name] = new symbol(name);
                    });
                }
            };
            x.prototype.symbols = function () {
                var retVal = [];
                for (var p in this) {
                    var v = this[p];
                    if (Assert.isFunction(v)) continue;
                    retVal.push(v);
                }
                return retVal;
            };
            x.prototype.contains = function (sym) {
                if (!(sym instanceof symbol)) return false;
                return this[sym.name] === sym;
            };
            return x;
        })();
        // JavaScript Expression Parser (JSEP) 0.2.8
        // JSEP may be freely distributed under the MIT License
        // http://jsep.from.so/
        // custom changes:
        //      binary_ops: add "'=>': 0" for alias usage like c# lambdas
        //      isIdentifierStart: add "|| (ch === 64)" for variable usage (`@`)
        expose.jsep = (function () {
            'use strict';
            // Node Types
            // ----------

            // This is the full set of types that any JSEP node can be.
            // Store them here to save space when minified
            var COMPOUND = 'Compound',
                IDENTIFIER = 'Identifier',
                MEMBER_EXP = 'MemberExpression',
                LITERAL = 'Literal',
                THIS_EXP = 'ThisExpression',
                CALL_EXP = 'CallExpression',
                UNARY_EXP = 'UnaryExpression',
                BINARY_EXP = 'BinaryExpression',
                LOGICAL_EXP = 'LogicalExpression',
                // Operations
                // ----------

                // Set `t` to `true` to save space (when minified, not gzipped)
                t = true,
                // Use a quickly-accessible map to store all of the unary operators
                // Values are set to `true` (it really doesn't matter)
                unary_ops = { '-': t, '!': t, '~': t, '+': t },
                // Also use a map for the binary operations but set their values to their
                // binary precedence for quick reference:
                // see [Order of operations](http://en.wikipedia.org/wiki/Order_of_operations#Programming_language)
                binary_ops = {
                    '=>': 0,
                    '||': 1, '&&': 2, '|': 3, '^': 4, '&': 5,
                    '==': 6, '!=': 6, '===': 6, '!==': 6,
                    '<': 7, '>': 7, '<=': 7, '>=': 7,
                    '<<': 8, '>>': 8, '>>>': 8,
                    '+': 9, '-': 9,
                    '*': 10, '/': 10, '%': 10
                },
                // Get return the longest key length of any object
                getMaxKeyLen = function (obj) {
                    var max_len = 0, len;
                    for (var key in obj) {
                        if ((len = key.length) > max_len && obj.hasOwnProperty(key)) {
                            max_len = len;
                        }
                    }
                    return max_len;
                },
                max_unop_len = getMaxKeyLen(unary_ops),
                max_binop_len = getMaxKeyLen(binary_ops),
                // Literals
                // ----------
                // Store the values to return for the various literals we may encounter
                literals = {
                    'true': true,
                    'false': false,
                    'null': null
                },
                // Except for `this`, which is special. This could be changed to something like `'self'` as well
                this_str = 'this',
                // Returns the precedence of a binary operator or `0` if it isn't a binary operator
                binaryPrecedence = function (op_val) {
                    return binary_ops[op_val] || 0;
                },
                // Utility function (gets called from multiple places)
                // Also note that `a && b` and `a || b` are *logical* expressions, not binary expressions
                createBinaryExpression = function (operator, left, right) {
                    var type = (operator === '||' || operator === '&&') ? LOGICAL_EXP : BINARY_EXP;
                    return {
                        type: type,
                        operator: operator,
                        left: left,
                        right: right
                    };
                },
                // `ch` is a character code in the next three functions
                isDecimalDigit = function (ch) {
                    return (ch >= 48 && ch <= 57); // 0...9
                },
                isIdentifierStart = function (ch) {
                    return (ch === 36) || (ch === 95) || (ch === 64) || // `$`, `_` and `@`
                        (ch >= 65 && ch <= 90) || // A...Z
                        (ch >= 97 && ch <= 122); // a...z
                },
                isIdentifierPart = function (ch) {
                    return (ch === 36) || (ch === 95) || // `$` and `_`
                        (ch >= 65 && ch <= 90) || // A...Z
                        (ch >= 97 && ch <= 122) || // a...z
                        (ch >= 48 && ch <= 57); // 0...9
                },
                // Parsing
                // -------
                // `expr` is a string with the passed in expression
                jsep = function (expr) {
                    // `index` stores the character number we are currently at while `length` is a constant
                    // All of the gobbles below will modify `index` as we move along
                    var index = 0,
                        charAtFunc = expr.charAt,
                        charCodeAtFunc = expr.charCodeAt,
                        exprI = function (i) { return charAtFunc.call(expr, i); },
                        exprICode = function (i) { return charCodeAtFunc.call(expr, i); },
                        length = expr.length,
                        // Push `index` up to the next non-space character
                        gobbleSpaces = function () {
                            var ch = exprICode(index);
                            // space or tab
                            while (ch === 32 || ch === 9) {
                                ch = exprICode(++index);
                            }
                        },
                        // Search for the operation portion of the string (e.g. `+`, `===`)
                        // Start by taking the longest possible binary operations (3 characters: `===`, `!==`, `>>>`)
                        // and move down from 3 to 2 to 1 character until a matching binary operation is found
                        // then, return that binary operation
                        gobbleBinaryOp = function () {
                            gobbleSpaces();
                            var biop, to_check = expr.substr(index, max_binop_len), tc_len = to_check.length;
                            while (tc_len > 0) {
                                if (binary_ops.hasOwnProperty(to_check)) {
                                    index += tc_len;
                                    return to_check;
                                }
                                to_check = to_check.substr(0, --tc_len);
                            }
                            return false;
                        },
                        // This function is responsible for gobbling an individual expression,
                        // e.g. `1`, `1+2`, `a+(b*2)-Math.sqrt(2)`
                        gobbleExpression = function () {
                            var ch_i, node, biop, prec, stack, biop_info, left, right, i;

                            // First, try to get the leftmost thing
                            // Then, check to see if there's a binary operator operating on that leftmost thing
                            left = gobbleToken();
                            biop = gobbleBinaryOp();

                            // If there wasn't a binary operator, just return the leftmost node
                            if (!biop) {
                                return left;
                            }

                            // Otherwise, we need to start a stack to properly place the binary operations in their
                            // precedence structure
                            biop_info = { value: biop, prec: binaryPrecedence(biop) };

                            right = gobbleToken();
                            if (!right) {
                                throw new Error("Expected expression after " + biop + " at character " + index);
                            }
                            stack = [left, biop_info, right];

                            // Properly deal with precedence using [recursive descent](http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm)
                            while ((biop = gobbleBinaryOp())) {
                                prec = binaryPrecedence(biop);

                                if (prec === 0) {
                                    break;
                                }
                                biop_info = { value: biop, prec: prec };

                                // Reduce: make a binary expression from the three topmost entries.
                                while ((stack.length > 2) && (prec <= stack[stack.length - 2].prec)) {
                                    right = stack.pop();
                                    biop = stack.pop().value;
                                    left = stack.pop();
                                    node = createBinaryExpression(biop, left, right);
                                    stack.push(node);
                                }

                                node = gobbleToken();
                                if (!node) {
                                    throw new Error("Expected expression after " + biop + " at character " + index);
                                }
                                stack.push(biop_info);
                                stack.push(node);
                            }

                            i = stack.length - 1;
                            node = stack[i];
                            while (i > 1) {
                                node = createBinaryExpression(stack[i - 1].value, stack[i - 2], node);
                                i -= 2;
                            }

                            return node;
                        },
                        // An individual part of a binary expression:
                        // e.g. `foo.bar(baz)`, `1`, `"abc"`, `(a % 2)` (because it's in parenthesis)
                        gobbleToken = function () {
                            var ch, curr_node, unop, to_check, tc_len;

                            gobbleSpaces();
                            ch = exprICode(index);

                            if (isDecimalDigit(ch) || ch === 46) {
                                // Char code 46 is a dot `.` which can start off a numeric literal
                                return gobbleNumericLiteral();
                            } else if (ch === 39 || ch === 34) {
                                // Single or double quotes
                                return gobbleStringLiteral();
                            } else if (isIdentifierStart(ch)) {
                                // `foo`, `bar.baz`
                                return gobbleVariable();
                            } else if (ch === 40) {
                                // Open parentheses
                                return gobbleGroup();
                            } else {
                                to_check = expr.substr(index, max_unop_len);
                                tc_len = to_check.length;
                                while (tc_len > 0) {
                                    if (unary_ops.hasOwnProperty(to_check)) {
                                        index += tc_len;
                                        return {
                                            type: UNARY_EXP,
                                            operator: to_check,
                                            argument: gobbleToken(),
                                            prefix: true
                                        };
                                    }
                                    to_check = to_check.substr(0, --tc_len);
                                }

                                return false;
                            }
                        },
                        // Parse simple numeric literals: `12`, `3.4`, `.5`. Do this by using a string to
                        // keep track of everything in the numeric literal and then calling `parseFloat` on that string
                        gobbleNumericLiteral = function () {
                            var number = '';
                            while (isDecimalDigit(exprICode(index))) {
                                number += exprI(index++);
                            }

                            if (exprI(index) === '.') { // can start with a decimal marker
                                number += exprI(index++);

                                while (isDecimalDigit(exprICode(index))) {
                                    number += exprI(index++);
                                }
                            }

                            if (exprI(index) === 'e' || exprI(index) === 'E') { // exponent marker
                                number += exprI(index++);
                                if (exprI(index) === '+' || exprI(index) === '-') { // exponent sign
                                    number += exprI(index++);
                                }
                                while (isDecimalDigit(exprICode(index))) { //exponent itself
                                    number += exprI(index++);
                                }
                                if (!isDecimalDigit(exprICode(index - 1))) {
                                    throw new Error('Expected exponent (' +
                                        number + exprI(index) + ') at character ' + index);
                                }
                            }


                            // Check to make sure this isn't a variable name that start with a number (123abc)
                            if (isIdentifierStart(exprICode(index))) {
                                throw new Error('Variable names cannot start with a number (' +
                                    number + exprI(index) + ') at character ' + index);
                            }

                            return {
                                type: LITERAL,
                                value: parseFloat(number),
                                raw: number
                            };
                        },
                        // Parses a string literal, staring with single or double quotes with basic support for escape codes
                        // e.g. `"hello world"`, `'this is\nJSEP'`
                        gobbleStringLiteral = function () {
                            var str = '', quote = exprI(index++), closed = false, ch;

                            while (index < length) {
                                ch = exprI(index++);
                                if (ch === quote) {
                                    closed = true;
                                    break;
                                } else if (ch === '\\') {
                                    // Check for all of the common escape codes
                                    ch = exprI(index++);
                                    switch (ch) {
                                        case 'n': str += '\n'; break;
                                        case 'r': str += '\r'; break;
                                        case 't': str += '\t'; break;
                                        case 'b': str += '\b'; break;
                                        case 'f': str += '\f'; break;
                                        case 'v': str += '\x0B'; break;
                                    }
                                } else {
                                    str += ch;
                                }
                            }

                            if (!closed) {
                                throw new Error('Unclosed quote after "' + str + '"');
                            }

                            return {
                                type: LITERAL,
                                value: str,
                                raw: quote + str + quote
                            };
                        },
                        // Gobbles only identifiers
                        // e.g.: `foo`, `_value`, `$x1`
                        // Also, this function checks if that identifier is a literal:
                        // (e.g. `true`, `false`, `null`) or `this`
                        gobbleIdentifier = function () {
                            var ch = exprICode(index), start = index, identifier;

                            if (isIdentifierStart(ch)) {
                                index++;
                            } else {
                                throw new Error('Unexpected ' + exprI(index) + 'at character ' + index);
                            }

                            while (index < length) {
                                ch = exprICode(index);
                                if (isIdentifierPart(ch)) {
                                    index++;
                                } else {
                                    break;
                                }
                            }
                            identifier = expr.slice(start, index);

                            if (literals.hasOwnProperty(identifier)) {
                                return {
                                    type: LITERAL,
                                    value: literals[identifier],
                                    raw: identifier
                                };
                            } else if (identifier === this_str) {
                                return { type: THIS_EXP };
                            } else {
                                return {
                                    type: IDENTIFIER,
                                    name: identifier
                                };
                            }
                        },
                        // Gobbles a list of arguments within the context of a function call. This function
                        // also assumes that the `(` has already been gobbled.
                        // e.g. `foo(bar, baz)` or `my_func()`
                        gobbleArguments = function () {
                            var ch_i, args = [], node;
                            while (index < length) {
                                gobbleSpaces();
                                ch_i = exprI(index);
                                if (ch_i === ')') { // done parsing
                                    index++;
                                    break;
                                } else if (ch_i === ',') { // between expressions
                                    index++;
                                } else {
                                    node = gobbleExpression();
                                    if (!node || node.type === COMPOUND) {
                                        throw new Error('Expected comma at character ' + index);
                                    }
                                    args.push(node);
                                }
                            }
                            return args;
                        },
                        // Gobble a non-literal variable name. This variable name may include properties
                        // e.g. `foo`, `bar.baz`, `foo['bar'].baz`
                        // It also gobbles function calls:
                        // e.g. `Math.acos(obj.angle)`
                        gobbleVariable = function () {
                            var ch_i, node, old_index;
                            node = gobbleIdentifier();
                            gobbleSpaces();
                            ch_i = exprI(index);
                            while (ch_i === '.' || ch_i === '[' || ch_i === '(') {
                                if (ch_i === '.') {
                                    index++;
                                    gobbleSpaces();
                                    node = {
                                        type: MEMBER_EXP,
                                        computed: false,
                                        object: node,
                                        property: gobbleIdentifier()
                                    };
                                } else if (ch_i === '[') {
                                    old_index = index;
                                    index++;
                                    node = {
                                        type: MEMBER_EXP,
                                        computed: true,
                                        object: node,
                                        property: gobbleExpression()
                                    };
                                    gobbleSpaces();
                                    ch_i = exprI(index);
                                    if (ch_i !== ']') {
                                        throw new Error('Unclosed [ at character ' + index);
                                    }
                                    index++;
                                    gobbleSpaces();
                                } else if (ch_i === '(') {
                                    // A function call is being made; gobble all the arguments
                                    index++;
                                    node = {
                                        type: CALL_EXP,
                                        'arguments': gobbleArguments(),
                                        callee: node
                                    };
                                }
                                gobbleSpaces();
                                ch_i = exprI(index);
                            }
                            return node;
                        },
                        // Responsible for parsing a group of things within parentheses `()`
                        // This function assumes that it needs to gobble the opening parenthesis
                        // and then tries to gobble everything within that parenthesis, assuming
                        // that the next thing it should see is the close parenthesis. If not,
                        // then the expression probably doesn't have a `)`
                        gobbleGroup = function () {
                            index++;
                            var node = gobbleExpression();
                            gobbleSpaces();
                            if (exprI(index) === ')') {
                                index++;
                                return node;
                            } else {
                                throw new Error('Unclosed ( at character ' + index);
                            }
                        },
                        nodes = [], ch_i, node;

                    while (index < length) {
                        ch_i = exprI(index);

                        // Expressions can be separated by semicolons, commas, or just inferred without any
                        // separators
                        if (ch_i === ';' || ch_i === ',') {
                            index++; // ignore separators
                        } else {
                            // Try to gobble each expression individually
                            if ((node = gobbleExpression())) {
                                nodes.push(node);
                                // If we weren't able to find a binary expression and are out of room, then
                                // the expression passed in probably has too much
                            } else if (index < length) {
                                throw new Error("Unexpected '" + exprI(index) + "' at character " + index);
                            }
                        }
                    }

                    // If there's only one expression just try returning the expression
                    if (nodes.length === 1) {
                        return nodes[0];
                    } else {
                        return {
                            type: COMPOUND,
                            body: nodes
                        };
                    }
                };

            // To be filled in by the template
            jsep.version = '0.2.8';
            jsep.toString = function () { return 'JavaScript Expression Parser (JSEP) v' + jsep.version; };

			/**
			 * @method jsep.addUnaryOp
			 * @param {string} op_name The name of the unary op to add
			 * @return jsep
			 */
            jsep.addUnaryOp = function (op_name) {
                unary_ops[op_name] = t;
                return this;
            };

			/**
			 * @method jsep.addBinaryOp
			 * @param {string} op_name The name of the binary op to add
			 * @param {number} precedence The precedence of the binary op (can be a float)
			 * @return jsep
			 */
            jsep.addBinaryOp = function (op_name, precedence) {
                max_binop_len = Math.max(op_name.length, max_binop_len);
                binary_ops[op_name] = precedence;
                return this;
            };

			/**
			 * @method jsep.removeUnaryOp
			 * @param {string} op_name The name of the unary op to remove
			 * @return jsep
			 */
            jsep.removeUnaryOp = function (op_name) {
                delete unary_ops[op_name];
                if (op_name.length === max_unop_len) {
                    max_unop_len = getMaxKeyLen(unary_ops);
                }
                return this;
            };

			/**
			 * @method jsep.removeBinaryOp
			 * @param {string} op_name The name of the binary op to remove
			 * @return jsep
			 */
            jsep.removeBinaryOp = function (op_name) {
                delete binary_ops[op_name];
                if (op_name.length === max_binop_len) {
                    max_binop_len = getMaxKeyLen(binary_ops);
                }
                return this;
            };

            return jsep;
        }());

        return expose;
    })();

    /**
     * Base types, can be considered as abstract classes.
     * This classes can be overwritten outside of the project, and later can be injected through constructors to change behaviours of core classes.
     * @namespace
     */
    var baseTypes = {
        /**
         * Data conversion base type (interface). With this we can abstract date conversion and users can choose (or write) their implementation.
         * @class
         */
        DateConverterBase: (function () {
            /*
             * Base for Date related types.
             * @constructor
             */
            var ctor = function (name) {
                this.name = name;
            };
            var proto = ctor.prototype;

            proto.toString = function () {
                return this.name;
            };

            /** Converts given value to date. */
            proto.parse = function (value) {
                throw helper.createError(i18N.notImplemented, ['DateConverterBase', 'parse']);
            };
            /** Converts given date to ISO string. */
            proto.toISOString = function (value) {
                throw helper.createError(i18N.notImplemented, ['DateConverterBase', 'toISOString']);
            };

            return ctor;
        })(),
        /**
         * Base of all data types.
         * @class
         */
        DataTypeBase: (function () {

            /*
             * Base of all data types.
             * @constructor
             * @param {string} name - Name of the data type.
             */
            var ctor = function (name) {
                this.name = name || 'DataTypeBase';
                this.isComplex = false;
            };
            var proto = ctor.prototype;

            proto.toString = function () {
                return this.name;
            };

            /**
             * Returns raw value representing given value.
             * @param {any} value - The value to use. It must be a valid value for the type.
             * @returns {any} Portable value. i.e: Enum->number and Date->string
             */
            proto.getRawValue = function (value) {
                return value;
            };

            /**
             * Checks if given value is valid for this type.
             * @param {any} value - The value to check.
             */
            proto.isValid = function (value) {
                return typeof value === this.name;
            };

            /**
             * Converts given value to OData format.
             * @param {any} value - The value to use.
             * @returns {string} OData query string compatible value.
             */
            proto.toODataValue = function (value) {
                return value.toString();
            };

            /**
             * Converts given value to Beetle format.
             * @param {any} value - The value to use.
             * @returns {string} Beetle query string compatible value.
             */
            proto.toBeetleValue = function (value) {
                return value.toString();
            };

            /** Gets default value for type. */
            proto.defaultValue = function () {
                throw helper.createError(i18N.notImplemented, [this.name, 'defaultValue']);
            };
            /** Generates a new unique value for this type. Used for auto-incremented values. */
            proto.autoValue = function () {
                throw helper.createError(i18N.notImplemented, [this.name, 'autoValue']);
            };
            /** Tries to convert given value to this type. */
            proto.handle = function (value) {
                throw helper.createError(i18N.notImplemented, [this.name, 'handle']);
            };

            return ctor;
        })(),
        /**
         * Base of all Expressions.
         * @class
         */
        ExpressionBase: (function () {
            /**
             * Javascript expression base class -like linq expressions.
             * @constructor
             * @param {any} name - Name of the expression.
             * @param {any} order - OData order for the expression.
                ///  When an expression with 2 comes after 3 this means the query cannot be executed as OData.
                ///  eg. query.where().select().where() should not be run as OData query, result differs when it is run as beetle query or local query.
             * @param {any} onlyBeetle - Is this expression is supported only by beetle?
             * @param {any} isProjection - Is this expression alters result type.
                ///  after result type is changed, if an expression is added to query, query becomes OData incompatible.
             */
            var ctor = function (name, order, onlyBeetle, isProjection) {
                this.name = name || 'ExpressionBase';
                this.order = order;
                this.onlyBeetle = onlyBeetle;
                this.isProjection = isProjection;
            };
            var proto = ctor.prototype;

            proto.toString = function () {
                return this.toBeetleQuery({});
            };

            /**
             * Converts expression to OData representation.
             * @param {QueryContext} queryContext - Query execution context.
             * @returns {string} OData query string compatible expression.
             */
            proto.toODataQuery = function (queryContext) {
                if (this.onlyBeetle === true) return this.toBeetleQuery(queryContext);

                var exp = Assert.isFunction(this.exp) ? helper.funcToLambda(this.exp) : this.exp;
                return helper.jsepToODataQuery(libs.jsep(exp), queryContext);
            };

            /**
             * Converts expression to Beetle representation.
             * @param {QueryContext} queryContext - Query execution context.
             * @returns {string} OData query string compatible expression.
             */
            proto.toBeetleQuery = function (queryContext) {
                if (!this.exp) return '';

                var exp = Assert.isFunction(this.exp) ? helper.funcToLambda(this.exp) : this.exp;
                return helper.jsepToBeetleQuery(libs.jsep(exp), queryContext);
            };

            /*
             * Clones the expression.
             * Queries are immutable like Linq;
             */
            proto.clone = function () {
                throw helper.createError(i18N.notImplemented, [this.name, 'clone']);
            };

            /*
             * Executes the expression on the provided array
             * @param {any[]} array - The array to use.
             * @returns {any} - Expression result.
             */
            proto.execute = function (array) {
                throw helper.createError(i18N.notImplemented, [this.name, 'execute']);
            };

            return ctor;
        })(),
        /**
         * Base of all query functions.
         * @class
         */
        QueryFuncBase: (function () {
            /**
             * Query function base class
             * @constructor
             * @param {any} name - Name of the function.
             * @param {any} beetleName - Name to use for beetle queries.
             * @param {any} argCount - Argument count for method.
             */
            var ctor = function (name, beetleName, argCount) {
                this.name = name;
                this.beetleName = beetleName;
                this.argCount = argCount;
            };
            var proto = ctor.prototype;

            /**
             * Converts function to OData representation.
             * @returns {string} OData query string compatible expression.
             */
            proto.toODataFunction = function () {
                var args = [];
                for (var i = 0; i < arguments.length; i++)
                    args.push(arguments[i]);

                return this.name + '(' + args.join(', ') + ')';
            };

            /**
             * Converts function to Beetle representation.
             * @returns {string} Beetle query string compatible expression.
             */
            proto.toBeetleFunction = function () {
                var source = '';
                var i = 0;
                if (arguments.length == this.argCount) {
                    source = arguments[0] + '.';
                    ++i;
                }

                var args = Array.prototype.slice.call(arguments, i);
                return source + this.beetleName + '(' + args.join(', ') + ')';
            };

            /**
             * Function's javascript implementation.
             * @returns {string} Beetle query string compatible expression.
             */
            proto.impl = function () {
                throw helper.createError(i18N.notImplemented, [this.name, 'impl']);
            };

            return ctor;
        })(),
        /**
         * Base of all queries.
         * @class
         */
        QueryBase: (function () {
            /**
             * Query base class. Contains common query methods.
             * @constructor
             */
            var ctor = function () {
                this.expressions = [];

                this.inlineCountEnabled = false;
                this.lastExpOrder = 0;
                this.isMultiTyped = false;
                this.lastProjection = 0;
                this.isClosed = false;
                this.hasBeetlePrm = false;
                this.options = null;
            };
            var proto = ctor.prototype;
            proto.executeAfterExecuter = false;

            proto.toString = function () {
                var params = [];

                if (this.inlineCountEnabled === true)
                    params.push('inlinecount: allpages');

                var qc = {};
                helper.forEach(this.expressions, function (exp) {
                    qc.expVarContext = exp.varContext;
                    params.push(exp.name + ': ' + exp.toBeetleQuery(qc));
                    qc.expVarContext = undefined;
                });

                return params.join(', ');
            };

            /**
             * Adds given expression to expression list and decides if now this query is projected and multi-typed.
             * @param {ExpressionBase} exp - Expression to add.
             */
            proto.addExpression = function (exp) {
                if (this.isClosed) throw helper.createError(i18N.queryClosed, null, { query: this });
                helper.assertPrm(exp, 'expression').isInstanceOf(baseTypes.ExpressionBase).check();
                this.expressions.push(exp);
                if (exp.isExecuter === true) {
                    var executeAfterExecuter = this.options && this.options.executeAfterExecuter;
                    if (executeAfterExecuter == null) executeAfterExecuter = this.executeAfterExecuter;
                    if (executeAfterExecuter)
                        return this.execute();
                    this.isClosed = true;
                }

                // to support both odata and beetle queries, I added an order for expressions.
                if (exp.order < this.lastExpOrder || (this.hasBeetlePrm && !exp.onlyBeetle))
                    this.isMultiTyped = true;
                if (exp.isProjection === true)
                    this.lastProjection = this.expressions.length - 1;
                if (exp.onlyBeetle)
                    this.hasBeetlePrm = true;

                return this;
            };

            /**
             * Indicates wheter or not include total count in result.
             * @param {boolean=} isEnabled - When true, total count will be included in result. Default value: true.
             */
            proto.inlineCount = function (isEnabled) {
                var q = this.clone();
                q.inlineCountEnabled = isEnabled !== false;
                return q;
            };

            /**
             * If model has inheritance, when querying base type we can tell which derived type we want to load.
             * @param {string} typeName - Derived type name.
             */
            proto.ofType = function (typeName) {
                var q = this.clone();
                return q.addExpression(new querying.expressions.OfTypeExp(typeName));
            };

            /**
             * Filter query based on given expression.
             * @param {string|predicateFunction} predicate - A function to test each element for a condition (can be string expression).
             * @param {Object|any[]} varContext - Variable context for the expression.
             */
            proto.where = function (predicate, varContext) {
                var q = this.clone();
                return q.addExpression(new querying.expressions.WhereExp(predicate, varContext));
            };

            /**
             * Sorts results based on given properties.
             * @param {string|propertySelectFunction} properties - The properties to sort by.
             * @param {boolean=} isDesc - Indicates if sorting will be descending. Default value is false.
             */
            proto.orderBy = function (properties, isDesc) {
                var q = this.clone();
                return q.addExpression(new querying.expressions.OrderByExp(properties, isDesc));
            };

            /**
             * Sorts results based on given properties descendingly.
             * @param {string|propertySelectFunction} properties - The properties to sort by.
             */
            proto.orderByDesc = function (properties) {
                return this.orderBy(properties, true);
            };

            /**
             * Selects only given properties using projection.
             * @param {string|string[]} properties - Properties or PropertyPaths to select (project).
             */
            proto.select = function (properties) {
                var q = this.clone();
                if (arguments.length == 1) {
                    var arg = arguments[0];
                    if (Assert.isArray(arg))
                        properties = arg.join(', ');
                } else properties = Array.prototype.slice.call(arguments).join(', ');
                return q.addExpression(new querying.expressions.SelectExp(properties));
            };

            /**
             * Skips given count records and start reading.
             * @param {number} count - The number of items to skip.
             */
            proto.skip = function (count) {
                var q = this.clone();
                return q.addExpression(new querying.expressions.SkipExp(count));
            };

            /**
             * Takes only given count records.
             * @param {number} count - The number of items to take.
             */
            proto.take = function (count) {
                return this.top(count);
            };

            /**
             * Takes only given count records .
             * @param {number} count - The number of items to take.
             */
            proto.top = function (count) {
                var q = this.clone();
                return q.addExpression(new querying.expressions.TopExp(count));
            };

            /**
             * Groups query by given keys (projects them into a new type) and returns values (projecting into new type).
             * @param {string|string[]} keySelector - A projection to extract the key for each element.
             * @param {string|string[]} valueSelector - A projection to create a result value from each group.
             */
            proto.groupBy = function (keySelector, valueSelector) {
                var q = this.clone();
                if (Assert.isArray(keySelector))
                    keySelector = keySelector.join(', ');
                if (Assert.isArray(valueSelector))
                    valueSelector = valueSelector.join(', ');
                return q.addExpression(new querying.expressions.GroupByExp(keySelector, valueSelector));
            };

            /**
             * Gets only distinct items, when selector is given it will be used as comparer (project and compares projected objects).
             * @param {string|string[]} selector - A projection to extract the key for each element.
             */
            proto.distinct = function (selector) {
                var q = this.clone();
                if (Assert.isArray(selector))
                    selector = selector.join(', ');
                return q.addExpression(new querying.expressions.DistinctExp(selector));
            };

            /** Reverse the collection. */
            proto.reverse = function () {
                var q = this.clone();
                return q.addExpression(new querying.expressions.ReverseExp());
            };

            /**
             * Selects given collection property for each element and returns all in a new array.
             * @param {string|string[]} properties - Properties or PropertyPaths to select (project).
             */
            proto.selectMany = function (properties) {
                var q = this.clone();
                return q.addExpression(new querying.expressions.SelectManyExp(properties));
            };

            /**
             * Gets all the items after first succesful predicate.
             * @param {string|predicateFunction} predicate - A function to test each element for a condition (can be string expression).
             * @param {Object|any[]} varContext - Variable context for the expression.
             */
            proto.skipWhile = function (predicate, varContext) {
                /// <param name="predicate"></param>
                /// <param name="varContext">Variable context for the expression.</param>
                var q = this.clone();
                return q.addExpression(new querying.expressions.SkipWhileExp(predicate, varContext));
            };

            /**
             * Gets all the items before first succesful predicate.
             * @param {string|predicateFunction} predicate - A function to test each element for a condition (can be string expression).
             * @param {Object|any[]} varContext - Variable context for the expression.
             */
            proto.takeWhile = function (predicate, varContext) {
                var q = this.clone();
                return q.addExpression(new querying.expressions.TakeWhileExp(predicate, varContext));
            };

            /**
             * If all items suits given predication returns true, otherwise false.
             * @param {string|predicateFunction} predicate - A function to test each element for a condition (can be string expression).
             * @param {Object|any[]} varContext - Variable context for the expression.
             */
            proto.all = function (predicate, varContext) {
                var q = this.clone();
                return q.addExpression(new querying.expressions.AllExp(predicate, varContext));
            };

            /**
             * If there is at least one item in query result (or any item suits given predication) returns true, otherwise false.
             * @param {string|predicateFunction} predicate - A function to test each element for a condition (can be string expression).
             * @param {Object|any[]} varContext - Variable context for the expression.
             */
            proto.any = function (predicate, varContext) {
                var q = this.clone();
                return q.addExpression(new querying.expressions.AnyExp(predicate, varContext));
            };

            /**
             * Calculates average of items of query (or from given projection result).
             * @param {string=} selector - Property path to use on calculation.
             */
            proto.avg = function (selector) {
                var q = this.clone();
                return q.addExpression(new querying.expressions.AvgExp(selector));
            };

            /**
             * Finds maximum value from items of query (or from given projection result).
             * @param {string=} selector - Property path to use on calculation.
             */
            proto.max = function (selector) {
                var q = this.clone();
                return q.addExpression(new querying.expressions.MaxExp(selector));
            };

            /**
             * Finds minimum value from items of query (or from given projection result).
             * @param {string=} selector - Property path to use on calculation.
             */
            proto.min = function (selector) {
                var q = this.clone();
                return q.addExpression(new querying.expressions.MinExp(selector));
            };

            /**
             * Finds summary value from items of query (or from given projection result).
             * @param {string=} selector - Property path to use on calculation.
             */
            proto.sum = function (selector) {
                var q = this.clone();
                return q.addExpression(new querying.expressions.SumExp(selector));
            };

            /**
             * Gets the count of items of query.
             * @param {string|predicateFunction} predicate - A function to test each element for a condition (can be string expression).
             * @param {Object|any[]} varContext - Variable context for the expression.
             */
            proto.count = function (predicate, varContext) {
                var q = this.clone();
                return q.addExpression(new querying.expressions.CountExp(predicate, varContext));
            };

            /**
             * Gets the first value from items of query (or from given predication result). When there is no item, throws exception.
             * @param {string|predicateFunction} predicate - A function to test each element for a condition (can be string expression).
             * @param {Object|any[]} varContext - Variable context for the expression.
             */
            proto.first = function (predicate, varContext) {
                var q = this.clone();
                return q.addExpression(new querying.expressions.FirstExp(predicate, varContext));
            };

            /**
             * Gets the first value (or null when there is no items) from items of query (or from given predication result).
             * @param {string|predicateFunction} predicate - A function to test each element for a condition (can be string expression).
             * @param {Object|any[]} varContext - Variable context for the expression.
             */
            proto.firstOrDefault = function (predicate, varContext) {
                var q = this.clone();
                return q.addExpression(new querying.expressions.FirstOrDefaultExp(predicate, varContext));
            };

            /**
             * Gets the single value from items (or from given predication result). Where zero or more than one item exists throws exception.
             * @param {string|predicateFunction} predicate - A function to test each element for a condition (can be string expression).
             * @param {Object|any[]} varContext - Variable context for the expression.
             */
            proto.single = function (predicate, varContext) {
                var q = this.clone();
                return q.addExpression(new querying.expressions.SingleExp(predicate, varContext));
            };

            /**
             * Gets the single value (or null when there is no items) from items (or from given predication result). Where more than one item exists throws exception.
             * @param {string|predicateFunction} predicate - A function to test each element for a condition (can be string expression).
             * @param {Object|any[]} varContext - Variable context for the expression.
             */
            proto.singleOrDefault = function (predicate, varContext) {
                var q = this.clone();
                return q.addExpression(new querying.expressions.SingleOrDefaultExp(predicate, varContext));
            };

            /**
             * Gets the last value from items of query (or from given predication result). When there is no item, throws exception.
             * @param {string|predicateFunction} predicate - A function to test each element for a condition (can be string expression).
             * @param {Object|any[]} varContext - Variable context for the expression.
             */
            proto.last = function (predicate, varContext) {
                var q = this.clone();
                return q.addExpression(new querying.expressions.LastExp(predicate, varContext));
            };

            /**
             * Gets the last value (or null when there is no items) from items of query (or from given predication result).
             * @param {string|predicateFunction} predicate - A function to test each element for a condition (can be string expression).
             * @param {Object|any[]} varContext - Variable context for the expression.
             */
            proto.lastOrDefault = function (predicate, varContext) {
                var q = this.clone();
                return q.addExpression(new querying.expressions.LastOrDefaultExp(predicate, varContext));
            };

            /**
             * Sets options to be used at execution.
             * @param {queryOptions} options - Query options. Multiple call to this function will override previous settings.
             */
            proto.withOptions = function (options) {
                var q = this.clone();
                q.options = helper.combine(this.options, options);
                return q;
            };

            /** Executes the query. */
            proto.execute = function () {
                throw helper.createError(i18N.notImplemented, ['Query', 'execute']);
            };

            /** Executes the query. Shortcut for execute. */
            proto.x = function () {
                return this.execute.apply(this, arguments);
            };

            /** * Creates a function that can execute query operations against given array. */
            proto.toFunction = function () {
                var that = this;
                return function (array, varContext) {
                    var qc = { varContext: varContext };
                    qc.aliases = [];
                    if (that.inlineCountEnabled)
                        qc.inlineCount = array.length;
                    helper.forEach(that.expressions, function (exp) {
                        qc.expVarContext = exp.varContext;
                        array = exp.execute(array, qc);
                        if (that.inlineCountEnabled
                                && !Assert.isInstanceOf(exp, querying.expressions.TopExp)
                                && !Assert.isInstanceOf(exp, querying.expressions.SkipExp)) {
                            qc.inlineCount = array.length;
                        }
                        qc.expVarContext = undefined;
                    });
                    if (that.inlineCountEnabled)
                        array.$inlineCount = qc.inlineCount;
                    return array;
                };
            };

            /** Clones whole query. */
            proto.clone = function () {
                throw helper.createError(i18N.notImplemented, ['Query', 'clone']);
            };

            /**
             * Copies properties to given query.
             * @param {QueryBase} query - The query to populate. Must be a subclass of QueryBase.
             */
            proto.copy = function (query) {
                helper.forEach(this.expressions, function (exp) {
                    query.expressions.push(exp.clone());
                });
                query.inlineCountEnabled = this.inlineCountEnabled;
                query.lastExpOrder = this.lastExpOrder;
                query.isMultiTyped = this.isMultiTyped;
                query.lastProjection = this.lastProjection;
                query.isClosed = this.isClosed;
                query.hasBeetlePrm = this.hasBeetlePrm;
                if (this.options)
                    query.options = helper.combine(null, this.options);
            };

            /** Finds given typed expression. */
            proto.getExpression = function (type, throwIfNotFound) {
                for (var i = this.lastProjection; i < this.expressions.length; i++) {
                    var exp = this.expressions[i];
                    if (Assert.isInstanceOf(exp, type)) return exp;
                }
                if (throwIfNotFound === true)
                    throw helper.createError(i18N.expressionCouldNotBeFound, { type: type, query: this });
                return null;
            };

            /** Removes given typed expressions. */
            proto.removeExpression = function (type) {
                for (var i = this.expressions.length - 1; i >= 0; i--) {
                    var exp = this.expressions[i];
                    if (Assert.isInstanceOf(exp, type))
                        this.expressions.splice(i, 1);
                }
                return this;
            };

            return ctor;
        })(),
        /**
         * Observable provider base class. Makes given object's properties observable.
         * @class
         */
        ObservableProviderBase: (function () {
            var ctor = function (name) {
                this.name = name || 'ObservableProviderBase';
            };
            var proto = ctor.prototype;

            proto.toString = function () {
                return this.name;
            };

            /**
             * When given property for given object is observable returns true, otherwise false.
             * @param {Object} object - The object.
             * @param {string} property - The property name.
             */
            proto.isObservable = function (object, property) {
                throw helper.createError(i18N.notImplemented, [this.name, 'isObservable']);
            };
            /**
             * Makes given object observable.
             * @param {Object} object - The object.
             * @param {EntityType} type - The entity type.
             * @param {ObservableProviderCallbackOptions} callbacks - Callback functions, beetle tracks entities using these callbacks..
             */
            proto.toObservable = function (object, type, callbacks) {
                throw helper.createError(i18N.notImplemented, [this.name, 'toObservable']);
            };
            /**
             * Reads an observable property value from object.
             * @param {Object} object - The object.
             * @param {string} property - The property name.
             */
            proto.getValue = function (object, property) {
                throw helper.createError(i18N.notImplemented, [this.name, 'getValue']);
            };
            /**
             * Sets the value of observable property of given object.
             * @param {Object} object - The object.
             * @param {string} property - The property name.
             * @param {any} value - The value to set.
             */
            proto.setValue = function (object, property, value) {
                throw helper.createError(i18N.notImplemented, [this.name, 'setValue']);
            };

            return ctor;
        })(),
        /**
         * Ajax provider base class. Operates ajax operations.
         * @class
         */
        AjaxProviderBase: (function () {
            var ctor = function (name) {
                this.name = name || 'AjaxProviderBase';
                this.syncSupported = true;
            };
            var proto = ctor.prototype;

            proto.toString = function () {
                return this.name;
            };

            /**
             * Ajax operation function.
             * @param {string} uri - Uri to make request.
             * @param {string} type - Request type (POST, GET..)
             * @param {string} dataType - Request data type (xml, json..)
             * @param {string} contentType - Request content type (application/x-www-form-urlencoded; charset=UTF-8, application/json..)
             * @param {any} data - Request data.
             * @param {boolean} async - If set to false, request will be made synchronously.
             * @param {number} timeout - AJAX call timeout value. if call won't be completed after given time, exception will be thrown.
             * @param {Object} extra - Implementor specific arguments.
             * @param {Object} headers - Custom HTTP headers.
             * @param {successCallback} successCallback - Function to call after operation succeeded.
             * @param {errorCallback} errorCallback - Function to call when operation fails.
             */
            proto.doAjax = function (uri, method, dataType, contentType, data, async, timeout, extra, headers, successCallback, errorCallback) {
                throw helper.createError(i18N.notImplemented, [this.name, 'doAjax']);
            };

            /**
             * Creates an error object by parsing XHR result.
             * @param {XMLHttpRequest} xhr - XML Http Request object. Can be used from implementors using Xhr.
             */
            proto.createError = function (xhr) {
                var obj = { status: xhr.status, xhr: xhr, detail: xhr.responseText };
                return helper.createError(xhr.statusText, obj);
            }

            /**
             * Returns a function that can get Http header for given key.
             * @param {XMLHttpRequest} xhr - XML Http Request object. Can be used from implementors using Xhr.
             * @returns {headerGetterFunction} Header getter function (string) => string.
             */
            proto.getHeaderGetter = function (xhr) {
                return function (header) {
                    return xhr.getResponseHeader(header);
                };
            }

            return ctor;
        })(),
        /**
         * Serialization service base class. Deserializes incoming data and serializes outgoing data.
         * @class
         */
        SerializationServiceBase: (function () {
            var ctor = function (name) {
                this.name = name || 'SerializationServiceBase';
            };
            var proto = ctor.prototype;

            proto.toString = function () {
                return this.name;
            };

            /**
             * Serializes given data to string.
             * @param {Object} data - Object to serialize.
             * @returns {string} Serialized string.
             */
            proto.serialize = function (data) {
                throw helper.createError(i18N.notImplemented, [this.name, 'serialize']);
            };
            /**
             * Deserializes given string to object.
             * @param {string} string - String to deserialize.
             * @returns {string} Deserialized object.
             */
            proto.deserialize = function (string) {
                throw helper.createError(i18N.notImplemented, [this.name, 'deserialize']);
            };

            return ctor;
        })(),
        /**
         * Promise provider base class. Creates deferred promises for async operations.
         * @class
         */
        PromiseProviderBase: (function () {
            var ctor = function (name) {
                this.name = name || 'PromiseProviderBase';
            };
            var proto = ctor.prototype;

            proto.toString = function () {
                return this.name;
            };

            /**
             * Creates deferred object.
             * @returns {Object} Deferred object which can be resolved or rejected.
             */
            proto.deferred = function () {
                throw helper.createError(i18N.notImplemented, [this.name, 'deferred']);
            };
            /**
             * Gets promise for deferred object.
             * @param {Object} deferred - Deferred object which can be resolved or rejected.
             * @returns {Promise} Returns a promise.
             */
            proto.getPromise = function (deferred) {
                throw helper.createError(i18N.notImplemented, [this.name, 'getPromise']);
            };
            /**
             * Resolves given promise for succesful operation.
             * @param {Object} deferred - Deferred object.
             * @param {any} data - Operation result.
             */
            proto.resolve = function (deferred, data) {
                throw helper.createError(i18N.notImplemented, [this.name, 'resolve']);
            };
            /**
             * Rejects given promise for failed operation.
             * @param {Object} deferred - Deferred object.
             * @param {Error} error - Error to pass to failed callback.
             */
            proto.reject = function (deferred, error) {
                throw helper.createError(i18N.notImplemented, [this.name, 'reject']);
            };

            return ctor;
        })(),
        /**
         * Data service base class.
         * @class
         */
        DataServiceBase: (function () {
            // cache metadata to reduce network traffic.
            var _metadataCache = [];

            /**
             *
             * @constructor
             * @param {any} uri - Service URI.
             * @param {MetadataManager|string|boolean} metadataPrm - [Metadata Manager] or [Metadata string] or [loadMetadata: when false no metadata will be used]
             * @param {ServiceOptions} injections - Injection object to change behaviour of the service,
             *      Can include these properties: ajaxProvider, serializationService, ajaxTimeout, dataType, contentType.
             *      When not given, defaults will be used.
             */
            var ctor = function (uri, metadataPrm, injections) {
                initialize(uri, metadataPrm, injections, this);
            };
            var proto = ctor.prototype;

            proto.toString = function () {
                return this.uri;
            };

            /** Checks if service is ready. */
            proto.isReady = function () {
                return !this._awaitingMetadata;
            };

            /**
             * Subscribe the ready callback.
             * @param {Function} callback - Function to call when the service is ready.
             */
            proto.ready = function (callback) {
                this._readyCallbacks.push(callback);

                checkReady(this);
            };

            /**
             * Gets entity type from metadata by its short name.
             * @param {string} shortName - Short name of the type.
             * @returns {metadata.EntityType} - Entity type, null if not found.
             */
            proto.getEntityType = function (shortName) {
                return this.metadataManager ? this.metadataManager.getEntityType(shortName) : null;
            };

            /**
             * Creates a query for a resource. Every data service can have their own query types.
             * @param {string} resourceName - Server resource name to combine with base uri.
             * @param {string=} shortName - Entity type's short name.
             * @param {EntityManager=} manager - Entity manager.
             * @returns {EntityQuery} Entity query. Can be build with method-chaining.
             */
            proto.createQuery = function (resourceName, shortName, manager) {
                helper.assertPrm(resourceName, 'resourceName').isNotEmptyString().check();
                if (shortName) return this.createEntityQuery(shortName, resourceName, manager);
                if (this.metadataManager) return this.metadataManager.createQuery(resourceName, null, manager);
                return new querying.EntityQuery(resourceName, null, manager);
            };

            /**
             * Creates a query for a resource. Every data service can have their own query types.
             * @param {string} shortName - Entity type's short name.
             * @param {string=} resourceName - Server resource name to combine with base uri.
             * @param {EntityManager=} manager - Entity manager.
             * @returns {EntityQuery} Entity query. Can be build with method-chaining.
             */
            proto.createEntityQuery = function (shortName, resourceName, manager) {
                if (!this.metadataManager)
                    throw helper.createError(i18N.noMetadataEntityQuery, { dataService: this });
                return this.metadataManager.createQuery(resourceName, shortName, manager);
            };

            /**
             * Register constructor and initializer (optional) for given type.
             * @param {string} shortName - Short name of the type.
             * @param {Function} constructor - Constructor function. Called right after the entity object is generated.
             * @param {Function} initializer - Initializer function. Called after entity started to being tracked (properties converted to observable).
             */
            proto.registerCtor = function (shortName, constructor, initializer) {
                if (this.metadataManager == null)
                    throw helper.createError(i18N.noMetadataEntityQuery);
                this.metadataManager.registerCtor(shortName, constructor, initializer);
            };

            /**
             * Creates an entity based on metadata information.
             * @param {string} shortName - Short name of the type.
             * @param {Object} initialValues - Entity initial values.
             * @returns {Entity} Entity with observable properties.
             */
            proto.createEntity = function (shortName, initialValues) {
                if (!this.metadataManager) throw helper.createError(i18N.noMetadataEntityQuery, { dataService: this });
                return this.metadataManager.createEntity(shortName, initialValues);
            };

            /**
             * Creates a raw entity based on metadata information.
             * @param {string} shortName - Short name of the type.
             * @param {Object} initialValues - Entity initial values.
             * @returns {Entity} Entity without observable properties.
             */
            proto.createRawEntity = function (shortName, initialValues) {
                if (!this.metadataManager) throw helper.createError(i18N.noMetadataEntityQuery, { dataService: this });
                return this.metadataManager.createRawEntity(shortName, initialValues);
            };

            /**
             * Creates an entity based on metadata information.
             * @param {Object} result - Entity initial object. This object instance will be made observable.
             * @param {string} typeName - Entity type name (full).
             * @returns {Entity} Entity with observable properties.
             */
            proto.toEntity = function (result, typeName) {
                var type = null;
                if (this.metadataManager)
                    type = this.metadataManager.getEntityTypeByFullName(typeName);
                if (!type) type = new metadata.EntityType(typeName);
                return core.EntityTracker.toEntity(result, type, settings.getObservableProvider());
            };

            /**
             * Converts given query to a OData query string format.
             * @param {EntityQuery} query - Entity query to convert to OData parameters.
             * @param {Object} varContext - Variable context for the query.
             * @returns {Object[]} Parameter object (name|value pair) list.
             */
            proto.toODataQueryParams = function (query, varContext) {
                if (query.isMultiTyped === true)
                    throw helper.createError(i18N.oDataNotSupportMultiTyped, { query: query });

                var params = [];
                if (query.inlineCountEnabled === true)
                    params.push({ name: '$inlinecount', value: 'allpages' });

                var qc = { varContext: varContext };
                helper.forEach(query.expressions, function (exp, i) {
                    qc.expVarContext = exp.varContext;
                    var name, value;
                    if (exp.onlyBeetle === true) {
                        name = '!e' + i;
                        value = exp.toBeetleQuery(qc);
                        value = exp.name + ':' + value;
                    } else {
                        name = '$' + exp.name;
                        value = exp.toODataQuery(qc);
                    }
                    params.push({ name: name, value: value });
                    qc.expVarContext = undefined;
                });

                return params;
            };

            /**
             * Converts given query to a Beetle query string format.
             * @param {EntityQuery} query - Entity query to convert to Beetle parameters.
             * @param {Object} varContext - Variable context for the query.
             * @returns {Object[]} Parameter object (name|value pair) list.
             */
            proto.toBeetleQueryParams = function (query, varContext) {
                var params = [];
                if (query.inlineCountEnabled === true)
                    params.push({ name: '!e0', value: 'inlinecount:allpages' });

                var qc = { varContext: varContext };
                helper.forEach(query.expressions, function (exp, i) {
                    qc.expVarContext = exp.varContext;
                    params.push({ name: '!e' + (i + 1), value: exp.name + ':' + exp.toBeetleQuery(qc) });
                    qc.expVarContext = undefined;
                });

                return params;
            };

            /**
             * Fetch metadata from server.
             * @param {Object} options - Fetch metadata options (async: boolean).
             * @param {Function} successCallback - Function to call after operation succeeded.
             * @param {Function} errorCallback - Function to call when operation fails.
             */
            proto.fetchMetadata = function (options, successCallback, errorCallback) {
                throw helper.createError(i18N.notImplemented, ['DataServiceBase', 'fetchMetadata']);
            };
            /**
             * When there is no metadata available services may be able to create entities asynchronously (server side must be able to support this).
             * @param {string} typeName - Type name to create.
             * @param {Object} initialValues - Entity initial values.
             * @param {Object=} options - Options (makeObservable: boolean, async: boolean).
             * @param {Function} successCallback - Function to call after operation succeeded.
             * @param {Function} errorCallback - Function to call when operation fails.
             */
            proto.createEntityAsync = function (typeName, initialValues, options, successCallback, errorCallback) {
                throw helper.createError(i18N.notImplemented, ['DataServiceBase', 'createEntityAsync']);
            };
            /**
             * Executes given query.
             * @param {EntityQuery} query
             * @param {QueryOptions} options - Query options.
             * @param {Function} successCallback - Function to call after operation succeeded.
             * @param {Function} errorCallback - Function to call when operation fails.
             */
            proto.executeQuery = function (query, options, successCallback, errorCallback) {
                throw helper.createError(i18N.notImplemented, ['DataServiceBase', 'executeQuery']);
            };
            /**
             * Executes given query parameters.
             * @param {string} resource - Server resource to query.
             * @param {Object[]} parameters - The query parameters.
             * @param {Object} bodyParameters - The body parameter objects.
             * @param {Object[]} queryParams - The query beetle parameters.
             * @param {QueryOptions} options - Query options.
             * @param {Function} successCallback - Function to call after operation succeeded.
             * @param {Function} errorCallback - Function to call when operation fails.
             */
            proto.executeQueryParams = function (resource, parameters, bodyParameters, queryParams, options, successCallback, errorCallback) {
                throw helper.createError(i18N.notImplemented, ['DataServiceBase', 'executeQueryParams']);
            };
            /**
             * Send changes to server.
             * @param {SavePackage} savePackage - An object containing entities to send to server for persistence.
             * @param {QueryOptions} options - Query options.
             * @param {Function} successCallback - Function to call after operation succeeded.
             * @param {Function} errorCallback - Function to call when operation fails.
             */
            proto.saveChanges = function (savePackage, options, successCallback, errorCallback) {
                throw helper.createError(i18N.notImplemented, ['DataServiceBase', 'saveChanges']);
            };

            var registerMetadataTypes;
            function initialize(uri, metadataPrm, injections, instance) {
                instance._awaitingMetadata = false;
                instance._readyCallbacks = [];

                if (uri == null) uri = '';
                else if (uri[uri.length - 1] !== '/') uri += '/';
                instance.uri = uri;

                injections = injections || {};
                instance.ajaxProvider = injections.ajaxProvider || settings.getAjaxProvider();
                instance.serializationService = injections.serializationService || settings.getSerializationService();

                instance.ajaxTimeout = injections.ajaxTimeout;
                instance.dataType = injections.dataType || 'json';
                instance.contentType = injections.contentType || 'application/json; charset=utf-8';

                registerMetadataTypes = injections.registerMetadataTypes;
                if (registerMetadataTypes == null)
                    registerMetadataTypes = settings.registerMetadataTypes;

                // If metadata parameter is false or undefined, it means do not use metadata
                if (metadataPrm !== false || metadataPrm === undefined) {
                    // When there is no metadata or metadata is true fetch metadata from server.
                    if (!metadataPrm || metadataPrm === true) {
                        // try to get metadata from cache
                        var cached = null;
                        if (settings.cacheMetadata === true)
                            cached = helper.findInArray(_metadataCache, uri, 'uri');
                        if (cached)
                            instance.metadataManager = cached.data;
                        else {
                            instance._awaitingMetadata = true;
                            instance.fetchMetadata(
                                null,
                                function (metadataObject) {
                                    instance._awaitingMetadata = false;
                                    instance.metadataManager = new metadata.MetadataManager(metadataObject);
                                    // cache retrieved and parsed metadata
                                    if (settings.cacheMetadata === true)
                                        _metadataCache.push({ uri: uri, data: instance.metadataManager });
                                    checkReady(instance);
                                },
                                function (e) {
                                    throw helper.createError(i18N.couldNotLoadMetadata, { exception: e, args: arguments, dataService: this });
                                });
                        }
                    } else if (Assert.isInstanceOf(metadataPrm, metadata.MetadataManager))
                        instance.metadataManager = metadataPrm;
                    else if (Assert.isObject(metadataPrm)) {
                        try {
                            instance.metadataManager = new metadata.MetadataManager(metadataPrm);
                        } catch (e) {
                            throw helper.createError(i18N.invalidArguments, { exception: e, args: arguments, dataService: this });
                        }
                    }
                }
                if (!instance._awaitingMetadata)
                    checkReady(instance);
            }

            function checkReady(instance) {
                if (instance.isReady()) {
                    var metadata = instance.metadataManager;
                    if (registerMetadataTypes && metadata) {
                        var managerName = metadata.name;
                        if (!(managerName in root))
                            root[managerName] = core.EntityManager;
                    }

                    var cs = instance._readyCallbacks.slice(0);
                    instance._readyCallbacks = [];
                    for (var i = 0; i < cs.length; i++) {
                        var c = cs[i];
                        if (c) c.call(instance);
                    }
                }
            }

            return ctor;
        })()
    };

    /**
     * Base types' implementations.
     * @namespace
     */
    var impls = {
        /**
         * Default date converter class. Uses browser's default Date object.
         * @class
         */
        DefaultDateConverter: (function () {
            var ctor = function () {
                baseTypes.DateConverterBase.call(this, 'Default Date Converter');
            };
            helper.inherit(ctor, baseTypes.DateConverterBase);
            var proto = ctor.prototype;

            proto.parse = function (value) {
                if (typeof value != "string") return null;
                if (value.length < 6) return null;
                if (!/^(?:(?:\d{2}[.\/-]\d{2}[.\/-]\d{4}|\d{4}[.\/-]\d{2}[.\/-]\d{2})T?[\:\.\+\s\d]*Z?$)|\w{3} \w{3} \d{2} \d{4} \d{2}:\d{2}:\d{2} GMT[\+\-]\d{4}/.test(value))
                    return null;
                if (/.\d{3}$/.test(value)) value += 'Z';
                try {
                    var d = Date.parse(value);
                    return isNaN(d) ? null : new Date(d);
                } catch (e) {
                    return null;
                }
            };

            proto.toISOString = function (value) {
                return value.toISOString();
            };

            return ctor;
        })(),
        /**
         * Knockout observable provider class. Makes given object's properties observable.
         * @class
         */
        KoObservableProvider: (function () {

            var ctor = function (ko) {
                baseTypes.ObservableProviderBase.call(this, 'Knockout Observable Provider');
                this.ko = ko;

                /**
                 * Observable value read-write interceptor.
                 * Because ko does not give old and new values together when notifying subscribers, I had to write this extender.
                 */
                if (ko.extenders.intercept == null) {
                    ko.extenders.intercept = function (target, interceptor) {
                        var result = ko.computed({
                            read: target,
                            write: function (newValue) {
                                var callback = interceptor && interceptor.callback;
                                if (callback)
                                    callback(interceptor.object, interceptor.property, target, newValue);
                            }
                        });

                        return result;
                    };
                }
            };
            helper.inherit(ctor, baseTypes.ObservableProviderBase);
            var proto = ctor.prototype;

            proto.isObservable = function (object, property) {
                return this.ko.isObservable(object[property]);
            };

            proto.toObservable = function (object, type, callbacks) {
                var pc = callbacks && callbacks.propertyChange;
                var ac = callbacks && callbacks.arrayChange;
                var dpc = callbacks && callbacks.dataPropertyChange;
                var snpc = callbacks && callbacks.scalarNavigationPropertyChange;
                var pnpc = callbacks && callbacks.pluralNavigationPropertyChange;
                var as = callbacks && callbacks.arraySet;
                var that = this;

                var ps = [];
                helper.forEachProperty(object, function (p, v) {
                    ps.push({ p: p, v: v });
                });
                if (type && type.hasMetadata) {
                    helper.forEach(type.dataProperties, function (dp) {
                        var v = object[dp.name];
                        if (v === undefined) v = null;
                        else v = dp.handle(v);
                        object[dp.name] = toObservableProperty(dp, v, dpc);
                        helper.removeFromArray(ps, dp.name, 'p');
                    });
                    helper.forEach(type.navigationProperties, function (np) {
                        var v = object[np.name];
                        if (v === undefined) v = null;
                        if (np.isScalar)
                            object[np.name] = toObservableProperty(np, v, snpc);
                        else
                            object[np.name] = toObservableArray(np, np.name, v, pnpc, as);
                        helper.removeFromArray(ps, np.name, 'p');
                    });
                }
                helper.forEach(ps, function (pv) {
                    var p = pv.p;
                    var v = pv.v;
                    if (Assert.isArray(v))
                        object[p] = toObservableArray(p, p, v, ac, as);
                    else
                        object[p] = toObservableProperty(p, v, pc);
                    if (!helper.findInArray(type.properties, p))
                        type.properties.push(p);
                });

                function toObservableProperty(property, value, callback) {
                    var retVal = that.ko.observable(value);
                    if (callback)
                        return that.ko.observable(value).extend({
                            intercept: {
                                object: object,
                                property: property,
                                callback: callback
                            }
                        });
                    return retVal;
                }

                function toObservableArray(property, propertyName, value, after, setCallback) {
                    var retVal;
                    value = value || [];
                    if (after)
                        value = helper.createTrackableArray(value, object, property,
                            function (o, p, i, r, a) {
                                if (retVal.$fromKo !== true)
                                    object[propertyName].valueHasMutated();
                                retVal.$fromKo = false;
                                after(o, p, i, r, a);
                            });
                    retVal = that.ko.observableArray(value);
                    retVal.subscribe(function () { retVal.$fromKo = true; }, null, "beforeChange");
                    if (setCallback)
                        retVal.equalityComparer = function (items, newItems) {
                            setCallback(object, property, items, newItems);
                        };
                    return retVal;
                }
            };

            proto.getValue = function (object, property) {
                return this.ko.utils.unwrapObservable(object[property]);
            };

            proto.setValue = function (object, property, value) {
                object[property](value);
            };

            return ctor;
        })(),
        /**
         * Property observable provider class. Makes given object's fields properties with getter setter and tracks values.
         * @class
         */
        PropertyObservableProvider: (function () {
            var ctor = function () {
                baseTypes.ObservableProviderBase.call(this, 'Property Observable Provider');
            };
            helper.inherit(ctor, baseTypes.ObservableProviderBase);
            var proto = ctor.prototype;

            proto.isObservable = function (object, property) {
                return object['$fields'] !== undefined && object['$fields'][property] !== undefined;
            };

            proto.toObservable = function (object, type, callbacks) {
                var pc = callbacks && callbacks.propertyChange;
                var ac = callbacks && callbacks.arrayChange;
                var dpc = callbacks && callbacks.dataPropertyChange;
                var snpc = callbacks && callbacks.scalarNavigationPropertyChange;
                var pnpc = callbacks && callbacks.pluralNavigationPropertyChange;
                var as = callbacks && callbacks.arraySet;

                var fields = {};
                var ps = [];
                helper.forEachProperty(object, function (p, v) {
                    ps.push({ p: p, v: v });
                });
                if (type && type.hasMetadata) {
                    helper.forEach(type.dataProperties, function (dp) {
                        var v = object[dp.name];
                        if (v === undefined) v = null;
                        else v = dp.handle(v);
                        delete object[dp.name];
                        toObservableProperty(dp, dp.name, dpc);
                        helper.removeFromArray(ps, dp.name, 'p');
                        fields[dp.name] = v;
                    });
                    helper.forEach(type.navigationProperties, function (np) {
                        var v = object[np.name];
                        if (v === undefined) v = null;
                        delete object[np.name];
                        if (np.isScalar) {
                            toObservableProperty(np, np.name, snpc);
                            fields[np.name] = v;
                        } else
                            toObservableArray(np, np.name, v, pnpc, as);
                        helper.removeFromArray(ps, np.name, 'p');
                    });
                }
                helper.forEach(ps, function (pv) {
                    var p = pv.p;
                    var v = pv.v;
                    delete object[p];
                    if (Assert.isArray(v))
                        toObservableArray(p, p, v, ac, as);
                    else {
                        toObservableProperty(p, p, pc);
                        fields[p] = v;
                    }
                    if (!helper.findInArray(type.properties, p))
                        type.properties.push(p);
                });
                object['$fields'] = fields;
                return object;

                function toObservableProperty(property, propertyName, callback) {
                    return Object.defineProperty(object, propertyName, {
                        get: function () {
                            return object['$fields'][propertyName];
                        },
                        set: function (newValue) {
                            if (callback) {
                                var a = getAccessor(object, propertyName);
                                callback(object, property, a, newValue);
                            } else
                                object['$fields'][propertyName] = newValue;
                        },
                        enumerable: true,
                        configurable: true
                    });
                }

                function toObservableArray(property, propertyName, value, after, setCallback) {
                    value = value || [];
                    if (after)
                        value = helper.createTrackableArray(value, object, property, after);
                    fields[propertyName] = value;

                    return Object.defineProperty(object, propertyName, {
                        get: function () {
                            return object['$fields'][propertyName];
                        },
                        set: function (newItems) {
                            if (setCallback)
                                setCallback(object, property, items, newItems);
                            else
                                object['$fields'][propertyName] = newItems;
                        }
                    });
                }

                function getAccessor(o, p) {
                    return function () {
                        return arguments.length == 0 ? o['$fields'][p] : o['$fields'][p] = arguments[0];
                    };
                }
            };

            proto.getValue = function (object, property) {
                return object[property];
            };

            proto.setValue = function (object, property, value) {
                object[property] = value;
            };

            return ctor;
        })(),
        /**
         * jQuery ajax provider class. Operates ajax operations via jQuery.
         * @class
         */
        JQueryAjaxProvider: (function () {
            var ctor = function ($) {
                baseTypes.AjaxProviderBase.call(this, 'jQuery Ajax Provider');
                this.$ = $;
            };
            helper.inherit(ctor, baseTypes.AjaxProviderBase);
            var proto = ctor.prototype;

            proto.doAjax = function (uri, method, dataType, contentType, data, async, timeout, extra, headers, successCallback, errorCallback) {
                var that = this;
                var o = {
                    url: uri,
                    accepts: {
                        json: 'application/json; odata=verbose',
                        xml: 'text/xml; application/xhtml+xml;application/xml',
                        text: 'text/xml'
                    },
                    type: method,
                    dataType: dataType,
                    contentType: contentType,
                    traditional: false,
                    data: data,
                    async: async,
                    headers: headers,
                    success: function (result, status, xhr) {
                        xhr.onreadystatechange = null;
                        xhr.abort = null;
                        if (result && result.Error) {
                            var err = that.createError(xhr);
                            err.message = result.Error;
                            errorCallback(err);
                        } else successCallback(result, that.getHeaderGetter(xhr), xhr);
                    },
                    error: function (xhr) {
                        xhr.onreadystatechange = null;
                        xhr.abort = null;
                        errorCallback(that.createError(xhr));
                    }
                };
                if (async !== false)
                    o.timeout = timeout;
                if (extra != null)
                    this.$.extend(o, extra);
                if (o.cache == null) o.cache = false;
                return this.$.ajax(o);
            };

            return ctor;
        })(),
        /**
         * Angularjs ajax provider class. Operates ajax operations via angularjs.
         * @class
         */
        AngularjsAjaxProvider: (function () {
            var ctor = function (angularjs) {
                baseTypes.AjaxProviderBase.call(this, 'Angular.js Ajax Provider');
                this.syncSupported = false;
                this.$http = angularjs.injector(["ng"]).get('$http');
            };
            helper.inherit(ctor, baseTypes.AjaxProviderBase);
            var proto = ctor.prototype;

            proto.doAjax = function (uri, method, dataType, contentType, data, async, timeout, extra, headers, successCallback, errorCallback) {
                if (async === false)
                    throw helper.createError(i18N.syncNotSupported, [this.name]);

                var o = {
                    method: method,
                    url: uri,
                    contentType: contentType,
                    data: data,
                    timeout: timeout,
                    headers: headers,
                    responseType: dataType,
                    transformResponse: [function (data) {
                        return data;
                    }]
                };
                if (extra != null)
                    helper.extend(o, extra);
                if (o.cache == null) o.cache = false;
                return this.$http(o)
                    .then(function (resp) {
                        var headers = resp.headers();
                        return successCallback(resp.data, function (header) {
                            if (!header) return headers;
                            return headers[header.toLowerCase()];
                        });
                    }, function (error) {
                        var obj = { status: error.status, detail: error.data, error: error };
                        var e = helper.createError(error.statusText, obj);
                        errorCallback(e);
                        return e;
                    });
            };

            return ctor;
        })(),
        /**
         * Angular ajax provider class. Operates ajax operations via angular.
         * @class
         */
        AngularAjaxProvider: (function () {
            var ctor = function (http, RequestConstructor, HeadersConstructor) {
                baseTypes.AjaxProviderBase.call(this, 'Angular Ajax Provider');
                this.syncSupported = false;
                this.http = http;
                this.RequestConstructor = RequestConstructor;
                this.HeadersConstructor = HeadersConstructor;
            };
            helper.inherit(ctor, baseTypes.AjaxProviderBase);
            var proto = ctor.prototype;

            proto.doAjax = function (uri, method, dataType, contentType, data, async, timeout, extra, headers, successCallback, errorCallback) {
                var hs = new this.HeadersConstructor();

                hs.append("Content-Type", contentType);
                if (headers != null) {
                    for (var p in headers) {
                        hs.append(p, headers[p]);
                    }
                }

                var requestOptions = {
                    url: uri,
                    method: method,
                    body: data,
                    headers: hs,
                    timeout: timeout
                };

                helper.extend(requestOptions, extra);

                var request = new this.RequestConstructor(requestOptions);

                return this.http.request(request)
                    .subscribe(function(resp) {
                        return successCallback(resp.text(), function(name) {
                            return resp.headers[name];
                        });
                    },
                    function(error) {
                        var obj = { status: error.status, detail: error._body, error: error };
                        var e = helper.createError(error.statusText, obj);
                        errorCallback(e);
                        return e;
                    });
            };

            return ctor;
        })(),
        /**
         * Pure javascript ajax provider class.
         * @class
         */
        VanillajsAjaxProvider: (function () {
            var ctor = function () {
                baseTypes.AjaxProviderBase.call(this, 'Vanilla-js Ajax Provider');
                this.syncSupported = true;
            };
            helper.inherit(ctor, baseTypes.AjaxProviderBase);
            var proto = ctor.prototype;

            proto.doAjax = function (uri, method, dataType, contentType, data, async, timeout, extra, headers, successCallback, errorCallback) {
                var that = this;

                var xhr = new XMLHttpRequest();
                xhr.open(method, uri, async);

                xhr.setRequestHeader("Accept", "application/json; odata=verbose, text/xml;application/xhtml+xml;application/xml");
                xhr.setRequestHeader("Content-Type", contentType);
                if (async !== false)
                    xhr.timeout = timeout;

                if (headers) {
                    for (var p in headers) {
                        xhr.setRequestHeader(p, headers[p]);
                    }
                }

                xhr.onload = function () {
                    xhr.onreadystatechange = null;
                    xhr.abort = null;

                    if (xhr.status === 200) {
                        successCallback(xhr.responseText, that.getHeaderGetter(xhr), xhr);
                    }
                    else {
                        errorCallback(that.createError(xhr));
                    }
                };

                xhr.ontimeout = function () {
                    xhr.onreadystatechange = null;
                    xhr.abort = null;

                    errorCallback(that.createError(xhr));
                };

                xhr.send(data);

                return xhr;
            }

            return ctor;
        })(),
        /**
         * Node.js ajax provider class.
         * @class
         */
        NodejsAjaxProvider: (function () {
            var ctor = function (http, https) {
                baseTypes.AjaxProviderBase.call(this, 'Node.js Ajax Provider');
                this.syncSupported = false;
                this.http = http;
                this.https = https;
            };
            helper.inherit(ctor, baseTypes.AjaxProviderBase);
            var proto = ctor.prototype;

            proto.doAjax = function (uri, method, dataType, contentType, data, async, timeout, extra, headers, successCallback, errorCallback) {
                if (async === false)
                    throw helper.createError(i18N.syncNotSupported, [this.name]);

                var reURLInformation = new RegExp([
                    '^(https?:)//', // protocol
                    '(([^:/?#]*)(?::([0-9]+))?)', // host (hostname and port)
                    '(/{0,1}[^?#]*)', // pathname
                    '(\\?[^#]*|)', // search
                    '(#.*|)$' // hash
                ].join(''));
                var uriParts = uri.match(reURLInformation),
                    protocol = uriParts[1] == "https:" ? this.https : this.http,
                    host = uriParts[3],
                    port = uriParts[4],
                    path = uriParts[5],
                    search = uriParts[6];
                path += search;

                headers = headers || {};
                headers["Content-Type"] = contentType;
                headers["Accept"] = "application/json; odata=verbose, text/xml;application/xhtml+xml;application/xml";
                headers["Content-Length"] = (data && data.length) || 0;

                var options = {
                    host: host,
                    path: path,
                    method: method,
                    headers: headers,
                    port: port || 80
                };

                var req = protocol.request(options, function (res) {
                    res.setEncoding("utf8");

                    var body = "";
                    res.on('data', function (chunk) {
                        body += chunk;
                    });
                    res.on("end", function () {
                        if (res.statusCode == 200) {
                            successCallback(body, function (name) {
                                if (!name) return res.headers;
                                return res.headers[name.toLowerCase()];
                            });
                        }
                        else {
                            var obj = { status: res.statusCode, detail: body };
                            var e = helper.createError(res.statusMessage, obj);
                            errorCallback(e);
                        }
                    });
                });

                if (data) {
                    req.write(data);
                }

                if (timeout) {
                    req.on("socket", function (socket) {
                        socket.setTimeout(timeout);
                        socket.on("timeout", function () {
                            req.abort();
                        });
                    });
                }

                req.on('error', function (e) {
                    errorCallback(e);
                });

                req.end();

                return req;
            }

            return ctor;
        })(),
        /**
         * JSON serialization class. Deserializes incoming data and serializes outgoing data.
         * @class
         */
        JsonSerializationService: (function () {
            var ctor = function () {
                baseTypes.SerializationServiceBase.call(this, 'Json Serializer');
            };
            helper.inherit(ctor, baseTypes.SerializationServiceBase);
            var proto = ctor.prototype;

            proto.serialize = function (data) {
                return JSON.stringify(data);
            };

            proto.deserialize = function (value) {
                if (Assert.isTypeOf(value, 'string'))
                    return JSON.parse(value);
                return value;
            };

            return ctor;
        })(),
        /**
         * Q promise provider class.
         * @class
         */
        QPromiseProvider: (function () {
            var ctor = function (Q) {
                baseTypes.PromiseProviderBase.call(this, 'Q Promise Provider');
                this.Q = Q;
            };
            helper.inherit(ctor, baseTypes.PromiseProviderBase);
            var proto = ctor.prototype;

            proto.deferred = function () {
                return this.Q.defer();
            };

            proto.getPromise = function (deferred) {
                return deferred.promise;
            };

            proto.resolve = function (deferred, data) {
                deferred.resolve(data);
            };

            proto.reject = function (deferred, error) {
                deferred.reject(error);
            };

            return ctor;
        })(),
        /**
         * Angular.js promise provider.
         * @class
         */
        AngularjsPromiseProvider: (function () {
            var ctor = function (angularjs) {
                baseTypes.PromiseProviderBase.call(this, 'Angular.js Promise Provider');
                this.ng = angularjs.injector(['ng']);
                this.$q = this.ng.get('$q');
                this.$rootScope = this.ng.get('$rootScope');
            };
            helper.inherit(ctor, baseTypes.PromiseProviderBase);
            var proto = ctor.prototype;

            proto.deferred = function () {
                return this.$q.defer();
            };

            proto.getPromise = function (deferred) {
                return deferred.promise;
            };

            proto.resolve = function (deferred, data) {
                deferred.resolve(data);
                this.$rootScope.$apply();
            };

            proto.reject = function (deferred, error) {
                deferred.reject(error);
                this.$rootScope.$apply();
            };

            return ctor;
        })(),
        /**
         * jQuery promise provider.
         * @class
         */
        JQueryPromiseProvider: (function () {
            var ctor = function ($) {
                baseTypes.PromiseProviderBase.call(this, 'jQuery Promise Provider');
                this.$ = $;
            };
            helper.inherit(ctor, baseTypes.PromiseProviderBase);
            var proto = ctor.prototype;

            proto.deferred = function () {
                return this.$.Deferred();
            };

            proto.getPromise = function (deferred) {
                return deferred.promise();
            };

            proto.resolve = function (deferred, data) {
                deferred.resolve(data);
            };

            proto.reject = function (deferred, error) {
                deferred.reject(error);
            };

            return ctor;
        })(),
        /**
         * ES6 promise provider.
         * @class
         */
        Es6PromiseProvider: (function () {
            var ctor = function () {
                baseTypes.PromiseProviderBase.call(this, 'ES6 Promise Provider');
            };
            helper.inherit(ctor, baseTypes.PromiseProviderBase);
            var proto = ctor.prototype;

            proto.deferred = function () {
                var deferred = {
                    resolve: null,
                    reject: null
                };

                deferred.promise = new Promise(function (resolve, reject) {
                    deferred.resolve = resolve;
                    deferred.reject = reject;
                });

                return deferred;
            };

            proto.getPromise = function (deferred) {
                return deferred.promise;
            };

            proto.resolve = function (deferred, data) {
                deferred.resolve(data);
            };

            proto.reject = function (deferred, error) {
                deferred.reject(error);
            };

            return ctor;
        })()
    };

    /**
     * Metadata types.
     * @namespace
     */
    var metadata = {
        /**
         * Represents a data (primitive) member.
         * @class
         */
        DataProperty: (function () {
            /**
             * @constructor
             * @param {EntityType} owner - Owner entity type.
             * @param {string} name - Name of the property.
             * @param {string} displayName - Value to use for displaying purposes.
             * @param {DataType} dataType - One of the supported Beetle data types.
             * @param {boolean} isNullable - Can be assigned with null or undefined.
             * @param {boolean} isKeyPart - Indicates if this property is one of the primary keys.
             * @param {generationPattern} genPattern - Auto generation strategy for the property (Identity, Computed, None).
             * @param {any} defaultValue - Default value for the property.
             * @param {boolean} useForConcurrency - When true, this property will be used together with keys for updates.
             */
            var ctor = function (owner, name, displayName, dataType, isNullable, isKeyPart, genPattern, defaultValue, useForConcurrency) {
                this.owner = owner;
                this.name = name;
                this.displayName = displayName || name;
                this.dataType = dataType;
                this.isNullable = isNullable;
                this.isKeyPart = isKeyPart;
                this.generationPattern = genPattern;
                this.defaultValue = defaultValue;
                this.useForConcurrency = useForConcurrency;
                this.relatedNavigationProperties = [];
                this.validators = [];
                this.isEnum = dataType instanceof core.dataTypes.enumeration;
                this.isComplex = dataType.isComplex;

                if (!this.isNullable) {
                    this.validators.push(new core.Validator.notNull(null, this.displayName))
                }
            };
            var proto = ctor.prototype;

            proto.toString = function () {
                return this.displayName;
            };

            /** Checks if given value is valid for this property. */
            proto.isValid = function (value) {
                if (value == null && settings.checkNulls) return !this.isNullable;
                else return this.dataType.isValid(value, this);
            };

            /**
             * Tries to convert given value to this type.
             * @returns {any} When value is of this type returns the value, if not tries to convert the value to this type, throws an error if fails.
             */
            proto.handle = function (value) {
                if (this.dataType != core.dataTypes.string && value === "")
                    value = null;

                if (value == null) {
                    if (!this.isNullable && settings.checkNulls)
                        throw helper.createError(i18N.notNullable, [this.displayName], { property: this });
                    return null;
                }
                value = this.dataType.handle(value, this);

                if (this.dataType == core.dataTypes.number && this.precision && value.toString().replace(/\./g, '').length > this.precision)
                    throw helper.createError(i18N.maxPrecisionError, [value, this.precision],
                        { dataType: dataType, value: value });
                if (this.dataType == core.dataTypes.number && this.scale != null) value = Number(value.toFixed(this.scale));

                return value;
            };

            /** Gets default value for this property. */
            proto.getDefaultValue = function () {
                if (this.defaultValue != null) return this.defaultValue;
                if (this.generationPattern == enums.generationPattern.Identity && this.isKeyPart === true) return this.dataType.autoValue();
                if (this.isNullable || (!settings.checkNulls)) return null;
                return this.dataType.defaultValue();
            };

            /**
             * Add new validation method to data property.
             * @param {string} name - Name of the validation.
             * @param {PredicateFunction} func - Validation function.
             * @param {string} message - Message to show when validation fails.
             * @param {any[]} args - Validator arguments.
             */
            proto.addValidation = function (name, func, message, args) {
                helper.assertPrm(name, 'name').isNotEmptyString().check();
                helper.assertPrm(func, 'func').isFunction().check();
                this.validators.push(new core.Validator(name, func, message, args));
            };

            /**
             * Validates property for provided entity.
             * @param {Entity} entity - Beetle entity. Will be used in validation messages (otherwise we could have used only value).
             * @returns {ValidationError[]} Validation result array. Empty when property is valid.
             */
            proto.validate = function (entity) {
                var retVal = [];
                if (this.validators.length > 0) {
                    var that = this;
                    var value = helper.getValue(entity, this.name);
                    helper.forEach(this.validators, function (v) {
                        var result = v.validate(value, entity);
                        if (result) retVal.push(helper.createValidationError(entity, value, that, result, v));
                    });
                }
                return retVal;
            };

            return ctor;
        })(),
        /**
         * Represents a navigation (relation) member.
         * @class
         */
        NavigationProperty: (function () {
            /**
             * @constructor
             * @param {EntityType} owner - Owner entity type.
             * @param {string} name - Name of the property.
             * @param {string} displayName - Value to use for displaying purposes.
             * @param {string} entityTypeName - Related entity type name.
             * @param {boolean} isScalar - Indicates if this property is a reference or array.
             * @param {string} associationName - To be able to match two way relations. Same relations have same association name for both side.
             * @param {boolean} cascadeDelete - Indicates if deleting this related entity causes cascade deletion.
             * @param {string[]} foreignKeyNames - Foreign key names binding this relation.
             */
            var ctor = function (owner, name, displayName, entityTypeName, isScalar, associationName, cascadeDelete, foreignKeyNames) {
                this.owner = owner;
                this.name = name;
                this.displayName = displayName || name;
                this.entityTypeName = entityTypeName;
                this.entityType = null;
                this.isScalar = isScalar;
                this.isComplex = associationName === undefined;
                this.associationName = associationName;
                this.cascadeDelete = cascadeDelete === true;
                this.foreignKeyNames = foreignKeyNames || [];
                this.inverse = null;
                this.foreignKeys = [];
                this.validators = [];
                this.triggerOwnerModify = false;
            };
            var proto = ctor.prototype;

            proto.toString = function () {
                return this.displayName;
            };

            /** Checks if given value can be assigned to this property. If not throws an error. */
            proto.checkAssign = function (value) {
                if (value == null) return;
                if (!value.$tracker) throw helper.createError(i18N.assignErrorNotEntity, [this], { property: this, value: value });
                var t = value.$tracker.entityType;
                if (!this.entityType.isAssignableWith(t)) throw helper.createError(i18N.assignError, [this.name, t.shortName], { property: this, value: value });
            };

            /**
             * Add new validation method to navigation property.
             * @param {string} name - Name of the validation.
             * @param {PredicateFunction} func - Validation function.
             * @param {string} message - Message to show when validation fails.
             * @param {any[]} args - Validator arguments.
             */
            proto.addValidation = function (name, func, message, args) {
                helper.assertPrm(name, 'name').isNotEmptyString().check();
                helper.assertPrm(func, 'func').isFunction().check();
                this.validators.push(new core.Validator(name, func, message, args));
            };

            /**
             * Validates property for provided entity.
             * @param {Entity} entity - Beetle entity. Will be used in validation messages (otherwise we could have used only value).
             * @returns {ValidationError[]} Validation result array. Empty when property is valid.
             */
            proto.validate = function (entity) {
                var retVal = [];
                if (this.validators.length > 0) {
                    var that = this;
                    var value = helper.getValue(entity, that.name);
                    helper.forEach(this.validators, function (v) {
                        var result = v.validate(value);
                        if (result) retVal.push(helper.createValidationError(entity, value, that, result, v));
                    });
                }
                return retVal;
            };

            return ctor;
        })(),
        /**
         * Represents an entity type.
         * @class
         */
        EntityType: (function () {
            /**
             * @constructor
             * @param {string} name - Name of the property.
             * @param {string} displayName - Value to use for displaying purposes.
             * @param {string} shortName - Entity's short name.
             * @param {string[]} keyNames - Primary key names.
             * @param {string} baseTypeName - Name of the base types - if any.
             * @param {string} setName - Entity set name. If this Entity is derived from another, set name is the root entity's name.
             * @param {string} setTypeName - Entity set type name. If this Entity is derived from another, set type is the root entity's type.
             * @param {boolean} isComplexType - Indicates if this is a complex type.
             * @param {MetadataManager} metadataManager - Owner metadata manager.
             */
            var ctor = function (name, displayName, shortName, keyNames, baseTypeName, setName, setTypeName, isComplexType, metadataManager) {
                this.name = name;
                this.displayName = displayName || name;
                this.shortName = shortName;
                this.keyNames = keyNames || [];
                this.baseTypeName = baseTypeName;
                this.setName = setName;
                this.setTypeName = setTypeName;
                this.metadataManager = metadataManager;
                this.hasMetadata = metadataManager != null;
                this.properties = [];
                this.dataProperties = [];
                this.navigationProperties = [];
                this.keys = [];
                this.floorType = this;
                this.baseType = null;
                this.validators = [];
                this.isComplexType = isComplexType == true;
                this.constructors = [];
                this.initializers = [];
            };
            var proto = ctor.prototype;

            proto.toString = function () {
                return this.name;
            };

            /**
             * Parses given string and finds property, looks recursively to navigation properties when needed.
             * @example
             *  if given path is OrderDetails.Supplier.Address.City,
             *  this method will look to related navigation properties until Address and it will retun City property as dataProperty (if exists).
             * @param {string} propertyPath - Path to desired property.
             * @returns {DataProperty|NavigationProperty} When found data or navigation property, otherwise null.
             */
            proto.getProperty = function (propertyPath) {
                return getProperty(propertyPath.split('.'), this);
            };

            /**
             * Creates a new query for this type.
             * @param {string} resourceName - Server resource name to combine with base uri.
             * @param {EntityManager=} manager - Entity manager.
             */
            proto.createQuery = function (resourceName, manager) {
                if (resourceName) return new querying.EntityQuery(resourceName, this, manager);

                var q = new querying.EntityQuery(this.setName, this, manager);
                return this.shortName == this.setTypeName ? q : q.ofType(this.shortName);
            };

            /**
             * Register constructor and initializer (optional) for the type.
             * @param {Function} constructor - Constructor function. Called right after the entity object is generated.
             * @param {Function} initializer - Initializer function. Called after entity started to being tracked (properties converted to observable).
             */
            proto.registerCtor = function (constructor, initializer) {
                if (constructor != null) {
                    helper.assertPrm(constructor, 'constructor').isFunction().check();
                    this.constructors.push(constructor);
                }
                if (initializer != null) {
                    helper.assertPrm(initializer, 'initializer').isFunction().check();
                    this.initializers.push(initializer);
                }
            };

            /**
             * Creates an entity for this type.
             * @param {Object} initialValues - Entity initial values.
             * @returns {Entity} Entity with observable properties.
             */
            proto.createEntity = function (initialValues) {
                var result = this.createRawEntity(initialValues);
                // make it observable
                return core.EntityTracker.toEntity(result, this, settings.getObservableProvider());
            };

            /**
             * Creates a raw entity for this type.
             * @param {Object} initialValues - Entity initial values.
             * @returns {Entity} Entity without observable properties.
             */
            proto.createRawEntity = function (initialValues) {
                var result = initialValues || {};
                // create properties with default values for each data property defined in metadata.
                helper.forEach(this.dataProperties, function (dp) {
                    if (result[dp.name] === undefined)
                        result[dp.name] = dp.getDefaultValue();
                });
                // create properties with default values for each navigation property defined in metadata.
                helper.forEach(this.navigationProperties, function (np) {
                    if (np.isComplex)
                        result[np.name] = np.entityType.createRawEntity();
                    else {
                        if (np.isScalar) result[np.name] = null;
                        else result[np.name] = [];
                    }
                });
                callCtor(this, result);
                result.$type = this.name;
                return result;
            };

            function callCtor(type, entity) {
                if (type.baseType) callCtor(type.baseType, entity);
                for (var i = 0; i < type.constructors.length; i++) {
                    type.constructors[i].call(entity, entity);
                }
            }

            /** Checks if this type can be set with given type. */
            proto.isAssignableWith = function (otherType) {
                return isAssignableWith(this, otherType);
            };

            /** Checks if this type can be set to given type. */
            proto.isAssignableTo = function (otherType) {
                return isAssignableTo(this, otherType);
            };

            /**
             * Checks if this type and given type has common ancestor.
             * This method is used to check key violation between different types.
             */
            proto.hasSameBaseType = function (type) {
                return this.floorType.name === type.floorType.name;
            };

            /**
             * Adds new dataProperty to this type.
             * @param {string} name - Name of the property.
             * @param {string} displayName - Display name of the property.
             * @param {DataType} dataType - Data type for the property.
             * @param {boolean} isNullable - Indicates if this property can be assigned with null.
             * @param {any} defaultValue - Default value
             */
            proto.addDataProperty = function (name, displayName, dataType, isNullable, defaultValue) {
                helper.assertPrm(name, 'name').isNotEmptyString().check();
                var dp = helper.findInArray(this.dataProperties, name, 'name');
                if (dp)
                    throw helper.createError(i18N.dataPropertyAlreadyExists, [name], { entityType: this, existing: dp });
                if (Assert.isNotEmptyString(dataType))
                    dataType = core.dataTypes.byName(dataType);
                helper.assertPrm(dataType, 'dataType').isInstanceOf(core.dataTypes.baseType).check();
                if (defaultValue != null && !dataType.isValid(defaultValue))
                    throw helper.createError(i18N.invalidDefaultValue, [defaultValue, dataType.name],
                        { entityType: this, dataType: dataType, defaultValue: defaultValue });
                var property = new metadata.DataProperty(this, name, displayName, dataType, isNullable === true, false, null, defaultValue);
                this.dataProperties.push(property);
            };

            /**
             * Add new validation method to entity type.
             * @param {string} name - Name of the validation.
             * @param {PredicateFunction} func - Validation function.
             * @param {string} message - Message to show when validation fails.
             * @param {any[]} args - Validator arguments.
             */
            proto.addValidation = function (name, func, message, args) {
                helper.assertPrm(name, 'name').isNotEmptyString().check();
                helper.assertPrm(func, 'func').isFunction().check();
                this.validators.push(new core.Validator(name, func, message, args));
            };

            /**
             * Validates type for provided entity.
             * @param {Entity} entity - Beetle entity. Will be used in validation messages (otherwise we could have used only value).
             * @returns {ValidationError[]} Validation result array. Empty when property is valid.
             */
            proto.validate = function (entity) {
                var retVal = [];
                if (this.validators.length > 0) {
                    helper.forEach(this.validators, function (v) {
                        var result = v.validate(entity);
                        if (result) retVal.push(helper.createValidationError(entity, null, null, result, v));
                    });
                }
                helper.forEach(this.dataProperties, function (dp) {
                    var result = dp.validate(entity);
                    if (result) retVal = retVal.concat(result);
                });
                helper.forEach(this.navigationProperties, function (np) {
                    var result = np.validate(entity);
                    if (result) retVal = retVal.concat(result);
                });
                return retVal;
            };

            function getProperty(propertyPaths, type) {
                var len = propertyPaths.length;
                for (var i = 0; i < len; i++) {
                    var p = propertyPaths[i];
                    if (i == len - 1) {
                        // if it is last property path, look it in all properties.
                        var dp = helper.findInArray(type.dataProperties, p, 'name');
                        return dp ? dp : helper.findInArray(type.navigationProperties, p, 'name');
                    } else {
                        // if it is not last property path, look it in navigation properties.
                        var np = helper.findInArray(type.navigationProperties, p, 'name');
                        if (np) type = np.entityType;
                        else return null;
                    }
                }
                return null;
            }

            function isAssignableWith(type1, type2) {
                if (type1.name === type2.name)
                    return true;
                else if (type2.baseType != null)
                    return isAssignableWith(type1, type2.baseType);

                return false;
            }

            function isAssignableTo(type1, type2) {
                var name = Assert.isTypeOf(type2, 'string') ? type2 : type2.name;
                if (type1.name === name)
                    return true;
                else if (type1.baseType != null)
                    return isAssignableTo(type1.baseType, type2);

                return false;
            }

            return ctor;
        })(),
        /**
         * Metadata container.
         * @class
         */
        MetadataManager: (function () {
            /**
             * @constructor
             * @param {string|Object} metadataPrm - [Metadata Object] or [Metadata string]
             */
            var ctor = function (metadataPrm) {
                this.types = [];
                this.typesDict = {};
                this.enums = {};
                this.name = null;
                this.displayName = null;

                if (metadataPrm)
                    this.parseBeetleMetadata(metadataPrm);
            };
            var proto = ctor.prototype;

            proto.toString = function () {
                return this.types.join(', ');
            };

            /**
             * Finds entity type by given entity type name (fully qualified).
             * @param {string} typeName - Full type name.
             * @param {boolean=} throwIfNotFound - Throws an error if given type name could not be found in cache.
             * @returns {EntityType}
             */
            proto.getEntityTypeByFullName = function (typeName, throwIfNotFound) {
                var type = helper.findInArray(this.types, typeName, 'name');
                if (!type && throwIfNotFound === true)
                    throw helper.createError(i18N.notFoundInMetadata, [typeName], { metadataManager: this, typeName: typeName });
                return type;
            };

            /**
             * Finds entity type by given entity type short name (only class name).
             * @param {string} shortName - Short type name.
             * @param {boolean=} throwIfNotFound - Throws an error if given type name could not be found in cache.
             * @returns {EntityType}
             */
            proto.getEntityType = function (shortName, throwIfNotFound) {
                if (Assert.isFunction(shortName)) shortName = helper.getFuncName(shortName);
                var type = this.typesDict[shortName];
                if (!type && throwIfNotFound === true)
                    throw helper.createError(i18N.notFoundInMetadata, [shortName], { metadataManager: this, typeShortName: shortName });
                return type;
            };

            /**
             * Creates a new query for this type.
             * @param {string} resourceName - Server resource name to combine with base uri.
             * @param {string=} shortName - Short type name.
             * @param {EntityManager=} manager - Entity manager.
             */
            proto.createQuery = function (resourceName, shortName, manager) {
                if (Assert.isFunction(shortName)) shortName = helper.getFuncName(shortName);
                // if shortName is given find entityType and create query for it.
                if (shortName) return this.getEntityType(shortName, true).createQuery(resourceName, manager);
                // try to find entity type by its resource (set) name.
                var typeList = helper.filterArray(this.types, function (item) { return item.setName == resourceName; });
                return typeList.length == 1 ? typeList[0].createQuery(resourceName, manager) : new querying.EntityQuery(resourceName, null, manager);
            };

            /**
             * Register constructor and initializer (optional) for the type.
             * @param {string} shortName - Short type name.
             * @param {Function=} constructor - Constructor function. Called right after the entity object is generated.
             * @param {Function=} initializer - Initializer function. Called after entity started to being tracked (properties converted to observable).
             */
            proto.registerCtor = function (shortName, constructor, initializer) {
                if (Assert.isFunction(shortName)) shortName = helper.getFuncName(shortName);
                var type = this.getEntityType(shortName, true);
                type.registerCtor(constructor, initializer);
            };

            /**
             * Creates an entity for this type.
             * @param {string} shortName - Short type name.
             * @param {Object} initialValues - Entity initial values.
             * @returns {Entity} Entity with observable properties.
             */
            proto.createEntity = function (shortName, initialValues) {
                if (Assert.isFunction(shortName)) shortName = helper.getFuncName(shortName);
                // find entity type.
                var type = this.getEntityType(shortName, true);
                // create entity for this type
                return type.createEntity(initialValues);
            };

            /**
             * Creates a raw entity for this type.
             * @param {string} shortName - Short type name.
             * @param {Object} initialValues - Entity initial values.
             * @returns {Entity} Entity without observable properties.
             */
            proto.createRawEntity = function (shortName, initialValues) {
                if (Assert.isFunction(shortName)) shortName = helper.getFuncName(shortName);
                // find entity type.
                var type = this.getEntityType(shortName, true);
                // create entity for this type
                return type.createRawEntity(initialValues);
            };

            /**
             * Imports metadata from given parameter.
             * @param {string|Object} metadataPrm - [Metadata Object] or [Metadata string]
             */
            proto.parseBeetleMetadata = function (metadataPrm) {
                if (Assert.isTypeOf(metadataPrm, 'string'))
                    metadataPrm = JSON.parse(metadataPrm);

                this.types = [];
                this.typesDict = {};
                this.enums = {};
                this.name = metadataPrm.n;
                this.displayName = helper.getResourceValue(metadataPrm.r, metadataPrm.l || metadataPrm.n);

                var es = metadataPrm.e;
                var enumTypes = {};
                if (es) {
                    for (var i = 0; i < es.length; i++) {
                        var e = es[i];
                        var enumObj = {};
                        helper.forEach(e.m, function (m) {
                            enumObj[m.n] = { name: m.n, value: m.v, displayName: helper.getResourceValue(m.r, m.l || m.n) };
                        });
                        var enm = new libs.Enum(enumObj);
                        this.enums[e.n] = enm;
                        enumTypes[e.n] = new core.dataTypes.enumeration(enm, e.n, helper.getResourceValue(e.r, e.l || e.n));
                    }
                }

                var that = this;
                var maps = metadataPrm.m;
                // first create an entityType for each entity.
                for (var j = 0; j < maps.length; j++) {
                    var map = maps[j];
                    var t = new metadata.EntityType(map.n, helper.getResourceValue(map.rn, map.s), map.s, map.k, map.b, map.q, map.t, map.c, this);
                    // create data properties
                    helper.forEach(map.d, function (dp) {
                        var dataType;
                        if (dp.e)
                            dataType = enumTypes[dp.t];
                        else
                            dataType = core.dataTypes.byName(dp.t);
                        var dn = helper.getResourceValue(dp.r, dp.l || dp.n);
                        var property = new metadata.DataProperty(t, dp.n, dn, dataType, dp.i === true, map.k && helper.findInArray(map.k, dp.n) != null,
                            dp.g ? (dp.g == "I" ? enums.generationPattern.Identity : enums.generationPattern.Computed) : null,
                            dp.d ? dataType.handle(dp.d) : null, dp.c);
                        if (dp.v)
                            helper.forEach(dp.v, function (v) {
                                property.validators.push(core.Validator.byCode(v.t, v.a, v.m, v.r, property.displayName, dp.r));
                            });
                        if (dp.p) property.precision = Number(dp.p);
                        if (dp.s) property.scale = Number(dp.s);
                        t.dataProperties.push(property);
                    });
                    // create navigation properties
                    var relations = map.r;
                    if (relations)
                        helper.forEach(relations, function (np) {
                            var navProp = new metadata.NavigationProperty(t, np.n, helper.getResourceValue(np.r, np.l || np.n), np.t, np.s === true, np.a, np.c);
                            if (np.f)
                                helper.forEach(np.f, function (fk) {
                                    navProp.foreignKeyNames.push(fk);
                                });
                            if (np.v)
                                helper.forEach(np.v, function (v) {
                                    navProp.validators.push(core.Validator.byCode(v.t, v.a, v.m, v.r, navProp.displayName, np.r));
                                });
                            t.navigationProperties.push(navProp);
                        });
                    var complex = map.x;
                    if (complex)
                        helper.forEach(complex, function (cp) {
                            var property = new metadata.NavigationProperty(t, cp.n, helper.getResourceValue(cp.r, cp.l || cp.n), cp.t, true);
                            t.navigationProperties.push(property);
                        });
                    this.types.push(t);
                    this.typesDict[t.shortName] = t;
                }

                // then create relation between inherited entities.
                helper.forEach(this.types, function (type) {
                    if (type.baseTypeName)
                        type.baseType = that.getEntityType(type.baseTypeName, true);
                    delete type.baseTypeName;
                });
                // fill inherited properties using base type informations.
                helper.forEach(this.types, function (type) {
                    var base = type.baseType;
                    while (base) {
                        type.floorType = base;
                        helper.forEach(base.dataProperties, function (dp) {
                            if (!helper.findInArray(type.dataProperties, dp.name, "name")) {
                                type.dataProperties.push(dp);
                            }
                        });
                        helper.forEach(base.navigationProperties, function (np) {
                            if (!helper.findInArray(type.navigationProperties, np.name, "name")) {
                                type.navigationProperties.push(np);
                            }
                        });
                        base = base.baseType;
                    }
                    // populate navigation properties inverse and create relation between data property and navigation property
                    helper.forEach(type.navigationProperties, function (np) {
                        if (!np.entityType) {
                            np.entityType = that.getEntityType(np.entityTypeName, true);
                            for (var k = 0; k < np.entityType.navigationProperties.length; k++) {
                                var tnp = np.entityType.navigationProperties[k];
                                if (tnp.associationName === np.associationName && np !== tnp) {
                                    np.inverse = tnp;
                                    break;
                                }
                            }
                        }
                        helper.forEach(np.foreignKeyNames, function (fkName) {
                            if (!helper.findInArray(np.foreignKeys, fkName, 'name')) {
                                var dp = helper.findInArray(type.dataProperties, fkName, 'name');
                                if (dp) {
                                    dp.relatedNavigationProperties.push(np);
                                    np.foreignKeys.push(dp);
                                }
                            }
                        });
                    });
                    // find keys from data properties. When using inheritance key property lives in base (floor) entity.
                    // so we could not find that until we fill inherited properties.
                    helper.forEach(type.keyNames, function (keyName) {
                        type.keys.push(helper.findInArray(type.dataProperties, keyName, 'name'));
                    });
                });
            };

            return ctor;
        })()
    };

    /**
     * Querying related types.
     * @namespace
     */
    var querying = (function () {

        (function queryFuncExtensions() {
            /** add missing query functions */
            if (!String.prototype.hasOwnProperty("substringOf")) {
                String.prototype.substringOf = function (source) {
                    return Assert.isNotEmptyString(source) && source.indexOf(this) >= 0;
                }
            }
            if (!String.prototype.hasOwnProperty("startsWith")) {
                String.prototype.startsWith = function (other) {
                    return this.indexOf(other) == 0;
                }
            }
            if (!String.prototype.hasOwnProperty("endsWith")) {
                String.prototype.endsWith = function (other) {
                    return this.indexOf(other) == (this.length - other.length);
                }
            }
            if (!Number.prototype.hasOwnProperty("round")) {
                Number.prototype.round = function () {
                    return Math.round(this);
                }
            }
            if (!Number.prototype.hasOwnProperty("ceiling")) {
                Number.prototype.ceiling = function () {
                    return Math.ceil(this);
                }
            }
            if (!Number.prototype.hasOwnProperty("floor")) {
                Number.prototype.floor = function () {
                    return Math.floor(this);
                }
            }
        })();

        /**
         * Supported query expressions.
         * Each query call will be collected as expressions and will be evaluated later.
         */
        return {
            /**
             * Linq like expressions to filter, order etc. arrays and server resources. Used by queries.
             * @namespace
             */
            expressions: {
                OfTypeExp: (function () {
                    var ctor = function (typeName) {
                        if (Assert.isFunction(typeName)) typeName = helper.getFuncName(typeName);
                        baseTypes.ExpressionBase.call(this, 'oftype', -1, true, true);
                        this.typeName = typeName;
                    };
                    helper.inherit(ctor, baseTypes.ExpressionBase);
                    var proto = ctor.prototype;

                    proto.toBeetleQuery = function () {
                        return this.typeName;
                    };

                    proto.clone = function () {
                        return new ctor(this.typeName);
                    };

                    proto.execute = function (array) {
                        var that = this;
                        return helper.filterArray(array, function (item) {
                            return item && ((item.$tracker && item.$tracker.entityType.isAssignableTo(that.typeName)) || (typeof item === that.typeName));
                        });
                    };

                    return ctor;
                })(),
                WhereExp: (function () {
                    var ctor = function (exp, varContext) {
                        baseTypes.ExpressionBase.call(this, 'filter', 2, false, false);

                        this.exp = exp;
                        this.varContext = varContext;
                    };
                    helper.inherit(ctor, baseTypes.ExpressionBase);
                    var proto = ctor.prototype;

                    proto.clone = function () {
                        return new ctor(this.exp, this.varContext);
                    };

                    proto.execute = function (array, queryContext) {
                        var predicate = Assert.isFunction(this.exp) ? this.exp : helper.jsepToFunction(libs.jsep(this.exp), queryContext);
                        return helper.filterArray(array, predicate);
                    };

                    return ctor;
                })(),
                OrderByExp: (function () {
                    var defaultExp = 'x => x';
                    var ctor = function (exp, isDesc) {
                        baseTypes.ExpressionBase.call(this, 'orderby', 1, false, false);

                        this.exp = exp || defaultExp;
                        this.isDesc = isDesc || false;
                    };
                    helper.inherit(ctor, baseTypes.ExpressionBase);
                    var proto = ctor.prototype;

                    proto.clone = function () {
                        return new ctor(this.exp, this.isDesc);
                    };

                    proto.toODataQuery = function (queryContext) {
                        var exp = Assert.isFunction(this.exp) ? helper.funcToLambda(this.exp).replace(" as ", " ") : this.exp;
                        if (this.isDesc) exp = invertExp(exp);

                        return helper.jsepToODataQuery(libs.jsep(exp), queryContext);
                    };

                    proto.toBeetleQuery = function (queryContext) {
                        if (!this.exp) return '';

                        var exp = Assert.isFunction(this.exp) ? helper.funcToLambda(this.exp).replace(" as ", " ") : this.exp;
                        if (this.isDesc) exp = invertExp(exp);

                        return helper.jsepToBeetleQuery(libs.jsep(exp), queryContext);
                    };

                    proto.execute = function (array, queryContext) {
                        var expStr;
                        if (Assert.isFunction(this.exp)) {
                            var that = this;
                            if (this.exp.length == 2) {
                                return array.sort(function (a, b) {
                                    var r = that.exp(a, b);
                                    return that.isDesc ? (-1 * r) : r;
                                });
                            }
                            else expStr = helper.funcToLambda(this.exp).replace(/ as /g, " ");
                        } else expStr = this.exp;

                        var comparers = [];
                        var expr = libs.jsep(expStr);
                        var exps = expr.type == 'Compound' ? expr.body : [expr];
                        var alias;
                        if (exps[0].operator == '=>') {
                            alias = {};
                            alias.alias = exps[0].left.name;
                            queryContext.aliases = [alias];
                            exps[0] = exps[0].right;
                        }
                        for (var i = 0; i < exps.length; i++) {
                            var isDesc = false;
                            var exp = exps[i];
                            var nexp = exps[i + 1];
                            if (nexp && nexp.type == 'Identifier') {
                                var ls = nexp.name.toLowerCase();
                                if (ls == 'desc') {
                                    isDesc = true;
                                    i++;
                                } else if (ls == 'asc') i++;
                            }
                            isDesc = isDesc != this.isDesc;
                            var comparer = (function (e, desc) {
                                return function (object1, object2) {
                                    var f = helper.jsepToFunction(e, queryContext);
                                    if (alias) alias.value = object1;
                                    var value1 = f(object1);
                                    if (alias) alias.value = object2;
                                    var value2 = f(object2);

                                    if (typeof value1 === "string" && typeof value2 === "string") {
                                        var cmp = value1.localeCompare(value2);
                                        return desc ? (cmp * -1) : cmp;
                                    }

                                    if (value1 == value2)
                                        return 0;
                                    else if (value1 > value2)
                                        return desc ? -1 : 1;
                                    else
                                        return desc ? 1 : -1;
                                };
                            })(exp, isDesc);
                            comparers.push(comparer);
                        }
                        var retVal = new Array();
                        retVal.push.apply(retVal, array);
                        return retVal.sort(function (object1, object2) {
                            for (var j = 0; j < comparers.length; j++) {
                                var result = comparers[j](object1, object2);
                                if (result != 0)
                                    return result;
                            }
                            return 0;
                        });
                    };

                    function invertExp(exp) {
                        exp += ',';
                        exp = exp.replace(/\,/, ' ,').replace(/ desc.*?\,/g, ' x,')
                            .replace(/ \,/g, ' desc,').replace(/ x\,/g, ',');
                        return exp.substr(0, exp.length - 1);
                    }

                    return ctor;
                })(),
                ExpandExp: (function () {
                    var ctor = function (exp) {
                        baseTypes.ExpressionBase.call(this, 'expand', 1, false, false);
                        this.exp = exp;
                    };
                    helper.inherit(ctor, baseTypes.ExpressionBase);
                    var proto = ctor.prototype;

                    proto.clone = function () {
                        return new ctor(this.exp);
                    };

                    proto.execute = function (array, queryContext) {
                        return array; // do nothing for local queries.
                    };

                    return ctor;
                })(),
                SelectExp: (function () {
                    var ctor = function (exp) {
                        baseTypes.ExpressionBase.call(this, 'select', 2, false, true);
                        this.exp = exp;
                    };
                    helper.inherit(ctor, baseTypes.ExpressionBase);
                    var proto = ctor.prototype;

                    proto.clone = function () {
                        return new ctor(this.exp);
                    };

                    proto.execute = function (array, queryContext) {
                        if (Assert.isFunction(this.exp)) return helper.mapArray(array, this.exp);
                        return helper.runSelectExp(array, this.exp, queryContext);
                    };

                    return ctor;
                })(),
                SkipExp: (function () {
                    var ctor = function (count) {
                        baseTypes.ExpressionBase.call(this, 'skip', 2, false, false);
                        this.count = count;
                    };
                    helper.inherit(ctor, baseTypes.ExpressionBase);
                    var proto = ctor.prototype;

                    proto.toODataQuery = function () {
                        return this.count;
                    };

                    proto.toBeetleQuery = function () {
                        return this.count;
                    };

                    proto.clone = function () {
                        return new ctor(this.count);
                    };

                    proto.execute = function (array) {
                        return array.slice(this.count);
                    };

                    return ctor;
                })(),
                TopExp: (function () {
                    var ctor = function (count) {
                        baseTypes.ExpressionBase.call(this, 'top', 2, false, false);
                        this.count = count;
                    };
                    helper.inherit(ctor, baseTypes.ExpressionBase);
                    var proto = ctor.prototype;

                    proto.toODataQuery = function () {
                        return this.count;
                    };

                    proto.toBeetleQuery = function () {
                        return this.count;
                    };

                    proto.clone = function () {
                        return new ctor(this.count);
                    };

                    proto.execute = function (array) {
                        return array.slice(0, this.count);
                    };

                    return ctor;
                })(),
                GroupByExp: (function () {
                    var ctor = function (keySelector, elementSelector) {
                        baseTypes.ExpressionBase.call(this, 'groupby', 3, true, true);

                        this.keySelector = keySelector;
                        this.elementSelector = elementSelector;
                    };
                    helper.inherit(ctor, baseTypes.ExpressionBase);
                    var proto = ctor.prototype;

                    proto.toBeetleQuery = function (queryContext) {
                        var retVal = '';
                        if (this.keySelector) {
                            var ks = Assert.isFunction(this.keySelector) ? helper.funcToLambda(this.keySelector) : this.keySelector;
                            retVal += helper.jsepToBeetleQuery(libs.jsep(ks), queryContext);
                        }
                        if (this.elementSelector) {
                            var es = Assert.isFunction(this.elementSelector) ? helper.funcToLambda(this.elementSelector) : this.elementSelector;
                            retVal += ';' + helper.jsepToBeetleQuery(libs.jsep(es), queryContext);
                        }
                        return retVal;
                    };

                    proto.clone = function () {
                        return new ctor(this.keySelector, this.elementSelector);
                    };

                    proto.execute = function (array, queryContext) {
                        var groups = [];
                        if (this.keySelector) {
                            // project keys
                            var keys = Assert.isFunction(this.keySelector)
                                ? helper.mapArray(array, this.keySelector)
                                : helper.runSelectExp(array, this.keySelector, queryContext);

                            for (var i = 0; i < keys.length; i++) {
                                var keyGroup = null;
                                var key = keys[i];
                                for (var j = 0; j < groups.length; j++) {
                                    // find if there is already a key with same values
                                    var group = groups[j];
                                    if (helper.objEquals(group.Key, key)) {
                                        keyGroup = group;
                                        break;
                                    }
                                }
                                // if key not found create one group for this key
                                if (!keyGroup) {
                                    keyGroup = { Key: key, Items: [] };
                                    groups.push(keyGroup);
                                }
                                keyGroup.Items.push(array[i]);
                            }
                        }
                        else groups.push({ Key: null, Items: array });

                        if (this.elementSelector) {
                            var projector;
                            if (Assert.isFunction(this.elementSelector))
                                projector = this.elementSelector;
                            else {
                                var es = libs.jsep(this.elementSelector);
                                var exps = es.type == 'Compound' ? es.body : [es];
                                projector = helper.jsepToProjector(exps, queryContext);
                            }
                            helper.forEach(groups, function (g, k) {
                                var items = g.Items;
                                items.Key = g.Key;
                                var result = projector(g.Items);
                                groups[k] = result;
                            });
                        }

                        return groups;
                    };

                    return ctor;
                })(),
                DistinctExp: (function () {
                    var ctor = function (exp) {
                        baseTypes.ExpressionBase.call(this, 'distinct', 3, true, true);
                        this.exp = exp;
                    };
                    helper.inherit(ctor, baseTypes.ExpressionBase);
                    var proto = ctor.prototype;

                    proto.clone = function () {
                        return new ctor(this.exp);
                    };

                    proto.execute = function (array, queryContext) {
                        if (!this.exp) return getDistincts(array);
                        if (Assert.isFunction(this.exp)) return getDistincts(helper.mapArray(array, this.exp));
                        return getDistincts(helper.runSelectExp(array, this.exp, queryContext));
                    };

                    function getDistincts(array) {
                        var distincts = [];
                        helper.forEach(array, function (item) {
                            var found = false;
                            for (var i = 0; i < distincts.length; i++) {
                                if (helper.objEquals(distincts[i], item)) {
                                    found = true;
                                    break;
                                }
                            }
                            if (found === false)
                                distincts.push(item);
                        });
                        return distincts;
                    }

                    return ctor;
                })(),
                ReverseExp: (function () {
                    var ctor = function () {
                        baseTypes.ExpressionBase.call(this, 'reverse', 3, true, false);
                    };
                    helper.inherit(ctor, baseTypes.ExpressionBase);
                    var proto = ctor.prototype;

                    proto.toBeetleQuery = function () {
                        return '';
                    };

                    proto.clone = function () {
                        return new ctor();
                    };

                    proto.execute = function (array) {
                        return array.reverse();
                    };

                    return ctor;
                })(),
                SelectManyExp: (function () {
                    var ctor = function (exp) {
                        baseTypes.ExpressionBase.call(this, 'selectMany', 3, true, true);
                        this.exp = exp;
                    };
                    helper.inherit(ctor, baseTypes.ExpressionBase);
                    var proto = ctor.prototype;

                    proto.clone = function () {
                        return new ctor(this.exp);
                    };

                    proto.execute = function (array, queryContext) {
                        if (array.length == 0) return array;

                        if (Assert.isFunction(this.exp)) return getMany(array, this.exp);
                        return getMany(array, helper.jsepToFunction(libs.jsep(this.exp), queryContext));
                    };

                    function getMany(array, projector) {
                        if (array.length == 0) return array;
                        var projections = [];
                        for (var j = 0; j < array.length; j++) {
                            var arr = projector(array[j]);
                            projections = projections.concat.apply(projections, arr);
                        }
                        return projections;
                    };

                    return ctor;
                })(),
                SkipWhileExp: (function () {
                    var ctor = function (exp, varContext) {
                        baseTypes.ExpressionBase.call(this, 'skipWhile', 3, true, false);
                        this.exp = exp;
                        this.varContext = varContext;
                    };
                    helper.inherit(ctor, baseTypes.ExpressionBase);
                    var proto = ctor.prototype;

                    proto.clone = function () {
                        return new ctor(this.exp, this.varContext);
                    };

                    proto.execute = function (array, queryContext) {
                        var predicate = Assert.isFunction(this.exp) ? this.exp : helper.jsepToFunction(libs.jsep(this.exp), queryContext);

                        var i = 0;
                        while (predicate(array[i]) == true) i++;
                        return array.slice(i + 1);
                    };

                    return ctor;
                })(),
                TakeWhileExp: (function () {
                    var ctor = function (exp, varContext) {
                        baseTypes.ExpressionBase.call(this, 'takeWhile', 3, true, false);
                        this.exp = exp;
                        this.varContext = varContext;
                    };
                    helper.inherit(ctor, baseTypes.ExpressionBase);
                    var proto = ctor.prototype;

                    proto.clone = function () {
                        return new ctor(this.exp, this.varContext);
                    };

                    proto.execute = function (array, queryContext) {
                        var predicate = Assert.isFunction(this.exp) ? this.exp : helper.jsepToFunction(libs.jsep(this.exp), queryContext);

                        var i = 0;
                        while (predicate(array[i]) == true) i++;
                        return array.slice(0, i);
                    };

                    return ctor;
                })(),
                AllExp: (function () {
                    var ctor = function (exp, varContext) {
                        baseTypes.ExpressionBase.call(this, 'exec;all', 3, true, true);
                        this.exp = exp;
                        this.varContext = varContext;
                        this.isExecuter = true;
                    };
                    helper.inherit(ctor, baseTypes.ExpressionBase);
                    var proto = ctor.prototype;

                    proto.clone = function () {
                        return new ctor(this.exp, this.varContext);
                    };

                    proto.execute = function (array, queryContext) {
                        var predicate = Assert.isFunction(this.exp) ? this.exp : helper.jsepToFunction(libs.jsep(this.exp), queryContext);
                        return querying.queryFuncs.all.impl(array, function () { return array; }, predicate);
                    };

                    return ctor;
                })(),
                AnyExp: (function () {
                    var ctor = function (exp, varContext) {
                        baseTypes.ExpressionBase.call(this, 'exec;any', 3, true, true);
                        if (exp)
                            this.exp = exp;
                        this.varContext = varContext;
                        this.isExecuter = true;
                    };
                    helper.inherit(ctor, baseTypes.ExpressionBase);
                    var proto = ctor.prototype;

                    proto.clone = function () {
                        return new ctor(this.exp, this.varContext);
                    };

                    proto.execute = function (array, queryContext) {
                        if (this.exp) {
                            var predicate = Assert.isFunction(this.exp) ? this.exp : helper.jsepToFunction(libs.jsep(this.exp), queryContext);
                            return querying.queryFuncs.any.impl(array, function () { return array; }, predicate);
                        }
                        return array.length > 0;
                    };

                    return ctor;
                })(),
                AvgExp: (function () {
                    var ctor = function (exp) {
                        baseTypes.ExpressionBase.call(this, 'exec;avg', 3, true, true);
                        if (exp)
                            this.exp = exp;
                        this.isExecuter = true;
                    };
                    helper.inherit(ctor, baseTypes.ExpressionBase);
                    var proto = ctor.prototype;

                    proto.clone = function () {
                        return new ctor(this.exp);
                    };

                    proto.execute = function (array, queryContext) {
                        var selector;
                        if (this.exp) {
                            selector = Assert.isFunction(this.exp)
                                ? this.exp
                                : helper.jsepToFunction(libs.jsep(this.exp), queryContext);
                        } else {
                            selector = function (v) { return v; };
                        }
                        return querying.queryFuncs.avg.impl(array, function () { return array; }, selector);
                    };

                    return ctor;
                })(),
                MaxExp: (function () {
                    var ctor = function (exp) {
                        baseTypes.ExpressionBase.call(this, 'exec;max', 3, true, true);
                        if (exp)
                            this.exp = exp;
                        this.isExecuter = true;
                    };
                    helper.inherit(ctor, baseTypes.ExpressionBase);
                    var proto = ctor.prototype;

                    proto.clone = function () {
                        return new ctor(this.exp);
                    };

                    proto.execute = function (array, queryContext) {
                        var selector;
                        if (this.exp) {
                            selector = Assert.isFunction(this.exp)
                                ? this.exp
                                : helper.jsepToFunction(libs.jsep(this.exp), queryContext);
                        } else {
                            selector = function (v) { return v; };
                        }
                        return querying.queryFuncs.max.impl(array, function () { return array; }, selector);
                    };

                    return ctor;
                })(),
                MinExp: (function () {
                    var ctor = function (exp) {
                        baseTypes.ExpressionBase.call(this, 'exec;min', 3, true, true);
                        if (exp)
                            this.exp = exp;
                        this.isExecuter = true;
                    };
                    helper.inherit(ctor, baseTypes.ExpressionBase);
                    var proto = ctor.prototype;

                    proto.clone = function () {
                        return new ctor(this.exp);
                    };

                    proto.execute = function (array, queryContext) {
                        var selector;
                        if (this.exp) {
                            selector = Assert.isFunction(this.exp)
                                ? this.exp
                                : helper.jsepToFunction(libs.jsep(this.exp), queryContext);
                        } else {
                            selector = function (v) { return v; };
                        }
                        return querying.queryFuncs.min.impl(array, function () { return array; }, selector);
                    };

                    return ctor;
                })(),
                SumExp: (function () {
                    var ctor = function (exp) {
                        baseTypes.ExpressionBase.call(this, 'exec;sum', 3, true, true);
                        if (exp)
                            this.exp = exp;
                        this.isExecuter = true;
                    };
                    helper.inherit(ctor, baseTypes.ExpressionBase);
                    var proto = ctor.prototype;

                    proto.clone = function () {
                        return new ctor(this.exp);
                    };

                    proto.execute = function (array, queryContext) {
                        var selector;
                        if (this.exp) {
                            selector = Assert.isFunction(this.exp)
                                ? this.exp
                                : helper.jsepToFunction(libs.jsep(this.exp), queryContext);
                        } else {
                            selector = function (v) { return v; };
                        }
                        return querying.queryFuncs.sum.impl(array, function () { return array; }, selector);
                    };

                    return ctor;
                })(),
                CountExp: (function () {
                    var ctor = function (exp, varContext) {
                        baseTypes.ExpressionBase.call(this, 'exec;count', 3, true, true);
                        if (exp)
                            this.exp = exp;
                        this.varContext = varContext;
                        this.isExecuter = true;
                    };
                    helper.inherit(ctor, baseTypes.ExpressionBase);
                    var proto = ctor.prototype;

                    proto.clone = function () {
                        return new ctor(this.exp, this.varContext);
                    };

                    proto.execute = function (array, queryContext) {
                        if (this.exp) {
                            var predicate = Assert.isFunction(this.exp) ? this.exp : helper.jsepToFunction(libs.jsep(this.exp), queryContext);
                            return helper.filterArray(array, predicate).length
                        }
                        return array.length;
                    };

                    return ctor;
                })(),
                FirstExp: (function () {
                    var ctor = function (exp, varContext) {
                        baseTypes.ExpressionBase.call(this, 'exec;first', 3, true, false);
                        if (exp)
                            this.exp = exp;
                        this.varContext = varContext;
                        this.isExecuter = true;
                    };
                    helper.inherit(ctor, baseTypes.ExpressionBase);
                    var proto = ctor.prototype;

                    proto.clone = function () {
                        return new ctor(this.exp, this.varContext);
                    };

                    proto.execute = function (array, queryContext) {
                        if (this.exp) {
                            var predicate = Assert.isFunction(this.exp) ? this.exp : helper.jsepToFunction(libs.jsep(this.exp), queryContext);
                            array = helper.filterArray(array, predicate);
                        }
                        if (array.length == 0) throw helper.createError(i18N.arrayEmpty, { array: array, expression: this });
                        return array[0];
                    };

                    return ctor;
                })(),
                FirstOrDefaultExp: (function () {
                    var ctor = function (exp, varContext) {
                        baseTypes.ExpressionBase.call(this, 'exec;firstOD', 3, true, false);
                        if (exp)
                            this.exp = exp;
                        this.varContext = varContext;
                        this.isExecuter = true;
                    };
                    helper.inherit(ctor, baseTypes.ExpressionBase);
                    var proto = ctor.prototype;

                    proto.clone = function () {
                        return new ctor(this.exp, this.varContext);
                    };

                    proto.execute = function (array, queryContext) {
                        if (this.exp) {
                            var predicate = Assert.isFunction(this.exp) ? this.exp : helper.jsepToFunction(libs.jsep(this.exp), queryContext);
                            array = helper.filterArray(array, predicate);
                        }
                        return array.length == 0 ? null : array[0];
                    };

                    return ctor;
                })(),
                SingleExp: (function () {
                    var ctor = function (exp, varContext) {
                        baseTypes.ExpressionBase.call(this, 'exec;single', 3, true, false);
                        if (exp)
                            this.exp = exp;
                        this.varContext = varContext;
                        this.isExecuter = true;
                    };
                    helper.inherit(ctor, baseTypes.ExpressionBase);
                    var proto = ctor.prototype;

                    proto.clone = function () {
                        return new ctor(this.exp, this.varContext);
                    };

                    proto.execute = function (array, queryContext) {
                        if (this.exp) {
                            var predicate = Assert.isFunction(this.exp) ? this.exp : helper.jsepToFunction(libs.jsep(this.exp), queryContext);
                            array = helper.filterArray(array, predicate);
                        }
                        if (array.length != 1) throw helper.createError(i18N.arrayNotSingle, { array: array, expression: this });
                        return array[0];
                    };

                    return ctor;
                })(),
                SingleOrDefaultExp: (function () {
                    var ctor = function (exp, varContext) {
                        baseTypes.ExpressionBase.call(this, 'exec;singleOD', 3, true, false);
                        if (exp)
                            this.exp = exp;
                        this.varContext = varContext;
                        this.isExecuter = true;
                    };
                    helper.inherit(ctor, baseTypes.ExpressionBase);
                    var proto = ctor.prototype;

                    proto.clone = function () {
                        return new ctor(this.exp, this.varContext);
                    };

                    proto.execute = function (array, queryContext) {
                        if (this.exp) {
                            var predicate = Assert.isFunction(this.exp) ? this.exp : helper.jsepToFunction(libs.jsep(this.exp), queryContext);
                            array = helper.filterArray(array, predicate);
                        }
                        if (array.length > 1) throw helper.createError(i18N.arrayNotSingleOrEmpty, { array: array, expression: this });
                        return array.length == 0 ? null : array[0];
                    };

                    return ctor;
                })(),
                LastExp: (function () {
                    var ctor = function (exp, varContext) {
                        baseTypes.ExpressionBase.call(this, 'exec;last', 3, true, false);
                        if (exp)
                            this.exp = exp;
                        this.varContext = varContext;
                        this.isExecuter = true;
                    };
                    helper.inherit(ctor, baseTypes.ExpressionBase);
                    var proto = ctor.prototype;

                    proto.clone = function () {
                        return new ctor(this.exp, this.varContext);
                    };

                    proto.execute = function (array, queryContext) {
                        if (this.exp) {
                            var predicate = Assert.isFunction(this.exp) ? this.exp : helper.jsepToFunction(libs.jsep(this.exp), queryContext);
                            array = helper.filterArray(array, predicate);
                        }
                        if (array.length == 0) throw helper.createError(i18N.arrayEmpty, { array: array, expression: this });
                        return array[array.length - 1];
                    };

                    return ctor;
                })(),
                LastOrDefaultExp: (function () {
                    var ctor = function (exp, varContext) {
                        baseTypes.ExpressionBase.call(this, 'exec;lastOD', 3, true, false);
                        if (exp)
                            this.exp = exp;
                        this.varContext = varContext;
                        this.isExecuter = true;
                    };
                    helper.inherit(ctor, baseTypes.ExpressionBase);
                    var proto = ctor.prototype;

                    proto.clone = function () {
                        return new ctor(this.exp, this.varContext);
                    };

                    proto.execute = function (array, queryContext) {
                        if (this.exp) {
                            var predicate = Assert.isFunction(this.exp) ? this.exp : helper.jsepToFunction(libs.jsep(this.exp), queryContext);
                            array = helper.filterArray(array, predicate);
                        }
                        return array.length == 0 ? null : array[array.length - 1];
                    };

                    return ctor;
                })()
            },
            /**
             * Supported query functions. When a query has one of these in an expression, functions will be executed dynamically for local queries.
             * @namespace
             */
            queryFuncs: (function () {

                var expose = {};

                /** Returns uppercase value. */
                expose.toupper = (function () {
                    var ctor = function () {
                        baseTypes.QueryFuncBase.call(this, 'toupper', 'ToUpper', 1);
                    };
                    helper.inherit(ctor, baseTypes.QueryFuncBase);
                    var proto = ctor.prototype;

                    proto.impl = function (value, source) {
                        var v = source ? source(value) : value;
                        return v ? v.replace(/ı/g, 'I').toLocaleUpperCase(navigator.language) : v;
                    };

                    return new ctor();
                })();
                expose.touppercase = expose.toupper;
                /** Returns lowercase value. */
                expose.tolower = (function () {
                    var ctor = function () {
                        baseTypes.QueryFuncBase.call(this, 'tolower', 'ToLower', 1);
                    };
                    helper.inherit(ctor, baseTypes.QueryFuncBase);
                    var proto = ctor.prototype;

                    proto.impl = function (value, source) {
                        var v = source ? source(value) : value;
                        return v ? v.replace(/İ/g, 'i').toLocaleLowerCase(navigator.language) : v;
                    };

                    return new ctor();
                })();
                expose.tolowercase = expose.tolower;
                /** Returns substring of given string. */
                expose.substring = (function () {
                    var ctor = function () {
                        baseTypes.QueryFuncBase.call(this, 'substring', 'Substring', 3);
                    };
                    helper.inherit(ctor, baseTypes.QueryFuncBase);
                    var proto = ctor.prototype;

                    proto.impl = function (value, source, pos, length) {
                        if (arguments.length == 3) {
                            length = pos;
                            pos = source;
                            source = value;
                        } else source = source(value);
                        pos = pos(value);
                        length = length(value);
                        var end = Number(pos) + Number(length);
                        return source && source.substring(pos, end);
                    };

                    return new ctor();
                })();
                /** When given value contains given find string returns true, otherwise returns false. */
                expose.substringof = (function () {
                    var ctor = function () {
                        baseTypes.QueryFuncBase.call(this, 'substringof', 'Contains', 2);
                    };
                    helper.inherit(ctor, baseTypes.QueryFuncBase);
                    var proto = ctor.prototype;

                    proto.toBeetleFunction = function (find, source) {
                        source = source ? source + '.' : '';
                        return source + 'Contains(' + find + ')';
                    };

                    proto.impl = function (value, find, source) {
                        source = (source ? source(value) : value);
                        source = helper.handleStrOptions(source, this.varContext);
                        find = helper.handleStrOptions(find(value), this.varContext);
                        return source ? source.indexOf(find) >= 0 : false;
                    };

                    return new ctor();
                })();
                /** Returns length of string. */
                expose.length = (function () {
                    var ctor = function () {
                        baseTypes.QueryFuncBase.call(this, 'length', 'Length', 1);
                    };
                    helper.inherit(ctor, baseTypes.QueryFuncBase);
                    var proto = ctor.prototype;

                    proto.toBeetleFunction = function (source) {
                        source = source ? source + '.' : '';
                        return source + 'Length';
                    };

                    proto.impl = function (value, source) {
                        var v = source ? source(value) : value;
                        return v ? v.length : 0;
                    };

                    return new ctor();
                })();
                /** Returns trimmed string. */
                expose.trim = (function () {
                    var ctor = function () {
                        baseTypes.QueryFuncBase.call(this, 'trim', 'Trim', 1);
                    };
                    helper.inherit(ctor, baseTypes.QueryFuncBase);
                    var proto = ctor.prototype;

                    proto.impl = function (value, source) {
                        var v = source ? source(value) : value;
                        return v ? v.trim() : v;
                    };

                    return new ctor();
                })();
                /** Returns concatenated string. */
                expose.concat = (function () {
                    var ctor = function () {
                        baseTypes.QueryFuncBase.call(this, 'concat', 'Concat', null);
                    };
                    helper.inherit(ctor, baseTypes.QueryFuncBase);
                    var proto = ctor.prototype;

                    proto.toBeetleFunction = function () {
                        return 'string.Concat(' + Array.prototype.slice.call(arguments).join(', ') + ')';
                    };

                    proto.impl = function (value) {
                        var s1 = arguments[1](value).toString();
                        if (!s1) return s1;

                        var argList = Array.prototype.slice.call(arguments, 2);
                        var args = [];
                        for (var i = 0; i < argList.length; i++) {
                            args.push(argList[i](value));
                        }
                        return s1.concat.apply(s1, args);
                    };

                    return new ctor();
                })();
                /**  Replace string from source with given value. */
                expose.replace = (function () {
                    var ctor = function () {
                        baseTypes.QueryFuncBase.call(this, 'replace', 'Replace', 3);
                    };
                    helper.inherit(ctor, baseTypes.QueryFuncBase);
                    var proto = ctor.prototype;

                    proto.impl = function (value, source, find, replace) {
                        if (arguments.length == 3) {
                            replace = find;
                            find = source;
                            source = value;
                        } else source = source(value);
                        if (!source) return source;

                        find = find(value);

                        if (helper.ignoreWhiteSpaces(this.varContext))
                            find = find.trim();
                        if (!helper.isCaseSensitive(this.varContext))
                            find = new RegExp(find, 'gi');
                        return source.replace(find, replace(value));
                    };

                    return new ctor();
                })();
                /** if source string starts with given parameter returns true, otherwise false. */
                expose.startswith = (function () {
                    var ctor = function () {
                        baseTypes.QueryFuncBase.call(this, 'startswith', 'StartsWith', 2);
                    };
                    helper.inherit(ctor, baseTypes.QueryFuncBase);
                    var proto = ctor.prototype;

                    proto.impl = function (value, source, find) {
                        if (arguments.length == 2) {
                            find = source;
                            source = value;
                        } else source = source(value);
                        source = helper.handleStrOptions(source, this.varContext);
                        find = helper.handleStrOptions(find(value), this.varContext);
                        return source ? source.indexOf(find, 0) === 0 : false;
                    };

                    return new ctor();
                })();
                /** if source string ends with given parameter returns true, otherwise false. */
                expose.endswith = (function () {
                    var ctor = function () {
                        baseTypes.QueryFuncBase.call(this, 'endswith', 'EndsWith', 2);
                    };
                    helper.inherit(ctor, baseTypes.QueryFuncBase);
                    var proto = ctor.prototype;

                    proto.impl = function (value, source, find) {
                        if (arguments.length == 2) {
                            find = source;
                            source = value;
                        } else source = source(value);
                        source = helper.handleStrOptions(source, this.varContext);
                        find = helper.handleStrOptions(find(value), this.varContext);
                        return source ? source.indexOf(find, source.length - find.length) !== -1 : false;
                    };

                    return new ctor();
                })();
                /** Returns indexof find string in source string. */
                expose.indexof = (function () {
                    var ctor = function () {
                        baseTypes.QueryFuncBase.call(this, 'indexof', 'IndexOf', 2);
                    };
                    helper.inherit(ctor, baseTypes.QueryFuncBase);
                    var proto = ctor.prototype;

                    proto.impl = function (value, source, find) {
                        if (arguments.length == 2) {
                            find = source;
                            source = value;
                        } else source = source(value);
                        if (source && !source.indexOf) source = source.toString();
                        source = helper.handleStrOptions(source, this.varContext);
                        find = helper.handleStrOptions(find(value), this.varContext);
                        return source ? source.indexOf(find) : -1;
                    };

                    return new ctor();
                })();
                /** if items contains given item returns true, otherwise false. Supports arrays and strings as items parameter.. */
                expose.contains = (function () {
                    var ctor = function () {
                        baseTypes.QueryFuncBase.call(this, 'contains', 'Contains', 2);
                    };
                    helper.inherit(ctor, baseTypes.QueryFuncBase);
                    var proto = ctor.prototype;

                    proto.toODataFunction = function (items, item) {
                        if (Assert.isTypeOf(items, 'string'))
                            return expose.substringof.toODataFunction(item, items);
                        var args = [];
                        helper.forEach(items, function (i) {
                            args.push(item + ' eq ' + core.dataTypes.toODataValue(i));
                        });
                        return '(' + args.join(' or ') + ')';
                    };

                    proto.toBeetleFunction = function (items, item) {
                        if (Assert.isTypeOf(items, 'string'))
                            return expose.substringof.toBeetleFunction(item, items);
                        var args = [];
                        helper.forEach(items, function (i) {
                            args.push(item + ' == ' + core.dataTypes.toBeetleValue(i));
                        });
                        return '(' + args.join(' || ') + ')';
                    };

                    proto.impl = function (value, items, item) {
                        if (arguments.length == 1)
                            items = value;
                        else if (arguments.length == 2) {
                            item = items;
                            items = value;
                        }
                        else
                            items = items(value);
                        item = (item ? item(value) : value);
                        if (Assert.isArray(items)) {
                            for (var i = 0; i < items.length; i++) {
                                var v = items[i];
                                if (v == item) return true;
                            }
                            return false;
                        }
                        items = helper.handleStrOptions(items, this.varContext);
                        item = helper.handleStrOptions(item, this.varContext);
                        return items ? items.indexOf(item) >= 0 : -1;
                    };

                    return new ctor();
                })();
                /** Rounds given value to nearest integer. */
                expose.round = (function () {
                    var ctor = function () {
                        baseTypes.QueryFuncBase.call(this, 'round', 'Math.Round', 1);
                    };
                    helper.inherit(ctor, baseTypes.QueryFuncBase);
                    var proto = ctor.prototype;

                    proto.toBeetleFunction = function (source) {
                        return 'Math.Round(' + source + ')';
                    };

                    proto.impl = function (value, source) {
                        return Math.round(source ? source(value) : value);
                    };

                    return new ctor();
                })();
                /** Returns smallest integer value that is greater than given value. */
                expose.ceiling = (function () {
                    var ctor = function () {
                        baseTypes.QueryFuncBase.call(this, 'ceiling', 'Math.Ceiling(%1)', 1);
                    };
                    helper.inherit(ctor, baseTypes.QueryFuncBase);
                    var proto = ctor.prototype;

                    proto.toBeetleFunction = function (source) {
                        return 'Math.Ceiling(' + source + ')';
                    };

                    proto.impl = function (value, source) {
                        return Math.ceil(source ? source(value) : value);
                    };

                    return new ctor();
                })();
                /** Returns biggest integer value that is smaller than given value. */
                expose.floor = (function () {
                    var ctor = function () {
                        baseTypes.QueryFuncBase.call(this, 'floor', 'Math.Floor', 1);
                    };
                    helper.inherit(ctor, baseTypes.QueryFuncBase);
                    var proto = ctor.prototype;

                    proto.toBeetleFunction = function (source) {
                        return 'Math.Floor(' + source + ')';
                    };

                    proto.impl = function (value, source) {
                        return Math.floor(source ? source(value) : value);
                    };

                    return new ctor();
                })();

                /** Returns second of date. */
                expose.second = (function () {
                    var ctor = function () {
                        baseTypes.QueryFuncBase.call(this, 'second', 'Second', 1);
                    };
                    helper.inherit(ctor, baseTypes.QueryFuncBase);
                    var proto = ctor.prototype;

                    proto.toBeetleFunction = function (source) {
                        source = source ? source + '.' : '';
                        return source + 'Second';
                    };

                    proto.impl = function (value, source) {
                        source = core.dataTypes.date.handle(source ? source(value) : value);
                        return source ? source.getSeconds() : source;
                    };

                    return new ctor();
                })();
                expose.getseconds = expose.second;
                /** Returns minute of date. */
                expose.minute = (function () {
                    var ctor = function () {
                        baseTypes.QueryFuncBase.call(this, 'minute', 'Minute', 1);
                    };
                    helper.inherit(ctor, baseTypes.QueryFuncBase);
                    var proto = ctor.prototype;

                    proto.toBeetleFunction = function (source) {
                        source = source ? source + '.' : '';
                        return source + 'Minute';
                    };

                    proto.impl = function (value, source) {
                        source = core.dataTypes.date.handle(source ? source(value) : value);
                        return source ? source.getMinutes() : source;
                    };

                    return new ctor();
                })();
                expose.getminutes = expose.minute;
                /** Returns hour of date. */
                expose.hour = (function () {
                    var ctor = function () {
                        baseTypes.QueryFuncBase.call(this, 'hour', 'Hour', 1);
                    };
                    helper.inherit(ctor, baseTypes.QueryFuncBase);
                    var proto = ctor.prototype;

                    proto.toBeetleFunction = function (source) {
                        source = source ? source + '.' : '';
                        return source + 'Hour';
                    };

                    proto.impl = function (value, source) {
                        source = core.dataTypes.date.handle(source ? source(value) : value);
                        return source ? source.getHours() : source;
                    };

                    return new ctor();
                })();
                expose.gethours = expose.hour;
                /** Returns day of date. */
                expose.day = (function () {
                    var ctor = function () {
                        baseTypes.QueryFuncBase.call(this, 'day', 'Day', 1);
                    };
                    helper.inherit(ctor, baseTypes.QueryFuncBase);
                    var proto = ctor.prototype;

                    proto.toBeetleFunction = function (source) {
                        source = source ? source + '.' : '';
                        return source + 'Day';
                    };

                    proto.impl = function (value, source) {
                        source = core.dataTypes.date.handle(source ? source(value) : value);
                        return source ? source.getDate() : source;
                    };

                    return new ctor();
                })();
                expose.getdate = expose.day;
                /** Returns month of date. */
                expose.month = (function () {
                    var ctor = function () {
                        baseTypes.QueryFuncBase.call(this, 'month', 'Month', 1);
                    };
                    helper.inherit(ctor, baseTypes.QueryFuncBase);
                    var proto = ctor.prototype;

                    proto.toBeetleFunction = function (source) {
                        source = source ? source + '.' : '';
                        return source + 'Month';
                    };

                    proto.impl = function (value, source) {
                        source = core.dataTypes.date.handle(source ? source(value) : value);
                        return source ? source.getMonth() + 1 : source;
                    };

                    return new ctor();
                })();
                expose.getmonth = expose.month;
                /** Returns year of date. */
                expose.year = (function () {
                    var ctor = function () {
                        baseTypes.QueryFuncBase.call(this, 'year', 'Year', 1);
                    };
                    helper.inherit(ctor, baseTypes.QueryFuncBase);
                    var proto = ctor.prototype;

                    proto.toBeetleFunction = function (source) {
                        source = source ? source + '.' : '';
                        return source + 'Year';
                    };

                    proto.impl = function (value, source) {
                        source = core.dataTypes.date.handle(source ? source(value) : value);
                        return source ? source.getFullYear() : source;
                    };

                    return new ctor();
                })();
                expose.getfullyear = expose.year;

                /** Returns max value in the array. */
                expose.max = (function () {
                    var ctor = function () {
                        baseTypes.QueryFuncBase.call(this, 'max', 'Max', 2);
                    };
                    helper.inherit(ctor, baseTypes.QueryFuncBase);
                    var proto = ctor.prototype;

                    proto.toODataFunction = function (items, item) {
                        throw helper.createError(i18N.functionNotSupportedForOData, ['Max']);
                    };

                    proto.impl = function (value, items, item) {
                        if (arguments.length == 1)
                            items = value;
                        else if (arguments.length == 2) {
                            item = items;
                            items = value;
                        }
                        else
                            items = items(value);
                        if (items.length == 0) return null;

                        var max = item ? item(items[0]) : items[0];
                        for (var i = 1; i < items.length; i++) {
                            var v = item ? item(items[i]) : items[i];
                            if (v > max) max = v;
                        }
                        return max;
                    };

                    return new ctor();
                })();
                /** Returns min value in the array. */
                expose.min = (function () {
                    var ctor = function () {
                        baseTypes.QueryFuncBase.call(this, 'min', 'Min', 2);
                    };
                    helper.inherit(ctor, baseTypes.QueryFuncBase);
                    var proto = ctor.prototype;

                    proto.toODataFunction = function (items, item) {
                        throw helper.createError(i18N.functionNotSupportedForOData, ['Min']);
                    };

                    proto.impl = function (value, items, item) {
                        if (arguments.length == 1)
                            items = value;
                        else if (arguments.length == 2) {
                            item = items;
                            items = value;
                        }
                        else
                            items = items(value);
                        if (items.length == 0) return null;

                        var min = item ? item(items[0]) : items[0];
                        for (var i = 1; i < items.length; i++) {
                            var v = item ? item(items[i]) : items[i];
                            if (v < min) min = v;
                        }
                        return min;
                    };

                    return new ctor();
                })();
                /** Returns sum value from the array. */
                expose.sum = (function () {
                    var ctor = function () {
                        baseTypes.QueryFuncBase.call(this, 'sum', 'Sum', 2);
                    };
                    helper.inherit(ctor, baseTypes.QueryFuncBase);
                    var proto = ctor.prototype;

                    proto.toODataFunction = function (items, item) {
                        throw helper.createError(i18N.functionNotSupportedForOData, ['Sum']);
                    };

                    proto.impl = function (value, items, item) {
                        if (arguments.length == 1)
                            items = value;
                        else if (arguments.length == 2) {
                            item = items;
                            items = value;
                        }
                        else
                            items = items(value);
                        var sum = 0;
                        for (var i = 0; i < items.length; i++) {
                            var v = items[i];
                            sum += item ? item(v) : v;
                        }
                        return sum;
                    };

                    return new ctor();
                })();
                /** Returns count of the array. */
                expose.count = (function () {
                    var ctor = function () {
                        baseTypes.QueryFuncBase.call(this, 'count', 'Count', 1);
                    };
                    helper.inherit(ctor, baseTypes.QueryFuncBase);
                    var proto = ctor.prototype;

                    proto.toODataFunction = function (items) {
                        throw helper.createError(i18N.functionNotSupportedForOData, ['Count']);
                    };

                    proto.impl = function (value, items) {
                        return (items ? items(value) : value).length;
                    };

                    return new ctor();
                })();
                /** Returns average value from the array. */
                expose.avg = (function () {
                    var ctor = function () {
                        baseTypes.QueryFuncBase.call(this, 'avg', 'Average', 2);
                    };
                    helper.inherit(ctor, baseTypes.QueryFuncBase);
                    var proto = ctor.prototype;

                    proto.toODataFunction = function (items, item) {
                        throw helper.createError(i18N.functionNotSupportedForOData, ['Avg']);
                    };

                    proto.impl = function (value, items, item) {
                        if (arguments.length == 1)
                            items = value;
                        else if (arguments.length == 2) {
                            item = items;
                            items = value;
                        }
                        else
                            items = items(value);
                        if (items.length == 0) return null;

                        return expose.sum.impl(value, function () { return items; }, item) / items.length;
                    };

                    return new ctor();
                })();
                /** if any item from the array provides given predicate returns true, otherwise false. */
                expose.any = (function () {
                    var ctor = function () {
                        baseTypes.QueryFuncBase.call(this, 'any', 'Any', 2);
                        this.needsAlias = true;
                    };
                    helper.inherit(ctor, baseTypes.QueryFuncBase);
                    var proto = ctor.prototype;

                    proto.toODataFunction = function (alias, items, predicate) {
                        return items + '/any(' + alias + ': ' + predicate + ')';
                    };

                    proto.impl = function (value, items, predicate) {
                        if (arguments.length == 1)
                            items = value;
                        else if (arguments.length == 2) {
                            predicate = items;
                            items = value;
                        }
                        else
                            items = items(value);
                        if (!predicate) return items.length > 0;

                        for (var i = 0; i < items.length; i++) {
                            var v = items[i];
                            if (predicate(v) === true) return true;
                        }
                        return false;
                    };

                    return new ctor();
                })();
                /** if all items of the array provides given predicate returns true, otherwise false. */
                expose.all = (function () {
                    var ctor = function () {
                        baseTypes.QueryFuncBase.call(this, 'all', 'All', 2);
                        this.needsAlias = true;
                    };
                    helper.inherit(ctor, baseTypes.QueryFuncBase);
                    var proto = ctor.prototype;

                    proto.toODataFunction = function (alias, items, predicate) {
                        return items + '/all(' + alias + ': ' + predicate + ')';
                    };

                    proto.impl = function (value, items, predicate) {
                        if (arguments.length == 1)
                            items = value;
                        else if (arguments.length == 2) {
                            predicate = items;
                            items = value;
                        }
                        else
                            items = items(value);

                        for (var i = 0; i < items.length; i++) {
                            var v = items[i];
                            if (predicate(v) !== true) return false;
                        }
                        return true;
                    };

                    return new ctor();
                })();

                /** Finds the function */
                expose.getFunc = function (funcName, throwIfNotFound) {
                    var func = expose[funcName.toLowerCase()];
                    if (func == null && throwIfNotFound !== false) throw helper.createError(i18N.unknownFunction, [funcName]);
                    return func;
                };

                return expose;
            })(),
            /**
             * Array query, executes against an array. Similar to Linq to Objects.
             * @example
             *  var array = [{name: 'Test', age: 15}, {name: 'Test2', age: 25}];
             *  var query = array.asQueryable().where('name == "Test"');
             *  var result = query.execute();
             * @class
             */
            ArrayQuery: (function () {
                /** Creates ArrayQuery for the current array */
                Array.prototype.asQueryable = function () {
                    return new querying.ArrayQuery(this);
                };
                /** Register "q" as a shortcut for "asQueryable" if possible. */
                if (!Array.hasOwnProperty('q')) {
                    Array.prototype.q = function () {
                        return this.asQueryable();
                    };
                }

                /**
                 * @constructor
                 * @param {any} array - Array to create query for.
                 */
                var ctor = function (array) {
                    this.array = array;
                    baseTypes.QueryBase.call(this);
                };
                helper.inherit(ctor, baseTypes.QueryBase);
                var proto = ctor.prototype;
                proto.executeAfterExecuter = true;

                /**
                 * Executes this query against the related array.
                 * @param {Object} varContext - Variable context and query options.
                 */
                proto.execute = function (varContext) {
                    if (this.options)
                        varContext = helper.combine(this.options.varContext, varContext);
                    return this.toFunction()(this.array, varContext);
                };

                proto.clone = function () {
                    var q = new querying.ArrayQuery(this.array);
                    this.copy(q);
                    return q;
                };

                proto.copy = function (query) {
                    baseTypes.QueryBase.prototype.copy.call(this, query);
                };

                /**
                 * Simulate promise call with local queries.
                 */
                proto.then = function (callback, failCallback) {
                    var pp = settings.getPromiseProvider();
                    if (pp == null) throw new Error('Cannot create promise when there is no promise provider.');

                    var d = pp.deferred();
                    var p = pp.getPromise(d);

                    try {
                        var result = this.execute();
                        setTimeout(function() { pp.resolve(d, result); });
                    }
                    catch (e) {
                        setTimeout(function(){ pp.reject(d, e); });
                    }

                    if (failCallback && p["fail"])
                        return p.then(callback).fail(failCallback);
                    return p.then(callback, failCallback);
                }

                return ctor;
            })(),
            /**
             * Entity query, executes against a service. Similar to Linq to Entities.
             * @class
             */
            EntityQuery: (function () {
                /**
                 * @constructor
                 * @param {string} resource - Server resource name to query.
                 * @param {EntityType|string} type - Entity type or short name.
                 * @param {EntityManager=} manager - Entity manager.
                 */
                var ctor = function (resource, type, manager) {
                    this.resource = resource;
                    this.entityType = handleEntityType(type, manager);
                    this.manager = manager;
                    this.liveValidate = manager && manager.liveValidate;
                    this.parameters = [];
                    this.bodyParameters = [];

                    baseTypes.QueryBase.call(this);
                };
                helper.inherit(ctor, baseTypes.QueryBase);
                var proto = ctor.prototype;

                proto.toString = function () {
                    var retVal = [];

                    var params = [];
                    helper.forEach(this.parameters, function (prm) {
                        params.push(prm.name + ': ' + (prm.value == null ? '' : prm.value));
                    });
                    retVal.push('parameters: (' + params.join(', ') + ')');

                    retVal.push('resource: (' + this.resource + ')');
                    if (this.entityType)
                        retVal.push('entityType: (' + this.entityType.shortName + ')');
                    if (this.manager)
                        retVal.push('manager: (' + this.manager.toString() + ')');
                    retVal.push(baseTypes.QueryBase.prototype.toString.call(this));

                    return retVal.join(', ');
                };

                /**
                 * Expand query with given properties.
                 * @param {string} propertyPath - Path to desired property.
                 * @example
                 *  This query takes Orders and OrderDetails for each order in one query:
                 *    var query = manager.createQuery('Order');
                 *    query = query.expand('OrderDetails');
                 */
                proto.expand = function (propertyPath) {
                    var q = this.clone();
                    return q.addExpression(new querying.expressions.ExpandExp(propertyPath));
                };

                /** @see expand */
                proto.include = function (propertyPath) {
                    return this.expand.apply(this, arguments);
                };

                /** Sets given name-value pair as parameter. Parameters will be passed server method. */
                proto.setParameter = function (name, value) {
                    var q = this.clone();
                    var prm = helper.findInArray(q.parameters, name, 'name');
                    if (prm) prm.value = value;
                    else q.parameters.push({ name: name, value: value });
                    return q;
                };

                /** Adds properties of given value to the body of the request. */
                proto.setBodyParameter = function (value) {
                    var q = this.clone();
                    q.bodyParameters.push(value);
                    return q;
                };

                /** Set merge option to NoTracking */
                proto.asNoTracking = function () {
                    return this.withOptions({ merge: enums.mergeStrategy.NoTracking });
                }

                /** Set merge option to NoTrackingRaw */
                proto.asNoTrackingRaw = function () {
                    return this.withOptions({ merge: enums.mergeStrategy.NoTrackingRaw });
                }

                /** Sets entity type for query (used when executing locally). */
                proto.setEntityType = function (type) {
                    type = handleEntityType(type, this.manager);
                    var q = this.clone();
                    q.entityType = type;
                    return q;
                };

                /**
                 * Executes this query using related entity manager.
                 * @param {QueryOptions=} options - Query options.
                 * @param {successCallback=} successCallback - Success callback function.
                 * @param {errorCallback=} errorCallback - Error callback function.
                 * @returns {Promise}
                 */
                proto.execute = function (options, successCallback, errorCallback) {
                    if (!this.manager) throw helper.createError(i18N.onlyManagerCreatedCanBeExecuted, { query: this, options: options });
                    return this.manager.executeQuery(this, options, successCallback, errorCallback);
                };

                /**
                 * Executes this query against related manager's local cache.
                 * @param {Object} varContext - Variable context for query.
                 * @returns {any[]}
                 */
                proto.executeLocally = function (varContext) {
                    if (!this.manager) throw helper.createError(i18N.onlyManagerCreatedCanBeExecuted, { query: this, options: options });
                    return this.manager.executeQueryLocally(this, varContext);
                };

                proto.clone = function () {
                    var q = new querying.EntityQuery(this.resource, this.entityType, this.manager);
                    this.copy(q);
                    return q;
                };

                proto.copy = function (query) {
                    baseTypes.QueryBase.prototype.copy.call(this, query);
                    helper.forEach(this.parameters, function (prm) {
                        query.parameters.push(prm);
                    });
                    query.bodyParameters = this.bodyParameters.slice(0);
                };

                /** When using promises, this shortcut can be used instead of "execute().then()" syntax. */
                proto.then = function (callback, failCallback, options) {
                    var p = this.execute(options);
                    if (failCallback && p["fail"])
                        return p.then(callback).fail(failCallback);
                    return p.then(callback, failCallback);
                }

                function handleEntityType(type, manager) {
                    if (type == null) return null;
                    if (Assert.isFunction(type)) {
                        type = helper.getFuncName(type);
                    }
                    if (Assert.isTypeOf(type, 'string')) {
                        if (manager == null) throw helper.createError(i18N.onlyManagerCreatedCanAcceptEntityShortName);
                        return manager.getEntityType(type, true);
                    }
                    helper.assertPrm(type, 'type').isInstanceOf(metadata.EntityType).check();
                    return type;
                }

                return ctor;
            })()
        };
    })();

    /**
     * Core types.
     * @namespace
     */
    var core = {
        /**
         * This class wraps given value to allow skipping callbacks.
         * @class
         * @param {any} value - Value to wrap.
         * @param {any} fromBeetle - Indicates if Beetle triggered the change.
         */
        ValueNotifyWrapper: function (value, fromBeetle) {
            this.value = value;
            this.fromBeetle = fromBeetle === true;
        },
        /**
         * Beetle event class.
         * @class
         */
        Event: (function () {
            /**
             * @constructor
             * @param {string} name - Name of the event.
             * @param {any} publisher - Event's owner.
             */
            var ctor = function (name, publisher) {
                this.name = name;
                this.subscribers = [];
                this.publisher = publisher;
            };
            var proto = ctor.prototype;

            proto.toString = function () {
                return this.name;
            };

            /**
             * Adds given function to subscribe list. Will be notified when this event triggered.
             * @param {Function} subscriber - Subscriber function.
             */
            proto.subscribe = function (subscriber) {
                if (!helper.findInArray(this.subscribers, subscriber))
                    this.subscribers.push(subscriber);
            };

            /**
             * Removes given function from subscriber list.
             * @param {Function} subscriber - Subscriber function.
             */
            proto.unsubscribe = function (subscriber) {
                helper.removeFromArray(this.subscribers, subscriber);
            };

            /**
             * Notifies all subscribers.
             * @param {any} data - Data to pass to subscribe functions.
             */
            proto.notify = function (data) {
                var args = arguments;
                helper.forEach(this.subscribers, function (subscriber) {
                    subscriber.apply(subscriber, args);
                });
            };

            return ctor;
        })(),
        /**
         * Beetle supported data types.
         * @namespace
         */
        dataTypes: (function () {

            /**
             * Date base type.
             * @class
             */
            var dateBase = (function () {
                var ctor = function (name) {
                    baseTypes.DataTypeBase.call(this, 'dateBase');
                    this.name = name;
                };
                helper.inherit(ctor, baseTypes.DataTypeBase);
                var proto = ctor.prototype;

                /** Default value: 01/01/1753 */
                proto.defaultValue = function () {
                    return new Date(Date.UTC(1753, 1, 1));
                };

                proto.isValid = function (value) {
                    return Object.prototype.toString.call(value) === '[object Date]';
                };

                proto.handle = function (value) {
                    if (!this.isValid(value)) {
                        var v = value;
                        value = this.tryParse(v);
                        if (!value) throwAssignError(this, v);
                    }
                    return value;
                };

                proto.autoValue = function () {
                    return new Date();
                };

                proto.getRawValue = function (value) {
                    return value == null ? null : settings.getDateConverter().toISOString(value);
                };

                /** Tries to parse given value to date. Returns null when data cannot be parsed. */
                proto.tryParse = function (value) {
                    return settings.getDateConverter().parse(value);
                };

                proto.toODataValue = function (value) {
                    value = this.handle(value);
                    return "datetime'" + settings.getDateConverter().toISOString(value) + "'";
                };

                proto.toBeetleValue = function (value) {
                    value = this.handle(value);
                    return '"' + settings.getDateConverter().toISOString(value) + '"';
                };

                return ctor;
            })();

            var expose = {};

            /** Object type. */
            expose.object = (function () {
                var ctor = function () {
                    baseTypes.DataTypeBase.call(this, 'object');
                };
                helper.inherit(ctor, baseTypes.DataTypeBase);
                var proto = ctor.prototype;

                proto.toODataValue = function (value) {
                    return value;
                };

                proto.toBeetleValue = function (value) {
                    return value;
                };

                return new ctor();
            })();
            /** Array type. */
            expose.array = (function () {
                var ctor = function () {
                    baseTypes.DataTypeBase.call(this, 'array');
                };
                helper.inherit(ctor, baseTypes.DataTypeBase);
                var proto = ctor.prototype;

                proto.isValid = function (value) {
                    return value instanceof Array;
                };

                proto.defaultValue = function () {
                    return [];
                };

                proto.handle = function (value) {
                    if (!this.isValid(value)) value = value.split(',');
                    return value;
                };

                proto.autoValue = function () {
                    throw helper.createError(i18N.notImplemented, [this.name, 'defaultValue']);
                };

                proto.toODataValue = function (value) {
                    return value;
                };

                proto.toBeetleValue = function (value) {
                    return value;
                };

                return new ctor();
            })();
            /** Function type. */
            expose.func = (function () {
                var ctor = function () {
                    baseTypes.DataTypeBase.call(this, 'function');
                };
                helper.inherit(ctor, baseTypes.DataTypeBase);

                return new ctor();
            })();
            /** String type. */
            expose.string = (function () {
                var i = 0;

                var ctor = function () {
                    baseTypes.DataTypeBase.call(this, 'string');
                };
                helper.inherit(ctor, baseTypes.DataTypeBase);
                var proto = ctor.prototype;

                proto.defaultValue = function () {
                    return '';
                };

                proto.handle = function (value) {
                    if (!this.isValid(value)) value = value.toString();
                    return value;
                };

                proto.autoValue = function () {
                    return 'key_' + (--i);
                };

                proto.toODataValue = function (value) {
                    return "'" + value.replace(/'/g, "''") + "'";
                };

                proto.toBeetleValue = function (value) {
                    return '"' + value.replace(/"/g, '""') + '"';
                };

                return new ctor();
            })();
            /** Guid type. */
            expose.guid = (function () {
                var ctor = function () {
                    baseTypes.DataTypeBase.call(this, 'guid');
                };
                helper.inherit(ctor, baseTypes.DataTypeBase);
                var proto = ctor.prototype;

                proto.defaultValue = function () {
                    return '00000000-0000-0000-0000-000000000000';
                };

                proto.isValid = function (value) {
                    return typeof value === 'string' && value.match(/^\{?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}?$/i);
                };

                proto.handle = function (value) {
                    if (!this.isValid(value)) throwAssignError(this, value);
                    return value;
                };

                proto.autoValue = function () {
                    return helper.createGuid();
                };

                proto.toODataValue = function (value) {
                    return "guid'" + value + "'";
                };

                proto.toBeetleValue = function (value) {
                    return '"' + value + '"';
                };

                return new ctor();
            })();
            /** Date type. */
            expose.date = (function () {
                var ctor = function () {
                    dateBase.call(this, 'date');
                };
                helper.inherit(ctor, dateBase);

                return new ctor();
            })();
            /** DateTimeOffset type. */
            expose.dateTimeOffset = (function () {
                var ctor = function () {
                    dateBase.call(this, 'dateTimeOffset');
                };
                helper.inherit(ctor, dateBase);
                var proto = ctor.prototype;

                proto.toODataValue = function (value) {
                    value = this.handle(value);
                    return "datetimeoffset'" + settings.getDateConverter().toISOString(value) + "'";
                };

                return new ctor();
            })();
            /** Time type. */
            expose.time = (function () {
                var ctor = function () {
                    baseTypes.DataTypeBase.call(this, 'time');
                };
                helper.inherit(ctor, baseTypes.DataTypeBase);
                var proto = ctor.prototype;

                proto.defaultValue = function () {
                    return '00:00:00';
                };

                proto.isValid = function (value) {
                    return /^([01]?\d|2[0-3])(((:[0-5]?\d){2}(\.\d{1,3}){0,1})|(:[0-5]?\d){0,2})?$/.test(value);
                };

                proto.handle = function (value) {
                    if (!this.isValid(value))
                        throwAssignError(this, value);
                    return value;
                };

                proto.autoValue = function () {
                    return '00:00:00';
                };

                proto.toODataValue = function (value) {
                    return "time'" + value + "'";
                };

                proto.toBeetleValue = function (value) {
                    return '"' + value + '"';
                };

                return new ctor();
            })();
            /** Boolean type. */
            expose.boolean = (function () {
                var ctor = function () {
                    baseTypes.DataTypeBase.call(this, 'boolean');
                };
                helper.inherit(ctor, baseTypes.DataTypeBase);
                var proto = ctor.prototype;

                proto.defaultValue = function () {
                    return false;
                };

                proto.handle = function (value) {
                    if (!this.isValid(value)) {
                        if (expose.string.isValid(value)) {
                            // try to convert string values to boolean
                            var v = value.toLowerCase();
                            if (v == 'true' || v == '1')
                                return true;
                            else if (v == 'false' || v == '0')
                                return false;
                        }
                        throw throwAssignError(this, value);
                    }
                    return value;
                };

                return new ctor();
            })();
            /** Integer type. Int16, Int32, Int64 etc. */
            expose.int = (function () {
                var i = 0;

                var ctor = function () {
                    baseTypes.DataTypeBase.call(this, 'int');
                };
                helper.inherit(ctor, baseTypes.DataTypeBase);
                var proto = ctor.prototype;

                proto.defaultValue = function () {
                    return 0;
                };

                proto.isValid = function (value) {
                    return typeof value === 'number' && value % 1 === 0;
                };

                proto.handle = function (value) {
                    var v = value;
                    if (typeof value !== 'number') value = Number(value);
                    if (!this.isValid(value)) throwAssignError(this, v);
                    return value;
                };

                proto.autoValue = function () {
                    return --i;
                };

                return new ctor();
            })();
            /** Number type. Float, decimal etc. */
            expose.number = (function () {
                var i = 0;

                var ctor = function () {
                    baseTypes.DataTypeBase.call(this, 'number');
                };
                helper.inherit(ctor, baseTypes.DataTypeBase);
                var proto = ctor.prototype;

                proto.defaultValue = function () {
                    return 0;
                };

                proto.isValid = function (value) {
                    return !isNaN(value) && typeof value === 'number';
                };

                proto.handle = function (value) {
                    var v = value;
                    if (typeof value !== 'number') value = Number(value);
                    if (!this.isValid(value)) throwAssignError(this, v);
                    return value;
                };

                proto.autoValue = function () {
                    return --i;
                };

                return new ctor();
            })();
            /** Byte type. Value must be between 0 and 256. */
            expose.byte = (function () {
                var i = 0;

                var ctor = function () {
                    baseTypes.DataTypeBase.call(this, 'byte');
                };
                helper.inherit(ctor, baseTypes.DataTypeBase);
                var proto = ctor.prototype;

                proto.defaultValue = function () {
                    return 0;
                };

                proto.isValid = function (value) {
                    return typeof value === 'number' && value % 1 === 0 && value >= 0 && value < 256;
                };

                proto.handle = function (value) {
                    var v = value;
                    if (typeof value !== 'number') value = Number(value);
                    if (!this.isValid(value)) throwAssignError(this, v);
                    return value;
                };

                proto.autoValue = function () {
                    return --i;
                };

                return new ctor();
            })();
            /** Binary type. */
            expose.binary = (function () {
                var ctor = function () {
                    baseTypes.DataTypeBase.call(this, 'binary');
                };
                helper.inherit(ctor, baseTypes.DataTypeBase);
                var proto = ctor.prototype;

                proto.defaultValue = function () {
                    return "\"AAAAAAAAAAA=\"";
                };

                proto.isValid = function () {
                    return true;
                };

                proto.handle = function (value) {
                    return value;
                };

                proto.toODataValue = function (value) {
                    value = this.handle(value);
                    return "X'" + settings.getDateConverter().toISOString(value) + "'";
                };

                proto.toBeetleValue = function (value) {
                    return '"' + value + '"';
                };

                return new ctor();
            })();
            /** Enum type. */
            expose.enumeration = (function () {
                var ctor = function (enumType, enumTypeName, displayName) {
                    baseTypes.DataTypeBase.call(this, 'enum');
                    this.enumType = enumType;
                    this.enumTypeName = enumTypeName;
                    this.displayName = displayName || enumTypeName;
                };
                helper.inherit(ctor, baseTypes.DataTypeBase);
                var proto = ctor.prototype;

                proto.defaultValue = function () {
                    return 0;
                };

                proto.isValid = function (value) {
                    return getMember(value, this.enumType) != null;
                };

                proto.handle = function (value) {
                    var member = getMember(value, this.enumType);
                    if (member == null)
                        throw helper.createError(i18N.invalidEnumValue, [value, this.enumTypeName], { enumType: this.enumType, value: value });
                    return member;
                };

                proto.getRawValue = function (value) {
                    if (Assert.isEnum(value, this.enumType))
                        return value.value;
                    return value;
                };

                proto.toODataValue = function (value) {
                    return this.getRawValue(value);
                };

                proto.toBeetleValue = function (value) {
                    return this.getRawValue(value);
                };

                function getMember(value, enumType) {
                    if (Assert.isArray(value)) {
                        var flags = 0;
                        helper.forEach(value, function (v) {
                            flags |= v.value;
                        });
                        return flags;
                    }

                    var n = Number(value);
                    if (!isNaN(n)) value = n;

                    if (Assert.isTypeOf(value, 'string')) {
                        var values = value.split(',');

                        value = 0;
                        for (var i = 0; i < values.length; i++) {
                            var v = enumType[values[i]];
                            if (v != null) value |= v.value;
                            else return null;
                        }

                        return value;
                    }

                    if (Assert.isTypeOf(value, 'number'))
                        return value;

                    return Assert.isEnum(value, enumType) ? value.value : null;
                }

                return ctor;
            })();
            /** Geometry spatial type. */
            expose.geometry = (function () {
                var ctor = function () {
                    baseTypes.DataTypeBase.call(this, 'geometry');
                    this.isComplex = true;
                };
                helper.inherit(ctor, baseTypes.DataTypeBase);
                var proto = ctor.prototype;

                proto.defaultValue = function () {
                    return { $type: 'System.Data.Spatial.DbGeometry', CoordinateSystemId: null, WellKnownText: '', WellKnownBinary: null };
                };

                proto.isValid = function (value) {
                    return value.$tracker && value.$tracker.getValue('WellKnownText');
                };

                proto.handle = function (value) {
                    if (value.$tracker)
                        value.$tracker.entityType.isComplexType = true;
                    return value;
                };

                return new ctor();
            })();
            /** Geography spatial type. */
            expose.geography = (function () {
                var ctor = function () {
                    baseTypes.DataTypeBase.call(this, 'geography');
                    this.isComplex = true;
                };
                helper.inherit(ctor, baseTypes.DataTypeBase);
                var proto = ctor.prototype;

                proto.defaultValue = function () {
                    return { $type: 'System.Data.Spatial.DbGeography', CoordinateSystemId: null, WellKnownText: '', WellKnownBinary: null };
                };

                proto.isValid = function (value) {
                    return value.$tracker && value.$tracker.getValue('WellKnownText');
                };

                proto.handle = function (value) {
                    if (value.$tracker)
                        value.$tracker.entityType.isComplexType = true;
                    return value;
                };

                return new ctor();
            })();

            /** Finds data type by its name. */
            expose.byName = function (name) {
                var type = expose[name];
                if (!type) throw helper.createError(i18N.unknownDataType, [name]);
                return type;
            };
            /** Finds proper data type for given value. */
            expose.byValue = function (value) {
                if (expose.string.isValid(value)) {
                    if (expose.guid.isValid(value))
                        return expose.guid;
                    else if (expose.date.tryParse(value))
                        return expose.date;
                    return expose.string;
                }
                if (expose.date.isValid(value))
                    return expose.date;
                if (expose.boolean.isValid(value))
                    return expose.boolean;
                if (expose.int.isValid(value))
                    return expose.int;
                if (expose.number.isValid(value))
                    return expose.number;
                if (expose.array.isValid(value))
                    return expose.array;
                if (Assert.isObject(value))
                    return expose.object;
                return expose.binary;
            };
            /**
             * Finds proper data type for given value and converts it to that type.
             * @example
             *  Json dates are strings, we use this method to auto-convert them to Javascript date.
             */
            expose.handle = function (value) {
                var v = expose.date.tryParse(value);
                if (v != null) return v;
                return value;
            };

            /** Converts given value to OData filter format value. */
            expose.toODataValue = function (value) {
                if (value == null) return 'null';
                return expose.byValue(value).toODataValue(value);
            };
            /** Converts given value to OData filter format value. */
            expose.toBeetleValue = function (value) {
                if (value == null) return 'null';
                return expose.byValue(value).toBeetleValue(value);
            };

            function throwAssignError(dataType, value) {
                throw helper.createError(i18N.assignError, [dataType.name, value], { dataType: dataType, value: value });
            }

            return expose;
        })(),
        /**
         * Beetle data and navigation property validators.
         * @namespace
         */
        Validator: (function () {
            /**
             * Creates a new validator.
             * @constructor
             * @param {string} name - Validator name.
             * @param {Function} func - Validator javascript implementation.
             * @param {string} message - Error message.
             * @param {any[]} args - Validator specific arguments.
             */
            var ctor = function (name, func, message, args) {
                this.name = name;
                this.func = func;
                this.message = message;
                this.args = args;
            };
            var proto = ctor.prototype;

            proto.toString = function () {
                var args = [];
                if (this.args)
                    for (var p in this.args) {
                        args.push(p + ': ' + this.args[p]);
                    }
                return args.length > 0 ? this.name + ' (' + args.join(', ') + ')' : this.name;
            };

            /**
             * Validates given parameters against validation function.
             * @param {any} value - Value to validate.
             * @param {Entity} entity - Value's owner entity. Might be used for some validations (i.e. compare).
             */
            proto.validate = function (value, entity) {
                return this.func(value, entity) == true ? null : this.message;
            };

            /**
             * Finds the validator by given code and initializes it with given arguments.
             * @param {string} code - Validator short code.
             * @param {any[]} args - Validator specific arguments.
             * @param {string} message - Error message.
             * @param {string=} messageResourceName - Error message's resource name for dynamic translation.
             * @param {string=} displayName - Validator owner's display name.
             * @param {string=} displayNameResourceName - Validator owner's display name's resource name for dynamic translation.
             */
            ctor.byCode = function (code, args, message, messageResourceName, displayName, displayNameResourceName) {
                var localizeFunc = settings.getLocalizeFunction();
                if (localizeFunc) {
                    message = (messageResourceName && localizeFunc(messageResourceName)) || message;
                    displayName = (displayNameResourceName && localizeFunc(displayNameResourceName)) || displayName;
                }
                args = args || [];
                args.push(message);
                args.push(displayName);
                switch (code) {
                    case 're':
                        return ctor.required.apply(null, args);
                    case 'sl':
                        return ctor.stringLength.apply(null, args);
                    case 'ma':
                        return ctor.maxLength.apply(null, args);
                    case 'mi':
                        return ctor.minLength.apply(null, args);
                    case 'ra':
                        return ctor.range.apply(null, args);
                    case 'rx':
                        return ctor.regularExpression.apply(null, args);
                    case 'ea':
                        return ctor.emailAddress.apply(null, args);
                    case 'cc':
                        return ctor.creditCard.apply(null, args);
                    case 'ur':
                        return ctor.url.apply(null, args);
                    case 'ph':
                        return ctor.phone.apply(null, args);
                    case 'po':
                        return ctor.postalCode.apply(null, args);
                    case 'ti':
                        return ctor.time.apply(null, args);
                    case 'co':
                        return ctor.compare.apply(null, args);
                    case 'nn':
                        return ctor.notNull.apply(null, args);
                    default:
                        throw helper.createError(i18N.unknownValidator, [code]);
                }
            };

            ctor.notNull = function (message, displayName) {
                var func = function (value) {
                    return value != null;
                };
                message = helper.formatString(message || i18N.notNullable, displayName);
                return new ctor('NotNull', func, message);
            };
            ctor.required = function (allowEmptyStrings, message, displayName) {
                var func = function (value) {
                    if (value == null) return false;
                    if (Assert.isTypeOf(value, 'string') && !allowEmptyStrings && value == '') return false;
                    return true;
                };
                message = helper.formatString(message || i18N.requiredError, displayName);
                return new ctor('Required', func, message, { allowEmptyStrings: allowEmptyStrings });
            };
            ctor.stringLength = function (min, max, message, displayName) {
                var func = function (value) {
                    if (!min && !max) return true;
                    if (!Assert.isNotEmptyString(value)) return false;
                    if (min && value.length < min) return false;
                    if (max && value.length > max) return false;
                    return true;
                };
                message = helper.formatString(message || i18N.stringLengthError, displayName, min, max);
                return new ctor('StringLength', func, message, { min: min, max: max });
            };
            ctor.maxLength = function (length, message, displayName) {
                var func = function (value) {
                    if (value == null) return true;
                    if (length && value.length > length) return false;
                    return true;
                };
                message = helper.formatString(message || i18N.maxLenError, displayName, length);
                return new ctor('MaxLength', func, message, { length: length });
            };
            ctor.minLength = function (length, message, displayName) {
                var func = function (value) {
                    if (value == null) return false;
                    if (length && value.length < length) return false;
                    return true;
                };
                message = helper.formatString(message || i18N.minLenError, displayName, length);
                return new ctor('MinLength', func, message, { length: length });
            };
            ctor.range = function (min, max, message, displayName) {
                var func = function (value) {
                    if (!min && !max) return true;
                    if (min && value < min) return false;
                    if (max && value > max) return false;
                    return true;
                };
                message = helper.formatString(message || i18N.rangeError, displayName, min, max);
                return new ctor('Range', func, message, { min: min, max: max });
            };
            ctor.regularExpression = function (pattern, message, displayName) {
                if (Assert.isTypeOf(pattern, 'string')) pattern = new RegExp(pattern);
                return regex('RegularExpression', pattern, message, displayName);
            };
            ctor.emailAddress = function (message, displayName) {
                return regex('EmailAddress', /^\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,3}$/, message, displayName);
            };
            ctor.creditCard = function (message, displayName) {
                return regex('CreditCard', /^((4\d{3})|(5[1-5]\d{2})|(6011)|(3[68]\d{2})|(30[012345]\d))[ -]?(\d{4})[ -]?(\d{4})[ -]?(\d{4}|3[4,7]\d{13})$/, message, displayName);
            };
            ctor.url = function (message, displayName) {
                return regex('Url', /(http|ftp|https):\/\/[\w-]+(\.[\w-]+)+([\w.,@?^=%&amp;:\/~+#-]*[\w@?^=%&amp;\/~+#-])?/, message, displayName);
            };
            ctor.phone = function (message, displayName) {
                return regex('Phone', /^(?!.*-.*-.*-)(?=(?:\d{8,10}$)|(?:(?=.{9,11}$)[^-]*-[^-]*$)|(?:(?=.{10,12}$)[^-]*-[^-]*-[^-]*$)  )[\d-]+$/, message, displayName);
            };
            ctor.postalCode = function (message, displayName) {
                return regex('PostalCode', /^\d{5}([\-]?\d{4})?$/, message, displayName);
            };
            ctor.time = function (message, displayName) {
                return regex('Time', /^([01]?\d|2[0-3])(((:[0-5]?\d){2}(\.\d{1,3}){0,1})|(:[0-5]?\d){0,2})?$/, message, displayName);
            };

            function regex(name, pattern, message, displayName) {
                var func = function (value) {
                    if (value == null) return false;
                    return pattern.test(value);
                };
                message = helper.formatString(message || i18N.invalidValue, displayName);
                return new ctor(name, func, message, { pattern: pattern });
            }

            /** Compares value with given property, both must be equal. */
            ctor.compare = function (property, message, displayName) {
                var func = function (value, entity) {
                    var other = helper.getValue(entity, property);
                    return value == other;
                };
                var localizeFunc = settings.getLocalizeFunction();
                var propertyDisplayName = (localizeFunc && localizeFunc(property)) || property;
                message = helper.formatString(message || i18N.compareError, displayName, propertyDisplayName);
                return new ctor("Compare", func, message, { property: property });
            };

            return ctor;
        })(),
        /**
         * Entity container holds entity sets.
         * @class
         */
        EntityContainer: (function () {
            /**
             * Entity Set holds entity references
             * @class
             */
            var entitySet = (function () {
                /**
                 * We hold every entity type (which has key) in seperate list.
                 * But for a derived type we create entries for each base type increase performance for inheritance scenarios.
                 * @constructor
                 * @example
                 *  Lets say we have this hierarchy;
                 *  Customer -> Company -> Entity -> EntityBase
                 *  When we add a customer three more entries will ve created (for Company, Entity and EntityBase)
                 *  So when we search a Company this Customer will be in the list.@param {any} type
                 * @param {any} type - Entity type for the set.
                 */
                var c = function (type) {
                    this.typeName = type.name;
                    this.keyIndex = [];
                };
                var p = c.prototype;

                p.toString = function () {
                    return this.typeName + ': ' + this.keyIndex.length;
                };

                /**
                 * Adds given entity to proper location in the index table using its key.
                 * @param {string} key - The key.
                 * @param {Entity} entity - The entity.
                 */
                p.push = function (key, entity) {
                    // find proper location
                    var location = findLocation(key, this.keyIndex);
                    // insert new index
                    this.keyIndex.splice(location, 0, { key: key, entity: entity });
                };

                /**
                 * Removes item with given key from index table.
                 * @param {string} key - The key.
                 */
                p.remove = function (key) {
                    // find the index entry.
                    var index = getIndex(key, this.keyIndex);
                    this.keyIndex.splice(index, 1);
                };

                /**
                 * Finds entity with given key.
                 * @param {string} key - The key.
                 * @returns {Entity} Entity if found, otherwise null.
                 */
                p.getEntity = function (key) {
                    // find the index entry
                    var entry = getEntry(key, this.keyIndex);
                    if (entry) return entry.entity;
                    return null;
                };

                /**
                 * Gets entities which has given foreign key for given navigation property.
                 * @param {string} fk - The foreign key.
                 * @param {NavigationProperty} navProperty - The navigation property.
                 */
                p.getRelations = function (fk, navProperty) {
                    var retVal = [];
                    // copy all items has same foreign key for given navigation propery to a new array.
                    for (var i = 0; i < this.keyIndex.length; i++) {
                        var ki = this.keyIndex[i];
                        if (ki.entity.$tracker.foreignKey(navProperty) === fk)
                            retVal.push(ki.entity);
                    }
                    return retVal;
                };

                /**
                 * After an entity's key changed we need to rebuild the index table.
                 * @param {Entity} entity - The entity.
                 * @param {string} oldKey - The old key.
                 * @param {string} newKey - The new key.
                 */
                p.relocateKey = function (entity, oldKey, newKey) {
                    // if there is an old index, remove it.
                    this.remove(oldKey);
                    // if new key has a value, add it to index tables.
                    if (newKey)
                        this.push(newKey, entity);
                };

                /**
                 * Returns entity collection of the set.
                 * @returns {Entity[]} Entries of entity set.
                 */
                p.getEntities = function () {
                    var retVal = [];
                    helper.forEach(this.keyIndex, function (ki) {
                        retVal.push(ki.entity);
                    });
                    return retVal;
                };

                function getEntry(key, keyIndex) {
                    var index = getIndex(key, keyIndex);
                    return index > -1 ? keyIndex[index] : null;
                }

                function getIndex(key, keyIndex) {
                    // find given key with binary search.
                    var len = keyIndex.length;
                    if (len > 0) {
                        var low = 0, high = len - 1, i;
                        while (low <= high) {
                            i = Math.floor((low + high) / 2);
                            if (keyIndex[i].key < key) {
                                low = i + 1;
                                continue;
                            }
                            if (keyIndex[i].key > key) {
                                high = i - 1;
                                continue;
                            }
                            return i;
                        }
                    }
                    return -1;
                }

                function findLocation(key, keyIndex) {
                    var i = 0;
                    while (i < keyIndex.length && key > keyIndex[i].key) i++;
                    return i;
                }

                return c;
            })();

            /**
             * Holds entity list and key-entity mappings for types.
             * Seperate key-entity lists are generated for every type and an entity stored in list of every type in its inheritance chain.
             */
            var ctor = function () {
                // To hold keyed entities.
                this.entitySets = [];
                // To hold all entities.
                this.allEntities = [];
            };
            var proto = ctor.prototype;

            proto.toString = function () {
                return this.allEntities.length;
            };

            /** Adds given entity to each entity set in the inheritance hierarchy. */
            proto.push = function (entity) {
                // add entity to entities array.
                this.allEntities.push(entity);
                var tracker = entity.$tracker;
                var type = tracker.entityType;
                // get entity key
                var key = tracker.key;
                if (key) {
                    while (type) {
                        // add this key index entry to all sets for inheritance hierarchy
                        var es = this.getEntitySet(type);
                        es.push(key, entity);
                        type = type.baseType;
                    }
                }
            };

            /** Removes given entity from each entity set in the inheritance hierarchy. */
            proto.remove = function (entity) {
                // remove entity from entities array.
                helper.removeFromArray(this.allEntities, entity);
                var tracker = entity.$tracker;
                var type = tracker.entityType;
                var key = tracker.key;
                if (!key) return;
                // if key has value.
                while (type) {
                    // remove this key index entry from all sets for inheritance hierarchy
                    var es = this.findEntitySet(type);
                    if (es) {
                        es.remove(key);
                        type = type.baseType;
                    }
                }
            };

            /** Gets cached entity list. */
            proto.getEntities = function () {
                return this.allEntities;
            };

            /**
             * Finds entity with given key by searching entity type's entity set.
             * @param {string} key - The key.
             * @param {EntityType} type - Entity type object.
             */
            proto.getEntityByKey = function (key, type) {
                if (!key) return null;
                // get entity set for type
                var es = this.findEntitySet(type);
                if (!es) return null;
                return es.getEntity(key);
            };

            /** Gets entities which has given foreign key for given navigation property by searching navigation's entity type's set. */
            proto.getRelations = function (entity, navProperty) {
                var type = navProperty.entityType;
                var key = entity.$tracker.key;
                if (!key) return null;
                // find related entity set
                var es = this.findEntitySet(type);
                // request relation from entity set
                if (es) return es.getRelations(key, navProperty);
                return null;
            };

            /** After an entity's key changed we need to rebuild the index tables for each entity set in the inheritance hiearachy. */
            proto.relocateKey = function (entity, oldKey, newKey) {
                // get entity type
                var type = entity.$tracker.entityType;
                // create index entry for each type in inheritance hierarachy
                while (type) {
                    var es = this.findEntitySet(type);
                    if (!es) {
                        es = createEntitySet(type, this.entitySets);
                        es.push(newKey, entity);
                    } else
                        es.relocateKey(entity, oldKey, newKey);
                    type = type.baseType;
                }
            };

            /** Gets all changed entities from cache (Modified, Added, Deleted). */
            proto.getChanges = function () {
                return helper.filterArray(this.allEntities, function (item) {
                    return !(item.$tracker.entityType.isComplexType && item.$tracker.owners.length > 0) && item.$tracker.isChanged();
                });
            };

            /** Returns cached entity count. */
            proto.count = function () {
                return this.allEntities.length;
            };

            /**
             * Finds entity set for given type in the cache.
             * @param {EntityType} type - Entity type object.
             * @returns {EntitySet} Entity set if found, otherwise null.
             */
            proto.findEntitySet = function (type) {
                return helper.findInArray(this.entitySets, type.name, 'typeName');
            };

            /**
             * Search entity set for given type in the cache, creates if there isn't any.
             * @param {EntityType} type - Entity type object.
             * @returns {EntitySet} Found or newly created entity set.
             */
            proto.getEntitySet = function (type) {
                var es = this.findEntitySet(type);
                if (!es) es = createEntitySet(type, this.entitySets);
                return es;
            };

            function createEntitySet(type, entitySets) {
                var es = new entitySet(type);
                entitySets.push(es);
                return es;
            }

            return ctor;
        })(),
        /**
         * Entity set works like a repository.
         * @class
         */
        EntitySet: (function () {
            /**
             * @constructor
             * @param {EntityType} type - Entity type object.
             * @param {EntityManager} manager - Owner entity manager instance.
             */
            var ctor = function (type, manager) {
                this.local = manager.entities.getEntitySet(type);

                querying.EntityQuery.call(this, type.setName, type, manager);
            };
            helper.inherit(ctor, querying.EntityQuery);
            var proto = ctor.prototype;

            proto.toString = function () {
                return 'EntitySet: ' + this.entityType.shortName;
            };

            /** Creates a new entity with Added state. */
            proto.create = function (initialValues) {
                var entity = this.entityType.createEntity(initialValues);
                this.manager.addEntity(entity);
                return entity;
            };

            /** Creates a new detached entity. */
            proto.createDetached = function (initialValues) {
                return this.entityType.createEntity(initialValues);
            };

            /** Creates a new raw detached entity. */
            proto.createRaw = function (initialValues) {
                return this.entityType.createRawEntity(initialValues);
            };

            /** Adds the given entity to the manager in the Added state. */
            proto.add = function (entity) {
                checkType(entity, this);
                this.manager.addEntity(entity);
            };

            /** Attaches the given entity to the manager in the Unchanged state. */
            proto.attach = function (entity) {
                checkType(entity, this);
                this.manager.attachEntity(entity);
            };

            /** Marks the given entity as Deleted. */
            proto.remove = function (entity) {
                checkType(entity, this);
                this.manager.deleteEntity(entity);
            };

            function checkType(entity, that) {
                if (entity == null)
                    throw helper.createError(i18N.cannotCheckInstanceOnNull);
                var t = that.entityType;
                if (!t.isAssignableWith(entity)) {
                    var ot = entity.$tracker && entity.$tracker.entityType;
                    var otn = (ot && ot.shortName) || 'Object';
                    if (!ot || !t.isAssignableWith(ot))
                        throw helper.createError(i18N.typeError, [otn, t.shortName]);
                }
            }

            return ctor;
        })(),
        /**
         * Entity tracker class. Tracks changes made on entities.
         * @class
         */
        EntityTracker: (function () {
            var ctor = function (entity, type, op) {
                delete entity.$type;
                delete entity.$id;
                initialize(entity, type, op || settings.getObservableProvider(), this);
                // Convert raw entity to observable.
                toObservable(entity, type, this);
                callIzer(type, entity);
            };
            var proto = ctor.prototype;

            proto.toString = function () {
                return 'EntityTracker: ' + this.entityType.shortName + ', key: ' + this.key;
            };

            /** Manager setter, it can only be set with entityManager derived object and because of this class is internal it cannot be set from outside. */
            proto.setManagerInfo = function (manager) {
                if (this.manager) throw helper.createError(i18N.entityAlreadyBeingTracked, { otherManager: this.manager });
                // Check if argument is an instance of entityManager.
                helper.assertPrm(manager, 'manager').isInstanceOf(core.EntityManager).check();
                this.manager = manager;
            };

            /** Gets if entity is changed. */
            proto.isChanged = function () {
                return this.entityState === enums.entityStates.Added || this.entityState === enums.entityStates.Deleted
                    || this.entityState === enums.entityStates.Modified;
            };

            /** Deletes entity from manager. */
            proto.delete = function () {
                checkManager(this);
                this.manager.deleteEntity(this.entity);
            };

            /** Detaches entity from manager. */
            proto.detach = function () {
                checkManager(this);
                this.manager.detachEntity(this.entity);
            };

            /** Change entity's state to Added. */
            proto.toAdded = function () {
                if (this.entityState == enums.entityStates.Added) return;
                var obj = { entity: this.entity, oldState: this.entityState, newState: enums.entityStates.Added, newChanged: !this.isChanged() };
                this.entityState = enums.entityStates.Added;
                this.entityStateChanged.notify(obj);
            };

            /** Change entity's state to Modified. */
            proto.toModified = function () {
                if (this.entityState == enums.entityStates.Modified) return;
                var obj = { entity: this.entity, oldState: this.entityState, newState: enums.entityStates.Modified, newChanged: !this.isChanged() };
                this.entityState = enums.entityStates.Modified;
                this.entityStateChanged.notify(obj);
            };

            /** Change entity's state to Deleted. */
            proto.toDeleted = function () {
                if (this.entityState == enums.entityStates.Deleted) return;
                var obj = { entity: this.entity, oldState: this.entityState, newState: enums.entityStates.Deleted, newChanged: !this.isChanged() };
                this.entityState = enums.entityStates.Deleted;
                this.entityStateChanged.notify(obj);
            };

            /** Change entity's state to Unchanged. */
            proto.toUnchanged = function () {
                if (this.entityState == enums.entityStates.Unchanged) return;
                var obj = { entity: this.entity, oldState: this.entityState, newState: enums.entityStates.Unchanged, newUnchanged: this.isChanged() };
                this.originalValues.length = 0;
                this.changedValues.length = 0;
                this.entityState = enums.entityStates.Unchanged;
                this.entityStateChanged.notify(obj);
            };

            /** Change entity's state to Detached. */
            proto.toDetached = function () {
                if (this.entityState == enums.entityStates.Detached) return;
                var obj = { entity: this.entity, oldState: this.entityState, newState: enums.entityStates.Detached, newUnchanged: this.isChanged() };
                this.entityState = enums.entityStates.Detached;
                this.entityStateChanged.notify(obj);
            };

            /** Resets all changes to initial values. */
            proto.rejectChanges = function () {
                var that = this;
                helper.forEach(this.originalValues, function (ov) {
                    that.setValue(ov.p, ov.v);
                });
                this.originalValues.length = 0;
            };

            /** Resets all changes to last accepted values. */
            proto.undoChanges = function () {
                var that = this;
                helper.forEach(this.changedValues, function (cv) {
                    that.setValue(cv.p, cv.v);
                });
                this.changedValues.length = 0;
            };

            /** Accept all changes. */
            proto.acceptChanges = function () {
                this.changedValues.length = 0;
            };

            /**
             * Gets internal value of the property from observable entity.
             * @param {string} property - Name of the property.
             */
            proto.getValue = function (property) {
                return this.observableProvider.getValue(this.entity, property);
            };

            /**
             * Sets internal value of the property of observable entity.
             * @param {string} property - Name of the property.
             * @param {any} value - Value to set.
             */
            proto.setValue = function (property, value) {
                this.observableProvider.setValue(this.entity, property, value);
            };

            /** Gets original value for property. */
            proto.getOriginalValue = function (property) {
                var ov = helper.findInArray(this.originalValues, property, 'p');
                return ov ? ov.v : this.getValue(property);
            };

            /**
             * Get foreign key value for this navigation property.
             * @param {NavigationProperty} navProperty - The navigation property object.
             * @returns {string} Comma separated foreign keys.
             */
            proto.foreignKey = function (navProperty) {
                var type = navProperty.entityType;
                if (type.keys.length == 0) return null;
                var retVal = [];
                for (var i = 0; i < type.keys.length; i++) {
                    var key = type.keys[i];
                    var fkName = navProperty.foreignKeyNames[i];
                    var value = this.getValue(fkName);
                    if (value == null) return null;
                    if (key.dataType.name == 'guid')
                        value = value.toLowerCase();
                    retVal.push(value);
                }
                return retVal.join(',');
            };

            /**
             * Creates a query that can load this navigation property.
             * @param {string} navPropName - The navigation property name.
             * @param {string} resourceName - The resource (query name) for entity type.
             * @returns {EntityQuery} Created entity query.
             */
            proto.createLoadQuery = function (navPropName, resourceName) {
                var navProp = helper.findInArray(this.entityType.navigationProperties, navPropName, 'name');
                return createLoadQuery(navProp, navPropName, resourceName, this);
            };

            /**
             * Loads given navigation property of the entity.
             * @param {string} navPropName - The navigation property name.
             * @param {string[]} expands - Expand navigations to apply when loading navigation property.
             * @param {string} resourceName - Resource name to query entities.
             * @param {QueryOptions} options - Query options.
             * @param {Function} successCallback - Function to call after operation succeeded.
             * @param {Function} errorCallback - Function to call when operation fails.
             */
            proto.loadNavigationProperty = function (navPropName, expands, resourceName, options, successCallback, errorCallback) {
                if (this.manager == null) throw helper.createError(i18N.entityNotBeingTracked, { entity: this.entity });
                var navProp = helper.findInArray(this.entityType.navigationProperties, navPropName, 'name');
                var query = createLoadQuery(navProp, navPropName, resourceName, this);
                // apply given expands
                if (expands) {
                    helper.forEach(expands, function (expand) {
                        query = query.expand(expand);
                    });
                }
                var that = this;
                // execute query on the manager.
                options = options || {};
                if (options.merge == null) options.merge = enums.mergeStrategy.Preserve;
                if (options.autoFixScalar == null) options.autoFixScalar = true;
                if (options.autoFixPlural == null) options.autoFixPlural = true;
                return this.manager.executeQuery(query, options,
                    function () {
                        if (!navProp.inverse)
                            that.manager.fixNavigations(entity);
                        if (successCallback)
                            successCallback.apply(null, arguments);
                    },
                    errorCallback);
            };

            /** Validates entity against metadata data annotation validations. */
            proto.validate = function () {
                if (this.entityState == enums.entityStates.Deleted)
                    this.validationErrors = [];
                else
                    mergeErrors(this.entityType.validate(this.entity), this);
                return this.validationErrors;
            };

            /** Creates a raw javascript object representing this entity. */
            proto.toRaw = function (includeNavigations, handledList) {
                // get entity information.
                handledList = handledList || [];
                handledList.push(this.entity);

                var type = this.entityType;
                var data = {};
                var that = this;
                if (type.hasMetadata) {
                    helper.forEach(type.dataProperties, function (dp) {
                        var v = that.getValue(dp.name);
                        if (v == null || !v.$tracker)
                            data[dp.name] = dp.dataType.getRawValue(v);
                        else if (includeNavigations == true || v.$tracker.entityType.isComplexType)
                            data[dp.name] = v.$tracker.toRaw(includeNavigations, handledList);
                    });
                    if (includeNavigations == true) {
                        helper.forEach(type.navigationProperties, function (np) {
                            var v = that.getValue(np.name);
                            if (v == null)
                                data[np.name] = null;
                            else {
                                if (Assert.isArray(v)) {
                                    data[np.name] = [];
                                    helper.forEach(v, function (item) {
                                        if (item == null || handledList.indexOf(item) >= 0) return;

                                        if (!item.$tracker)
                                            data[np.name].push(item);
                                        else
                                            data[np.name].push(item.$tracker.toRaw(true, handledList));
                                    });
                                }
                                else {
                                    data[np.name] = handledList.indexOf(v) >= 0 ? null : v.$tracker.toRaw(true, handledList);
                                }
                            }
                        });
                    }
                }
                // process unmapped properties
                helper.forEach(type.properties, function (p) {
                    var v = that.getValue(p);
                    // if value is array enumerate each item
                    if (Assert.isArray(v)) {
                        // create new array property in return data.
                        data[p] = [];
                        helper.forEach(v, function (item) {
                            if (item == null || handledList.indexOf(v) >= 0) return;

                            if (!item.$tracker)
                                data[p].push(item);
                            else if (includeNavigations == true)
                                data[p].push(item.$tracker.toRaw(true, handledList));
                        });
                    } else {
                        if (v == null || !v.$tracker)
                            data[p] = v;
                        else if (includeNavigations == true || v.$tracker.entityType.isComplexType)
                            data[p] = handledList.indexOf(v) >= 0 ? null : v.$tracker.toRaw(includeNavigations, handledList);
                    }
                });
                return data;
            };

            /**
             * Starts tracking the entity, this is a static method.
             * @param {Object} result - The raw result.
             * @param {EntityType} result - The entity type.
             * @param {ObservableProvider} result - Observable provider instance.
             */
            ctor.toEntity = function (result, type, op) {
                // Crate entity tracker with this static method.
                return new core.EntityTracker(result, type, op).entity;
            };

            function initialize(entity, type, op, instance) {
                instance.entity = entity;
                entity.$tracker = instance;
                instance.entityType = type;
                instance.entityState = enums.entityStates.Detached;
                instance.observableProvider = op;
                instance.forceUpdate = false;
                instance.originalValues = [];
                instance.changedValues = [];
                instance.manager = null;
                instance.owners = [];
                instance.validationErrors = [];
                instance.validationErrorsChanged = new core.Event('validationErrorsChanged', instance);
                instance.entityStateChanged = new core.Event('entityStateChanged', instance);
                instance.propertyChanged = new core.Event('propertyChanged', instance);
                instance.arrayChanged = new core.Event('arrayChanged', instance);
                // get key's initial value.
                if (type.hasMetadata)
                    instance.key = getKey(instance);
            }

            function toObservable(entity, type, tracker) {
                // if we have only one callback, we need to write lots of if, to avoid this we send all callbacks seperately.
                var callbacks = {
                    propertyChange: propertyChange,
                    arrayChange: arrayChange,
                    dataPropertyChange: dataPropertyChange,
                    scalarNavigationPropertyChange: scalarNavigationPropertyChange,
                    pluralNavigationPropertyChange: pluralNavigationPropertyChange,
                    arraySet: arraySet
                };
                return tracker.observableProvider.toObservable(entity, type, callbacks);
            }

            function callIzer(type, entity) {
                if (type.baseType) callIzer(type.baseType, entity);
                for (var i = 0; i < type.initializers.length; i++) {
                    type.initializers[i].call(entity, entity);
                }
            }

            function getKey(tracker, p, v) {
                var type = tracker.entityType;
                if (type.keys.length == 0) return null;
                var retVal = [];
                // Get every key value and join them with ','.
                for (var i = 0; i < type.keys.length; i++) {
                    var key = type.keys[i];
                    var value;
                    // if property and value is given this means key is about to change so we need to check existing entities with new key.
                    if (key === p) value = v;
                    else
                        value = tracker.getValue(key.name);
                    if (key.dataType.name == 'guid' && value)
                        value = value.toLowerCase();
                    retVal.push(value);
                }
                return retVal.join(',');
            }

            function createLoadQuery(navProp, navPropName, resourceName, tracker) {
                if (!navProp)
                    throw helper.createError(i18N.propertyNotFound, [navPropName],
                        { propertyName: navPropName, entity: tracker.entity, manager: tracker.manager });
                // create query for entity type
                var query = navProp.entityType.createQuery(resourceName, tracker.manager);
                // if navigation is scalar use foreign key to filter via primary key
                if (navProp.isScalar) {
                    helper.forEach(navProp.foreignKeys, function (fk, i) {
                        var property = navProp.entityType.keys[i];
                        var value = tracker.getValue(fk.name);
                        query = query.where(property.name + " == @0", [value]);
                    });
                } else { // if navigation is plural use the entity's key to load related entities via foreign key
                    helper.forEach(navProp.foreignKeyNames, function (fk, i) {
                        var value = tracker.getValue(tracker.entityType.keys[i]);
                        query = query.where(fk + " == @0", [value]);
                    });
                }
                return query;
            }

            function propertyChange(entity, property, accessor, newValue) {
                var noCallbackExternal = false;
                if (Assert.isInstanceOf(newValue, core.ValueNotifyWrapper)) {
                    noCallbackExternal = !newValue.fromBeetle;
                    newValue = newValue.value;
                }

                var oldValue = accessor();
                if (oldValue === newValue) return;

                var tracker = entity.$tracker;
                var handleUnmappedProperties;
                if (tracker.manager) handleUnmappedProperties = tracker.manager.handleUnmappedProperties;
                if (handleUnmappedProperties == null) handleUnmappedProperties = settings.handleUnmappedProperties;
                if (handleUnmappedProperties === true) {
                    newValue = core.dataTypes.handle(newValue);
                    if (oldValue === newValue) return;
                }

                accessor(newValue);

                // mark this entity as modified.
                if (tracker.manager)
                    setModified(entity, property, oldValue, tracker);
                if (!noCallbackExternal)
                    tracker.propertyChanged.notify({ entity: entity, property: property, oldValue: oldValue, newValue: newValue });
            }

            function arrayChange(entity, property, items, removedItems, addedItems) {
                var tracker = entity.$tracker;
                if (tracker.manager)
                    setModified(entity, null, null, tracker);
                tracker.arrayChanged.notify({ entity: entity, property: property, items: items, removedItems: removedItems, addedItems: addedItems });
            }

            function dataPropertyChange(entity, property, accessor, newValue) {
                var noCallbackBeetle = false;
                var noCallbackExternal = false;
                if (Assert.isInstanceOf(newValue, core.ValueNotifyWrapper)) {
                    noCallbackBeetle = newValue.fromBeetle;
                    noCallbackExternal = !newValue.fromBeetle;
                    newValue = newValue.value;
                }

                var oldValue = accessor();
                if (oldValue === newValue) return;

                // check new value's type and convert if necessary.
                newValue = property.handle(newValue);

                if (oldValue === newValue) return;
                if (property.dataType === core.dataTypes.date || property.dataType === core.dataTypes.dateTimeOffset) {
                    if (oldValue != null && newValue != null && oldValue.valueOf() === newValue.valueOf())
                        return;
                }

                var tracker = entity.$tracker;
                var oldKey = null, newKey = null;
                if (property.isKeyPart) {
                    oldKey = tracker.key;
                    newKey = getKey(tracker, property, newValue);
                    // if this property is primary key check if this key already exists.
                    if (tracker.manager) {
                        var e = tracker.manager.getEntityByKey(newKey, tracker.entityType.floorType);
                        if (e && e !== entity)
                            throw helper.createError(i18N.sameKeyExists, { key: newKey, entity: e });
                    }
                    tracker.key = newKey;
                }

                var liveValidate = tracker.liveValidate;
                // mark this entity as modified.
                if (tracker.manager) {
                    setModified(entity, property.name, property.dataType.getRawValue(oldValue), tracker);
                    if (liveValidate == null) liveValidate = tracker.manager.liveValidate;
                }
                else if (tracker.entityType.isComplexType)
                    helper.forEach(tracker.owners, function (owner) {
                        setModified(owner.entity, owner.property.name + '.' + property.name, newValue, owner.entity.$tracker);
                    });

                // set new value
                accessor(newValue);

                if (liveValidate == null) liveValidate = settings.liveValidate;
                // validate data property.
                if (liveValidate === true)
                    mergeErrors(property.validate(entity), tracker, property);
                if (!noCallbackExternal)
                    tracker.propertyChanged.notify({ entity: entity, property: property, oldValue: oldValue, newValue: newValue });

                // if this property is primary key fix index tables.
                if (property.isKeyPart) {
                    if (tracker.manager) // After a pk changed, tell entity container to update its index tables.
                        tracker.manager.entities.relocateKey(entity, oldKey, newKey);
                    // Update navigations' foreign keys to match with new key.
                    updateForeignKeys(entity);
                }
                if (property.isComplex) {
                    var oldOwners = oldValue.$tracker.owners;
                    for (var i = oldOwners.length - 1; i >= 0; i--) {
                        var owner = oldOwners[i];
                        if (owner.entity == entity && owner.property == property)
                            oldOwners.splice(i, 1);
                    }
                    if (oldOwners.length == 0 && oldValue.$tracker.manager)
                        oldValue.$tracker.manager.detachEntity(oldValue);
                    newValue.$tracker.owners.push({ entity: entity, property: property });
                }
                if (tracker.manager) {
                    var autoFixScalar = tracker.manager.autoFixScalar;
                    if (autoFixScalar == null) autoFixScalar = settings.autoFixScalar;
                    // if this property is foreign key fix related navigation properties.
                    helper.forEach(property.relatedNavigationProperties, function (np) {
                        if (np.isScalar === true) {
                            var fk = tracker.foreignKey(np);
                            if (fk) {
                                // get old navigation property related to this foreign key.
                                var oldFkEntity = tracker.getValue(np.name);
                                // if old entity has same key there is nothing to do.
                                if (oldFkEntity && oldFkEntity.$tracker.key === fk) return;
                                // find new entity from cache.
                                var fkEntity = null;
                                if (autoFixScalar)
                                    fkEntity = tracker.manager.getEntityByKey(fk, np.entityType);

                                if (fkEntity)
                                    tracker.setValue(np.name, fkEntity); // if found set as new value.
                                else if (oldFkEntity)
                                    tracker.setValue(np.name, new core.ValueNotifyWrapper(null, true)); // if not found set navigation to null but preserve foreign key.
                            } else
                                tracker.setValue(np.name, null); // if foreign key is null set navigation to null.
                        }
                    });
                }
            }

            function scalarNavigationPropertyChange(entity, property, accessor, newValue) {
                var noCallbackBeetle = false;
                var noCallbackExternal = false;
                if (Assert.isInstanceOf(newValue, core.ValueNotifyWrapper)) {
                    noCallbackBeetle = newValue.fromBeetle;
                    noCallbackExternal = !newValue.fromBeetle;
                    newValue = newValue.value;
                }

                var oldValue = accessor();
                if (oldValue === newValue) return;

                // check if this navigation property can be set with newValue.
                property.checkAssign(newValue);

                accessor(newValue);

                // validate navigation property.
                var tracker = entity.$tracker;
                var liveValidate = tracker.liveValidate;
                if (liveValidate == null && tracker.manager)
                    liveValidate = tracker.manager.liveValidate;
                if (liveValidate == null) liveValidate = settings.liveValidate;
                if (liveValidate === true)
                    mergeErrors(property.validate(entity), tracker, property);
                if (!noCallbackExternal)
                    tracker.propertyChanged.notify({ entity: entity, property: property, oldValue: oldValue, newValue: newValue });

                // Check if newValue is in the manager, if not attach it.
                processEntity(newValue, tracker.manager);
                // arrange owners of this entity.
                if (property.isComplex) {
                    if (newValue == null)
                        throw helper.createError(i18N.complexCannotBeNull, [property.displayName], { entity: entity, property: property });
                    var owners = oldValue.$tracker.owners;
                    for (var i = owners.length - 1; i >= 0; i--) {
                        var owner = owners[i];
                        if (owner.entity == entity && owner.property == property)
                            owners.splice(i, 1);
                    }
                    if (oldValue.$tracker.owners.length == 0 && oldValue.$tracker.manager)
                        oldValue.$tracker.manager.detachEntity(oldValue);
                    newValue.$tracker.owners.push({ entity: entity, property: property });
                    setModified(entity, property.name, newValue.$tracker.toRaw(), tracker);
                } else {
                    // Set related foreign key properties (unless preserving fks wanted).
                    if (!noCallbackBeetle) {
                        if (property.triggerOwnerModify && property.foreignKeys.length == 0)
                            setModified(entity, null, null, tracker);
                        helper.setForeignKeys(entity, property, newValue);
                    }

                    var inverse = property.inverse;
                    if (inverse) {
                        if (inverse.isScalar) {
                            // If other side of the navigation is also scalar (1-1) set the value.
                            if (oldValue) oldValue.$tracker.setValue(inverse.name, null);
                            if (newValue) newValue.$tracker.setValue(inverse.name, entity);
                        } else {
                            // If there were an old value remove it from other side's array.
                            if (oldValue) oldValue.$tracker.getValue(inverse.name).remove(entity);
                            // Add new value to array
                            if (newValue) {
                                var array = newValue.$tracker.getValue(inverse.name);
                                if (!helper.findInArray(array, entity))
                                    array.push(entity);
                            }
                        }
                    }
                }
            }

            function pluralNavigationPropertyChange(entity, property, items, removedItems, addedItems) {
                var tracker = entity.$tracker;
                // validate navigation property.
                var liveValidate = tracker.liveValidate;
                if (liveValidate == null && tracker.manager)
                    liveValidate = tracker.manager.liveValidate;
                if (liveValidate == null) liveValidate = settings.liveValidate;
                if (liveValidate === true)
                    mergeErrors(property.validate(entity), tracker, property);
                if (property.triggerOwnerModify)
                    setModified(entity, null, null, tracker);
                tracker.arrayChanged.notify({ entity: entity, property: property, items: items, removedItems: removedItems, addedItems: addedItems });

                var inverse = property.inverse;
                if (inverse) {
                    // Set removed items's scalar navigation properties to null.
                    if (removedItems)
                        helper.forEach(removedItems, function (removedEntity) {
                            if (removedEntity.$tracker.getValue(inverse.name) == entity)
                                removedEntity.$tracker.setValue(inverse.name, null);
                        });
                    // Set added items's scalar properties to this entity.
                    if (addedItems)
                        helper.forEach(addedItems, function (newEntity) {
                            processEntity(newEntity, tracker.manager);
                            newEntity.$tracker.setValue(inverse.name, entity);
                        });
                }
                else {
                    // Set removed items's foreign key to null.
                    if (removedItems)
                        helper.forEach(removedItems, function (removedEntity) {
                            helper.setForeignKeys(removedEntity, property, null);
                        });
                    // Set added items's foreign key.
                    if (addedItems)
                        helper.forEach(addedItems, function (newEntity) {
                            processEntity(newEntity, tracker.manager);
                            helper.setForeignKeys(newEntity, property, entity);
                        });
                }
            }

            function arraySet(entity, property, items, newItems) {
                if (items === newItems) return;

                var behaviour = settings.getArraySetBehaviour();
                if (behaviour == enums.arraySetBehaviour.NotAllowed)
                    throw helper.createError(i18N.settingArrayNotAllowed,
                        { entity: entity, property: property });

                var toRemove = [];
                var toAdd = helper.filterArray(newItems, function () { return true; });
                if (behaviour == enums.arraySetBehaviour.Replace) {
                    helper.forEach(items, function (item) {
                        if (helper.findInArray(toAdd, item))
                            helper.removeFromArray(toAdd, item);
                        else
                            toRemove.push(item);
                    });
                }
                else if (behaviour == enums.arraySetBehaviour.Append) {
                    helper.forEach(newItems, function (item) {
                        if (helper.findInArray(items, item))
                            helper.removeFromArray(toAdd, item);
                    });
                }
                if (toRemove.length > 0)
                    for (var i = toRemove.length - 1; i >= 0; i--)
                        items.splice(helper.indexOf(items, toRemove[i]), 1);
                if (toAdd.length > 0) items.push.apply(items, toAdd);
            }

            function updateForeignKeys(entity) {
                var tracker = entity.$tracker;
                // For each navigation property.
                helper.forEach(tracker.entityType.navigationProperties, function (np) {
                    // If property has inverse navigation defined.
                    if (np.isScalar) {
                        var inverse = np.inverse;
                        if (inverse && inverse.isScalar) {
                            var value = tracker.getValue(np.name);
                            if (value)
                                helper.setForeignKeys(value, inverse, entity);
                        }
                    } else {
                        // Get the current items.
                        var array = tracker.getValue(np.name);
                        if (array && array.length > 0) {
                            // Set foreign keys to new value.
                            helper.forEach(array, function (item) {
                                helper.setForeignKeys(item, np, entity);
                            });
                        }
                    }
                });
            }

            function processEntity(entity, manager) {
                if (!entity || !manager) return;
                if (entity.$tracker.entityType.isComplexType || manager.isInManager(entity)) return;
                if (entity.$tracker.manager == manager) return;
                if (entity.$tracker.manager)
                    throw helper.createError(i18N.entityAlreadyBeingTracked, null, { entity: entity, manager: manager });
                // add to the manager.
                manager.addEntity(entity);
            }

            function setModified(entity, property, value, tracker) {
                var state = tracker.entityState;
                // mark this entity as modified.
                if (state == enums.entityStates.Unchanged) {
                    helper.forEach(tracker.entityType.navigationProperties, function (np) {
                        var inverse = np.inverse;
                        if (inverse && inverse.triggerOwnerModify) {
                            var val = tracker.getValue(np.name);
                            if (val) {
                                if (np.isScalar)
                                    setModified(val, null, null, val.$tracker);
                                else
                                    helper.forEach(val, function (v) {
                                        setModified(v, null, null, v.$tracker);
                                    });
                            }
                        }
                    });
                    tracker.toModified();
                }
                // set original value
                setOriginalValue(property, value, tracker.originalValues, tracker.changedValues);
            }

            function setOriginalValue(property, value, originalValues, changedValues) {
                // Create new original value if property is changing for first time.
                if (property == null) return;
                if (value != null && value.$tracker != null)
                    if (!value.$tracker.entityType.isComplexType) return;

                var oi = helper.findInArray(originalValues, property, 'p');
                if (!oi)
                    originalValues.push({ p: property, v: value });
                var ci = helper.findInArray(changedValues, property, 'p');
                if (!ci)
                    changedValues.push({ p: property, v: value });
            }

            function mergeErrors(newErrors, instance, property) {
                var removed = [];
                // copy existing errors to a new array.
                var oldErrors = property
                    ? helper.filterArray(instance.validationErrors, function (ve) { return ve.property === property; })
                    : helper.filterArray(instance.validationErrors, function () { return true; });
                helper.forEach(oldErrors, function (ve) {
                    // if error already exists remove it from result list.
                    if (helper.findInArray(newErrors, ve.validator, 'validator'))
                        helper.removeFromArray(newErrors, ve.validator, 'validator');
                    else {
                        helper.removeFromArray(instance.validationErrors, ve);
                        removed.push(ve);
                    }
                });
                instance.validationErrors = instance.validationErrors.concat(newErrors);
                if (removed.length > 0 || newErrors.length > 0)
                    instance.validationErrorsChanged.notify({ errors: instance.validationErrors, added: newErrors, removed: removed });
            }

            function checkManager(instance) {
                if (instance.manager == null)
                    throw helper.createError(i18N.entityNotBeingTracked, { entity: entity, manager: instance });
            }

            return ctor;
        })(),
        /**
         * Entity manager class. All operations must be made using manager to be properly tracked.
         * @class
         */
        EntityManager: (function () {
            /**
             * @constructor
             * @param {string|DataService} service - Service url (default service from settings will be used) or service instance.
             * @param {MetadataManager|string|boolean} metadataPrm - [Metadata Manager] or [Metadata string] or [loadMetadata: when false no metadata will be used]
             * @param {Object} injections - Injection object to change behaviour of the manager.
             */
            var ctor = function (service, metadataPrm, injections) {
                initialize(arguments, this);
            };
            var proto = ctor.prototype;

            proto.toString = function () {
                return this.dataService.toString() + ', ' +
                    i18N.pendingChanges + ': ' + this.pendingChangeCount + ', ' +
                    i18N.validationErrors + ': ' + this.validationErrors.length;
            };

            /** Checks if manager is ready. */
            proto.isReady = function () {
                return this.dataService.isReady();
            };

            /** Subscribe ready callback, returns promise when available. */
            proto.ready = function (callback) {
                this._readyCallbacks.push(callback);

                var pp = this.promiseProvider;
                var p = null;
                if (pp) {
                    var d = pp.deferred();
                    this._readyPromises.push(d);
                    p = pp.getPromise(d);
                }

                var that = this;
                setTimeout(function () {
                    checkReady(that);
                }, 10);
                return p;
            };

            /**
             * Gets entity type by its short name from data service.
             * @param {string} shortName - Entity type short name.
             * @returns {EntityType} Entity type object if found, otherwise null.
             */
            proto.getEntityType = function (shortName) {
                return this.dataService.getEntityType(shortName);
            };

            /**
             * Creates a query for a resource. Every data service can have their own query types.
             * @param {string} resourceName - Server resource name to combine with base uri.
             * @param {string=} shortName - Entity type's short name.
             * @returns {EntityQuery} Entity query. Can be build with method-chaining.
             */
            proto.createQuery = function (resourceName, shortName) {
                return this.dataService.createQuery(resourceName, shortName, this);
            };

            /**
             * Creates a query for a resource. Every data service can have their own query types.
             * @param {string} shortName - Entity type's short name.
             * @param {string=} resourceName - Server resource name to combine with base uri.
             * @returns {EntityQuery} Entity query. Can be build with method-chaining.
             */
            proto.createEntityQuery = function (shortName, resourceName) {
                return this.dataService.createEntityQuery(shortName, resourceName, this);
            };

            /**
             * Register constructor and initializer (optional) for given type.
             * @param {string} shortName - Short name of the type.
             * @param {Function} constructor - Constructor function. Called right after the entity object is generated.
             * @param {Function} initializer - Initializer function. Called after entity started to being tracked (properties converted to observable).
             */
            proto.registerCtor = function (shortName, constructor, initializer) {
                this.dataService.registerCtor(shortName, constructor, initializer);
            };

            /**
             * Creates an entity based on metadata information.
             * @param {string} shortName - Short name of the type.
             * @param {Object} initialValues - Entity initial values.
             * @returns {Entity} Entity with observable properties.
             */
            proto.createEntity = function (shortName, initialValues) {
                var result = this.dataService.createEntity(shortName, initialValues);
                var results = [result];
                mergeEntities(results, null, enums.mergeStrategy.ThrowError, enums.entityStates.Added, this);
                return result;
            };

            /**
             * Creates an entity based on metadata information but doesn't attach to manager.
             * @param {string} shortName - Short name of the type.
             * @param {Object} initialValues - Entity initial values.
             * @returns {Entity} Entity with observable properties.
             */
            proto.createDetachedEntity = function (shortName, initialValues) {
                return this.dataService.createEntity(shortName, initialValues);
            };

            /**
             * Creates a raw entity based on metadata information.
             * @param {string} shortName - Short name of the type.
             * @param {Object} initialValues - Entity initial values.
             * @returns {Entity} Entity without observable properties.
             */
            proto.createRawEntity = function (shortName, initialValues) {
                return this.dataService.createRawEntity(shortName, initialValues);
            };

            /**
             * Request server to create entity.
             * @param {string} shortName - Short name of the type.
             * @param {Object} initialValues - Entity initial values.
             * @param {successCallback=} successCallback - Success callback function.
             * @param {errorCallback=} errorCallback - Error callback function.
             * @returns {Promise} Returns promise if possible.
             */
            proto.createEntityAsync = function (typeName, initialValues, options, successCallback, errorCallback) {
                return createAsync(typeName, initialValues, options, successCallback, errorCallback, this);
            };

            /**
             * Request server to create entity. Entity won't be attached to manager.
             * @param {string} shortName - Short name of the type.
             * @param {Object} initialValues - Entity initial values.
             * @param {successCallback=} successCallback - Success callback function.
             * @param {errorCallback=} errorCallback - Error callback function.
             * @returns {Promise} Returns promise if possible.
             */
            proto.createDetachedEntityAsync = function (typeName, initialValues, options, successCallback, errorCallback) {
                if (!options) options = { state: enums.entityStates.Detached };
                else options.state = enums.entityStates.Detached;
                return createAsync(typeName, initialValues, options, successCallback, errorCallback, this);
            };

            /**
             * Request server to create entity. Entity won't be attached to manager and won't be made observable.
             * @param {string} shortName - Short name of the type.
             * @param {Object} initialValues - Entity initial values.
             * @param {successCallback=} successCallback - Success callback function.
             * @param {errorCallback=} errorCallback - Error callback function.
             * @returns {Promise} Returns promise if possible.
             */
            proto.createRawEntityAsync = function (typeName, initialValues, options, successCallback, errorCallback) {
                if (!options) options = { makeObservable: false };
                else options.makeObservable = false;
                return createAsync(typeName, initialValues, options, successCallback, errorCallback, this);
            };

            function createAsync(typeName, initialValues, options, successCallback, errorCallback, instance) {
                // Create promise if possible.
                options = options || {};
                var async = options.async;
                if (async == null) async = instance.workAsync;
                if (async == null) async = settings.workAsync;
                options.async = async;
                var pp = !async ? null : instance.promiseProvider;
                var d = null;
                if (pp) d = pp.deferred();

                var makeObservable = options.makeObservable;
                if (makeObservable == null) makeObservable = true;
                var detached = options.state == enums.entityStates.Detached;
                var retVal = instance.dataService.createEntityAsync(
                    typeName, initialValues, options,
                    function (entity, allEntities, headerGetter, xhr) {
                        try {
                            var isSingle = false;
                            if (!Assert.isArray(entity)) {
                                entity = [entity];
                                isSingle = true;
                            }
                            if (makeObservable && !detached) // Merge incoming entities.
                                mergeEntities(entity, allEntities, enums.mergeStrategy.ThrowError, enums.entityStates.Added, instance);
                            // If only one entity returned (most likely) return it, otherwise return the array.
                            if (isSingle)
                                entity = entity[0];

                            var extra = {};
                            extra.userData = headerGetter("X-UserData");
                            var extraNeeded = extra.userData != null;
                            if (options.includeHeaderGetter === true) {
                                extra.headerGetter = headerGetter;
                                extraNeeded = true;
                            }
                            if (options.includeXhr === true) {
                                extra.xhr = xhr;
                                extraNeeded = true;
                            }

                            if (extraNeeded)
                                entity.$extra = extra;

                            onSuccess(successCallback, pp, d, entity);
                        } catch (e) {
                            onError(errorCallback, pp, d, entity, instance);
                        }
                    },
                    function (error) {
                        error.typeName = typeName;
                        onError(errorCallback, pp, d, error, instance);
                    }
                );

                if (pp) return pp.getPromise(d);
                return retVal;
            }

            /**
             * Executes given query.
             * @param {EntityQuery} query
             * @param {QueryOptions} options - Query options.
             * @param {Function} successCallback - Function to call after operation succeeded.
             * @param {Function} errorCallback - Function to call when operation fails.
             * @returns {Promise} Returns promise if possible.
             */
            proto.executeQuery = function (query, options, successCallback, errorCallback) {
                if (query.options)
                    options = helper.combine(query.options, options);
                var modifiedArgs = notifyExecuting(this, query, options);
                query = modifiedArgs.query;
                options = modifiedArgs.options;
                options = options || {};

                // Create promise if possible.
                var async = options.async;
                if (async == null) async = this.workAsync;
                if (async == null) async = settings.workAsync;
                var pp = !async ? null : this.promiseProvider;
                var d = null;
                if (pp) d = pp.deferred();

                // get execute options from parameters.
                var merge = enums.mergeStrategy.Preserve, execution = enums.executionStrategy.Server, locals = null;
                if (Assert.isEnum(options, enums.mergeStrategy)) {
                    merge = options;
                    options = { makeObservable: merge != enums.mergeStrategy.NoTrackingRaw };
                } else if (Assert.isEnum(options, enums.executionStrategy))
                    execution = options;
                else {
                    if (options.merge) merge = options.merge;
                    if (options.execution) execution = options.execution;
                    options.async = async;
                    options.makeObservable = merge != enums.mergeStrategy.NoTrackingRaw;
                    if (options.handleUnmappedProperties == null) options.handleUnmappedProperties = this.handleUnmappedProperties;
                }

                var noTracking = merge == enums.mergeStrategy.NoTracking || merge == enums.mergeStrategy.NoTrackingRaw;
                if (noTracking && execution == enums.executionStrategy.Both)
                    throw helper.createError(i18N.executionBothNotAllowedForNoTracking,
                        { executionStrategy: execution, mergeStrategy: merge });

                // execute locally if needed.
                if (execution == enums.executionStrategy.Local || execution == enums.executionStrategy.LocalIfEmptyServer)
                    locals = this.executeQueryLocally(query, options.varContext);

                var retVal = null;
                // if there is no need for server query, return
                if (execution == enums.executionStrategy.Local || (execution == enums.executionStrategy.LocalIfEmptyServer && locals && (locals.length == null || locals.length > 0))) {
                    locals = notifyExecuted(this, query, options, locals);
                    onSuccess(successCallback, pp, d, locals);
                } else {
                    var that = this;
                    retVal = this.dataService.executeQuery(
                        query, options,
                        function (newEntities, allEntities, headerGetter, xhr) {
                            try {
                                // merge results.
                                var isSingle = false;
                                // read inline count header.
                                var inlineCount = headerGetter("X-InlineCount");
                                if (inlineCount != null) inlineCount = Number(inlineCount);

                                if (newEntities) {
                                    if (!noTracking) {
                                        // we convert result to array to get modified result (replaced with cached by manager when necessary).
                                        if (!Assert.isArray(newEntities)) {
                                            newEntities = [newEntities];
                                            isSingle = true;
                                        }
                                        mergeEntities(newEntities, allEntities, merge, enums.entityStates.Unchanged, that, options.autoFixScalar, options.autoFixPlural);
                                        if (isSingle)
                                            newEntities = newEntities[0];
                                    }
                                }
                                // if option need local and server results both, after server query re-run same query on local.
                                if (execution == enums.executionStrategy.Both) {
                                    newEntities = that.executeQueryLocally(query, options.varContext, true);
                                    if (newEntities.$inlineCountDiff != null) {
                                        if (inlineCount != null)
                                            inlineCount += newEntities.$inlineCountDiff;
                                        delete newEntities.$inlineCountDiff;
                                    }
                                }
                                if (newEntities && Assert.isObject(newEntities)) {
                                    if (query.inlineCountEnabled && inlineCount != null)
                                        newEntities.$inlineCount = inlineCount;

                                    var extra = {};
                                    extra.userData = headerGetter("X-UserData");
                                    var extraNeeded = extra.userData != null;
                                    if (options.includeHeaderGetter === true) {
                                        extra.headerGetter = headerGetter;
                                        extraNeeded = true;
                                    }
                                    if (options.includeXhr === true) {
                                        extra.xhr = xhr;
                                        extraNeeded = true;
                                    }

                                    if (extraNeeded)
                                        newEntities.$extra = extra;
                                }
                                newEntities = notifyExecuted(that, query, options, newEntities);
                                onSuccess(successCallback, pp, d, newEntities);
                            } catch (e) {
                                onError(errorCallback, pp, d, e, that);
                            }
                        },
                        function (error) {
                            error.query = query;
                            onError(errorCallback, pp, d, error, that);
                        }
                    );
                }

                if (pp) return pp.getPromise(d);
                return retVal;
            };

            /**
             * Executes given query against local cache.
             * @param {EntityQuery} query
             * @param {Object|any[]} varContext - Variable context for the query.
             * @param {boolean} calculateInlineCountDiff - When true, effect of the local entities to server entities will be calculated.
             */
            proto.executeQueryLocally = function (query, varContext, calculateInlineCountDiff) {
                // get entity type of the query
                var et = query.entityType;
                var entities;
                if (et) {
                    // find entity set for the entity type.
                    var entitySet = this.entities.findEntitySet(et);
                    // get entities from entity set (container)
                    if (entitySet)
                        entities = entitySet.getEntities();
                    else return [];
                } else
                    throw helper.createError(i18N.typeRequiredForLocalQueries);

                if (calculateInlineCountDiff && query.getExpression(querying.expressions.GroupByExp, false)) {
                    events.warning.notify({ message: i18N.countDiffCantBeCalculatedForGrouped, query: query, options: varContext });
                    calculateInlineCountDiff = false;
                }

                var array = [], addeds = [], deleteds = [];
                helper.forEach(entities, function (entity) {
                    if (entity.$tracker.entityState == enums.entityStates.Added) {
                        if (calculateInlineCountDiff)
                            addeds.push(entity);
                        array.push(entity);
                    }
                    else if (entity.$tracker.entityState == enums.entityStates.Deleted) {
                        if (calculateInlineCountDiff)
                            deleteds.push(entity);
                    }
                    else
                        array.push(entity);
                });
                // get array handling function for query
                var func = query.toFunction();
                // run function against entities
                array = func(array, varContext);
                if (calculateInlineCountDiff && array.$inlineCount != null) {
                    var addedEffect = addeds.length > 0 ? func(addeds, varContext).$inlineCount : 0;
                    var deletedEffect = deleteds.length > 0 ? func(deleteds, varContext).$inlineCount : 0;
                    array.$inlineCountDiff = addedEffect - deletedEffect;
                }
                return array;
            };

            /**
             * Finds entity with given key by searching entity type's entity set.
             * @param {string} key - Entity key as a string. When entity has more than one key, the key is keys joined with a ','.
             * @param {EntityType} type - Entity type or type short name.
             */
            proto.getEntityByKey = function (key, type) {
                var t = Assert.isInstanceOf(type, metadata.EntityType) ? type : this.getEntityType(type, true);
                return this.entities.getEntityByKey(key, t);
            };

            /** Marks entity as deleted and clear all navigations. */
            proto.deleteEntity = function (entity) {
                // check if given entity is being tracked by this manager.
                checkEntity(entity, this);
                // do cascade deletes.
                var that = this;
                var toDelete = [];
                helper.forEach(entity.$tracker.entityType.navigationProperties, function (np) {
                    if (np.cascadeDelete) {
                        if (np.inverse && np.inverse.cascadeDelete)
                            throw helper.createError(i18N.twoEndCascadeDeleteNotAllowed, { entity: entity, property: np });

                        var value = entity.$tracker.getValue(np.name);
                        if (value) {
                            if (np.isScalar) {
                                if (np.foreignKeys != null && np.foreignKeys.length > 0)
                                    toDelete.push(value);
                                else
                                    that.deleteEntity(value);
                            }
                            else {
                                var items = helper.filterArray(value, function () { return true; });
                                helper.forEach(items, function (item) {
                                    that.deleteEntity(item);
                                });
                            }
                        }
                    }
                });
                // If entity is in Added state, we can remove it from manager.
                if (entity.$tracker.entityState === enums.entityStates.Added)
                    // Clear navigations.
                    this.detachEntity(entity);
                else {
                    // Clear navigations.
                    clearNavigations(entity, true);
                    // Mark entity as deleted.
                    entity.$tracker.toDeleted();
                }
                for (var i = 0; i < toDelete.length; i++) {
                    that.deleteEntity(toDelete[i]);
                }
            };

            /**
             * Adds given entity to manager's entity container. State will be 'Added'.
             * @param {Entity} detachedEntity - The detached entity.
             * @param {MergeOptions} options - Entity merge options.
             */
            proto.addEntity = function (detachedEntity, options) {
                mergeEntity(detachedEntity, options, enums.entityStates.Added, this);
            };

            /**
             * Attaches given entity to manager's entity container. State will be 'Unchanged'.
             * @param {Entity} detachedEntity - The detached entity.
             * @param {MergeOptions} options - Entity merge options.
             */
            proto.attachEntity = function (detachedEntity, options) {
                mergeEntity(detachedEntity, options, enums.entityStates.Unchanged, this);
            };

            function mergeEntity(detachedEntity, options, defaultState, instance) {
                // get merge options
                var merge = enums.mergeStrategy.ThrowError, state = defaultState;
                if (options) {
                    if (options.merge != null) merge = options.merge;
                    if (options.state != null) state = options.state;
                }
                else options = {};
                // Merge entities to cache.
                if (!Assert.isArray(detachedEntity)) detachedEntity = [detachedEntity];
                mergeEntities(detachedEntity, null, merge, state, instance, options.autoFixScalar, options.autoFixPlural);
            }

            /** Detaches entity from manager and stops tracking. */
            proto.detachEntity = function (entity, includeRelations) {
                var manager = this;
                if (!Assert.isArray(entity)) entity = [entity];
                var detachList = includeRelations === true ? this.flatEntities(entity) : entity;

                helper.forEach(detachList, function (toDetach) {
                    if (toDetach.$tracker.entityType.isComplexType && toDetach.$tracker.owners.length > 0)
                        throw helper.createError(i18N.cannotDetachComplexTypeWithOwners);

                    // check if given entity is being tracked by this manager.
                    checkEntity(toDetach, manager);
                    // Clear navigations.
                    clearNavigations(toDetach, true);
                    // remove subscriptions for this entity.
                    unsubscribeFromEntity(toDetach, manager);
                    // Set entity state to detached.
                    toDetach.$tracker.toDetached();
                    toDetach.$tracker.manager = null;
                    // Remove from cache.
                    manager.entities.remove(toDetach);
                });
            };

            /**
             * Reject all changes made to this entity to initial values and detach from context if its newly added.
             * @param {Entity} entity - The entity.
             * @param {boolean} includeRelations - If set to true, rejectChanges will be called for all navigation properties too.
             */
            proto.rejectChanges = function (entity, includeRelations) {
                var manager = this;
                if (!Assert.isArray(entity)) entity = [entity];
                var rejectList = includeRelations === true ? this.flatEntities(entity) : entity;
                helper.forEach(rejectList, function (toReject) {
                    var tracker = toReject.$tracker;
                    // if entity is in Added state, detach it
                    if (tracker.entityState == enums.entityStates.Added)
                        manager.detachEntity(toReject);
                    else if (tracker.entityState != enums.entityStates.Detached) {
                        tracker.rejectChanges();
                        tracker.toUnchanged();
                    }
                });
            };

            /**
             * Resets all changes to last accepted values.
             * @param {Entity} entity - The entity.
             * @param {boolean} includeRelations - If set to true, undoChanges will be called for all navigation properties too.
             */
            proto.undoChanges = function (entity, includeRelations) {
                if (!Assert.isArray(entity)) entity = [entity];
                var undoList = includeRelations === true ? this.flatEntities(entity) : entity;
                helper.forEach(undoList, function (toUndo) {
                    toUndo.$tracker.undoChanges();
                });
            };

            /**
             * Accept all changes made to this entity (clear changed values).
             * @param {Entity} entity - The entity.
             * @param {boolean} includeRelations - If set to true, acceptChanges will be called for all navigation properties too.
             */
            proto.acceptChanges = function (entity, includeRelations) {
                if (!Assert.isArray(entity)) entity = [entity];
                var acceptList = includeRelations === true ? this.flatEntities(entity) : entity;
                helper.forEach(acceptList, function (toAccept) {
                    toAccept.$tracker.acceptChanges();
                });
            };

            /**
             * Creates save package with entity raw values and user data.
             * @param {Entity[]=} entities - Save options.
             * @param {SaveOptions=} options - Save options.
             */
            proto.createSavePackage = function (entities, options) {
                entities = entities || this.getChanges();
                options = options || {};

                var validationErrors = [];
                var validateOnSave = options.validateOnSave;
                if (validateOnSave == null) validateOnSave = this.validateOnSave;
                if (validateOnSave == null) validateOnSave = settings.validateOnSave;
                if (validateOnSave === true) {
                    helper.forEach(entities, function (entity) {
                        if (entity.$tracker.entityState != enums.entityStates.Deleted) {
                            var result = entity.$tracker.validate();
                            if (result && result.length > 0)
                                validationErrors.push({ entity: entity, validationErrors: result });
                        }
                    });
                }
                if (validationErrors.length > 0)
                    throw helper.createError(i18N.validationFailed, { entities: entities, entitiesInError: validationErrors });

                var userData = options.userData || null;
                var forceUpdate = options.forceUpdate;
                if (forceUpdate == null) forceUpdate = this.forceUpdate;
                if (forceUpdate == null) forceUpdate = settings.forceUpdate;
                var data = { userData: userData, forceUpdate: forceUpdate };

                var entityList = this.exportEntities(entities, options);
                data.entities = entityList;
                return data;
            };

            /**
             * Exports entities from manager to raw list.
             * @param {Entity[]=} entities - Entities to export.
             * @param {ExportOptions=} options - Export options.
             */
            proto.exportEntities = function (entities, options) {
                options = options || {};
                var entityList = [];
                entities = entities || this.entities.getEntities();
                var minimizePackage = options.minimizePackage;
                if (minimizePackage == null) minimizePackage = this.minimizePackage;
                if (minimizePackage == null) minimizePackage = settings.minimizePackage;

                helper.forEach(entities, function (entity, id) {
                    // get entity information.
                    var tracker = entity.$tracker;
                    var type = tracker.entityType;
                    var state = tracker.entityState;
                    var fu = tracker.forceUpdate;
                    var originalValues = {};
                    var e;

                    if (minimizePackage === true) {
                        if (state == enums.entityStates.Modified) {
                            if (tracker.originalValues.length > 0) {
                                e = {};
                                // generate object with minimum properties
                                helper.forEach(tracker.originalValues, function (ov) {
                                    var lastProperty;
                                    var propertyPaths = ov.p.split('.');
                                    if (propertyPaths.length > 1) {
                                        for (var i = 0; i < propertyPaths.length - 1; i++) {
                                            var p = propertyPaths[i];
                                            entity = entity.$tracker.getValue(p);
                                            if (e[p] == null)
                                                e[p] = {};
                                            e = e[p];
                                        }
                                        lastProperty = propertyPaths[propertyPaths.length - 1];
                                    } else lastProperty = ov.p;

                                    var v = tracker.getValue(lastProperty);
                                    if (v && v.$tracker)
                                        v = v.$tracker.toRaw();
                                    e[lastProperty] = v;
                                });
                                // get key values
                                helper.forEach(tracker.entityType.dataProperties, function (dp) {
                                    if (dp.isKeyPart === true || dp.useForConcurrency === true)
                                        e[dp.name] = tracker.getValue(dp.name);
                                });
                            } else if (fu === true)
                                e = tracker.toRaw(entity);
                            else return;
                        } else if (state == enums.entityStates.Deleted) {
                            e = {};
                            // get key values
                            helper.forEach(tracker.entityType.dataProperties, function (dp) {
                                if (dp.isKeyPart === true || dp.useForConcurrency === true)
                                    e[dp.name] = tracker.getValue(dp.name);
                            });
                        } else if (state == enums.entityStates.Added)
                            e = tracker.toRaw(entity);
                        else
                            return;
                    } else
                        e = tracker.toRaw(entity);

                    // populate originalValues for modified entities.
                    if (state == enums.entityStates.Modified)
                        helper.forEach(tracker.originalValues, function (ov) {
                            var v = ov.v;
                            if (v && v.$tracker)
                                v = v.$tracker.toRaw();
                            originalValues[ov.p] = v;
                        });

                    // create an object with type, state and original values.
                    var t = {
                        t: type.name,
                        s: state.toString(),
                        o: originalValues,
                        i: id,
                        f: fu
                    };

                    // set entity index in array to use after save.
                    e.$t = t;
                    entityList.push(e);
                });
                return entityList;
            };

            /**
             * Imports exported entities and starts tracking them.
             * @param {Entity[]} exportedEntities - Previously exported entities to import.
             * @param {MergeStrategy} merge - Merge strategy to apply when importing.
             */
            proto.importEntities = function (exportedEntities, merge) {
                var that = this;
                if (!Assert.isArray(exportedEntities)) exportedEntities = [exportedEntities];
                helper.forEach(exportedEntities, function (exportedEntity) {
                    var tracker = exportedEntity.$t;
                    var state;
                    switch (tracker.s) {
                        case 'Unchanged':
                            state = enums.entityStates.Unchanged;
                            break;
                        case 'Added':
                            state = enums.entityStates.Added;
                            break;
                        case 'Deleted':
                            state = enums.entityStates.Deleted;
                            break;
                        case 'Modified':
                            state = enums.entityStates.Modified;
                            break;
                        default:
                            throw helper.createError(i18N.unsoppertedState, [tracker.s], { entity: exportedEntity });
                    }
                    var entity = that.toEntity(exportedEntity, tracker.t);
                    mergeEntities(entity, [entity], merge || enums.mergeStrategy.Preserve, state, that, true, true);
                    if (tracker.o)
                        for (var p in tracker.o) {
                            var v = tracker.o[p];
                            entity.$tracker.originalValues.push({ p: p, v: v });
                        }
                    entity.$tracker.forceUpdate = tracker.forceUpdate;

                    delete entity.$t;
                });
            };

            /** Check if there is any pending changes. */
            proto.hasChanges = function () {
                return this.pendingChangeCount > 0;
            };

            /**
             * Gets changes made in this manager's cache.
             * @returns {Entity[]}
             */
            proto.getChanges = function () {
                return this.entities.getChanges();
            };

            /**
             * Saves all changes made in this manager to server via Data Service instance.
             * @param {SaveOptions} options - Options to modify saving behaviour.
             * @param {successCallback} successCallback - Function to call after operation succeeded.
             * @param {errorCallback} errorCallback - Function to call when operation fails.
             * @returns {Promise} Returns promise if supported.
             */
            proto.saveChanges = function (options, successCallback, errorCallback) {
                return this.savePackage(null, options, successCallback, errorCallback)
            }

            /**
             * Saves all changes made in this manager to server via Data Service instance.
             * @param {SavePackage} savePackage - Save package. When provided, this package will be used (no package will be created).
             * @param {successCallback} successCallback - Function to call after operation succeeded.
             * @param {errorCallback} errorCallback - Function to call when operation fails.
             * @returns {Promise} Returns promise if supported.
             */
            proto.savePackage = function (savePackage, options, successCallback, errorCallback) {
                options = options || {};

                // Create promise if possible.
                var async = options.async;
                if (async == null) async = this.workAsync;
                if (async == null) async = settings.workAsync;
                options.async = async;
                var pp = !async ? null : this.promiseProvider;
                var d = null;
                if (pp) d = pp.deferred();

                var changes;
                if (!savePackage) {
                    if (options.entities) {
                        changes = options.entities
                        if (!Assert.isArray(changes)) changes = [changes];
                    }
                    else changes = this.getChanges();

                    if (!changes.length) {
                        onSuccess(successCallback, pp, d, { AffectedCount: 0, GeneratedValues: [] });
                    }
                    else {
                        try {
                            savePackage = this.createSavePackage(changes, options);
                        }
                        catch (error) {
                            onError(errorCallback, pp, d, error, this);
                        }
                    }
                }

                var retVal = null;
                if (savePackage) {
                    options = notifySaving(this, changes, savePackage, options);

                    try {
                        var that = this;
                        retVal = this.dataService.saveChanges(
                            savePackage,
                            options,
                            function (result, headerGetter, xhr) {
                                try {
                                    // merge generated entities
                                    if (result.GeneratedEntities != null && result.GeneratedEntities.length > 0)
                                        mergeEntities(result.GeneratedEntities, null, enums.mergeStrategy.Preserve, enums.entityStates.Unchanged, that, options.autoFixScalar, options.autoFixPlural);

                                    // set returned generated value to existing entity.
                                    if (result.GeneratedValues && changes) {
                                        helper.forEach(result.GeneratedValues, function (g) {
                                            if (g.Index < 0) return;

                                            var entity = changes[g.Index];
                                            var lastProperty;
                                            var propertyPaths = g.Property.split('.');
                                            if (propertyPaths.length > 1) {
                                                for (var i = 0; i < propertyPaths.length - 1; i++)
                                                    entity = entity.$tracker.getValue(propertyPaths[i]);
                                                lastProperty = propertyPaths[propertyPaths.length - 1];
                                            } else lastProperty = g.Property;

                                            var tracker = entity.$tracker;
                                            var value = g.Value;
                                            if (value != null) {
                                                // do not set objects
                                                if (value.$type) return;
                                                // skip if array contains object or array value
                                                if (Assert.isArray(value) && value.q().any(function (v) { v.$type || Assert.isArray(v); })) return;
                                            }
                                            tracker.setValue(lastProperty, value);
                                        });
                                    }

                                    // Accept all changes, means Added -> Unchanged; Modified -> Unchanged, Clear Original Values; Deleted -> Remove from cache.
                                    if (changes) {
                                        acceptSaves(changes, that.entities, that);
                                    }

                                    result.userData = headerGetter("X-UserData");
                                    if (options.includeHeaderGetter === true)
                                        result.headerGetter = headerGetter;
                                    if (options.includeXhr === true)
                                        result.xhr = xhr;

                                    notifySaved(that, changes, savePackage, options);
                                    onSuccess(successCallback, pp, d, result);
                                }
                                catch (error) {
                                    error.changes = changes;
                                    error.savePackage = savePackage;
                                    onError(errorCallback, pp, d, error, that);
                                }
                            },
                            function (error) {
                                error.changes = changes;
                                error.savePackage = savePackage;
                                onError(errorCallback, pp, d, error, that);
                            }
                        );
                    }
                    catch (error) {
                        onError(errorCallback, pp, d, error, this);
                    }
                }

                if (pp) return pp.getPromise(d);
                return retVal;
            };

            /**
             * Creates an entity based on metadata information.
             * @param {Object} result - Entity initial object. This object instance will be made observable.
             * @param {string} typeName - Entity type name (full).
             * @returns {Entity} Entity with observable properties.
             */
            proto.toEntity = function (result, typeName) {
                return this.dataService.toEntity(result, typeName || result.$type);
            };

            /** Fix scalar and plural navigations for entity from cache. */
            proto.fixNavigations = function (entity) {
                if (!this.isInManager(entity))
                    throw helper.createError(i18N.entityNotBeingTracked, null, { entity: entity });
                entityAttached(entity, true, true, this);
            };

            /** Checks if given entity is being tracked by this manager. */
            proto.isInManager = function (entity) {
                return entity.$tracker.manager == this;
            };

            /**
             * Flat relation to a single array. With this we can merge entities with complex navigations.
             * @example
             *  Lets say we have an Order with 3 OrderDetails and that OrderDetails have Supplier assigned,
             *  with flatting this entity, we can merge Order, OrderDetails and Suppliers with one call.
             * @param {Entity[]} entities - Entities to flat.
             * @returns {Entity[]} All entities from all relations.
             */
            proto.flatEntities = function (entities) {
                var that = this;
                var flatList = arguments[1] || [];
                if (!Assert.isArray(entities)) entities = [entities];
                helper.forEach(entities, function (entity) {
                    if (entity == null) return;

                    if (Assert.isArray(entity)) {
                        that.flatEntities(entity, flatList);
                        return;
                    }

                    if (helper.findInArray(flatList, entity)) return;
                    // If property is entity, push it to the list.
                    var tracker = entity.$tracker;
                    var type = entity.$type;

                    if (tracker) {
                        flatList.push(entity);
                        if (tracker.entityType.hasMetadata) {
                            helper.forEach(tracker.entityType.dataProperties, function (dp) {
                                if (dp.isComplex) {
                                    var dv = tracker.getValue(dp.name);
                                    if (dv) {
                                        // If property is array, flat each item.
                                        if (Assert.isArray(dv))
                                            that.flatEntities(dv, flatList);
                                        // If property is entity, flat it.
                                        else if (dv.$tracker || dv.$type)
                                            that.flatEntities([dv], flatList);
                                    }
                                }
                            });
                            helper.forEach(tracker.entityType.navigationProperties, function (np) {
                                var nv = tracker.getValue(np.name);
                                if (nv) {
                                    // If property is array, flat each item.
                                    if (Assert.isArray(nv))
                                        that.flatEntities(nv, flatList);
                                    // If property is entity, flat it.
                                    else if (nv.$tracker || nv.$type)
                                        that.flatEntities([nv], flatList);
                                }
                            });
                        } else {
                            helper.forEach(tracker.entityType.properties, function (up) {
                                var uv = tracker.getValue(up);
                                if (uv) {
                                    // If property is array, flat each item.
                                    if (Assert.isArray(uv))
                                        that.flatEntities(uv, flatList);
                                    // If property is entity, flat it.
                                    else if (uv.$tracker || uv.$type)
                                        that.flatEntities([uv], flatList);
                                }
                            });
                        }
                    } else if (type) {
                        flatList.push(entity);
                        for (var p in entity) {
                            // If property is tracker information skip it.
                            if (p === '$type') continue;
                            var v = entity[p];
                            if (v) {
                                // If property is array, flat each item.
                                if (Assert.isArray(v))
                                    that.flatEntities(v, flatList);
                                // If property is entity, flat it.
                                else if (v.$tracker || v.$type)
                                    that.flatEntities([v], flatList);
                            }
                        }
                    }
                });
                return flatList;
            };

            /**
             * Returns tracking info for given entity.
             * @param {Entity} entity - The entity.
             * @returns {EntityTracker} Entity tracking object.
             */
            proto.entry = function (entity) {
                return entity.$tracker;
            };

            /**
             * Creates entity set for given type.
             * @param {EntityType|string|Function} - Entity type.
             * @returns {EntitySet}
             */
            proto.createSet = function (type) {
                if (!Assert.isInstanceOf(type, metadata.EntityType)) {
                    if (Assert.isFunction(type)) type = helper.getFuncName(type);
                    type = this.getEntityType(type, true);
                }
                return new core.EntitySet(type, this);
            }

            /**
             * Finds entity set for given type name.
             * @param {string|Function} - Entity type.
             * @returns {EntitySet}
             */
            proto.set = function (shortName) {
                if (Assert.isFunction(shortName)) shortName = helper.getFuncName(shortName);
                return this.entitySets && this.entitySets[shortName];
            }

            /** Clears local cache, validation errors and resets change counter to 0.*/
            proto.clear = function () {
                helper.forEach(this.entities.allEntities, function (e) {
                    e.$tracker.manager = null;
                });

                this.pendingChangeCount = 0;
                this.entities = new core.EntityContainer();
                this.validationErrors = [];
            }

            /** Initialize instance with provided arguments. */
            function initialize(args, instance) {
                instance._readyCallbacks = [];
                instance._readyPromises = [];

                // Entity manager can take 1 to 3 arguments.
                if (args.length < 1 || args.length > 3)
                    throw helper.createError(i18N.managerInvalidArgs, { entityManager: instance });
                var service = args[0], metadataPrm = args[1], injections = args[2];
                // If first parameter is data service instance use it
                if (Assert.isInstanceOf(service, baseTypes.DataServiceBase)) {
                    instance.dataService = service;
                    injections = args[1];
                }
                else if (Assert.isTypeOf(service, 'string')) {
                    if (args.length === 2) {
                        if (Assert.isObject(metadataPrm) && !Assert.isInstanceOf(metadataPrm, metadata.MetadataManager)) {
                            injections = metadataPrm;
                            metadataPrm = undefined;
                        }
                    }

                    // If first parameter is string, use it as an Uri and create the default service.
                    var dst = settings.getDefaultServiceType();
                    var serviceType = dst === enums.serviceTypes.OData ? services.ODataService : services.BeetleService;
                    instance.dataService = new serviceType(service, metadataPrm, injections);
                }
                else throw helper.createError(i18N.managerInvalidArgs, { entityManager: this, arguments: args });

                injections = injections || {};
                instance.promiseProvider = injections.promiseProvider || settings.getPromiseProvider();
                instance.autoFixScalar = injections.autoFixScalar;
                instance.autoFixPlural = injections.autoFixPlural;
                instance.validateOnMerge = injections.validateOnMerge;
                instance.validateOnSave = injections.validateOnSave;
                instance.liveValidate = injections.liveValidate;
                instance.handleUnmappedProperties = injections.handleUnmappedProperties;
                instance.forceUpdate = injections.forceUpdate;
                instance.workAsync = injections.workAsync;
                instance.minimizePackage = injections.minimizePackage;

                // Create a integer value to hold change count. This value will be updated after every entity state change.
                instance.pendingChangeCount = 0;
                // Create the entity container.
                instance.entities = new core.EntityContainer();
                instance.validationErrors = [];
                // Events.
                instance.entityStateChanged = new core.Event('entityStateChanged', instance);
                instance.validationErrorsChanged = new core.Event('validationErrorsChanged', instance);
                instance.hasChangesChanged = new core.Event('hasChangesChanged', instance);
                instance.queryExecuting = new core.Event('queryExecuting', instance);
                instance.queryExecuted = new core.Event('queryExecuted', instance);
                instance.saving = new core.Event('saving', instance);
                instance.saved = new core.Event('saved', instance);

                var registerMetadataTypes = injections.registerMetadataTypes;
                if (registerMetadataTypes == null)
                    registerMetadataTypes = settings.registerMetadataTypes;
                instance.dataService.ready(function () {
                    var metadata = instance.dataService.metadataManager;
                    if (metadata) {
                        var types = metadata.types;
                        if (types) {
                            instance.entitySets = {};
                            for (var i = 0; i < types.length; i++) {
                                var type = types[i];
                                var shortName = type.shortName;
                                if (registerMetadataTypes) {
                                    if (!(shortName in instance))
                                        instance[shortName] = getManagerEntityClass(shortName, instance);

                                    if (!(shortName in root))
                                        root[shortName] = getGlobalEntityClass(type);
                                }

                                var setName = type.setName;
                                if (setName && !instance.entitySets.hasOwnProperty(setName)) {
                                    var set = instance.createSet(type);
                                    if (registerMetadataTypes) instance[setName] = set;
                                    instance.entitySets[shortName] = set;
                                }
                            }
                        }
                        var enums = metadata.enums;
                        if (enums) {
                            for (var enumName in enums) {
                                if (!(enumName in root))
                                    root[enumName] = enums[enumName];
                            }
                        }
                    }

                    checkReady(instance);
                });

                function getManagerEntityClass(shortName, manager) {
                    return function (initialValues) {
                        helper.extend(this, initialValues);
                        manager.createEntity(shortName, this);
                    };
                }

                function getGlobalEntityClass(type) {
                    return function (initialValues, createRaw) {
                        helper.extend(this, initialValues);
                        if (createRaw == true)
                            type.createRawEntity(this);
                        else
                            type.createEntity(this);
                    };
                }
            }

            /** Checks if manager is ready, if so calls callbacks. */
            function checkReady(instance) {
                if (instance.isReady()) {
                    var cs = instance._readyCallbacks.slice(0);
                    var ps = instance._readyPromises.slice(0);
                    instance._readyCallbacks = [];
                    instance._readyPromises = [];

                    for (var i = 0; i < cs.length; i++) {
                        var c = cs[i];
                        if (c) c.call(instance);

                        var d = ps[i];
                        if (d) {
                            var pp = instance.promiseProvider;
                            if (pp) pp.resolve(d);
                        }
                    }
                }
            }

            /**
             * Merges entities to local cache.
             * @param {Entity[]} newEntities - Entities to merge.
             * @param {Entity[]} flatList - If entities were flatten before we re-use it, otherwise they will be flattened.
             * @param {MergeStrategy} merge - Merge strategy option.
             * @param {EntityState} state - Entity state to use while merging.
             * @param {EntityManager} instance - Entity manager instance.
             * @param {boolean} autoFixScalar - Automatically fix scalar navigations using foreign keys (fast).
             * @param {boolean} autoFixPlural - Automatically fix plural navigations looking for foreign references (slow).
             */
            function mergeEntities(newEntities, flatList, merge, state, instance, autoFixScalar, autoFixPlural) {
                if (!state) state = enums.entityStates.Added;
                else if (state === enums.entityStates.Detached) return;

                if (!merge) merge = enums.mergeStrategy.Preserve;
                if (autoFixScalar == null) autoFixScalar = instance.autoFixScalar;
                if (autoFixScalar == null) autoFixScalar = settings.autoFixScalar;
                if (autoFixPlural == null) autoFixPlural = instance.autoFixPlural;
                if (autoFixPlural == null) autoFixPlural = settings.autoFixPlural;
                var validateOnMerge = instance.validateOnMerge;
                if (validateOnMerge == null) validateOnMerge = settings.validateOnMerge;

                // Flat list, means merge navigations also.
                flatList = flatList || instance.flatEntities(Assert.isArray(newEntities) ? newEntities : [newEntities]);
                var added = [], toOverwrite = [], toReplace = [];
                var that = instance;
                helper.forEach(flatList, function (e) {
                    if (e == null) return;

                    var tracker = e.$tracker;
                    // if entity is not made observable yet, convert it
                    if (!tracker) {
                        if (!e.$type || !settings.trackUnknownTypes) return;
                        tracker = that.toEntity(e, e.$type).$tracker;
                    }
                    else if (tracker.entityType.isComplexType || instance.isInManager(e)) return;
                    var type = tracker.entityType;

                    // Get entity key.
                    var key = tracker.key;
                    var existingEntity = null;
                    // Try to find same entity in manager.
                    if (key) existingEntity = that.entities.getEntityByKey(key, type.floorType);
                    // If there is already an entity with same base type and key.
                    if (existingEntity) {
                        // Self explanatory.
                        if (type.name !== existingEntity.$tracker.entityType.name)
                            throw helper.createError(i18N.sameKeyOnDifferentTypesError, [existingEntity.$tracker.entityType.shortName, type.shortName],
                                { existingEntity: existingEntity, entity: e, manager: that });
                        if (merge === enums.mergeStrategy.ThrowError)
                            throw helper.createError(i18N.sameKeyExists,
                                { existingEntity: existingEntity, entity: e, manager: that });
                        else if (merge === enums.mergeStrategy.Preserve)
                            toReplace.push({ o: e, n: existingEntity }); // Add to replace temp list.
                        else {
                            toReplace.push({ o: e, n: existingEntity }); // Add to replace temp list.
                            toOverwrite.push({ o: existingEntity, n: e }); // Add to overwrite temp list.
                        }
                    } else {
                        // Add entity to cache.
                        that.entities.push(e);
                        // Start tracking.
                        e.$tracker.setManagerInfo(that);
                        // Add to added temp list.
                        added.push(e);
                    }
                });

                // Fix entitys navigations and check existing items.
                helper.forEach(added, function (a) {
                    // Fix navigations.
                    entityAttached(a, autoFixScalar, autoFixPlural, that);
                    // subscribe to entity events.
                    subscribeToEntity(a, that);
                    setEntityState(a, state);
                    // validate newly added entity.
                    if (validateOnMerge === true)
                        a.$tracker.validate();
                });
                // Overwrite all existing entities (If mergeStrategy said so).
                helper.forEach(toOverwrite, function (ow) {
                    // overwrite entity properties
                    overwriteEntity(ow.o, ow.n);
                    setEntityState(ow.o, state);
                });
                // If an existing entity is found in cache, we don't add new entity to the cache, so we need to fix returning array.
                // This could create strange behaviour when adding or attaching a single entity because even if we change given array,
                // developer still works with discarded entity. So we do not allow adding or attaching an existing entity (like most ORMs).
                helper.forEach(toReplace, function (tr) {
                    var index = helper.indexOf(newEntities, tr.o);
                    if (index >= 0) newEntities[index] = tr.n;
                    resultReplaced(tr.o, tr.n, autoFixScalar, autoFixPlural, that);
                });
            }

            /** If given entity is not being tracked by this manager, throws an error. */
            function checkEntity(entity, instance) {
                if (!instance.isInManager(entity))
                    throw helper.createError(i18N.entityNotBeingTracked, { entity: entity, manager: instance });
            }

            /** Changes entity's state. */
            function setEntityState(entity, state) {
                if (state === enums.entityStates.Unchanged)
                    entity.$tracker.toUnchanged();
                else if (state === enums.entityStates.Modified)
                    entity.$tracker.toModified();
                else if (state === enums.entityStates.Added)
                    entity.$tracker.toAdded();
                else if (state === enums.entityStates.Deleted)
                    entity.$tracker.toDeleted();
                else throw helper.createError(i18N.mergeStateError, [state], { entity: entity, state: state });
            }

            /** Overwrites oldEntity's all properties with newEntity property values. */
            function overwriteEntity(oldEntity, newEntity) {
                var tracker = newEntity.$tracker;
                // Overwrite all properties.
                helper.forEach(tracker.entityType.dataProperties, function (dp) {
                    oldEntity.$tracker.setValue(dp.name, tracker.getValue(dp.name));
                });
            }

            /** Finds navigation and fixes navigation properties for given attached entity. */
            function entityAttached(entity, autoFixScalar, autoFixPlural, instance) {
                if (autoFixScalar != true && autoFixPlural != true) return;
                var tracker = entity.$tracker;
                var type = tracker.entityType;

                helper.forEach(type.navigationProperties, function (np) {
                    var value = tracker.getValue(np.name);
                    if (np.isComplex) {
                        var owners = value.$tracker.owners;
                        var found = false;
                        for (var i = 0; i < owners.length; i++) {
                            var owner = owners[i];
                            if (owner.entity == entity && owner.property == np) {
                                found = true;
                                break;
                            }
                        }
                        if (!found)
                            owners.push({ entity: entity, property: np });
                    } else {
                        if (np.isScalar) // fix replaced entity
                            handleScalar(tracker, value, np, np.name, autoFixScalar, instance);
                        else {
                            // fix replaced entities
                            handlePlural(value, instance);
                            if (autoFixPlural)
                                fixPlural(entity, np, value, instance);
                        }
                    }
                });
                helper.forEach(type.dataProperties, function (dp) {
                    if (dp.isComplex) {
                        var value = tracker.getValue(dp.name);
                        value.$tracker.entityType.isComplexType = true;
                        var owners = value.$tracker.owners;
                        var found = false;
                        for (var i = 0; i < owners.length; i++) {
                            var owner = owners[i];
                            if (owner.entity == entity && owner.property == dp) {
                                found = true;
                                break;
                            }
                        }
                        if (!found)
                            owners.push({ entity: entity, property: dp });
                    }
                });
                helper.forEach(tracker.entityType.properties, function (p) {
                    var value = tracker.getValue(p);
                    if (Assert.isArray(value))
                        handlePlural(value, instance);
                    else
                        handleScalar(tracker, value, null, p, false, instance);
                });
            }

            /** If scalar navigation value is replaced with existing entity, fixes it. */
            function handleScalar(tracker, value, np, npName, autoFix, instance) {
                if (value) {
                    if (value.$tracker && value.$tracker.manager != instance) {
                        value = instance.getEntityByKey(value.$tracker.key, value.$tracker.entityType);
                    }
                    tracker.setValue(npName, value);
                }
                else if (np && autoFix)
                    fixScalar(tracker, np, instance);
            }

            /** If plural navigation items is replaced with existing entities, fixes them. */
            function handlePlural(array, instance) {
                for (var i = array.length - 1; i >= 0; i--) {
                    var item = array[i];
                    if (item && item.$tracker && item.$tracker.manager != instance) {
                        item = instance.getEntityByKey(item.$tracker.key, item.$tracker.entityType);
                    }
                    if (!item) array.splice(i, 1);
                    else array.splice(i, 1, item);
                }
            }

            /** When a query result entity already exists in cache existing entity will be returned as result. This method fixes missing navigations. */
            function resultReplaced(result, existing, autoFixScalar, autoFixPlural, instance) {
                var te = existing.$tracker;
                if (te.entityState == enums.entityStates.Deleted) return;

                var tr = result.$tracker, vr, ve;
                var type = tr.entityType;
                helper.forEach(type.navigationProperties, function (np) {
                    if (np.isComplex) return;

                    vr = tr.getValue(np.name);
                    ve = te.getValue(np.name);
                    if (np.isScalar) {
                        if (!ve) {
                            if (autoFixScalar == true)
                                fixScalar(te, np, instance);
                            else if (!((np.inverse && np.inverse.isScalar) || (autoFixPlural === true && np.inverse && !np.inverse.isScalar))) {
                                // when auto fix is not enabled, try to get items from query result
                                var fkr = tr.foreignKey(np);
                                var fke = te.foreignKey(np);
                                if (fkr == fke && vr != null) {
                                    handleScalar(te, vr, np, np.name, false, instance);
                                }
                            }
                        }
                    }
                    else {
                        if (autoFixPlural)
                            fixPlural(existing, np, ve, instance);
                        else if (!(autoFixScalar === true && np.inverse)) {
                            // copy plural values from result to existing entity
                            helper.forEach(vr, function (vri) {
                                if (!instance.isInManager(vri)) {
                                    vri = instance.getEntityByKey(vri.$tracker.key, vri.$tracker.entityType);
                                }
                                if (vri && !helper.findInArray(ve, vri)) {
                                    ve.push(vri);
                                }
                            });
                        }
                    }
                });
            }

            /** Fixes scalar navigation property. */
            function fixScalar(tracker, np, instance) {
                var fk = tracker.foreignKey(np);
                var found = instance.entities.getEntityByKey(fk, np.entityType);
                if (found && found.$tracker.entityState == enums.entityStates.Deleted) found = null;
                tracker.setValue(np.name, found);
            }

            /** Fixes plural navigation property. */
            function fixPlural(entity, np, array, instance) {
                // get related items from entity container.
                var relations = instance.entities.getRelations(entity, np);
                if (relations)
                    helper.forEach(relations, function (item) {
                        if (item.$tracker.entityState != enums.entityStates.Deleted && !helper.findInArray(array, item))
                            array.push(item);
                    });
            }

            /**
             * Clears navigation properties of given entity.
             * @param {boolean} preserveFK - When true, we can keep beetle from emptying related foreign key properties.
             */
            function clearNavigations(entity, preserveFK) {
                var tracker = entity.$tracker;
                var type = tracker.entityType;
                var nullValue = preserveFK ? new core.ValueNotifyWrapper(null, true) : null;
                if (type.hasMetadata) {
                    // If type has metadata clear all navigation properties.
                    helper.forEach(type.navigationProperties, function (np) {
                        if (np.isScalar)
                            tracker.setValue(np.name, nullValue);
                        else
                            tracker.getValue(np.name).splice(0);
                    });
                }
            }

            /** Accept all changes made via this entity manager, remove deleted from cache, change state of Added and Modified to Unchanged. */
            function acceptSaves(changes, entities, instance) {
                helper.forEach(changes, function (entity) {
                    if (entity.$tracker.entityState == enums.entityStates.Deleted) {
                        entity.$tracker.toDetached();
                        // remove subscriptions for this entity.
                        unsubscribeFromEntity(entity, instance);
                        // remove from cache.
                        entities.remove(entity);
                    } else
                        entity.$tracker.toUnchanged();
                });
            }

            /**
             * Merges old and new errors and make callback.
             * @param {Entity} entity - The entity.
             * @param {Object} changes - Validation error changes.
             * @param {EntityManager} instance - Entity manager instance.
             */
            function mergeErrors(entity, changes, instance) {
                if (changes.removed.length > 0)
                    for (var i = changes.removed.length - 1; i >= 0; i--)
                        instance.validationErrors.splice(helper.indexOf(instance.validationErrors, changes.removed[i]), 1);
                if (changes.added.length > 0)
                    instance.validationErrors.push.apply(instance.validationErrors, changes.added);
                if (changes.removed.length > 0 || changes.added.length > 0)
                    instance.validationErrorsChanged.notify({ errors: instance.validationErrors, added: changes.added, removed: changes.removed });
            }

            /** Subscribe to entity events. */
            function subscribeToEntity(entity, instance) {
                entity.$tracker.entityStateChanged.subscribe(function (change) {
                    var oldCount = instance.pendingChangeCount;
                    if (change.newChanged === true) instance.pendingChangeCount++;
                    else if (change.newUnchanged === true) instance.pendingChangeCount--;
                    instance.entityStateChanged.notify(change);
                    if (oldCount == 0 && instance.pendingChangeCount > 0)
                        instance.hasChangesChanged.notify({ hasChanges: true });
                    else if (oldCount > 0 && instance.pendingChangeCount == 0)
                        instance.hasChangesChanged.notify({ hasChanges: false });
                });
                entity.$tracker.validationErrorsChanged.subscribe(function (result) {
                    mergeErrors(entity, result, instance);
                });
            }

            /** Unsubscribe from entity events. */
            function unsubscribeFromEntity(entity, instance) {
                // unsubscribe from entity events.
                entity.$tracker.entityStateChanged.unsubscribe(instance.entityStateChanged.notify);
                entity.$tracker.validationErrorsChanged.unsubscribe(instance.validationErrorsChanged.notify);
                // remove existing validation errors
                var errors = helper.filterArray(instance.validationErrors, function (ve) { return ve.entity == entity; });
                helper.removeFromArray(instance.validationErrors, entity, 'entity');
                instance.validationErrorsChanged.notify({ errors: instance.validationErrors, removed: errors, added: [] });
            }

            /** Notifies subscribers about executing query. */
            function notifyExecuting(manager, query, options) {
                var obj = { manager: manager, query: query, options: options };
                manager.queryExecuting.notify(obj);
                events.queryExecuting.notify(obj);
                return obj;
            }

            /** Notifies subscribers about executed query. */
            function notifyExecuted(manager, query, options, result) {
                var obj = { manager: manager, query: query, options: options, result: result };
                manager.queryExecuted.notify(obj);
                events.queryExecuted.notify(obj);
                return obj.result;
            }

            /** Notifies subscribers about save operation. */
            function notifySaving(manager, changes, pkg, options) {
                var obj = { manager: manager, changes: changes, savePackage: pkg, options: options };
                manager.saving.notify(obj);
                events.saving.notify(obj);
                return obj.options;
            }

            /** Notifies subscribers about save completion. */
            function notifySaved(manager, changes, pkg, options) {
                var obj = { manager: manager, changes: changes, savePackage: pkg, options: options };
                manager.saved.notify(obj);
                events.saved.notify(obj);
            }

            /** Called when a operation is completed succesfully. */
            function onSuccess(successCallback, promiseProvider, deferred, data) {
                if (successCallback) successCallback(data);
                if (promiseProvider) promiseProvider.resolve(deferred, data);
            }

            /** Called when a operation is failed. */
            function onError(errorCallback, promiseProvider, deferred, error, manager) {
                error.manager = manager;
                if (errorCallback) errorCallback(error);
                if (promiseProvider) promiseProvider.reject(deferred, error);
                if (!errorCallback && !promiseProvider)
                    throw error;
            }

            return ctor;
        })(),
        /**
         * Base entity class.
         * @constructor
         * @param {EntityType} type - Entity type object.
         * @param {EntityManager=} manager - Entity manager.
         * @param {Object=} initialValues - Entity's initial values.
         * @returns {Entity}
         */
        EntityBase: function (type, manager, initialValues) {
            if (initialValues)
                helper.extend(this, initialValues);

            type.createEntity(this);

            if (manager != null)
                manager.addEntity(this);
        }
    };

    /**
     * Data service implementations like ODataService, BeetleService etc..
     * @namespace
     */
    var services = (function () {
        var expose = {};

        /**
         * Beetle Service class.
         * @class
         */
        expose.BeetleService = (function () {
            /**
             * @constructor
             * @param {any} uri - Service URI.
             * @param {MetadataManager|string|boolean} metadataPrm - [Metadata Manager] or [Metadata string] or [loadMetadata: when false no metadata will be used]
             * @param {Object} injections - Injection object to change behaviour of the service.
             */
            var ctor = function (uri, metadataPrm, injections) {
                baseTypes.DataServiceBase.call(this, uri, metadataPrm, injections);
            };
            helper.inherit(ctor, baseTypes.DataServiceBase);
            var proto = ctor.prototype;

            /**
             * Fetch metadata from server.
             * @param {Object} options - Fetch metadata options (async: boolean).
             * @param {Function} successCallback - Function to call after operation succeeded.
             * @param {Function} errorCallback - Function to call when operation fails.
             */
            proto.fetchMetadata = function (options, successCallback, errorCallback) {
                var retVal = null;
                var that = this;
                options = options || {};
                var async = options.async;
                if (async == null) async = !this.ajaxProvider.syncSupported;
                var timeout = options.timeout || this.ajaxTimeout || settings.ajaxTimeout;
                var extra = options.extra;
                var call = this.ajaxProvider.doAjax(
                    this.uri + 'Metadata',
                    'GET', this.dataType, this.contentType, null, async, timeout, extra, null,
                    function (data, headerGetter, xhr) {
                        // deserialize return value to object.
                        retVal = that.serializationService.deserialize(data); // parse string
                        successCallback(retVal, headerGetter, xhr);
                        return retVal;
                    },
                    errorCallback
                );
                return async ? call : retVal;
            };

            /**
             * When there is no metadata available services may be able to create entities asynchronously (server side must be able to support this).
             * @param {string} typeName - Type name to create.
             * @param {Object} initialValues - Entity initial values.
             * @param {Object=} options - Options (makeObservable: boolean, async: boolean).
             * @param {Function} successCallback - Function to call after operation succeeded.
             * @param {Function} errorCallback - Function to call when operation fails.
             */
            proto.createEntityAsync = function (typeName, initialValues, options, successCallback, errorCallback) {
                if (Assert.isFunction(typeName)) typeName = helper.getFuncName(typeName);
                var that = this;
                options = options || {};
                var makeObservable = options.makeObservable;
                if (makeObservable == null) makeObservable = true;
                var async = options.async;
                if (async == null) async = settings.workAsync;
                var timeout = options.timeout || this.ajaxTimeout || settings.ajaxTimeout;
                var extra = options.extra;
                // if type could not be found in metadata request it from server.
                var uri = that.uri + "CreateType?";
                var queryString = "typeName=" + typeName;
                if (initialValues != null)
                    queryString += "&initialValues=" + that.serializationService.serialize(initialValues);
                uri += queryString;
                var retVal;
                var call = this.ajaxProvider.doAjax(
                    uri,
                    'GET', this.dataType, this.contentType, null, async, timeout, extra, options.headers,
                    function (data, headerGetter, xhr) {
                        // deserialize return value to object.
                        data = that.serializationService.deserialize(data);
                        // Fix the relations between object (using $ref and $id values)
                        var allEntities = that.fixResults(data, makeObservable);
                        retVal = data;
                        successCallback(data, allEntities, headerGetter, xhr);
                        return retVal;
                    },
                    function (error) {
                        errorCallback(error);
                    }
                );
                return async ? call : retVal;
            };

            /**
             * Executes given query.
             * @param {EntityQuery} query
             * @param {QueryOptions} options - Query options.
             * @param {Function} successCallback - Function to call after operation succeeded.
             * @param {Function} errorCallback - Function to call when operation fails.
             */
            proto.executeQuery = function (query, options, successCallback, errorCallback) {
                var qp = this.toBeetleQueryParams(query, options && options.varContext);
                return this.executeQueryParams(query.resource, query.parameters, query.bodyParameters, qp, options, successCallback, errorCallback);
            };

            /**
             * Executes given query parameters.
             * @param {string} resource - Server resource to query.
             * @param {Object[]} parameters - The query parameters.
             * @param {Object} bodyParameters - The body parameter objects.
             * @param {Object[]} queryParams - The query beetle parameters.
             * @param {QueryOptions} options - Query options.
             * @param {Function} successCallback - Function to call after operation succeeded.
             * @param {Function} errorCallback - Function to call when operation fails.
             */
            proto.executeQueryParams = function (resource, parameters, bodyParameters, queryParams, options, successCallback, errorCallback) {
                options = options || {};
                var makeObservable = options.makeObservable;
                var handleUnmappedProperties = options.handleUnmappedProperties;

                var method = options.method || ((options.useBody || bodyParameters.length > 0) && "POST") || "GET";
                var dataType = options.dataType || this.dataType;
                var contentType = options.contentType || this.contentType;

                var async = options.async;
                if (async == null) async = settings.workAsync;
                var timeout = options.timeout || this.ajaxTimeout || settings.ajaxTimeout;

                var uri = options.uri || this.uri || "";
                if (uri && uri[uri.length - 1] != "/") uri += "/";
                uri = uri + resource;

                var prmsArr = [];
                helper.forEach(parameters, function (prm) {
                    var value = Assert.isFunction(prm.value) ? prm.value() : prm.value;

                    if (value instanceof Date) {
                        var converter = settings.getDateConverter();
                        value = converter.toISOString(value);
                    }

                    prmsArr.push(prm.name + "=" + (value != null ? encodeURIComponent(value) : ""));
                });

                var bodyParameter;
                if (bodyParameters.length > 0) {
                    bodyParameter = {};
                    for (var i = 0; i < bodyParameters.length; i++) {
                        var bp = bodyParameters[i];
                        if (bp == null) continue;

                        if (Assert.isFunction(bp)) {
                            bp = bp();
                        }
                        // break the merge for arrays. use only this array as body parameter.
                        if (bp instanceof Array) {
                            bodyParameter = bp;
                            break;
                        }
                        if (bp.$tracker) {
                            bp = bp.$tracker.toRaw(true);
                        }
                        bodyParameter = helper.extend(bodyParameter, bp);
                    }
                }

                if (options.useBody && queryParams.length > 0) {
                    prmsArr.push("!beetle-use-body", queryParams.length);
                    bodyParameter = bodyParameter || {};
                    helper.forEach(queryParams, function (qp) {
                        bodyParameter[qp.name] = qp.value;
                    });
                }
                else {
                    helper.forEach(queryParams, function (qp) {
                        prmsArr.push(qp.name + "=" + encodeURIComponent(qp.value));
                    });
                }

                var queryString = prmsArr.join("&");
                uri += "?" + queryString;
                var data = bodyParameter == null ? null : this.serializationService.serialize(bodyParameter);

                var that = this;
                // execute query using ajax provider
                var retVal;
                var call = this.ajaxProvider.doAjax(
                    uri,
                    method, dataType, contentType, data, async, timeout, options.extra, options.headers,
                    function (data, headerGetter, xhr) {
                        // deserialize returned data (if deserializable).
                        try {
                            data = that.serializationService.deserialize(data);
                        } catch (e) {
                        }
                        if (data != null && data.$d != null)
                            data = data.$d;
                        // fix relations and convert to entities.
                        var allEntities = null;
                        if (data) {
                            var isSingle = false;
                            if (!Assert.isArray(data)) {
                                data = [data];
                                isSingle = true;
                            }
                            allEntities = that.fixResults(data, makeObservable, handleUnmappedProperties);
                            if (isSingle) data = data[0];
                        }
                        retVal = data;
                        successCallback(data, allEntities, headerGetter, xhr);
                        return retVal;
                    },
                    function (error) {
                        errorCallback(error);
                    }
                );
                return async ? call : retVal;
            };

            /**
             * Saves all changes using AjaxProvider.
             * @param {SavePackage} savePackage - Save package. When provided, this package will be used (no package will be created).
             * @param {successCallback} successCallback - Function to call after operation succeeded.
             * @param {errorCallback} errorCallback - Function to call when operation fails.
             * @returns {Promise} Returns promise if supported.
             */
            proto.saveChanges = function (savePackage, options, successCallback, errorCallback) {
                var that = this;
                options = options || {};
                var async = options.async;
                if (async == null) async = settings.workAsync;
                var timeout = options.timeout || this.ajaxTimeout || settings.ajaxTimeout;
                var extra = options.extra;
                var uri = options.uri || this.uri || '';
                if (uri && uri[uri.length - 1] != '/') uri += '/';
                var saveAction = options.saveAction || 'SaveChanges';
                uri = uri + saveAction;
                var saveData = this.serializationService.serialize(savePackage);
                var method = options.method || 'POST';
                var dataType = options.dataType || this.dataType;
                var contentType = options.contentType || this.contentType;
                var retVal;
                var call = this.ajaxProvider.doAjax(
                    uri,
                    method, dataType, contentType, saveData, async, timeout, extra, options.headers,
                    function (result, headerGetter, xhr) {
                        // deserialize returned data (if deserializable).
                        try {
                            result = that.serializationService.deserialize(result);
                        } catch (e) {
                        }
                        // fix relations.
                        if (result) {
                            that.fixResults(result, false, null);
                            delete result.$id;
                            delete result.$type;
                        }
                        retVal = result;
                        successCallback(result, headerGetter, xhr);
                        return retVal;
                    },
                    function (error) {
                        errorCallback(error);
                    }
                );
                return async ? call : retVal;
            };

            /**
             * Fix the relations (like $ref links for circular references) between loaded raw data.
             * @param {Object[]} results - Raw entity objects.
             * @param {boolean} makeObservable - When not false entities will be converted to observables.
             * @param {boolean} handleUnmappedProperties - When true, all values will be handled by their value (i.e. some type changes, string->Date).
             */
            proto.fixResults = function (results, makeObservable, handleUnmappedProperties) {
                var that = this;
                var flatList = arguments[3] || [];
                if (!Assert.isArray(results)) results = [results];
                if (handleUnmappedProperties == null) handleUnmappedProperties = settings.handleUnmappedProperties;
                // Push results id's.
                helper.forEach(results, function (result, i) {
                    if (result == null) return;

                    if (result.$ref)
                        results[i] = flatList[result.$ref - 1];
                    else if (result.$id)
                        fixSingle(result);
                    else if (Assert.isArray(result))
                        that.fixResults(result, makeObservable, handleUnmappedProperties, flatList);
                    else if (handleUnmappedProperties !== false)
                        results[i] = core.dataTypes.handle(result);
                });
                return flatList;

                /** Create navigation fixes for single raw result. */
                function fixSingle(result) {
                    var id = result.$id;
                    delete result.$id;

                    // insert entity to it's id position, so we can get it by id later.
                    flatList[id - 1] = result;

                    for (var property in result) {
                        if (property === '$type') continue;
                        var value = result[property];

                        if (value != null) {
                            // if its a reference add it to fixup list.
                            if (value.$ref)
                                result[property] = flatList[value.$ref - 1];
                            else if (value.$id)
                                fixSingle(value); // if its a entity, fix it.
                            else if (Assert.isArray(value))  // if its array
                                that.fixResults(value, makeObservable, handleUnmappedProperties, flatList);
                            else if (handleUnmappedProperties !== false)
                                result[property] = core.dataTypes.handle(value);
                        }
                    }

                    if (makeObservable !== false) {
                        // make observable
                        var typeName = result.$type;
                        var type = that.metadataManager && that.metadataManager.getEntityTypeByFullName(typeName);
                        if (type || (settings.trackUnknownTypes && (type = new metadata.EntityType(typeName)))) {
                            core.EntityTracker.toEntity(result, type);
                        }
                    }
                }
            };

            return ctor;
        })();

        /**
         * OData Service class.
         * Derives from Beetle Service, so supports beetle queries.
         * OData service tries to use OData query structure but can fallback to beetle way.
         * @class
         */
        expose.ODataService = (function () {

            /**
             * @constructor
             * @param {any} uri - Service URI.
             * @param {MetadataManager|string|boolean} metadataPrm - [Metadata Manager] or [Metadata string] or [loadMetadata: when false no metadata will be used]
             * @param {Object} injections - Injection object to change behaviour of the service.
             */
            var ctor = function (uri, metadataPrm, injections) {
                services.BeetleService.call(this, uri, metadataPrm, injections);
                this.useBeetleQueryStrings = false;
            };
            helper.inherit(ctor, expose.BeetleService);
            var proto = ctor.prototype;

            /**
             * Executes given query.
             * Try to use OData, but might fallback to beetle way.
             * @param {EntityQuery} query
             * @param {QueryOptions} options - Query options.
             * @param {Function} successCallback - Function to call after operation succeeded.
             * @param {Function} errorCallback - Function to call when operation fails.
             */
            proto.executeQuery = function (query, options, successCallback, errorCallback) {
                var qp;
                options = options || {};
                var varContext = options.varContext;
                var useBeetleQueryStrings;
                if (options.useBeetleQueryStrings != null)
                    useBeetleQueryStrings = options.useBeetleQueryStrings;
                else useBeetleQueryStrings = this.useBeetleQueryStrings;
                if (useBeetleQueryStrings === true)
                    qp = this.toBeetleQueryParams(query, varContext);
                else if (query.isMultiTyped === true) {
                    qp = this.toBeetleQueryParams(query, varContext);
                    events.warning.notify({ message: i18N.beetleQueryChosenMultiTyped, query: query, options: options });
                } else if (options.useBody) {
                    qp = this.toBeetleQueryParams(query, varContext);
                    events.warning.notify({ message: i18N.beetleQueryChosenPost, query: query, options: options });
                } else
                    qp = this.toODataQueryParams(query, varContext);
                return this.executeQueryParams(query.resource, query.parameters, query.bodyParameters, qp, options, successCallback, errorCallback);
            };

            return ctor;
        })();

        return expose;
    })();

    /**
     * Beetle enums.
     * @namespace
     */
    var enums = {
        /**
         * Entity states.
         * Possible values: Detached, Unchanged, Added, Deleted, Modified
         */
        entityStates: new libs.Enum('Detached', 'Unchanged', 'Added', 'Deleted', 'Modified'),
        /**
         * Language operators.
         * Possible values: !, - (unary), &&, ||, ==, ===, !=, !==, >, <, >=, <=, +, -, *, /, %, &, |, <<, >>
         */
        langOperators: (function () {
            var ctor = function (name, code, func, oData, js) {
                this.name = name;
                this.code = code;
                this.asFunc = func;
                this.oData = oData,
                    this.js = js || code;
            };

            var ops = [];
            // unary
            ops.push(new ctor('Not', '!', function (obj) { return !obj(); }, 'not '));
            ops.push(new ctor('Negative', '-', function (obj) { return -1 * obj(); }));
            // logical
            ops.push(new ctor('And', '&&', function (obj1, obj2) { return obj1() && obj2(); }, 'and'));
            ops.push(new ctor('Or', '||', function (obj1, obj2) { return obj1() || obj2(); }, 'or'));
            ops.push(new ctor('Equals', '==', function (obj1, obj2) { return helper.equals(obj1(), obj2(), false, this); }, 'eq'));
            ops.push(new ctor('StrictEquals', '==', function (obj1, obj2) { return helper.equals(obj1(), obj2(), true, this); }, 'eq', '==='));
            ops.push(new ctor('NotEqual', '!=', function (obj1, obj2) { return !helper.equals(obj1(), obj2(), false, this); }, 'ne'));
            ops.push(new ctor('StrictNotEqual', '!=', function (obj1, obj2) { return !helper.equals(obj1(), obj2(), true, this); }, 'ne', '!=='));
            ops.push(new ctor('Greater', '>', function (obj1, obj2) {
                return obj1() > obj2();
            }, 'gt'));
            ops.push(new ctor('Lesser', '<', function (obj1, obj2) {
                return obj1() < obj2();
            }, 'lt'));
            ops.push(new ctor('GreaterEqual', '>=', function (obj1, obj2) {
                var o1 = obj1(), o2 = obj2();
                return o1 == o2 || o1 > o2;
            }, 'ge'));
            ops.push(new ctor('LesserEqual', '<=', function (obj1, obj2) {
                var o1 = obj1(), o2 = obj2();
                return o1 == o2 || o1 < o2;
            }, 'le'));
            // arithmetic
            ops.push(new ctor('Sum', '+', function (obj1, obj2) { return obj1() + obj2(); }, 'add'));
            ops.push(new ctor('Subtract', '-', function (obj1, obj2) { return obj1() - obj2(); }, 'sub'));
            ops.push(new ctor('Multiply', '*', function (obj1, obj2) { return obj1() * obj2(); }, 'mul'));
            ops.push(new ctor('Divide', '/', function (obj1, obj2) { return obj1() / obj2(); }, 'div'));
            ops.push(new ctor('Modulo', '%', function (obj1, obj2) { return obj1() % obj2(); }, 'mod'));
            // bitwise
            ops.push(new ctor('BitAnd', '&', function (obj1, obj2) { return obj1() & obj2(); }));
            ops.push(new ctor('BitOr', '|', function (obj1, obj2) { return obj1() | obj2(); }));
            ops.push(new ctor('BitShiftLeft', '<<', function (obj1, obj2) { return obj1() << obj2(); }));
            ops.push(new ctor('BitShiftRight', '>>', function (obj1, obj2) { return obj1() >> obj2(); }));

            ctor.find = function (code) {
                var a = helper.filterArray(ops, function (op) {
                    return op.code == code || op.oData == code || op.js == code;
                });
                return a.length > 0 ? a[0] : null;
            };

            return ctor;
        })(),
        /**
         * Merge strategies. Can be passed to execute query method of entity manager.
         * Possible values:
         *  Preserve: Cached entities are preserved and will be returned as query result (when entity with same key found).
         *  Overwrite: Cached entity values will be overwritten and cached entity will be returned as query result (when entity with same key found).
         *  ThrowError: Error will be thrown (when entity with same key found).
         *  NoTracking: Query result will not be merged into the cache.
         *  NoTrackingRaw: Query results will not be merged into the cache and will not be converted to entities (raw objects will be returned).
         */
        mergeStrategy: new libs.Enum('Preserve', 'Overwrite', 'ThrowError', 'NoTracking', 'NoTrackingRaw'),
        /**
         * Query execution strategies. Can be passed to execute query method of entity manager.
         * Possible values:
         *  Server: Get entities only from server.
         *  Local: Get entities only from local cache.
         *  Both: Get entities from local cache then from server and then mix them up.
         *  LocalIfEmptyServer: Get entities from local cache if no result is found get them from server.
         */
        executionStrategy: new libs.Enum('Server', 'Local', 'Both', 'LocalIfEmptyServer'),
        /**
         * Property value auto generation type.
         * Possible values:
         *  Identity: Auto-Increment identity column.
         *  Server: Calculated column.
         */
        generationPattern: new libs.Enum('Identity', 'Computed'),
        /**
         * What to do when user sets an observable array's value with a new array.
         * Possible values:
         *  NotAllowed: An exception will be thrown.
         *  Replace: Old items will be replaced with next array items.
         *  Append: New array items will be appended to existing array.
         */
        arraySetBehaviour: new libs.Enum('NotAllowed', 'Replace', 'Append'),
        /**
         * Supported service types.
         * Possible values: OData, Beetle
         */
        serviceTypes: new libs.Enum('OData', 'Beetle')
    };

    /**
     * Manager independent static events.
     * @namespace
     */
    var events = {
        /** Notifies before a query is being executed. You can modify query and options from the args. */
        queryExecuting: new core.Event('beetleQueryExecuting', this),
        /** Notifies after a query is executed. You can modify result from the args. */
        queryExecuted: new core.Event('beetleQueryExecuted', this),
        /** Notifies before save call started. You can modify options from the args. */
        saving: new core.Event('beetleSaving', this),
        /** Notifies after save call completed. */
        saved: new core.Event('beetleSaved', this),
        /** Notifies when a information level event is occurred. */
        info: new core.Event('beetleInfo', this),
        /** Notifies when a warning level event is occurred. */
        warning: new core.Event('beetleWarning', this),
        /** Notifies when a error level event is occurred. */
        error: new core.Event('beetleError', this)
    };

    /**
     * Beetle settings.
     * @namespace
     */
    var settings = (function () {
        // set default values with backing fields
        var _observableProvider;
        if (ko)
            _observableProvider = new impls.KoObservableProvider(ko);
        else
            _observableProvider = new impls.PropertyObservableProvider();

        var _promiseProvider;
        if (Q)
            _promiseProvider = new impls.QPromiseProvider(Q);
        else if (angularjs)
            _promiseProvider = new impls.AngularjsPromiseProvider(angularjs);
        else if (typeof Promise != 'undefined')
            _promiseProvider = new impls.Es6PromiseProvider();
        else if ($)
            _promiseProvider = new impls.JQueryPromiseProvider($);

        var _ajaxProvider;
        if (angularjs)
            _ajaxProvider = new impls.AngularjsAjaxProvider(angularjs);
        else if (angular)
            _ajaxProvider = new impls.AngularAjaxProvider(angular.http, angular.Request, angular.Headers);
        else if ($)
            _ajaxProvider = new impls.JQueryAjaxProvider($);
        else if (node)
            _ajaxProvider = new impls.NodejsAjaxProvider(node.http, node.https);
        else
            _ajaxProvider = new impls.VanillajsAjaxProvider();

        var _serializationService = new impls.JsonSerializationService();

        var _arraySetBehaviour = enums.arraySetBehaviour.NotAllowed;
        var _defaultServiceType = enums.serviceTypes.Beetle;
        var _dateConverter = new impls.DefaultDateConverter();

        var _localizeFunction;

        var expose = {};

        /** When an entity loaded into manager, navigation fixer tries to fix all navigation properties.
         * But fixing plural navigations may take time (getting cached entities by their foreign keys).
         * These are the default settings, these settings also can be given by query options,
         * example: manager.executeQuery(query, {merge: mergeStrategy.Preserve, autoFixScalar: false})
         */

        /** Auto fix scalar navigation properties (after merge and after foreign key change). */
        expose.autoFixScalar = true;
        /** Auto fix plural navigation properties (after merge). */
        expose.autoFixPlural = false;
        /** Validate entities before adding to manager cache. */
        expose.validateOnMerge = true;
        /** Validate entities before saving to server. */
        expose.validateOnSave = true;
        /** Validate entities on every property change. */
        expose.liveValidate = true;
        /** When a value is set, try to change its type by its data property or value (for anon types). */
        expose.handleUnmappedProperties = true;
        /** for local queries use case sensitive string comparisons. */
        expose.isCaseSensitive = false;
        /** for local queries trim values before string comparisons. */
        expose.ignoreWhiteSpaces = false;
        /** when true, each entity will be updated -even there is no modified property. */
        expose.forceUpdate = false;
        /** when true, loaded meta-data will be cached for url. */
        expose.cacheMetadata = true;
        /** when true, metadata entities will be registered as classes to manager (tracked entity) and global scope (detached entity). Also, enums will be registered to global scope. */
        expose.registerMetadataTypes = false;
        /**
         * when not equals to false all Ajax calls will be made asynchronously,
         * when false createEntityAsync, executeQuery, saveChanges will returns results immediately (when supported).
         */
        expose.workAsync = true;
        /** default timeout for AJAX calls. this value is used when not given with options argument. */
        expose.ajaxTimeout = null;
        /**
         * when true, while creating save package, for modified only changed and key properties, for deleted only key properties will be used.
         * entities will be created with only sent properties filled, other properties will have default values, please use carefully.
         */
        expose.minimizePackage = false;
        /** when true, objects will be tracked even when there is no metadata for their types. */
        expose.trackUnknownTypes = false;
        /** when true, throws error when a not nullable property set with null. */
        expose.checkNulls = false;

        /**
         * Gets default observable provider instance.
         * @returns {baseTypes.ObservableProviderBase} Current observable provider instance.
         */
        expose.getObservableProvider = function () {
            return _observableProvider;
        };

        /**
         * Sets default observable provider instance. Will be used when another instance is not injected.
         * @param {ObservableProviderBase} provider - Observable provider instance.
         */
        expose.setObservableProvider = function (provider) {
            _observableProvider = getValue(provider, baseTypes.ObservableProviderBase);
        };

        /**
         * Gets default promise provider instance.
         * @returns {baseTypes.PromiseProviderBase} Current promise provider instance.
         */
        expose.getPromiseProvider = function () {
            return _promiseProvider;
        };

        /**
         * Sets default promise provider instance. Will be used when another instance is not injected.
         * @param {PromiseProviderBase} provider - Promise provider instance.
         */
        expose.setPromiseProvider = function (provider) {
            _promiseProvider = provider != null ? getValue(provider, baseTypes.PromiseProviderBase) : null;
        };

        /**
         * Gets default ajax provider instance.
         * @returns {baseTypes.AjaxProviderBase} Current ajax provider instance.
         */
        expose.getAjaxProvider = function () {
            return _ajaxProvider;
        };

        /**
         * Sets default ajax provider instance. Will be used when another instance is not injected.
         * @param {AjaxProviderBase} provider - Ajax provider instance.
         */
        expose.setAjaxProvider = function (provider) {
            _ajaxProvider = getValue(provider, baseTypes.AjaxProviderBase);
        };

        /**
         * Gets default serialization service instance.
         * @returns {baseTypes.SerializationServiceBase} Current serialization service instance.
         */
        expose.getSerializationService = function () {
            return _serializationService;
        };

        /**
         * Sets default serialization service instance. Will be used when another instance is not injected.
         * @param {SerializationServiceBase} service - Serialization service instance.
         */
        expose.setSerializationService = function (service) {
            _serializationService = getValue(service, baseTypes.SerializationServiceBase);
        };

        /**
         * Gets array set behaviour.
         * @returns {enums.arraySetBehaviour} Current array set behaviour enum value.
         */
        expose.getArraySetBehaviour = function () {
            return _arraySetBehaviour;
        };

        /**
         * Sets array set behaviour.
         * @param {arraySetBehaviour} behaviour - Array set behaviour enum value.
         */
        expose.setArraySetBehaviour = function (behaviour) {
            /// <param name="provider">Array set behaviour.</param>
            _arraySetBehaviour = getValue(behaviour, null, enums.arraySetBehaviour);
        };

        /**
         * Gets default service type.
         * @returns {enums.serviceTypes} Current service type enum value.
         */
        expose.getDefaultServiceType = function () {
            return _defaultServiceType;
        };

        /**
         * Sets default service type.
         * @param {serviceTypes} serviceType - Service type enum value.
         */
        expose.setDefaultServiceType = function (serviceType) {
            /// <param name="serviceType">Service type enum value.</param>
            _defaultServiceType = getValue(serviceType, null, enums.serviceTypes);
        };

        /**
         * Gets date converter.
         * @returns {baseTypes.SerializationServiceBase} Current date converter instance.
         */
        expose.getDateConverter = function () {
            return _dateConverter;
        };

        /**
         * Sets date converter.
         * @param {SerializationServiceBase} service - Date converter instance.
         */
        expose.setDateConverter = function (converter) {
            _dateConverter = getValue(converter, baseTypes.DateConverterBase);
        };

        /**
         * Gets the localization function.
         * @returns {Function} Current localization function: (string) => string.
         */
        expose.getLocalizeFunction = function () {
            return _localizeFunction;
        };

        /**
         * Sets the localization function.
         * @param {Function} func - Localization function: (string) => string.
         */
        expose.setLocalizeFunction = function (func) {
            helper.assertPrm(func, 'func').isFunction().check();
            _localizeFunction = func;
        };

        function getValue(value, type, typeEnum) {
            if (type && Assert.isInstanceOf(value, type))
                return value;
            else if (typeEnum != null) {
                if (Assert.isNotEmptyString(value)) {
                    var symbols = typeEnum.symbols();
                    for (var i = 0; i < symbols.length; i++) {
                        var sym = symbols[i];
                        if (helper.equals(sym.name, value, false, { isCaseSensitive: false, ignoreWhiteSpaces: false })) {
                            value = sym;
                            break;
                        }
                    }
                }
                if (Assert.isEnum(value, typeEnum)) return value;
            }
            throw helper.createError(i18N.invalidArguments, null, { args: arguments });
        }

        return expose;
    })();

    /** localization */
    var i18N = {
        argCountMismatch: 'Argument count mismatch for "%0".',
        arrayEmpty: 'The array does not contain any element.',
        arrayNotSingle: 'The array does not contain exactly one element.',
        arrayNotSingleOrEmpty: 'The array does not contain zero or one element.',
        assignError: 'Cannot set %0 property with %1.',
        assignErrorNotEntity: 'Cannot set %0 property, value is not an entity.',
        autoGeneratedCannotBeModified: 'Auto generated properties cannot be modified.',
        beetleQueryChosenMultiTyped: 'Beetle query string is used because query is multi-typed.',
        beetleQueryChosenPost: 'Beetle query string is used because query method is POST.',
        beetleQueryNotSupported: 'Beetle queries are not supported, you may use only OData query parameters (where, orderBy, select, expand, top, skip).',
        cannotBeEmptyString: '%0 value can not be empty string.',
        cannotCheckInstanceOnNull: 'Cannot check instance on null value.',
        cannotDetachComplexTypeWithOwners: 'Complex types with owners cannot be detached.',
        compareError: '%0 property value must be equal with %1.',
        complexCannotBeNull: '%0 complex property can not be null.',
        couldNotLoadMetadata: 'Could not load metadata.',
        couldNotLocateNavFixer: 'Could not locate any observable provider.',
        couldNotLocatePromiseProvider: 'Could not locate any promise provider.',
        couldNotParseToken: 'Could not parse %0.',
        countDiffCantBeCalculatedForGrouped: 'In-line count difference can not be calculated when query contains a "groupBy" expression',
        dataPropertyAlreadyExists: 'Data property already exists: %0.',
        entityAlreadyBeingTracked: 'Entity is already being tracked by another manager.',
        entityNotBeingTracked: 'Entity is not being tracked by a manager.',
        executionBothNotAllowedForNoTracking: 'Execution strategy cannot be Both when merge strategy is NoTracking or NoTrackingRaw.',
        expressionCouldNotBeFound: 'Expression could not be found.',
        functionNeedsAlias: '%0 function needs alias to work properly. You can set alias like Linq, p => p.Name.',
        functionNotSupportedForOData: 'OData does not support %0 function.',
        instanceError: '%0 is not an instance of %1.',
        invalidArguments: 'Invalid arguments.',
        invalidDefaultValue: '%0 is not a valid default value for %0.',
        invalidEnumValue: 'Invalid enum value, %0 cannot be found in %1.',
        invalidExpression: '%0 can only have %1 type expressions.',
        invalidPropertyAlias: 'Invalid property alias.',
        invalidStatement: 'Invalid statement.',
        invalidValue: 'Invalid value for %0 property.',
        managerInvalidArgs: 'Invalid arguments. Valid args are: {DataService, [Injections]} or {Uri, [Metadata], [Injections]}.',
        maxLenError: '%0 property length cannot exceed %1.',
        maxPrecisionError: 'Value %0 exceeded maximum precision of %1.',
        mergeStateError: 'Cannot merge entities with %0 state.',
        minLenError: '%0 property length must be greater than %1.',
        noMetadataEntityQuery: 'Cannot create entity query when no metadata is available.',
        noMetadataRegisterCtor: 'Cannot register constructor when no metadata is available.',
        noOpenGroup: 'Could not find any open group.',
        notFoundInMetadata: 'Could not find %0 in metadata.',
        notImplemented: '%0 %1 is not implemented.',
        notNullable: 'Cannot set %0 with null, property is not nullable.',
        oDataNotSupportMultiTyped: 'Multi-Typed queries cannot be used for OData services.',
        odataDoesNotSupportAlias: 'OData services does not support query alias and primitive typed resources.',
        onlyManagerCreatedCanBeExecuted: 'Only queries which are created from a manager can be directly executed.',
        onlyManagerCreatedCanAcceptEntityShortName: 'Only queries which are created from a manager can accept entity type short name parameter.',
        pendingChanges: 'Pending changes',
        pluralNeedsInverse: 'To load plural relations, navigation property must have inverse.',
        projectionsMustHaveAlias: 'All projected values must have a property name or alias.',
        propertyNotFound: 'Could not find property: %0.',
        queryClosed: 'Query is closed, expression cannot be added. Queries must be executed after some expressions like first, single, any, all etc..',
        rangeError: '%0 property value must be between %1 and %2.',
        requiredError: '%0 property is required.',
        sameKeyExists: 'There is already an entity with same key in the manager.',
        sameKeyOnDifferentTypesError: 'Two different types of entities cannot have same keys when they are from same inheritance root (%0, %1).',
        settingArrayNotAllowed: 'Setting array property is not allowed, you may change this via beetle.settings.setArraySetBehaviour(behaviour).',
        stringLengthError: '%0 property length must be between %1 and %2.',
        syncNotSupported: '%0 does not support sync ajax calls.',
        twoEndCascadeDeleteNotAllowed: 'Two-end cascade deletes are not supported.',
        typeError: '%0 type is not %1.',
        typeMismatch: '%0 value type mismatch. expected type: %1, given type: %2, value: %3',
        typeRequiredForLocalQueries: 'To execute queries locally, entity type must be provided (createQuery("Entities", "Entity") or createEntityQuery("Entity")).',
        unclosedQuote: 'Unclosed quote in "%0".',
        unclosedToken: 'Unclosed "%0".',
        unexpectedProperty: 'Unexpected property "%0".',
        unexpectedToken: 'Unexpected %0.',
        unknownDataType: 'Unknown data type: %0.',
        unknownExpression: 'Unknown expression.',
        unknownFunction: 'Unknown function: %0.',
        unknownParameter: 'Unknown parameter: %0.',
        unknownValidator: 'Unknown validator type: %0.',
        unsoppertedState: 'Unsupported entity state: %0.',
        validationErrors: 'Validation errors',
        validationFailed: 'Validation failed.',
        valueCannotBeNull: 'Value cannot be null: %0.',
        operatorNotSupportedForOData: 'Operator is not supported for OData: %0.'
    };
    var i18Ns = { en: i18N };

    /** Export types */
    return {
        version: '3.0.6',
        /**
         * Register localization
         * @param {string} code - Language code.
         * @param {Object} i18n - Localization object.
         * @param {boolean} active - Make this the current localization.
         */
        registerI18N: function (code, i18n, active) {
            i18Ns[code] = i18n;
            if (active) i18N = i18n;
        },
        /**
         * Change current localization. Code must be registered before.
         * @param {string} code - Language code.
         */
        setI18N: function (code) {
            var i18n = i18Ns[code];
            if (!i18n)
                throw new Error("Beetle could not find translation for " + code);

            i18N = i18n;
        },

        helper: helper,
        Assert: Assert,
        libs: libs,

        // namespaces
        baseTypes: baseTypes,
        impls: impls,
        metadata: metadata,
        querying: querying,
        core: core,
        services: services,
        enums: enums,
        events: events,
        settings: settings,

        // shortcuts
        MetadataManager: metadata.MetadataManager,
        EntityManager: core.EntityManager,
        EntityBase: core.EntityBase,
        EntitySet: core.EntitySet,
        ODataService: services.ODataService,
        BeetleService: services.BeetleService,
        Event: core.Event,
        Validator: core.Validator,
        ValueNotifyWrapper: core.ValueNotifyWrapper,

        // enums
        entityStates: enums.entityStates,
        mergeStrategy: enums.mergeStrategy,
        executionStrategy: enums.executionStrategy,
        arraySetBehaviour: enums.arraySetBehaviour,
        serviceTypes: enums.serviceTypes
    };
});