Exemple #1
0
        /// <summary>
        /// Builds an expression for an attribute field
        /// </summary>
        /// <param name="serviceInstance">The service instance.</param>
        /// <param name="parameterExpression">The parameter expression.</param>
        /// <param name="entityField">The entity field.</param>
        /// <param name="values">The filter parameter values: FieldName, <see cref="ComparisonType">Comparison Type</see>, (optional) Comparison Value(s)</param>
        /// <returns></returns>
        public static Expression GetAttributeExpression(IService serviceInstance, ParameterExpression parameterExpression, EntityField entityField, List <string> values)
        {
            if (!values.Any())
            {
                // if no filter parameter values where specified, don't filter
                return(new NoAttributeFilterExpression());
            }

            var service = new AttributeValueService(( RockContext )serviceInstance.Context);

            var attributeValues = service.Queryable().Where(v =>
                                                            v.EntityId.HasValue);

            AttributeCache attributeCache = null;

            if (entityField.AttributeGuid.HasValue)
            {
                attributeCache = AttributeCache.Get(entityField.AttributeGuid.Value);
                var attributeId = attributeCache != null ? attributeCache.Id : 0;

                attributeValues = attributeValues.Where(v => v.AttributeId == attributeId);
            }
            else
            {
                attributeValues = attributeValues.Where(v => v.Attribute.Key == entityField.Name && v.Attribute.FieldTypeId == entityField.FieldType.Id);
            }

            ParameterExpression attributeValueParameterExpression = Expression.Parameter(typeof(AttributeValue), "v");

            // Determine the appropriate comparison type to use for this Expression.
            // Attribute Value records only exist for Entities that have a value specified for the Attribute.
            // Therefore, if the specified comparison works by excluding certain values we must invert our filter logic:
            // first we find the Attribute Values that match those values and then we exclude the associated Entities from the result set.
            ComparisonType?comparisonType          = ComparisonType.EqualTo;
            ComparisonType?evaluatedComparisonType = comparisonType;

            // If Values.Count >= 2, then Values[0] is ComparisonType, and Values[1] is a CompareToValue. Otherwise, Values[0] is a CompareToValue (for example, a SingleSelect attribute)
            if (values.Count >= 2)
            {
                comparisonType = values[0].ConvertToEnumOrNull <ComparisonType>();

                switch (comparisonType)
                {
                case ComparisonType.DoesNotContain:
                    evaluatedComparisonType = ComparisonType.Contains;
                    break;

                case ComparisonType.IsBlank:
                    evaluatedComparisonType = ComparisonType.IsNotBlank;
                    break;

                case ComparisonType.LessThan:
                    evaluatedComparisonType = ComparisonType.GreaterThanOrEqualTo;
                    break;

                case ComparisonType.LessThanOrEqualTo:
                    evaluatedComparisonType = ComparisonType.GreaterThan;
                    break;

                case ComparisonType.NotEqualTo:
                    evaluatedComparisonType = ComparisonType.EqualTo;
                    break;

                default:
                    evaluatedComparisonType = comparisonType;
                    break;
                }

                values[0] = evaluatedComparisonType.ToString();
            }

            var filterExpression = entityField.FieldType.Field.AttributeFilterExpression(entityField.FieldConfig, values, attributeValueParameterExpression);

            if (filterExpression != null)
            {
                if (filterExpression is NoAttributeFilterExpression)
                {
                    // Special Case: If AttributeFilterExpression returns NoAttributeFilterExpression, just return the NoAttributeFilterExpression.
                    // For example, If this is a CampusFieldType and they didn't pick any campus, we don't want to do any filtering for this datafilter.
                    return(filterExpression);
                }
                else
                {
                    attributeValues = attributeValues.Where(attributeValueParameterExpression, filterExpression, null);
                }
            }
            else
            {
                // AttributeFilterExpression returned NULL ( the FieldType didn't specify any additional filter on AttributeValue),
                // ideally the FieldType should have returned a NoAttributeFilterExpression, but just in case, don't filter
                System.Diagnostics.Debug.WriteLine($"Unexpected NULL result from FieldType.Field.AttributeFilterExpression for { entityField.FieldType }");
                return(new NoAttributeFilterExpression());
            }

            IQueryable <int> ids = attributeValues.Select(v => v.EntityId.Value);

            MemberExpression   propertyExpression = Expression.Property(parameterExpression, "Id");
            ConstantExpression idsExpression      = Expression.Constant(ids.AsQueryable(), typeof(IQueryable <int>));
            Expression         expression         = Expression.Call(typeof(Queryable), "Contains", new Type[] { typeof(int) }, idsExpression, propertyExpression);

            if (attributeCache != null)
            {
                // Test the default value against the expression filter. If it pass, then we can include all the attribute values with no value.
                var comparedToDefault = IsComparedToValue(attributeValueParameterExpression, filterExpression, attributeCache.DefaultValue);

                if (comparedToDefault)
                {
                    var allAttributeValueIds = service.Queryable().Where(v => v.Attribute.Id == attributeCache.Id && v.EntityId.HasValue && !string.IsNullOrEmpty(v.Value)).Select(a => a.EntityId.Value);

                    ConstantExpression allIdsExpression      = Expression.Constant(allAttributeValueIds.AsQueryable(), typeof(IQueryable <int>));
                    Expression         notContainsExpression = Expression.Not(Expression.Call(typeof(Queryable), "Contains", new Type[] { typeof(int) }, allIdsExpression, propertyExpression));

                    expression = Expression.Or(expression, notContainsExpression);
                }

                // If there is an EntityTypeQualifierColumn/Value on this attribute, also narrow down the entity query to the ones with matching QualifierColumn/Value
                if (attributeCache.EntityTypeQualifierColumn.IsNotNullOrWhiteSpace() && attributeCache.EntityTypeQualifierValue.IsNotNullOrWhiteSpace())
                {
                    Expression   qualifierParameterExpression = null;
                    PropertyInfo qualifierColumnProperty      = parameterExpression.Type.GetProperty(attributeCache.EntityTypeQualifierColumn);

                    // make sure the QualifierColumn is an actual mapped property on the Entity
                    if (qualifierColumnProperty != null && qualifierColumnProperty.GetCustomAttribute <NotMappedAttribute>() == null)
                    {
                        qualifierParameterExpression = parameterExpression;
                    }
                    else
                    {
                        if (attributeCache.EntityTypeQualifierColumn == "GroupTypeId" && parameterExpression.Type == typeof(Rock.Model.GroupMember))
                        {
                            // Special Case for GroupMember with Qualifier of 'GroupTypeId' (which is really Group.GroupTypeId)
                            qualifierParameterExpression = Expression.Property(parameterExpression, "Group");
                        }
                        else if (attributeCache.EntityTypeQualifierColumn == "RegistrationTemplateId" && parameterExpression.Type == typeof(Rock.Model.RegistrationRegistrant))
                        {
                            // Special Case for RegistrationRegistrant with Qualifier of 'RegistrationTemplateId' (which is really Registration.RegistrationInstance.RegistrationTemplateId)
                            qualifierParameterExpression = Expression.Property(parameterExpression, "Registration");
                            qualifierParameterExpression = Expression.Property(qualifierParameterExpression, "RegistrationInstance");
                        }
                        else
                        {
                            // Unable to determine how the EntityTypeQualiferColumn relates to the Entity. Probably will be OK, but spit out a debug message
                            System.Diagnostics.Debug.WriteLine($"Unable to determine how the EntityTypeQualiferColumn {attributeCache.EntityTypeQualifierColumn} relates to entity {parameterExpression.Type} on attribute {attributeCache.Name}:{attributeCache.Guid}");
                        }
                    }

                    if (qualifierParameterExpression != null)
                    {
                        // if we figured out the EntityQualifierColumn/Value expression, apply it
                        // This would effectively add something like 'WHERE [GroupTypeId] = 10' to the WHERE clause
                        MemberExpression entityQualiferColumnExpression      = Expression.Property(qualifierParameterExpression, attributeCache.EntityTypeQualifierColumn);
                        object           entityTypeQualifierValueAsType      = Convert.ChangeType(attributeCache.EntityTypeQualifierValue, entityQualiferColumnExpression.Type);
                        Expression       entityQualiferColumnEqualExpression = Expression.Equal(entityQualiferColumnExpression, Expression.Constant(entityTypeQualifierValueAsType, entityQualiferColumnExpression.Type));

                        // If the qualifier Column is GroupTypeId, we'll have to do an OR clause of all the GroupTypes that inherit from this
                        // This would effectively add something like 'WHERE ([GroupTypeId] = 10) OR ([GroupTypeId] = 12) OR ([GroupTypeId] = 17)' to the WHERE clause
                        if (attributeCache.EntityTypeQualifierColumn == "GroupTypeId" && attributeCache.EntityTypeQualifierValue.AsIntegerOrNull().HasValue)
                        {
                            var qualifierGroupTypeId = attributeCache.EntityTypeQualifierValue.AsInteger();

                            List <int> inheritedGroupTypeIds = null;
                            using (var rockContext = new RockContext())
                            {
                                var groupType = new GroupTypeService(rockContext).Get(qualifierGroupTypeId);
                                inheritedGroupTypeIds = groupType.GetAllDependentGroupTypeIds(rockContext);
                            }

                            if (inheritedGroupTypeIds != null)
                            {
                                foreach (var inheritedGroupTypeId in inheritedGroupTypeIds)
                                {
                                    Expression inheritedEntityQualiferColumnEqualExpression = Expression.Equal(entityQualiferColumnExpression, Expression.Constant(inheritedGroupTypeId));
                                    entityQualiferColumnEqualExpression = Expression.Or(entityQualiferColumnEqualExpression, inheritedEntityQualiferColumnEqualExpression);
                                }
                            }
                        }

                        expression = Expression.And(entityQualiferColumnEqualExpression, expression);
                    }
                }
            }

            // If we have used an inverted comparison type for the evaluation, invert the Expression so that it excludes the matching Entities.
            if (comparisonType != evaluatedComparisonType)
            {
                return(Expression.Not(expression));
            }
            else
            {
                return(expression);
            }
        }