示例#1
0
        public LambdaExpression ToExpression(IEnumerable <PropertyFilter> propertyFilters, Type parameterType)
        {
            ParameterExpression parameter = Expression.Parameter(parameterType, "p");

            if (propertyFilters == null || propertyFilters.Count() == 0)
            {
                return(Expression.Lambda(Expression.Constant(true), parameter));
            }

            Expression resultCondition = null;

            foreach (var filter in propertyFilters)
            {
                if (string.IsNullOrEmpty(filter.Property))
                {
                    continue;
                }

                Expression memberAccess = null;
                foreach (var property in filter.Property.Split('.'))
                {
                    var parentExpression = memberAccess ?? (Expression)parameter;
                    if (parentExpression.Type.GetProperty(property) == null)
                    {
                        throw new ClientException("Invalid generic filter parameter: Type '" + parentExpression.Type.FullName + "' does not have property '" + property + "'.");
                    }
                    memberAccess = Expression.Property(parentExpression, property);
                }

                // Change the type of the parameter 'value'. It is necessary for comparisons.

                bool propertyIsNullableValueType = memberAccess.Type.IsGenericType && memberAccess.Type.GetGenericTypeDefinition() == typeof(Nullable <>);
                Type propertyBasicType           = propertyIsNullableValueType ? memberAccess.Type.GetGenericArguments().Single() : memberAccess.Type;

                ConstantExpression constant;
                // Operations 'equal' and 'notequal' are supported for backward compatibility.
                if (new[] { "equals", "equal", "notequals", "notequal", "greater", "greaterequal", "less", "lessequal" }.Contains(filter.Operation, StringComparer.OrdinalIgnoreCase))
                {
                    // Constant value should be of same type as the member it is compared to.
                    object convertedValue;
                    if (filter.Value == null || propertyBasicType.IsAssignableFrom(filter.Value.GetType()))
                    {
                        convertedValue = filter.Value;
                    }

                    // Guid object's type was not automatically recognized when deserializing from JSON:
                    else if (propertyBasicType == typeof(Guid) && filter.Value is string)
                    {
                        convertedValue = Guid.Parse(filter.Value.ToString());
                    }

                    // DateTime object's type was not automatically recognized when deserializing from JSON:
                    else if (propertyBasicType == typeof(DateTime) && filter.Value is string)
                    {
                        convertedValue = ParseJsonDateTime((string)filter.Value, filter.Property, propertyBasicType);
                    }

                    else if ((propertyBasicType == typeof(decimal) || propertyBasicType == typeof(int)) && filter.Value is string)
                    {
                        throw new FrameworkException($"Invalid JSON format of {propertyBasicType.Name} property '{filter.Property}'. Numeric value should not be passed as a string in JSON serialized object.");
                    }
                    else
                    {
                        convertedValue = Convert.ChangeType(filter.Value, propertyBasicType);
                    }

                    if (convertedValue == null && memberAccess.Type.IsValueType && !propertyIsNullableValueType)
                    {
                        Type nullableMemberType = typeof(Nullable <>).MakeGenericType(memberAccess.Type);
                        memberAccess = Expression.Convert(memberAccess, nullableMemberType);
                    }

                    constant = Expression.Constant(convertedValue, memberAccess.Type);
                }
                else if (new[] { "startswith", "endswith", "contains", "notcontains" }.Contains(filter.Operation, StringComparer.OrdinalIgnoreCase))
                {
                    // Constant value should be string.
                    constant = Expression.Constant(filter.Value.ToString(), typeof(string));
                }
                else if (new[] { "datein", "datenotin" }.Contains(filter.Operation, StringComparer.OrdinalIgnoreCase))
                {
                    constant = null;
                }
                else if (new[] { "in", "notin" }.Contains(filter.Operation, StringComparer.OrdinalIgnoreCase))
                {
                    if (filter.Value == null)
                    {
                        throw new ClientException($"Invalid generic filter parameter for operation '{filter.Operation}' on {propertyBasicType.Name} property '{filter.Property}'."
                                                  + $" The provided value is null, instead of an Array.");
                    }

                    // The list element should be of same type as the member it is compared to.
                    var parameterMismatchInfo = new Lazy <string>(() =>
                                                                  $"Invalid generic filter parameter for operation '{filter.Operation}' on {propertyBasicType.Name} property '{filter.Property}'."
                                                                  + $" The provided value type is '{filter.Value.GetType()}', instead of an Array of {propertyBasicType.Name}.");

                    if (!(filter.Value is IEnumerable))
                    {
                        throw new ClientException(parameterMismatchInfo.Value);
                    }

                    var list = (IEnumerable)filter.Value;

                    // Guid object's type was not automatically recognized when deserializing from JSON:
                    if (propertyBasicType == typeof(Guid) && list is IEnumerable <string> )
                    {
                        list = ((IEnumerable <string>)list).Select(s => !string.IsNullOrEmpty(s) ? (Guid?)Guid.Parse(s) : null).ToList();
                    }

                    // DateTime object's type was not automatically recognized when deserializing from JSON:
                    if (propertyBasicType == typeof(DateTime) && list is IEnumerable <string> )
                    {
                        list = ((IEnumerable <string>)list).Select(s => ParseJsonDateTime(s, filter.Property, propertyBasicType)).ToList();
                    }

                    // Adjust the list element type to exactly match the property type:
                    if (GetElementType(list) != memberAccess.Type)
                    {
                        if (list is IList && ((IList)list).Count == 0)
                        {
                            list = EmptyList(memberAccess.Type);
                        }
                        else if (propertyBasicType == typeof(Guid))
                        {
                            AdjustListTypeNullable <Guid>(ref list, propertyIsNullableValueType);
                        }
                        else if (propertyBasicType == typeof(DateTime))
                        {
                            AdjustListTypeNullable <DateTime>(ref list, propertyIsNullableValueType);
                        }
                        else if (propertyBasicType == typeof(int))
                        {
                            AdjustListTypeNullable <int>(ref list, propertyIsNullableValueType);
                        }
                        else if (propertyBasicType == typeof(decimal))
                        {
                            AdjustListTypeNullable <decimal>(ref list, propertyIsNullableValueType);
                        }

                        if (!(GetElementType(list).IsAssignableFrom(memberAccess.Type)))
                        {
                            throw new ClientException(parameterMismatchInfo.Value);
                        }
                    }

                    constant = Expression.Constant(list, list.GetType());
                }
                else
                {
                    throw new ClientException($"Unsupported generic filter operation '{filter.Operation}' on a property.");
                }

                Expression expression;
                switch (filter.Operation.ToLower())
                {
                case "equals":
                case "equal":
                    if (propertyBasicType == typeof(string))
                    {
                        expression = Expression.Call(typeof(DatabaseExtensionFunctions).GetMethod("EqualsCaseInsensitive"), memberAccess, constant);
                    }
                    else
                    {
                        expression = Expression.Equal(memberAccess, constant);
                    }
                    break;

                case "notequals":
                case "notequal":
                    if (propertyBasicType == typeof(string))
                    {
                        expression = Expression.Call(typeof(DatabaseExtensionFunctions).GetMethod("NotEqualsCaseInsensitive"), memberAccess, constant);
                    }
                    else
                    {
                        expression = Expression.NotEqual(memberAccess, constant);
                    }
                    break;

                case "greater":
                    if (propertyBasicType == typeof(string))
                    {
                        expression = Expression.Call(typeof(DatabaseExtensionFunctions).GetMethod("IsGreaterThen"), memberAccess, constant);
                    }
                    else if (propertyBasicType == typeof(Guid))
                    {
                        expression = Expression.Call(typeof(DatabaseExtensionFunctions).GetMethod("GuidIsGreaterThan"), memberAccess, constant);
                    }
                    else
                    {
                        expression = Expression.GreaterThan(memberAccess, constant);
                    }
                    break;

                case "greaterequal":
                    if (propertyBasicType == typeof(string))
                    {
                        expression = Expression.Call(typeof(DatabaseExtensionFunctions).GetMethod("IsGreaterThenOrEqual"), memberAccess, constant);
                    }
                    else if (propertyBasicType == typeof(Guid))
                    {
                        expression = Expression.Call(typeof(DatabaseExtensionFunctions).GetMethod("GuidIsGreaterThanOrEqual"), memberAccess, constant);
                    }
                    else
                    {
                        expression = Expression.GreaterThanOrEqual(memberAccess, constant);
                    }
                    break;

                case "less":
                    if (propertyBasicType == typeof(string))
                    {
                        expression = Expression.Call(typeof(DatabaseExtensionFunctions).GetMethod("IsLessThen"), memberAccess, constant);
                    }
                    else if (propertyBasicType == typeof(Guid))
                    {
                        expression = Expression.Call(typeof(DatabaseExtensionFunctions).GetMethod("GuidIsLessThan"), memberAccess, constant);
                    }
                    else
                    {
                        expression = Expression.LessThan(memberAccess, constant);
                    }
                    break;

                case "lessequal":
                    if (propertyBasicType == typeof(string))
                    {
                        expression = Expression.Call(typeof(DatabaseExtensionFunctions).GetMethod("IsLessThenOrEqual"), memberAccess, constant);
                    }
                    else if (propertyBasicType == typeof(Guid))
                    {
                        expression = Expression.Call(typeof(DatabaseExtensionFunctions).GetMethod("GuidIsLessThanOrEqual"), memberAccess, constant);
                    }
                    else
                    {
                        expression = Expression.LessThanOrEqual(memberAccess, constant);
                    }
                    break;

                case "startswith":
                case "endswith":
                {
                    Expression stringMember;
                    if (propertyBasicType == typeof(string))
                    {
                        stringMember = memberAccess;
                    }
                    else
                    {
                        var castMethod = typeof(DatabaseExtensionFunctions).GetMethod("CastToString", new[] { memberAccess.Type });
                        if (castMethod == null)
                        {
                            throw new FrameworkException("Generic filter operation '" + filter.Operation + "' is not supported on property type '" + propertyBasicType.Name + "'. There is no overload of 'DatabaseExtensionFunctions.CastToString' function for the type.");
                        }
                        stringMember = Expression.Call(castMethod, memberAccess);
                    }
                    string dbMethodName = filter.Operation.Equals("startswith", StringComparison.OrdinalIgnoreCase) ? "StartsWithCaseInsensitive" : "EndsWithCaseInsensitive";
                    expression = Expression.Call(typeof(DatabaseExtensionFunctions).GetMethod(dbMethodName), stringMember, constant);
                    break;
                }

                case "contains":
                case "notcontains":
                {
                    Expression stringMember;
                    if (propertyBasicType == typeof(string))
                    {
                        stringMember = memberAccess;
                    }
                    else
                    {
                        var castMethod = typeof(DatabaseExtensionFunctions).GetMethod("CastToString", new[] { memberAccess.Type });
                        if (castMethod == null)
                        {
                            throw new FrameworkException("Generic filter operation '" + filter.Operation + "' is not supported on property type '" + propertyBasicType.Name + "'. There is no overload of 'DatabaseExtensionFunctions.CastToString' function for the type.");
                        }
                        stringMember = Expression.Call(castMethod, memberAccess);
                    }
                    expression = Expression.Call(typeof(DatabaseExtensionFunctions).GetMethod("ContainsCaseInsensitive"), stringMember, constant);

                    if (filter.Operation.Equals("notcontains", StringComparison.OrdinalIgnoreCase))
                    {
                        expression = Expression.Not(expression);
                    }
                    break;
                }

                case "datein":
                case "datenotin":
                {
                    if (propertyBasicType != typeof(DateTime))
                    {
                        throw new FrameworkException("Generic filter operation '" + filter.Operation
                                                     + "' is not supported for property type '" + propertyBasicType.Name + "'. Expected property type 'DateTime'.");
                    }

                    var match = DateRangeRegex.Match(filter.Value.ToString());
                    if (!match.Success)
                    {
                        throw new FrameworkException("Generic filter operation '" + filter.Operation + "' expects format 'yyyy-mm-dd', 'yyyy-mm' or 'yyyy'. Value '" + filter.Value + "' has invalid format.");
                    }

                    DateTime?date1, date2;
                    int      year = int.Parse(match.Groups["y"].Value);
                    if (string.IsNullOrEmpty(match.Groups["m"].Value))
                    {
                        date1 = new DateTime(year, 1, 1);
                        date2 = date1.Value.AddYears(1);
                    }
                    else
                    {
                        int month = int.Parse(match.Groups["m"].Value);
                        if (string.IsNullOrEmpty(match.Groups["d"].Value))
                        {
                            date1 = new DateTime(year, month, 1);
                            date2 = date1.Value.AddMonths(1);
                        }
                        else
                        {
                            int day = int.Parse(match.Groups["d"].Value);
                            date1 = new DateTime(year, month, day);
                            date2 = date1.Value.AddDays(1);
                        }
                    }

                    expression = Expression.AndAlso(
                        Expression.GreaterThanOrEqual(memberAccess, Expression.Constant(date1, typeof(DateTime?))),
                        Expression.LessThan(memberAccess, Expression.Constant(date2, typeof(DateTime?))));

                    if (filter.Operation.Equals("datenotin", StringComparison.OrdinalIgnoreCase))
                    {
                        expression = Expression.Not(expression);
                    }
                    break;
                }

                case "in":
                case "notin":
                {
                    Type collectionBasicType = typeof(IQueryable).IsAssignableFrom(constant.Type)
                                ? typeof(Queryable) : typeof(Enumerable);
                    Type collectionElement = GetElementType(constant.Type);
                    var  containsMethod    = collectionBasicType.GetMethods()
                                             .Where(m => m.Name == "Contains" && m.GetParameters().Count() == 2)
                                             .Single()
                                             .MakeGenericMethod(collectionElement);

                    Expression convertedMemberAccess = memberAccess.Type != collectionElement
                                ? Expression.Convert(memberAccess, collectionElement)
                                : memberAccess;

                    expression = Expression.Call(containsMethod, constant, convertedMemberAccess);

                    if (filter.Operation.Equals("notin", StringComparison.OrdinalIgnoreCase))
                    {
                        expression = Expression.Not(expression);
                    }
                    break;
                }

                default:
                    throw new FrameworkException("Unsupported generic filter operation '" + filter.Operation + "' on property (while generating expression).");
                }

                resultCondition = resultCondition != null?Expression.AndAlso(resultCondition, expression) : expression;
            }

            return(Expression.Lambda(resultCondition, parameter));
        }
示例#2
0
        public LambdaExpression ToExpression(IEnumerable <PropertyFilter> propertyFilters, Type parameterType)
        {
            ParameterExpression parameter = Expression.Parameter(parameterType, "p");

            if (propertyFilters == null || propertyFilters.Count() == 0)
            {
                return(Expression.Lambda(Expression.Constant(true), parameter));
            }

            Expression resultCondition = null;

            foreach (var criteria in propertyFilters)
            {
                if (string.IsNullOrEmpty(criteria.Property))
                {
                    continue;
                }

                Expression memberAccess = null;
                foreach (var property in criteria.Property.Split('.'))
                {
                    var parentExpression = memberAccess ?? (Expression)parameter;
                    if (parentExpression.Type.GetProperty(property) == null)
                    {
                        throw new ClientException("Invalid generic filter parameter: Type '" + parentExpression.Type.FullName + "' does not have property '" + property + "'.");
                    }
                    memberAccess = Expression.Property(parentExpression, property);
                }

                // Change the type of the parameter 'value'. it is necessary for comparisons (specially for booleans)

                bool memberIsNullableValueType = memberAccess.Type.IsGenericType && memberAccess.Type.GetGenericTypeDefinition() == typeof(Nullable <>);
                Type basicType = memberIsNullableValueType ? memberAccess.Type.GetGenericArguments().Single() : memberAccess.Type;

                ConstantExpression constant;
                // Operations 'equal' and 'notequal' are supported for backward compatibility.
                if (new[] { "equals", "equal", "notequals", "notequal", "greater", "greaterequal", "less", "lessequal" }.Contains(criteria.Operation, StringComparer.OrdinalIgnoreCase))
                {
                    // Constant value should be of same type as the member it is compared to.

                    object convertedValue;
                    if (criteria.Value == null || basicType.IsAssignableFrom(criteria.Value.GetType()))
                    {
                        convertedValue = criteria.Value;
                    }
                    else if (basicType == typeof(Guid) && criteria.Value is string) // Guid object's type was not automatically recognized when deserializing from JSON.
                    {
                        convertedValue = Guid.Parse(criteria.Value.ToString());
                    }
                    else if (basicType == typeof(DateTime) && criteria.Value is string) // DateTime object's type was not automatically recognized when deserializing from JSON.
                    {
                        string dateString = "\"" + ((string)criteria.Value).Replace("/", "\\/") + "\"";
                        using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(dateString)))
                        {
                            var serializer = new DataContractJsonSerializer(typeof(DateTime));
                            try
                            {
                                convertedValue = (DateTime)serializer.ReadObject(stream);
                            }
                            catch (SerializationException ex)
                            {
                                throw new ClientException("Invalid JSON format of " + basicType.Name + " property '" + criteria.Property + "'. " + ex.Message, ex);
                            }
                        }
                    }
                    else if ((basicType == typeof(decimal) || basicType == typeof(int)) && criteria.Value is string)
                    {
                        throw new FrameworkException("Invalid JSON format of " + basicType.Name + " property '" + criteria.Property + "'. Numeric value should not be passed as a string in JSON serialized object.");
                    }
                    else
                    {
                        convertedValue = Convert.ChangeType(criteria.Value, basicType);
                    }

                    if (convertedValue == null && memberAccess.Type.IsValueType && !memberIsNullableValueType)
                    {
                        Type nullableMemberType = typeof(Nullable <>).MakeGenericType(memberAccess.Type);
                        memberAccess = Expression.Convert(memberAccess, nullableMemberType);
                    }

                    constant = Expression.Constant(convertedValue, memberAccess.Type);
                }
                else if (new[] { "startswith", "endswith", "contains", "notcontains" }.Contains(criteria.Operation, StringComparer.OrdinalIgnoreCase))
                {
                    // Constant value should be string.
                    constant = Expression.Constant(criteria.Value.ToString(), typeof(string));
                }
                else if (new[] { "datein", "datenotin" }.Contains(criteria.Operation, StringComparer.OrdinalIgnoreCase))
                {
                    constant = null;
                }
                else
                {
                    throw new FrameworkException("Unsupported generic filter operation '" + criteria.Operation + "' on property.");
                }

                Expression expression;
                switch (criteria.Operation.ToLower())
                {
                case "equals":
                case "equal":
                    if (basicType == typeof(string))
                    {
                        expression = Expression.Call(typeof(DatabaseExtensionFunctions).GetMethod("EqualsCaseInsensitive"), memberAccess, constant);
                    }
                    else
                    {
                        expression = Expression.Equal(memberAccess, constant);
                    }
                    break;

                case "notequals":
                case "notequal":
                    if (basicType == typeof(string))
                    {
                        expression = Expression.Call(typeof(DatabaseExtensionFunctions).GetMethod("NotEqualsCaseInsensitive"), memberAccess, constant);
                    }
                    else
                    {
                        expression = Expression.NotEqual(memberAccess, constant);
                    }
                    break;

                case "greater":
                    if (basicType == typeof(string))
                    {
                        expression = Expression.Call(typeof(DatabaseExtensionFunctions).GetMethod("IsGreaterThen"), memberAccess, constant);
                    }
                    else
                    {
                        expression = Expression.GreaterThan(memberAccess, constant);
                    }
                    break;

                case "greaterequal":
                    if (basicType == typeof(string))
                    {
                        expression = Expression.Call(typeof(DatabaseExtensionFunctions).GetMethod("IsGreaterThenOrEqual"), memberAccess, constant);
                    }
                    else
                    {
                        expression = Expression.GreaterThanOrEqual(memberAccess, constant);
                    }
                    break;

                case "less":
                    if (basicType == typeof(string))
                    {
                        expression = Expression.Call(typeof(DatabaseExtensionFunctions).GetMethod("IsLessThen"), memberAccess, constant);
                    }
                    else
                    {
                        expression = Expression.LessThan(memberAccess, constant);
                    }
                    break;

                case "lessequal":
                    if (basicType == typeof(string))
                    {
                        expression = Expression.Call(typeof(DatabaseExtensionFunctions).GetMethod("IsLessThenOrEqual"), memberAccess, constant);
                    }
                    else
                    {
                        expression = Expression.LessThanOrEqual(memberAccess, constant);
                    }
                    break;

                case "startswith":
                case "endswith":
                {
                    Expression stringMember;
                    if (basicType == typeof(string))
                    {
                        stringMember = memberAccess;
                    }
                    else
                    {
                        var castMethod = typeof(DatabaseExtensionFunctions).GetMethod("CastToString", new[] { memberAccess.Type });
                        if (castMethod == null)
                        {
                            throw new FrameworkException("Generic filter operation '" + criteria.Operation + "' is not supported on property type '" + basicType.Name + "'. There is no overload of 'DatabaseExtensionFunctions.CastToString' function for the type.");
                        }
                        stringMember = Expression.Call(castMethod, memberAccess);
                    }
                    string dbMethodName = criteria.Operation.Equals("startswith", StringComparison.OrdinalIgnoreCase) ? "StartsWithCaseInsensitive" : "EndsWithCaseInsensitive";
                    expression = Expression.Call(typeof(DatabaseExtensionFunctions).GetMethod(dbMethodName), stringMember, constant);
                    break;
                }

                case "contains":
                case "notcontains":
                {
                    Expression stringMember;
                    if (basicType == typeof(string))
                    {
                        stringMember = memberAccess;
                    }
                    else
                    {
                        var castMethod = typeof(DatabaseExtensionFunctions).GetMethod("CastToString", new[] { memberAccess.Type });
                        if (castMethod == null)
                        {
                            throw new FrameworkException("Generic filter operation '" + criteria.Operation + "' is not supported on property type '" + basicType.Name + "'. There is no overload of 'DatabaseExtensionFunctions.CastToString' function for the type.");
                        }
                        stringMember = Expression.Call(castMethod, memberAccess);
                    }
                    expression = Expression.Call(typeof(DatabaseExtensionFunctions).GetMethod("ContainsCaseInsensitive"), stringMember, constant);

                    if (criteria.Operation.Equals("notcontains", StringComparison.OrdinalIgnoreCase))
                    {
                        expression = Expression.Not(expression);
                    }
                    break;
                }

                case "datein":
                case "datenotin":
                {
                    if (basicType != typeof(DateTime))
                    {
                        throw new FrameworkException("Generic filter operation '" + criteria.Operation
                                                     + "' is not supported for property type '" + basicType.Name + "'. Expected property type 'DateTime'.");
                    }

                    var match = DateRangeRegex.Match(criteria.Value.ToString());
                    if (!match.Success)
                    {
                        throw new FrameworkException("Generic filter operation '" + criteria.Operation + "' expects format 'yyyy-mm-dd', 'yyyy-mm' or 'yyyy'. Value '" + criteria.Value + "' has invalid format.");
                    }

                    DateTime?date1, date2;
                    int      year = int.Parse(match.Groups["y"].Value);
                    if (string.IsNullOrEmpty(match.Groups["m"].Value))
                    {
                        date1 = new DateTime(year, 1, 1);
                        date2 = date1.Value.AddYears(1);
                    }
                    else
                    {
                        int month = int.Parse(match.Groups["m"].Value);
                        if (string.IsNullOrEmpty(match.Groups["d"].Value))
                        {
                            date1 = new DateTime(year, month, 1);
                            date2 = date1.Value.AddMonths(1);
                        }
                        else
                        {
                            int day = int.Parse(match.Groups["d"].Value);
                            date1 = new DateTime(year, month, day);
                            date2 = date1.Value.AddDays(1);
                        }
                    }

                    expression = Expression.AndAlso(
                        Expression.GreaterThanOrEqual(memberAccess, Expression.Constant(date1, typeof(DateTime?))),
                        Expression.LessThan(memberAccess, Expression.Constant(date2, typeof(DateTime?))));

                    if (criteria.Operation.Equals("datenotin", StringComparison.OrdinalIgnoreCase))
                    {
                        expression = Expression.Not(expression);
                    }
                    break;
                }

                default:
                    throw new FrameworkException("Unsupported generic filter operation '" + criteria.Operation + "' on property (while generating expression).");
                }

                resultCondition = resultCondition != null?Expression.AndAlso(resultCondition, expression) : expression;
            }

            return(Expression.Lambda(resultCondition, parameter));
        }