Luis Fernando avatar Luis Fernando committed 3bd8cbe

GlobalValueResolver improvments

Comments (0)

Files changed (10)

TelerikMvcGridCustomBindingHelper.NHibernate/NHibernateGridCustomBindingHelper.cs

     /// </summary>
     /// <typeparam name="TEntity">The type of your Entity class</typeparam>
     /// <typeparam name="TViewModel">The type of your ViewModel class</typeparam>
-    public class NHibernateGridCustomBindingHelper<TEntity, TViewModel> : GridCustomBindingHelperBase<TEntity, TViewModel>
+    public class NHibernateGridCustomBindingHelper<TEntity, TViewModel> : BaseGridCustomBindingHelper<TEntity, TViewModel>
         where TViewModel : class
         where TEntity : class
     {

TelerikMvcGridCustomBindingHelper/BaseGridCustomBindingHelper.cs

+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Dynamic;
+using System.Linq.Expressions;
+using System.Reflection;
+using Telerik.Web.Mvc;
+using Telerik.Web.Mvc.Extensions;
+using Telerik.Web.Mvc.Infrastructure;
+using TelerikMvcGridCustomBindingHelper.Aggregates;
+using TelerikMvcGridCustomBindingHelper.Exceptions;
+using TelerikMvcGridCustomBindingHelper.Mapper;
+using TelerikMvcGridCustomBindingHelper.Projections;
+using TelerikMvcGridCustomBindingHelper.Util;
+
+namespace TelerikMvcGridCustomBindingHelper
+{
+    /// <summary>
+    /// Base class to make Helper classes to work with Telerik Grid for ASP.NET MVC using CustomBinding mode coupled with
+    /// some mapping techniques to convert Entities objects into ViewModels objects.
+    /// </summary>
+    /// <typeparam name="TEntity">The type of your Entity class</typeparam>
+    /// <typeparam name="TViewModel">The type of your ViewModel class</typeparam>
+    public abstract class BaseGridCustomBindingHelper<TEntity, TViewModel>
+        where TViewModel : class
+        where TEntity : class
+    {
+        #region Properties
+
+        /// <summary>
+        /// Default sort member name.
+        /// </summary>
+        public string DefaultSortMemberName { get; set; }
+
+        /// <summary>
+        /// List of viewmodels, may be a set of groups and aggregates or a simple list.
+        /// </summary>
+        public IEnumerable ProcessedData
+        {
+            get
+            {
+                EnsureDataSourceIsProcessed();
+                return ProcessedDataField;
+            }
+        }
+
+        /// <summary>
+        /// IEnumerable of viewmodels without groups or aggregates.
+        /// </summary>
+        public IEnumerable<TViewModel> ViewModels
+        {
+            get
+            {
+                EnsureDataSourceIsProcessed();
+                return ViewModelsField;
+            }
+        }
+
+        /// <summary>
+        /// IEnumerable of entities from the database.
+        /// </summary>
+        public IEnumerable<TEntity> Entities
+        {
+            get
+            {
+                EnsureDataSourceIsProcessed();
+                return EntitiesField;
+            }
+        }
+
+        /// <summary>
+        /// Total number of records in the database.
+        /// </summary>
+        public long Total
+        {
+            get
+            {
+                EnsureDataSourceIsProcessed();
+                return TotalField;
+            }
+        }
+
+        #endregion
+
+        #region Fields
+
+        protected ProjectionsOptionsImpl<TEntity> ProjectionsOptions;
+        protected bool AcceptNullValuesWhenFilteringField;
+        protected bool UseProjectionsField;
+        protected bool IsDataSourceProcessed;
+        protected long TotalField;
+        protected IEnumerable ProcessedDataField;
+        protected IEnumerable<TViewModel> ViewModelsField;
+        protected IList<TEntity> EntitiesField;
+        protected readonly GridCommand CommandField;
+        protected Dictionary<string, object> AggregatesField;
+        protected CustomAggregateFunctionBase InheritedClassCustomAggregateCalculatorField;
+        protected bool IgnoreGroupAggregatesField;
+        protected Func<IQueryable<TEntity>, object> ExpressionCustomAggregateFunctionField;
+        private Func<IQueryable<TViewModel>, object> _aggregateViewModelFunctionField;
+        protected bool UseCaseInsensitiveSearchField;
+
+        #endregion
+
+        #region Constructors
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="BaseGridCustomBindingHelper{TEntity,TViewModel}"/> class. 
+        /// Base constructor
+        /// </summary>
+        /// <param name="command">Telerik GridCommand containing informations about paging, filtering, sorting, aggregates</param>
+        /// <param name="defaultSortMemberName">Default sort member name for this entity</param>
+        protected BaseGridCustomBindingHelper(GridCommand command, string defaultSortMemberName)
+        {
+            DefaultSortMemberName = defaultSortMemberName;
+
+            // Check if the command has been initialized
+            CommandField = !IsEmptyCommand(command) ? command : new GridCommand();
+        }
+
+        #endregion
+
+        #region Public Methods
+
+        /// <summary>
+        /// Create a new GridModel with the processed data and the total of records in the database
+        /// </summary>
+        /// <returns>GridModel</returns>
+        public GridModel BuildGridModel()
+        {
+            EnsureDataSourceIsProcessed();
+
+            return new GridModel { Data = ProcessedDataField, Total = (int)Total, Aggregates = AggregatesField };
+        }
+
+        #endregion
+
+        #region Helper Functions
+
+        protected abstract void EnsureDataSourceIsProcessed();
+
+        private static bool IsEmptyCommand(GridCommand command)
+        {
+            return command.PageSize == 0;
+        }
+
+        protected static Expression<Func<TSource, TProperty>> GetPropertyExpression<TSource, TProperty>(string propName)
+        {
+            return Reflector.GetPropertyExpression<TSource, TProperty>(propName);
+        }
+
+        protected static GridPropertyInfo GetGridPropertyInfo(string viewModelPropertyName)
+        {
+            var gridPropertyInfo = GridModelMapper.GetPropertyMap<TEntity, TViewModel>(viewModelPropertyName);
+
+            Guard.IfIsNull(gridPropertyInfo).Throw<PropertyNotFoundException>(typeof(TEntity), viewModelPropertyName);
+
+            return gridPropertyInfo;
+        }
+
+        protected static IEnumerable<AggregateDescriptor> BuildGroupAggregateDescriptors(IEnumerable<GroupDescriptor> groupDescriptors)
+        {
+            var aggregateDescriptors = new List<AggregateDescriptor>();
+
+            var aggregateDescriptor = new AggregateDescriptor();
+            aggregateDescriptor.Aggregates.AddRange(groupDescriptors.First().AggregateFunctions);
+            aggregateDescriptors.Add(aggregateDescriptor);
+
+            return aggregateDescriptors;
+        }
+
+        protected static List<IFilterDescriptor> BuildGroupFilterDescriptors(IGroup @group, IEnumerable<GroupDescriptor> groupDescriptors, IList<IFilterDescriptor> filterDescriptors)
+        {
+            var filters = filterDescriptors.ToList();
+
+            var groups = FlattenAggregateGroupsHierarchy(@group);
+
+            filters.AddRange(groupDescriptors.Select(groupDescriptor =>
+                new FilterDescriptor
+                {
+                    Member = groupDescriptor.Member,
+                    Operator = FilterOperator.IsEqualTo,
+                    Value = groups.Single(x => x.Member.Equals(groupDescriptor.Member)).Key
+                }).ToList());
+
+            return filters;
+        }
+
+        private static IEnumerable<CustomAggregateFunctionsGroup> FlattenAggregateGroupsHierarchy(IGroup firstGroup)
+        {
+            var aggregateFunctionsGroups = new List<CustomAggregateFunctionsGroup>();
+
+            if (firstGroup.HasSubgroups)
+            {
+                foreach (var subgroup in firstGroup.Subgroups)
+                {
+                    aggregateFunctionsGroups.AddRange(FlattenAggregateGroupsHierarchy(subgroup));
+                }
+            }
+
+            aggregateFunctionsGroups.Add((CustomAggregateFunctionsGroup)firstGroup);
+
+            return aggregateFunctionsGroups.DistinctBy(x => x.Member).ToList();
+        }
+
+        #endregion
+
+        #region ApplyDefaultSorting
+
+        protected static void ApplyDefaultSorting(GridCommand command, string defaultSortMemberName)
+        {
+            if (command.SortDescriptors.Any() || command.GroupDescriptors.Any())
+                return;
+
+            ProcessDefaultSortMemberName(ref defaultSortMemberName, GridModelMapper.ConfigurationObject.DefaultSortMemberNameType);
+
+            var defaultSortDirection = GridModelMapper.ConfigurationObject.DefaultSortDirection;
+
+            if (string.IsNullOrWhiteSpace(defaultSortMemberName) == false)
+            {
+                command.SortDescriptors.Add(
+                    new SortDescriptor { Member = defaultSortMemberName, SortDirection = defaultSortDirection });
+            }
+        }
+
+        private static void ProcessDefaultSortMemberName(ref string defaultSortMemberName, IDMemberName idMemberName)
+        {
+            if (string.IsNullOrWhiteSpace(defaultSortMemberName))
+            {
+                var prefixOrSuffix = GridModelMapper.ConfigurationObject.DefaultSortMemberNameOrPrefixOrSuffix;
+                switch (idMemberName)
+                {
+                    case IDMemberName.Id:
+                        defaultSortMemberName = "Id";
+                        break;
+                    case IDMemberName.EntityNamePlusId:
+                        defaultSortMemberName = string.Concat(typeof(TEntity).Name, "Id");
+                        break;
+                    case IDMemberName.IdPlusEntityName:
+                        defaultSortMemberName = string.Concat("Id", typeof(TEntity).Name);
+                        break;
+                    case IDMemberName.PrefixPlusEntityName:
+                        defaultSortMemberName = string.Concat(prefixOrSuffix, typeof(TEntity).Name);
+                        break;
+                    case IDMemberName.EntityNamePlusSuffix:
+                        defaultSortMemberName = string.Concat(typeof(TEntity).Name, prefixOrSuffix);
+                        break;
+                    case IDMemberName.FixedMemberName:
+                        defaultSortMemberName = GridModelMapper.ConfigurationObject.DefaultSortMemberNameOrPrefixOrSuffix;
+                        break;
+                    case IDMemberName.Guess:
+                        defaultSortMemberName = GuessDefaultSortMemberName();
+                        break;
+                }
+
+                if (string.IsNullOrWhiteSpace(defaultSortMemberName))
+                    defaultSortMemberName = GridModelMapper.ConfigurationObject.DefaultSortMemberNameFunc(typeof(TEntity));
+            }
+
+            defaultSortMemberName = Reflector.FixMemberPathCase<TEntity>(defaultSortMemberName);
+        }
+
+        private static string GuessDefaultSortMemberName()
+        {
+            var tmp = string.Empty;
+
+            ProcessDefaultSortMemberName(ref tmp, IDMemberName.Id);
+            if (string.IsNullOrWhiteSpace(tmp) == false) return tmp;
+
+            ProcessDefaultSortMemberName(ref tmp, IDMemberName.EntityNamePlusId);
+            if (string.IsNullOrWhiteSpace(tmp) == false) return tmp;
+
+            ProcessDefaultSortMemberName(ref tmp, IDMemberName.IdPlusEntityName);
+            if (string.IsNullOrWhiteSpace(tmp) == false) return tmp;
+
+            var properties = typeof(TEntity).GetProperties().Where(p => p.Name.StartsWith("Id") || p.Name.EndsWith("Id"));
+
+            var firstOrDefault = properties.FirstOrDefault();
+
+            return firstOrDefault != null ? firstOrDefault.Name : null;
+        }
+
+        #endregion
+
+        #region ApplyGrouping
+
+        protected IEnumerable ApplyGrouping(IEnumerable<TViewModel> data, IEnumerable<GroupDescriptor> groupDescriptors)
+        {
+            Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> selector = null;
+
+            foreach (var groupDescriptor in groupDescriptors.Reverse())
+            {
+                var propertyInfo = Reflector.GetMemberInfo<TViewModel>(groupDescriptor.Member).As<PropertyInfo>();
+
+                Guard.IfIsNull(propertyInfo).Throw<PropertyNotFoundException>(typeof(TViewModel), groupDescriptor.Member);
+
+                var addGroupExpressionMethod = Reflector.GetMemberName<BaseGridCustomBindingHelper<TEntity, TViewModel>>(x => x.AddGroupExpression<object>(null, null));
+
+                var method = GetType().GetMethod(
+                    addGroupExpressionMethod,
+                    BindingFlags.Instance | BindingFlags.NonPublic,
+                    Type.DefaultBinder,
+                    new[] { typeof(GroupDescriptor), typeof(Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>>) },
+                    null
+                    );
+
+                var genericMethod = method.MakeGenericMethod(new[] { propertyInfo.PropertyType });
+
+                selector = genericMethod.Invoke(this, new object[] { groupDescriptor, selector }) as
+                           Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>>;
+            }
+
+            if (selector != null)
+                return selector.Invoke(data).ToList();
+
+            return data;
+        }
+
+        // This method has to be protected so we can call it with reflection
+        // ReSharper disable MemberCanBePrivate.Global
+        protected Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> AddGroupExpression<TProperty>(GroupDescriptor groupDescriptor, Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> selector)
+        {
+            var exp = GetPropertyExpression<TViewModel, TProperty>(groupDescriptor.Member);
+            return AddGroupExpression(exp.Compile(), selector, groupDescriptor);
+        }
+        // ReSharper restore MemberCanBePrivate.Global
+        private Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> AddGroupExpression<TKey>(Func<TViewModel, TKey> propertySelector, Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> selector, GroupDescriptor groupDescriptor)
+        {
+            if (selector == null)
+                return items => BuildInnerGroup(items, propertySelector, groupDescriptor);
+            return BuildGroup(propertySelector, selector, groupDescriptor);
+        }
+
+        private Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> BuildGroup<TKey>(Func<TViewModel, TKey> groupSelector, Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> selectorBuilder, GroupDescriptor groupDescriptor)
+        {
+            var tempSelector = selectorBuilder;
+
+            return group => group.GroupBy(groupSelector)
+                .Select(g => new CustomAggregateFunctionsGroup
+                                {
+                                    Key = g.Key,
+                                    HasSubgroups = true,
+                                    Items = tempSelector.Invoke(g),
+                                    Member = groupDescriptor.Member,
+                                    Aggregates = ApplyGroupAggregates(g, groupDescriptor.AggregateFunctions, g.Key, groupDescriptor.Member)
+                                });
+        }
+
+        private IEnumerable<AggregateFunctionsGroup> BuildInnerGroup<TKey>(IEnumerable<TViewModel> group, Func<TViewModel, TKey> groupSelector, GroupDescriptor groupDescriptor)
+        {
+            return group.GroupBy(groupSelector)
+                .Select(g => new CustomAggregateFunctionsGroup
+                                {
+                                    Key = g.Key,
+                                    Items = g.ToList(),
+                                    Member = groupDescriptor.Member,
+                                    Aggregates = ApplyGroupAggregates(g, groupDescriptor.AggregateFunctions, g.Key, groupDescriptor.Member)
+                                });
+        }
+
+        private Dictionary<string, object> ApplyGroupAggregates(IEnumerable<TViewModel> enumerable, IEnumerable<AggregateFunction> aggregateFunctions, object groupKeyValue, string groupKeyName)
+        {
+            if (IgnoreGroupAggregatesField)
+                return new Dictionary<string, object>();
+
+            var aggregates = new Dictionary<string, AggregatesContainer>();
+            enumerable = enumerable.ToList();
+
+            foreach (var aggregateFunction in aggregateFunctions)
+            {
+                var viewModelProperty = aggregateFunction.SourceField;
+
+                switch (aggregateFunction.AggregateMethodName)
+                {
+                    case "Sum":
+                        aggregates.Push(viewModelProperty, enumerable.Sum(GetPropertyExpression<TViewModel, decimal?>(viewModelProperty).Compile()), AggregateMethod.Sum);
+                        break;
+                    case "Count":
+                        aggregates.Push(viewModelProperty, enumerable.Count(), AggregateMethod.Count);
+                        break;
+                    case "Average":
+                        aggregates.Push(viewModelProperty, enumerable.Average(GetPropertyExpression<TViewModel, decimal?>(viewModelProperty).Compile()), AggregateMethod.Average);
+                        break;
+                    case "Min":
+                        aggregates.Push(viewModelProperty, enumerable.Min(GetPropertyExpression<TViewModel, decimal?>(viewModelProperty).Compile()), AggregateMethod.Min);
+                        break;
+                    case "Max":
+                        aggregates.Push(viewModelProperty, enumerable.Max(GetPropertyExpression<TViewModel, decimal?>(viewModelProperty).Compile()), AggregateMethod.Max);
+                        break;
+                    case "Custom":
+                        if (_aggregateViewModelFunctionField != null)
+                            aggregates.Push(viewModelProperty, _aggregateViewModelFunctionField.Invoke(enumerable.AsQueryable()), AggregateMethod.Custom);
+                        else if (ExpressionCustomAggregateFunctionField != null)
+                        {
+                            var entities = EntitiesField != null
+                                ? EntitiesField.AsQueryable().Where(string.Format("{0} == @0", groupKeyName), groupKeyValue)
+                                : GridModelMapper.Map<TViewModel, TEntity>(enumerable).AsQueryable();
+                            aggregates.Push(viewModelProperty, SafeInvokeAggregateExpression(entities), AggregateMethod.Custom);
+                        }
+                        else if (InheritedClassCustomAggregateCalculatorField != null)
+                            aggregates.Push(viewModelProperty, InheritedClassCustomAggregateCalculatorField.GetValueFromGroup(enumerable, EntitiesField, groupKeyValue, groupKeyName), AggregateMethod.Custom);
+                        break;
+                }
+            }
+
+            return aggregates.ToDictionary(x => x.Key, x => (object)x.Value.GetValues());
+        }
+
+        protected object SafeInvokeAggregateExpression(IQueryable<TEntity> entities)
+        {
+            try
+            {
+                return ExpressionCustomAggregateFunctionField.Invoke(entities);
+            }
+            catch
+            {
+                return string.Empty;
+            }
+        }
+
+        public abstract class CustomAggregateFunctionBase
+        {
+            protected internal abstract object GetValueFromGroup(IEnumerable<TViewModel> viewModelsGroup, IList<TEntity> soruceEntities, object groupKeyValue, string groupKeyName);
+        }
+
+        #endregion
+
+        #region Fluent API
+
+        public BaseGridCustomBindingHelper<TEntity, TViewModel> UseCaseInsensitiveSearch(bool useCaseInsensitiveSearch = true)
+        {
+            UseCaseInsensitiveSearchField = useCaseInsensitiveSearch;
+            return this;
+        }
+
+        protected BaseGridCustomBindingHelper<TEntity, TViewModel> AddAggregateFunctionBase<TAggregateFunction>(Expression<Func<TViewModel, object>> property) where TAggregateFunction : CustomAggregateFunctionBase, new()
+        {
+            CheckAggregations();
+
+            InheritedClassCustomAggregateCalculatorField = ObjectFactory<TAggregateFunction>.Create();
+            CommandField.Aggregates.Create(property.GetPropertyPath(), null, AggregateMethod.Custom);
+            return this;
+        }
+
+        protected BaseGridCustomBindingHelper<TEntity, TViewModel> AddAggregateFunctionBase(Expression<Func<TViewModel, object>> property, Func<IQueryable<TEntity>, object> entitiesFunction, Func<IQueryable<TViewModel>, object> viewModelFunction = null)
+        {
+            CheckAggregations();
+
+            ExpressionCustomAggregateFunctionField = entitiesFunction;
+            _aggregateViewModelFunctionField = viewModelFunction;
+            CommandField.Aggregates.Create(property.GetPropertyPath(), null, AggregateMethod.Custom);
+            return this;
+        }
+
+        private bool _alreadyAddedAggregation;
+        private void CheckAggregations()
+        {
+            if (_alreadyAddedAggregation)
+                throw new NotSupportedException("Only one custom aggregation is supported at a time.");
+            _alreadyAddedAggregation = true;
+        }
+
+        /// <summary>
+        /// Does not calculate the aggregations of each group,
+        /// this is good if you only need aggregates in the grids footer area, not to mention that it
+        /// greatly reduces round trips in the database.
+        /// </summary>
+        public BaseGridCustomBindingHelper<TEntity, TViewModel> IgnoreGroupAggregates(bool ignoreGroupAggregates = true)
+        {
+            IgnoreGroupAggregatesField = ignoreGroupAggregates;
+            return this;
+        }
+
+        /// <summary>
+        /// Projects the ViewModel properties in the database query to return only the columns needed to perform the mapping.
+        /// </summary>
+        public BaseGridCustomBindingHelper<TEntity, TViewModel> UseProjections(bool useProjections = true)
+        {
+            UseProjectionsField = useProjections;
+            return this;
+        }
+
+        public BaseGridCustomBindingHelper<TEntity, TViewModel> UseProjections(Action<ProjectionsOptions<TEntity>> options)
+        {
+            ProjectionsOptions = new ProjectionsOptionsImpl<TEntity>();
+            options.Invoke(ProjectionsOptions);
+
+            UseProjectionsField = true;
+            return this;
+        }
+
+        public BaseGridCustomBindingHelper<TEntity, TViewModel> AcceptNullValuesWhenFiltering(bool acceptNullValuesWhenFiltering = true)
+        {
+            AcceptNullValuesWhenFilteringField = acceptNullValuesWhenFiltering;
+            return this;
+        }
+
+        #endregion
+    }
+}

TelerikMvcGridCustomBindingHelper/GridCustomBindingHelper.cs

     /// </summary>
     /// <typeparam name="TEntity">The type of your Entity class</typeparam>
     /// <typeparam name="TViewModel">The type of your ViewModel class</typeparam>
-    public class GridCustomBindingHelper<TEntity, TViewModel> : GridCustomBindingHelperBase<TEntity, TViewModel>
+    public class GridCustomBindingHelper<TEntity, TViewModel> : BaseGridCustomBindingHelper<TEntity, TViewModel>
         where TViewModel : class
         where TEntity : class
     {

TelerikMvcGridCustomBindingHelper/GridCustomBindingHelperBase.cs

-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-using System.Linq.Dynamic;
-using System.Linq.Expressions;
-using System.Reflection;
-using Telerik.Web.Mvc;
-using Telerik.Web.Mvc.Extensions;
-using Telerik.Web.Mvc.Infrastructure;
-using TelerikMvcGridCustomBindingHelper.Aggregates;
-using TelerikMvcGridCustomBindingHelper.Exceptions;
-using TelerikMvcGridCustomBindingHelper.Mapper;
-using TelerikMvcGridCustomBindingHelper.Projections;
-using TelerikMvcGridCustomBindingHelper.Util;
-
-namespace TelerikMvcGridCustomBindingHelper
-{
-    /// <summary>
-    /// Base class to make Helper classes to work with Telerik Grid for ASP.NET MVC using CustomBinding mode coupled with
-    /// some mapping techniques to convert Entities objects into ViewModels objects.
-    /// </summary>
-    /// <typeparam name="TEntity">The type of your Entity class</typeparam>
-    /// <typeparam name="TViewModel">The type of your ViewModel class</typeparam>
-    public abstract class GridCustomBindingHelperBase<TEntity, TViewModel>
-        where TViewModel : class
-        where TEntity : class
-    {
-        #region Properties
-
-        /// <summary>
-        /// Default sort member name.
-        /// </summary>
-        public string DefaultSortMemberName { get; set; }
-
-        /// <summary>
-        /// List of viewmodels, may be a set of groups and aggregates or a simple list.
-        /// </summary>
-        public IEnumerable ProcessedData
-        {
-            get
-            {
-                EnsureDataSourceIsProcessed();
-                return ProcessedDataField;
-            }
-        }
-
-        /// <summary>
-        /// IEnumerable of viewmodels without groups or aggregates.
-        /// </summary>
-        public IEnumerable<TViewModel> ViewModels
-        {
-            get
-            {
-                EnsureDataSourceIsProcessed();
-                return ViewModelsField;
-            }
-        }
-
-        /// <summary>
-        /// IEnumerable of entities from the database.
-        /// </summary>
-        public IEnumerable<TEntity> Entities
-        {
-            get
-            {
-                EnsureDataSourceIsProcessed();
-                return EntitiesField;
-            }
-        }
-
-        /// <summary>
-        /// Total number of records in the database.
-        /// </summary>
-        public long Total
-        {
-            get
-            {
-                EnsureDataSourceIsProcessed();
-                return TotalField;
-            }
-        }
-
-        #endregion
-
-        #region Fields
-
-        protected ProjectionsOptionsImpl<TEntity> ProjectionsOptions;
-        protected bool AcceptNullValuesWhenFilteringField;
-        protected bool UseProjectionsField;
-        protected bool IsDataSourceProcessed;
-        protected long TotalField;
-        protected IEnumerable ProcessedDataField;
-        protected IEnumerable<TViewModel> ViewModelsField;
-        protected IList<TEntity> EntitiesField;
-        protected readonly GridCommand CommandField;
-        protected Dictionary<string, object> AggregatesField;
-        protected CustomAggregateFunctionBase InheritedClassCustomAggregateCalculatorField;
-        protected bool IgnoreGroupAggregatesField;
-        protected Func<IQueryable<TEntity>, object> ExpressionCustomAggregateFunctionField;
-        private Func<IQueryable<TViewModel>, object> _aggregateViewModelFunctionField;
-        protected bool UseCaseInsensitiveSearchField;
-
-        #endregion
-
-        #region Constructors
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="GridCustomBindingHelperBase{TEntity,TViewModel}"/> class. 
-        /// Base constructor
-        /// </summary>
-        /// <param name="command">Telerik GridCommand containing informations about paging, filtering, sorting, aggregates</param>
-        /// <param name="defaultSortMemberName">Default sort member name for this entity</param>
-        protected GridCustomBindingHelperBase(GridCommand command, string defaultSortMemberName)
-        {
-            DefaultSortMemberName = defaultSortMemberName;
-
-            // Check if the command has been initialized
-            CommandField = !IsEmptyCommand(command) ? command : new GridCommand();
-        }
-
-        #endregion
-
-        #region Public Methods
-
-        /// <summary>
-        /// Create a new GridModel with the processed data and the total of records in the database
-        /// </summary>
-        /// <returns>GridModel</returns>
-        public GridModel BuildGridModel()
-        {
-            EnsureDataSourceIsProcessed();
-
-            return new GridModel { Data = ProcessedDataField, Total = (int)Total, Aggregates = AggregatesField };
-        }
-
-        #endregion
-
-        #region Helper Functions
-
-        protected abstract void EnsureDataSourceIsProcessed();
-
-        private static bool IsEmptyCommand(GridCommand command)
-        {
-            return command.PageSize == 0;
-        }
-
-        protected static Expression<Func<TSource, TProperty>> GetPropertyExpression<TSource, TProperty>(string propName)
-        {
-            return Reflector.GetPropertyExpression<TSource, TProperty>(propName);
-        }
-
-        protected static GridPropertyInfo GetGridPropertyInfo(string viewModelPropertyName)
-        {
-            var gridPropertyInfo = GridModelMapper.GetPropertyMap<TEntity, TViewModel>(viewModelPropertyName);
-
-            Guard.IfIsNull(gridPropertyInfo).Throw<PropertyNotFoundException>(typeof(TEntity), viewModelPropertyName);
-
-            return gridPropertyInfo;
-        }
-
-        protected static IEnumerable<AggregateDescriptor> BuildGroupAggregateDescriptors(IEnumerable<GroupDescriptor> groupDescriptors)
-        {
-            var aggregateDescriptors = new List<AggregateDescriptor>();
-
-            var aggregateDescriptor = new AggregateDescriptor();
-            aggregateDescriptor.Aggregates.AddRange(groupDescriptors.First().AggregateFunctions);
-            aggregateDescriptors.Add(aggregateDescriptor);
-
-            return aggregateDescriptors;
-        }
-
-        protected static List<IFilterDescriptor> BuildGroupFilterDescriptors(IGroup @group, IEnumerable<GroupDescriptor> groupDescriptors, IList<IFilterDescriptor> filterDescriptors)
-        {
-            var filters = filterDescriptors.ToList();
-
-            var groups = FlattenAggregateGroupsHierarchy(@group);
-
-            filters.AddRange(groupDescriptors.Select(groupDescriptor =>
-                new FilterDescriptor
-                {
-                    Member = groupDescriptor.Member,
-                    Operator = FilterOperator.IsEqualTo,
-                    Value = groups.Single(x => x.Member.Equals(groupDescriptor.Member)).Key
-                }).ToList());
-
-            return filters;
-        }
-
-        private static IEnumerable<CustomAggregateFunctionsGroup> FlattenAggregateGroupsHierarchy(IGroup firstGroup)
-        {
-            var aggregateFunctionsGroups = new List<CustomAggregateFunctionsGroup>();
-
-            if (firstGroup.HasSubgroups)
-            {
-                foreach (var subgroup in firstGroup.Subgroups)
-                {
-                    aggregateFunctionsGroups.AddRange(FlattenAggregateGroupsHierarchy(subgroup));
-                }
-            }
-
-            aggregateFunctionsGroups.Add((CustomAggregateFunctionsGroup)firstGroup);
-
-            return aggregateFunctionsGroups.DistinctBy(x => x.Member).ToList();
-        }
-
-        #endregion
-
-        #region ApplyDefaultSorting
-
-        protected static void ApplyDefaultSorting(GridCommand command, string defaultSortMemberName)
-        {
-            if (command.SortDescriptors.Any() || command.GroupDescriptors.Any())
-                return;
-
-            ProcessDefaultSortMemberName(ref defaultSortMemberName, GridModelMapper.ConfigurationObject.DefaultSortMemberNameType);
-
-            var defaultSortDirection = GridModelMapper.ConfigurationObject.DefaultSortDirection;
-
-            if (string.IsNullOrWhiteSpace(defaultSortMemberName) == false)
-            {
-                command.SortDescriptors.Add(
-                    new SortDescriptor { Member = defaultSortMemberName, SortDirection = defaultSortDirection });
-            }
-        }
-
-        private static void ProcessDefaultSortMemberName(ref string defaultSortMemberName, IDMemberName idMemberName)
-        {
-            if (string.IsNullOrWhiteSpace(defaultSortMemberName))
-            {
-                var prefixOrSuffix = GridModelMapper.ConfigurationObject.DefaultSortMemberNameOrPrefixOrSuffix;
-                switch (idMemberName)
-                {
-                    case IDMemberName.Id:
-                        defaultSortMemberName = "Id";
-                        break;
-                    case IDMemberName.EntityNamePlusId:
-                        defaultSortMemberName = string.Concat(typeof(TEntity).Name, "Id");
-                        break;
-                    case IDMemberName.IdPlusEntityName:
-                        defaultSortMemberName = string.Concat("Id", typeof(TEntity).Name);
-                        break;
-                    case IDMemberName.PrefixPlusEntityName:
-                        defaultSortMemberName = string.Concat(prefixOrSuffix, typeof(TEntity).Name);
-                        break;
-                    case IDMemberName.EntityNamePlusSuffix:
-                        defaultSortMemberName = string.Concat(typeof(TEntity).Name, prefixOrSuffix);
-                        break;
-                    case IDMemberName.FixedMemberName:
-                        defaultSortMemberName = GridModelMapper.ConfigurationObject.DefaultSortMemberNameOrPrefixOrSuffix;
-                        break;
-                    case IDMemberName.Guess:
-                        defaultSortMemberName = GuessDefaultSortMemberName();
-                        break;
-                }
-
-                if (string.IsNullOrWhiteSpace(defaultSortMemberName))
-                    defaultSortMemberName = GridModelMapper.ConfigurationObject.DefaultSortMemberNameFunc(typeof(TEntity));
-            }
-
-            defaultSortMemberName = Reflector.FixMemberPathCase<TEntity>(defaultSortMemberName);
-        }
-
-        private static string GuessDefaultSortMemberName()
-        {
-            var tmp = string.Empty;
-
-            ProcessDefaultSortMemberName(ref tmp, IDMemberName.Id);
-            if (string.IsNullOrWhiteSpace(tmp) == false) return tmp;
-
-            ProcessDefaultSortMemberName(ref tmp, IDMemberName.EntityNamePlusId);
-            if (string.IsNullOrWhiteSpace(tmp) == false) return tmp;
-
-            ProcessDefaultSortMemberName(ref tmp, IDMemberName.IdPlusEntityName);
-            if (string.IsNullOrWhiteSpace(tmp) == false) return tmp;
-
-            var properties = typeof(TEntity).GetProperties().Where(p => p.Name.StartsWith("Id") || p.Name.EndsWith("Id"));
-
-            var firstOrDefault = properties.FirstOrDefault();
-
-            return firstOrDefault != null ? firstOrDefault.Name : null;
-        }
-
-        #endregion
-
-        #region ApplyGrouping
-
-        protected IEnumerable ApplyGrouping(IEnumerable<TViewModel> data, IEnumerable<GroupDescriptor> groupDescriptors)
-        {
-            Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> selector = null;
-
-            foreach (var groupDescriptor in groupDescriptors.Reverse())
-            {
-                var propertyInfo = Reflector.GetMemberInfo<TViewModel>(groupDescriptor.Member).As<PropertyInfo>();
-
-                Guard.IfIsNull(propertyInfo).Throw<PropertyNotFoundException>(typeof(TViewModel), groupDescriptor.Member);
-
-                var addGroupExpressionMethod = Reflector.GetMemberName<GridCustomBindingHelperBase<TEntity, TViewModel>>(x => x.AddGroupExpression<object>(null, null));
-
-                var method = GetType().GetMethod(
-                    addGroupExpressionMethod,
-                    BindingFlags.Instance | BindingFlags.NonPublic,
-                    Type.DefaultBinder,
-                    new[] { typeof(GroupDescriptor), typeof(Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>>) },
-                    null
-                    );
-
-                var genericMethod = method.MakeGenericMethod(new[] { propertyInfo.PropertyType });
-
-                selector = genericMethod.Invoke(this, new object[] { groupDescriptor, selector }) as
-                           Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>>;
-            }
-
-            if (selector != null)
-                return selector.Invoke(data).ToList();
-
-            return data;
-        }
-
-        // This method has to be protected so we can call it with reflection
-        // ReSharper disable MemberCanBePrivate.Global
-        protected Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> AddGroupExpression<TProperty>(GroupDescriptor groupDescriptor, Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> selector)
-        {
-            var exp = GetPropertyExpression<TViewModel, TProperty>(groupDescriptor.Member);
-            return AddGroupExpression(exp.Compile(), selector, groupDescriptor);
-        }
-        // ReSharper restore MemberCanBePrivate.Global
-        private Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> AddGroupExpression<TKey>(Func<TViewModel, TKey> propertySelector, Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> selector, GroupDescriptor groupDescriptor)
-        {
-            if (selector == null)
-                return items => BuildInnerGroup(items, propertySelector, groupDescriptor);
-            return BuildGroup(propertySelector, selector, groupDescriptor);
-        }
-
-        private Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> BuildGroup<TKey>(Func<TViewModel, TKey> groupSelector, Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> selectorBuilder, GroupDescriptor groupDescriptor)
-        {
-            var tempSelector = selectorBuilder;
-
-            return group => group.GroupBy(groupSelector)
-                .Select(g => new CustomAggregateFunctionsGroup
-                                {
-                                    Key = g.Key,
-                                    HasSubgroups = true,
-                                    Items = tempSelector.Invoke(g),
-                                    Member = groupDescriptor.Member,
-                                    Aggregates = ApplyGroupAggregates(g, groupDescriptor.AggregateFunctions, g.Key, groupDescriptor.Member)
-                                });
-        }
-
-        private IEnumerable<AggregateFunctionsGroup> BuildInnerGroup<TKey>(IEnumerable<TViewModel> group, Func<TViewModel, TKey> groupSelector, GroupDescriptor groupDescriptor)
-        {
-            return group.GroupBy(groupSelector)
-                .Select(g => new CustomAggregateFunctionsGroup
-                                {
-                                    Key = g.Key,
-                                    Items = g.ToList(),
-                                    Member = groupDescriptor.Member,
-                                    Aggregates = ApplyGroupAggregates(g, groupDescriptor.AggregateFunctions, g.Key, groupDescriptor.Member)
-                                });
-        }
-
-        private Dictionary<string, object> ApplyGroupAggregates(IEnumerable<TViewModel> enumerable, IEnumerable<AggregateFunction> aggregateFunctions, object groupKeyValue, string groupKeyName)
-        {
-            if (IgnoreGroupAggregatesField)
-                return new Dictionary<string, object>();
-
-            var aggregates = new Dictionary<string, AggregatesContainer>();
-            enumerable = enumerable.ToList();
-
-            foreach (var aggregateFunction in aggregateFunctions)
-            {
-                var viewModelProperty = aggregateFunction.SourceField;
-
-                switch (aggregateFunction.AggregateMethodName)
-                {
-                    case "Sum":
-                        aggregates.Push(viewModelProperty, enumerable.Sum(GetPropertyExpression<TViewModel, decimal?>(viewModelProperty).Compile()), AggregateMethod.Sum);
-                        break;
-                    case "Count":
-                        aggregates.Push(viewModelProperty, enumerable.Count(), AggregateMethod.Count);
-                        break;
-                    case "Average":
-                        aggregates.Push(viewModelProperty, enumerable.Average(GetPropertyExpression<TViewModel, decimal?>(viewModelProperty).Compile()), AggregateMethod.Average);
-                        break;
-                    case "Min":
-                        aggregates.Push(viewModelProperty, enumerable.Min(GetPropertyExpression<TViewModel, decimal?>(viewModelProperty).Compile()), AggregateMethod.Min);
-                        break;
-                    case "Max":
-                        aggregates.Push(viewModelProperty, enumerable.Max(GetPropertyExpression<TViewModel, decimal?>(viewModelProperty).Compile()), AggregateMethod.Max);
-                        break;
-                    case "Custom":
-                        if (_aggregateViewModelFunctionField != null)
-                            aggregates.Push(viewModelProperty, _aggregateViewModelFunctionField.Invoke(enumerable.AsQueryable()), AggregateMethod.Custom);
-                        else if (ExpressionCustomAggregateFunctionField != null)
-                        {
-                            var entities = EntitiesField != null
-                                ? EntitiesField.AsQueryable().Where(string.Format("{0} == @0", groupKeyName), groupKeyValue)
-                                : GridModelMapper.Map<TViewModel, TEntity>(enumerable).AsQueryable();
-                            aggregates.Push(viewModelProperty, SafeInvokeAggregateExpression(entities), AggregateMethod.Custom);
-                        }
-                        else if (InheritedClassCustomAggregateCalculatorField != null)
-                            aggregates.Push(viewModelProperty, InheritedClassCustomAggregateCalculatorField.GetValueFromGroup(enumerable, EntitiesField, groupKeyValue, groupKeyName), AggregateMethod.Custom);
-                        break;
-                }
-            }
-
-            return aggregates.ToDictionary(x => x.Key, x => (object)x.Value.GetValues());
-        }
-
-        protected object SafeInvokeAggregateExpression(IQueryable<TEntity> entities)
-        {
-            try
-            {
-                return ExpressionCustomAggregateFunctionField.Invoke(entities);
-            }
-            catch
-            {
-                return string.Empty;
-            }
-        }
-
-        public abstract class CustomAggregateFunctionBase
-        {
-            protected internal abstract object GetValueFromGroup(IEnumerable<TViewModel> viewModelsGroup, IList<TEntity> soruceEntities, object groupKeyValue, string groupKeyName);
-        }
-
-        #endregion
-
-        #region Fluent API
-
-        public GridCustomBindingHelperBase<TEntity, TViewModel> UseCaseInsensitiveSearch(bool useCaseInsensitiveSearch = true)
-        {
-            UseCaseInsensitiveSearchField = useCaseInsensitiveSearch;
-            return this;
-        }
-
-        protected GridCustomBindingHelperBase<TEntity, TViewModel> AddAggregateFunctionBase<TAggregateFunction>(Expression<Func<TViewModel, object>> property) where TAggregateFunction : CustomAggregateFunctionBase, new()
-        {
-            CheckAggregations();
-
-            InheritedClassCustomAggregateCalculatorField = ObjectFactory<TAggregateFunction>.Create();
-            CommandField.Aggregates.Create(property.GetPropertyPath(), null, AggregateMethod.Custom);
-            return this;
-        }
-
-        protected GridCustomBindingHelperBase<TEntity, TViewModel> AddAggregateFunctionBase(Expression<Func<TViewModel, object>> property, Func<IQueryable<TEntity>, object> entitiesFunction, Func<IQueryable<TViewModel>, object> viewModelFunction = null)
-        {
-            CheckAggregations();
-
-            ExpressionCustomAggregateFunctionField = entitiesFunction;
-            _aggregateViewModelFunctionField = viewModelFunction;
-            CommandField.Aggregates.Create(property.GetPropertyPath(), null, AggregateMethod.Custom);
-            return this;
-        }
-
-        private bool _alreadyAddedAggregation;
-        private void CheckAggregations()
-        {
-            if (_alreadyAddedAggregation)
-                throw new NotSupportedException("Only one custom aggregation is supported at a time.");
-            _alreadyAddedAggregation = true;
-        }
-
-        /// <summary>
-        /// Does not calculate the aggregations of each group,
-        /// this is good if you only need aggregates in the grids footer area, not to mention that it
-        /// greatly reduces round trips in the database.
-        /// </summary>
-        public GridCustomBindingHelperBase<TEntity, TViewModel> IgnoreGroupAggregates(bool ignoreGroupAggregates = true)
-        {
-            IgnoreGroupAggregatesField = ignoreGroupAggregates;
-            return this;
-        }
-
-        /// <summary>
-        /// Projects the ViewModel properties in the database query to return only the columns needed to perform the mapping.
-        /// </summary>
-        public GridCustomBindingHelperBase<TEntity, TViewModel> UseProjections(bool useProjections = true)
-        {
-            UseProjectionsField = useProjections;
-            return this;
-        }
-
-        public GridCustomBindingHelperBase<TEntity, TViewModel> UseProjections(Action<ProjectionsOptions<TEntity>> options)
-        {
-            ProjectionsOptions = new ProjectionsOptionsImpl<TEntity>();
-            options.Invoke(ProjectionsOptions);
-
-            UseProjectionsField = true;
-            return this;
-        }
-
-        public GridCustomBindingHelperBase<TEntity, TViewModel> AcceptNullValuesWhenFiltering(bool acceptNullValuesWhenFiltering = true)
-        {
-            AcceptNullValuesWhenFilteringField = acceptNullValuesWhenFiltering;
-            return this;
-        }
-
-        #endregion
-    }
-}

TelerikMvcGridCustomBindingHelper/Mapper/IValueResolver.cs

 using System;
 using AutoMapper;
+using TelerikMvcGridCustomBindingHelper.Util;
 
 namespace TelerikMvcGridCustomBindingHelper.Mapper
 {
     {
         public object Resolve(ResultContext source)
         {
-            if (source.Value != null && !(source.Value is TSource))
+            object value = null;
+
+            if (!(source.Value is TSource))
+            {
+                value = Reflector.TryChangeType(source.Value, typeof (TSource), GridModelMapper.ConfigurationObject.DefaultCulture);
+                if (value == null && source.Value != null)
+                {
+                    throw new AutoMapperMappingException(string.Format("Value supplied is of type {0} but expected {1}.\nChange the value resolver source type, or redirect the source value supplied to the value resolver using FromMember.", typeof(TSource), source.Value.GetType()));
+                }
+            }
+
+            if (value != null && !(value is TSource))
             {
                 throw new AutoMapperMappingException(string.Format("Value supplied is of type {0} but expected {1}.\nChange the value resolver source type, or redirect the source value supplied to the value resolver using FromMember.", typeof(TSource), source.Value.GetType()));
             }
 
-            return ResolveCore((TSource)source.Value);
+            return ResolveCore((TSource)value);
         }
 
         protected abstract TDestination ResolveCore(TSource source);

TelerikMvcGridCustomBindingHelper/Mapper/ValueResolvers.cs

                 result = result || ContainsKey(ValueResolverKey.GlobalKey(valueType, gridPropertyInfo.PropertyType));
             }
 
+            if (result == false && (gridPropertyInfo.PropertyType != null && gridPropertyInfo.ViewModelPropertyType != null))
+            {
+                result = ContainsKey(ValueResolverKey.GlobalKey(gridPropertyInfo.ViewModelPropertyType, gridPropertyInfo.PropertyType));
+            }
+
             return result;
         }
 
 
             var tryGetValue = TryGetValue(ValueResolverKey.New(rc.EntityType, rc.ViewModelType, gridPropertyInfo), out valueResolver);
 
-            if (tryGetValue == false)
+            if (tryGetValue == false && value != null)
+            {
                 tryGetValue = TryGetValue(ValueResolverKey.GlobalKey(value.GetType(), gridPropertyInfo.PropertyType), out valueResolver);
+            }
+
+            if (tryGetValue == false && (gridPropertyInfo.PropertyType != null && gridPropertyInfo.ViewModelPropertyType != null))
+            {
+                tryGetValue = TryGetValue(ValueResolverKey.GlobalKey(gridPropertyInfo.ViewModelPropertyType, gridPropertyInfo.PropertyType), out valueResolver);
+            }
 
             return tryGetValue == false ? value : valueResolver.Resolve(rc);
         }
         public override bool Equals(object obj)
         {
             if (ReferenceEquals(null, obj)) return false;
-            if (obj.GetType() != typeof (ValueResolverKey)) return false;
-            return Equals((ValueResolverKey) obj);
+            if (obj.GetType() != typeof(ValueResolverKey)) return false;
+            return Equals((ValueResolverKey)obj);
         }
 
         public override int GetHashCode()
             {
                 var result = ToString().GetHashCode();
 
-                if (EntityType != null) result = (result*397) ^ EntityType.GetHashCode();
+                if (EntityType != null) result = (result * 397) ^ EntityType.GetHashCode();
 
-                if (ViewModelType != null) result = (result*397) ^ ViewModelType.GetHashCode();
+                if (ViewModelType != null) result = (result * 397) ^ ViewModelType.GetHashCode();
 
                 if (GridPropertyInfo != null)
                 {
                     if (GridPropertyInfo.PropertyPath != null)
-                        result = (result*397) ^ GridPropertyInfo.PropertyPath.GetHashCode();
+                        result = (result * 397) ^ GridPropertyInfo.PropertyPath.GetHashCode();
                     if (GridPropertyInfo.PropertyType != null)
-                        result = (result*397) ^ GridPropertyInfo.PropertyType.GetHashCode();
+                        result = (result * 397) ^ GridPropertyInfo.PropertyType.GetHashCode();
                 }
 
                 return result;

TelerikMvcGridCustomBindingHelper/TelerikMvcGridCustomBindingHelper.csproj

     <Compile Include="Util\ParameterToMemberExpressionRebinder.cs" />
     <Compile Include="Util\Contract.cs" />
     <Compile Include="Util\Extencions.cs" />
-    <Compile Include="GridCustomBindingHelperBase.cs" />
+    <Compile Include="BaseGridCustomBindingHelper.cs" />
     <Compile Include="DynamicQuery\Dynamic.cs" />
     <Compile Include="GridCustomBindingHelper.cs" />
     <Compile Include="GridQueryProvider.cs" />

Tests/BugFixes/Issue19.cs

 using Telerik.Web.Mvc;
 using TelerikMvcGridCustomBindingHelper;
 using TelerikMvcGridCustomBindingHelper.Mapper;
+using TelerikMvcGridCustomBindingHelper.Util;
 
 namespace Tests.BugFixes
 {
         }
 
         [Test]
-        public void Test1()
+        public void test1()
         {
             const decimal expected = 5049903980094673M;
 
             result2.Should().Be(expected);
         }
 
+        [Test]
+        public void test1_2()
+        {
+            const decimal expected = 5049903980094673M;
+
+            const long bigLong = 5049903980094673L;
+            object bigDouble = 5049903980094673D;
+
+            var result1 = Reflector.ChangeType(bigLong, typeof(decimal));
+            result1.Should().Be(expected);
+
+            var result2 = Reflector.ChangeType(bigDouble, typeof(decimal));
+            result2.Should().Be(expected);
+        }
+
         static readonly object[] Values =
             {
                 new object[] { 5049903980094673D, 5049903980094673M },
                 new object[] { 504990398.0094673D, 504990398.0094673M },
                 new object[] { 504990398.0094673M, 504990398.0094673M },
                 new object[] { 504990398.009, 504990398.009M },
-                new object[] { 5049903980094673.0D, 5049903980094673.0M },
+                new object[] { 5049903980094673.0D, 5049903980094673.0M }
             };
 
         [TestCaseSource("Values")]
-        public void Test2(object value, decimal expected)
+        public void test2(object value, decimal expected)
         {
             var val = Convert.ToDouble(value);
 
             result.Should().Be(expected);
         }
 
+        [TestCaseSource("Values")]
+        public void test2_2(object value, decimal expected)
+        {
+            var result = Reflector.ChangeType(value, typeof(decimal));
+
+            result.Should().Be(expected);
+        }
+
         [Test]
         public void Test3()
         {

Tests/DynamicWhereClause/DynamicWhereClauseTests.cs

         }
     }
 
-    public class ExpressionDynamicWhereClauseTests
+    public class ExpressionWhereClauseTests
     {
         [SetUp]
         public void SetUp()

Tests/FeatureRequests/ValueResolverTests.cs

     {
         private IList<Foo> _foos;
 
-        public class DecimalValueResolver : TelerikMvcGridCustomBindingHelper.Mapper.IValueResolver
+        class DecimalValueResolver : TelerikMvcGridCustomBindingHelper.Mapper.IValueResolver
         {
             public object Resolve(ResultContext source)
             {
-                return Convert.ToDecimal(source.Value);
+                return Convert.ToDecimal(source.Value) + 0.5m;
             }
         }
 
-        public class GlobalDecimalValueResolver : GlobalValueResolver<double, decimal>
+        class GlobalDecimalValueResolver : GlobalValueResolver<double, decimal>
         {
             protected override decimal ResolveCore(double source)
             {
-                return Convert.ToDecimal(source);
+                return Convert.ToDecimal(source) + 0.5m;
             }
         }
 
-        [TestFixtureSetUp]
+        [SetUp]
         public void TestFixtureSetUp()
         {
             Mapper.Reset();
             GridModelMapper.Reset();
 
-            GridModelMapper.CreateMap<Foo, FooModel>()
-                .MapProperty(x => x.ThresholdAmount, vm => vm.Threshold);//.WithResolver<DecimalValueResolver>();
-
-            GridModelMapper.Configuration.AddGlobalValueResolver<GlobalDecimalValueResolver>();
-
             _foos = Builder<Foo>.CreateListOfSize(30).All()
                 .TheFirst(10).With(foo => foo.ThresholdAmount = 10.5M)
                 .TheNext(10).With(foo => foo.ThresholdAmount = 20.5M)
                 .Build();
         }
 
-        [Test]
-        public void decimal_value_resolver_should_convert_a_double_into_a_decimal_on_queryover()
+        private static void BuildMaps(bool useGlobalValueResolver)
         {
+            if (useGlobalValueResolver)
+            {
+                GridModelMapper.CreateMap<Foo, FooModel>()
+                    .MapProperty(x => x.ThresholdAmount, vm => vm.Threshold);
+                GridModelMapper.Configuration.AddGlobalValueResolver<GlobalDecimalValueResolver>();
+            }
+            else
+            {
+                GridModelMapper.CreateMap<Foo, FooModel>()
+                    .MapProperty(x => x.ThresholdAmount, vm => vm.Threshold).WithOptions(options => options.SetValueResolver<DecimalValueResolver>());
+            }
+        }
+
+        [TestCase(true)]
+        [TestCase(false)]
+        public void decimal_value_resolver_should_convert_a_double_into_a_decimal_on_queryover(bool useGlobalValueResolver)
+        {
+            BuildMaps(useGlobalValueResolver);
+
             var queryOver = Substitute.For<IQueryOver<Foo, Foo>>();
 
             var command = new GridCommand { PageSize = 50 };
-            command.FilterDescriptors.Add(new FilterDescriptor("Threshold", FilterOperator.IsEqualTo, 20.5));
+            command.FilterDescriptors.Add(new FilterDescriptor("Threshold", FilterOperator.IsEqualTo, 20));
 
             new NHibernateGridCustomBindingHelper<Foo, FooModel>(command, queryOver).BuildGridModel();
 
             queryOver.RootCriteria.Received(1).Add(Arg.Is<ICriterion>(criterion => criterion.ToString() == string.Format("ThresholdAmount = Decimal@{0}(hash)", 20.5M.GetHashCode())));
         }
 
-        [Test]
-        public void decimal_value_resolver_should_convert_a_double_into_a_decimal_on_queryover_with_operator_isNotEqualTo()
+        [TestCase(true)]
+        [TestCase(false)]
+        public void decimal_value_resolver_should_convert_a_double_into_a_decimal_on_queryover_with_operator_isNotEqualTo(bool useGlobalValueResolver)
         {
+            BuildMaps(useGlobalValueResolver);
+
             var queryOver = Substitute.For<IQueryOver<Foo, Foo>>();
 
             var command = new GridCommand { PageSize = 50 };
-            command.FilterDescriptors.Add(new FilterDescriptor("Threshold", FilterOperator.IsNotEqualTo, 20.5));
+            command.FilterDescriptors.Add(new FilterDescriptor("Threshold", FilterOperator.IsNotEqualTo, 20));
 
             new NHibernateGridCustomBindingHelper<Foo, FooModel>(command, queryOver).BuildGridModel();
 
             queryOver.RootCriteria.Received(1).Add(Arg.Is<ICriterion>(criterion => criterion.ToString() == string.Format("not (ThresholdAmount = Decimal@{0}(hash))", 20.5M.GetHashCode())));
         }
 
-        [Test]
-        public void decimal_value_resolver_should_convert_a_double_into_a_decimal_on_queryable()
+        [TestCase(true)]
+        [TestCase(false)]
+        public void decimal_value_resolver_should_convert_a_double_into_a_decimal_on_queryable(bool useGlobalValueResolver)
         {
+            BuildMaps(useGlobalValueResolver);
+
             var queryable = _foos.AsQueryable();
 
             var command = new GridCommand { PageSize = 50 };
-            command.FilterDescriptors.Add(new FilterDescriptor("Threshold", FilterOperator.IsEqualTo, 20.5));
+            command.FilterDescriptors.Add(new FilterDescriptor("Threshold", FilterOperator.IsEqualTo, 20));
 
             var helper = new GridCustomBindingHelper<Foo, FooModel>(command, queryable);
 
             helper.Entities.Should().OnlyContain(foo => foo.ThresholdAmount == 20.5M);
         }
 
+        [TestCase(true)]
+        [TestCase(false)]
+        public void decimal_value_resolver_should_convert_a_double_into_a_decimal_on_queryable_with_operator_isNotEqualTo(bool useGlobalValueResolver)
+        {
+            BuildMaps(useGlobalValueResolver);
+
+            var queryable = _foos.AsQueryable();
+
+            var command = new GridCommand { PageSize = 50 };
+            command.FilterDescriptors.Add(new FilterDescriptor("Threshold", FilterOperator.IsNotEqualTo, 20));
+
+            var helper = new GridCustomBindingHelper<Foo, FooModel>(command, queryable);
+
+            helper.Entities.Should().HaveCount(20);
+            helper.Entities.Take(10).Should().OnlyContain(foo => foo.ThresholdAmount == 10.5M);
+            helper.Entities.Skip(10).Should().OnlyContain(foo => foo.ThresholdAmount == 30.5M);
+        }
+
+        #region Test models
+
         // ReSharper disable ClassNeverInstantiated.Global
         // ReSharper disable MemberCanBePrivate.Global
         // ReSharper disable UnusedAutoPropertyAccessor.Global
         {
             public double Threshold { get; set; }
         }
+
         // ReSharper restore UnusedAutoPropertyAccessor.Global
         // ReSharper restore MemberCanBePrivate.Global
         // ReSharper restore ClassNeverInstantiated.Global
+
+        #endregion
     }
 }
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.