Example #1
0
        /// <summary>
        /// Create an evaluation-settings object.
        /// </summary>
        /// <remarks>
        /// The settings object will be reused for all entities requested in this iteration.
        /// The context entity will be set in each time.
        /// </remarks>
        internal static EvaluationSettings CreateEvaluationSettings(CalculatedFieldSettings settings)
        {
            EvaluationSettings evalSettings = new EvaluationSettings();

            evalSettings.TimeZoneName = settings.TimeZone;

            if (evalSettings.TimeZoneName == null)
            {
                var requestContext = RequestContext.GetContext();
                if (requestContext != null)
                {
                    evalSettings.TimeZoneName = requestContext.TimeZone;
                }

                if (evalSettings.TimeZoneName == null)
                {
                    throw new InvalidOperationException("TimeZone information must be provided in settings object, or via RequestContext.");
                }
            }

            return(evalSettings);
        }
        /// <summary>
        /// Get the static information about a calculated field, which can be used to more quickly evaluate individual entities.
        /// </summary>
        /// <param name="fieldIDs">The field IDs to load</param>
        /// <param name="settings">Additional settings.</param>
        /// <returns></returns>
        public IReadOnlyCollection <CalculatedFieldMetadata> GetCalculatedFieldMetadata(IReadOnlyCollection <long> fieldIDs, CalculatedFieldSettings settings)
        {
            // Validate
            if (fieldIDs == null)
            {
                throw new ArgumentNullException("fieldIDs");
            }

            IReadOnlyCollection <KeyValuePair <long, CalculatedFieldMetadata> > cacheResult;
            IReadOnlyCollection <CalculatedFieldMetadata> result;

            cacheResult = GetOrAddMultiple(fieldIDs, fields => InnerProvider.GetCalculatedFieldMetadata(fields.ToList(), settings));

            result = cacheResult.Select(pair => pair.Value).ToList();

            return(result);
        }
Example #3
0
        /// <summary>
        /// Fetch results for calculated fields.
        /// </summary>
        /// <remarks>
        /// Processes all specified fields for all specified entities.
        /// </remarks>
        /// <param name="fieldIDs">The calculated fields to evaluate.</param>
        /// <param name="entityIDs">The root entities for which the calculation is being evaluated.</param>
        /// <param name="settings">Additional settings.</param>
        /// <returns>Calculation results.</returns>
        public IReadOnlyCollection <CalculatedFieldResult> GetCalculatedFieldValues(IReadOnlyCollection <long> fieldIDs, IReadOnlyCollection <long> entityIDs, CalculatedFieldSettings settings)
        {
            if (fieldIDs == null)
            {
                throw new ArgumentNullException("fieldIDs");
            }
            if (entityIDs == null)
            {
                throw new ArgumentNullException("entityIDs");
            }
            if (settings == null)
            {
                throw new ArgumentNullException("settings");
            }

            // Create the evaluators for this field
            IReadOnlyCollection <CalculatedFieldMetadata> fieldsMetadata;

            fieldsMetadata = _metadataProvider.GetCalculatedFieldMetadata(fieldIDs, settings);

            // Create result container
            List <CalculatedFieldResult> results = new List <CalculatedFieldResult>(fieldIDs.Count);

            // Create expression evaluator settings
            EvaluationSettings evalSettings = CreateEvaluationSettings(settings);

            // Fill
            foreach (long fieldId in fieldIDs)
            {
                CalculatedFieldMetadata fieldMetadata;
                IReadOnlyCollection <CalculatedFieldSingleResult> fieldResults;
                CalculatedFieldResult result;

                // Find the evaluator
                // (Deemed cheaper to just scan, rather than use a dictionary)
                fieldMetadata = fieldsMetadata.First(fe => fe.CalculatedFieldId == fieldId);

                if (fieldMetadata.Exception != null)
                {
                    result = new CalculatedFieldResult(fieldId, fieldMetadata.Exception);
                }
                else
                {
                    // And get the values
                    fieldResults = GetSingleFieldValues(fieldMetadata, entityIDs, evalSettings);
                    result       = new CalculatedFieldResult(fieldId, fieldResults);
                }

                results.Add(result);
            }

            return(results);
        }
        /// <summary>
        /// Creates the evaluator for a group of calculated fields.
        /// </summary>
        /// <param name="calculatedFieldIds">ID of the calculated field.</param>
        /// <param name="settings">Settings.</param>
        /// <returns></returns>
        public IReadOnlyCollection <CalculatedFieldMetadata> GetCalculatedFieldMetadata(IReadOnlyCollection <long> calculatedFieldIds, CalculatedFieldSettings settings)
        {
            if (calculatedFieldIds == null)
            {
                throw new ArgumentNullException("calculatedFieldIds");
            }

            IReadOnlyCollection <Field>    calculatedFields;
            List <CalculatedFieldMetadata> result;

            // Load field schema info
            string preloadQuery = "fieldCalculation, fieldIsOnType.id, isOfType.alias";

            calculatedFields = _entityRepository.Get <Field>(calculatedFieldIds, preloadQuery);

            result = new List <CalculatedFieldMetadata>();

            foreach (long fieldId in calculatedFieldIds)
            {
                Field field;
                CalculatedFieldMetadata metadata;

                // Get field
                field = calculatedFields.FirstOrDefault(f => f.Id == fieldId);
                if (field == null)
                {
                    throw new ArgumentException(string.Format("Specified field ID {0} is not a field.", fieldId), "calculatedFieldIds");
                }

                // Get calculation
                metadata = GetSingleCalculatedFieldMetadata(field, settings);
                result.Add(metadata);
            }

            return(result);
        }
        /// <summary>
        /// Create compilation settings that are appropriate for a given field.
        /// </summary>
        /// <param name="calculatedField">The calculated field.</param>
        /// <param name="settings">The settings.</param>
        /// <returns></returns>
        internal static BuilderSettings CreateBuilderSettingsForField(Field calculatedField, CalculatedFieldSettings settings)
        {
            // Determine calculation context type
            EntityType type        = calculatedField.FieldIsOnType;
            ExprType   contextType = ExprTypeHelper.EntityOfType(new EntityRef(type.Id));

            // Determine result type
            ExprType expectedResult = ExprTypeHelper.FromFieldEntity(calculatedField);

            expectedResult.DisallowList = true;

            // Create settings
            BuilderSettings builderSettings = new BuilderSettings();

            builderSettings.ScriptHost         = ScriptHostType.Any;
            builderSettings.RootContextType    = contextType;
            builderSettings.ExpectedResultType = expectedResult;

            return(builderSettings);
        }
        /// <summary>
        /// Creates the evaluator for a single calculated field.
        /// </summary>
        /// <param name="calculatedField">Entity for the calculated field.</param>
        /// <param name="settings">Settings.</param>
        /// <returns></returns>
        private CalculatedFieldMetadata GetSingleCalculatedFieldMetadata(Field calculatedField, CalculatedFieldSettings settings)
        {
            string          calculation = null;
            IExpression     expression  = null;
            ParseException  exception   = null;
            BuilderSettings builderSettings;

            try
            {
                // Get calculation
                calculation = calculatedField.FieldCalculation;
                if (string.IsNullOrEmpty(calculation))
                {
                    throw new ArgumentException("The field has no calculation script. It may not be a calculated field.");
                }

                // Get settings
                builderSettings = CreateBuilderSettingsForField(calculatedField, settings);

                // Compile
                expression = _expressionCompiler.Compile(calculation, builderSettings);

                // Register cache invalidations
                if (CacheContext.IsSet())
                {
                    CalculationDependencies dependencies = _expressionCompiler.GetCalculationDependencies(expression);

                    using (CacheContext cacheContext = CacheContext.GetContext())
                    {
                        cacheContext.Entities.Add(calculatedField.Id);
                        cacheContext.Entities.Add(dependencies.IdentifiedEntities);
                        cacheContext.Entities.Add(builderSettings.RootContextType.EntityTypeId);
                    }
                }
            }
            catch (InvalidMemberParseException ex)
            {
                exception = ex;

                // If a parse-exception resulted from being unable to look up a member name, then it may be corrected by renaming some arbitrary field or relationship
                // that could not be otherwise detected by dependencies.IdentifiedEntities. So invalidate parse exceptions if any field/relationship changes.
                if (CacheContext.IsSet())
                {
                    using (CacheContext cacheContext = CacheContext.GetContext())
                    {
                        cacheContext.Entities.Add(ex.TypeId);
                        // TODO: ideally just listen for all fields/relationships attached to type
                        var fieldTypes = PerTenantEntityTypeCache.Instance.GetDescendantsAndSelf(new EntityRef("core:field").Id);
                        cacheContext.EntityTypes.Add(fieldTypes);
                        cacheContext.EntityTypes.Add(new EntityRef("core:relationship").Id);
                    }
                }
            }
            catch (ParseException ex)
            {
                exception = ex;
            }

            // Build metadata
            CalculatedFieldMetadata metadata = new CalculatedFieldMetadata(calculatedField.Id, calculation, expression, exception);

            return(metadata);
        }
        /// <summary>
        /// Fetch results for calculated fields.
        /// </summary>
        /// <remarks>
        /// Extension method for processing a single entity and single field.
        /// Script errors cause null result.
        /// </remarks>
        /// <param name="provider">The calculated field provider.</param>
        /// <param name="fieldId">The calculated field to evaluate.</param>
        /// <param name="entityId">The root entity for which the calculation is being evaluated.</param>
        /// <param name="settings">Additional settings.</param>
        /// <returns>Calculation results.</returns>
        public static object GetCalculatedFieldValue(this ICalculatedFieldProvider provider, long fieldId, long entityId, CalculatedFieldSettings settings)
        {
            if (provider == null)
            {
                throw new ArgumentNullException("provider");                // assert false
            }
            var entities = new[] { entityId };

            // Call provider
            var results = provider.GetCalculatedFieldValues(fieldId, entities, settings);

            // Extract results
            if (results == null)
            {
                throw new Exception("Unexpected null result.");             // assert false .. provider isn't following the rules
            }
            // Parse exception means there are no individual results
            if (results.ParseException != null)
            {
                return(null);
            }

            CalculatedFieldSingleResult singleResult = results.Entities.FirstOrDefault();

            if (singleResult == null)
            {
                throw new Exception("Could not load calculated field for entity. Likely cause that entity could not be loaded.");
            }

            // Return result
            return(singleResult.Result);
        }
        /// <summary>
        /// Fetch results for calculated fields.
        /// </summary>
        /// <remarks>
        /// Extension method for processing a single field.
        /// Script errors cause null result.
        /// </remarks>
        /// <param name="provider">The calculated field provider.</param>
        /// <param name="fieldId">The calculated field to evaluate.</param>
        /// <param name="entityIds">The root entities for which the calculation is being evaluated.</param>
        /// <param name="settings">Additional settings.</param>
        /// <returns>Calculation results.</returns>
        public static CalculatedFieldResult GetCalculatedFieldValues(this ICalculatedFieldProvider provider, long fieldId, IReadOnlyCollection <long> entityIds, CalculatedFieldSettings settings)
        {
            if (provider == null)
            {
                throw new ArgumentNullException("provider");                // assert false
            }
            var fields = new[] { fieldId };

            // Call provider
            var results = provider.GetCalculatedFieldValues(fields, entityIds, settings);

            // Extract results
            CalculatedFieldResult result = results.FirstOrDefault();

            if (result == null)
            {
                throw new Exception("Unexpected null result.");             // assert false .. provider isn't following the rules
            }
            return(result);
        }
        /// <summary>
        /// Fetch metadata for a single calculated field.
        /// </summary>
        /// <param name="provider">The calculated field metadata provider being extended.</param>
        /// <param name="fieldId">The field to load.</param>
        /// <param name="settings">Additional settings.</param>
        /// <returns>Calculation results.</returns>
        public static CalculatedFieldMetadata GetCalculatedFieldMetadata(this ICalculatedFieldMetadataProvider provider, long fieldId, CalculatedFieldSettings settings)
        {
            if (provider == null)
            {
                throw new ArgumentNullException("provider"); // assert false
            }
            var fields = new[] { fieldId };

            // Call provider
            var metadatas = provider.GetCalculatedFieldMetadata(fields, settings);

            // Extract results
            CalculatedFieldMetadata metadata = metadatas.FirstOrDefault();

            return(metadata);
        }