/// <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); }
/// <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); }