This is a migrated thread and some comments may be shown as answers.

[Solved] how to use CustomBinding with ViewModel

26 Answers 622 Views
Grid
This is a migrated thread and some comments may be shown as answers.
This question is locked. New answers and comments are not allowed.
Alexandre Jobin
Top achievements
Rank 1
Alexandre Jobin asked on 17 Feb 2011, 08:00 PM
hi!

i need to use the CustomBinding to search the Database first and then let the Grid do his work. Since that im using a ViewModel with my grid, i need to convert the result to my ViewModel but im not sure how. Here's what i do:

public GridModel GetBesoinIndexGridViewModel(GridCommand command, string searchKeyword, EtatPublication etatPublication)
{
    // Get a IQueryable of objects filtered by a keyword
    var publicationsIntervenants = this.publicationIntervenantRepository.SearchForBesoin(searchKeyword);
 
    // Apply the grid commands to the data. Everything will be executed on the Database side
    var gridModel = Telerik.Web.Mvc.Extensions.QueryableExtensions.ToGridModel(publicationsIntervenants,
            command.Page,
            command.PageSize,
            command.SortDescriptors,
            command.FilterDescriptors,
            command.GroupDescriptors);
 
    // Convert data to ModelView
    // how???
 
    // Return gridViewModel
    return gridModel;
}

so as you can see, i try to executed everything on the database side and i want to finish be taking the result and convert it to my ViewModel. How can i do that if i have GroupDescriptors or not?

thank you very much for the help!

alex



26 Answers, 1 is accepted

Sort by
0
Alexandre Jobin
Top achievements
Rank 1
answered on 22 Feb 2011, 07:20 PM
no idea on this one?
0
Graeme
Top achievements
Rank 1
answered on 26 Feb 2011, 06:52 AM
Yeah, I have the same issue.

I want to get an IQuerayble from my repository (actually I'd rather get an IEnumerable as  I don't want to expose IQueraybles). I then want to filter etc based on the commands properties and then use AutoMapper to take a strongly typed IEnumberable<TModel> to map the result to a IEnumberable<TViewModel>.

I want all the filtering features,grouping, paging features etc that you get with the IQueryable extension ToGridModel. I need to make at least 40 grid pages. Having to implement a custom binding helper for every grid seems like a lot more effort than it should be.

Is there anyway just to use the part of the code that implements the sorting/grouping/etc?

Anyone got any ideas?

Thanks

Graeme

0
Alexandre Jobin
Top achievements
Rank 1
answered on 28 Feb 2011, 06:09 PM
Presently, this is what i use to minimize the code that i have to write. Everything work except the Grouping logic that i don't know how to implement correctly. This is the reason of this discussion!

With this code:
  • Filtering, sorting, paging are made on the database side. That way, the database only return what you really need to have.
  • The repository return an IQueryable. I havent found another way to do it without that.
  • The repository use the Include function to be sure that you don't make any Select N + 1
  • The grid use a ViewModel



Base class
public abstract class TelerikGridCustomBindingExtensions<TEntity>
{
    public IQueryable<TEntity> ApplyFiltering(IQueryable<TEntity> data, IList<IFilterDescriptor> filterDescriptors)
    {
        if (filterDescriptors.Any())
        {
            data = data.Where(ExpressionBuilder.Expression<TEntity>(filterDescriptors));
        }
 
        return data;
    }

    public IQueryable<TEntity> ApplyPaging(IQueryable<TEntity> data, int currentPage, int pageSize)
    {
        if (pageSize > 0 && currentPage > 0)
        {
            data = data.Skip((currentPage - 1) * pageSize);
        }
 
        data = data.Take(pageSize);
 
        return data;
    }
 
    public IQueryable<TEntity> ApplySorting(IQueryable<TEntity> data, IList<GroupDescriptor> groupDescriptors, IList<SortDescriptor> sortDescriptors)
    {
        if (groupDescriptors.Any())
        {
            foreach (var groupDescriptor in groupDescriptors.Reverse())
            {
                data = AddSortExpression(data, groupDescriptor.SortDirection, groupDescriptor.Member);
            }
        }
        if (sortDescriptors.Any())
        {
            foreach (SortDescriptor sortDescriptor in sortDescriptors)
            {
                data = AddSortExpression(data, sortDescriptor.SortDirection, sortDescriptor.Member);
            }
        }
        return data;
    }
 
    protected abstract IQueryable<TEntity> AddSortExpression(IQueryable<TEntity> data, ListSortDirection sortDirection, string memberName);
}



Implemented class (this is my binding logic for each grid that i have)
public class BesoinIndexGridViewModelHelper : TelerikGridCustomBindingExtensions<PublicationIntervenant>
{
    protected override IQueryable<PublicationIntervenant> AddSortExpression(IQueryable<PublicationIntervenant> data, System.ComponentModel.ListSortDirection sortDirection, string memberName)
    {
        if (sortDirection == ListSortDirection.Ascending)
        {
            switch (memberName)
            {
                case "Code":
                    data = data.OrderBy(x => x.Publication.Code);
                    break;
 
                case "IntervenantCode":
                    data = data.OrderBy(x => x.Intervenant.Code);
                    break;
 
                default:
                    throw new NotImplementedException();
            }
        }
        else
        {
            switch (memberName)
            {
                case "Code":
                    data = data.OrderByDescending(x => x.Publication.Code);
                    break;
 
                case "IntervenantCode":
                    data = data.OrderByDescending(x => x.Intervenant.Code);
                    break;
 
                default:
                    throw new NotImplementedException();
            }
        }
 
        return data;
    }
}


My private function to apply the custom binding
private GridModel<ViewModels.BesoinIndexGridViewModel> GetBesoinIndexGridViewModel(GridCommand command, string searchKeyword, int anneeInventaire)
{
    var gridHelper = new Helpers.BesoinIndexGridViewModelHelper();
 
    // get the data needed for the grid
    var publicationsIntervenants = this.publicationIntervenantRepository.SearchForBesoin(searchKeyword, anneeInventaire);
 
    ////Apply filtering
    publicationsIntervenants = gridHelper.ApplyFiltering(publicationsIntervenants, command.FilterDescriptors);
    var count = publicationsIntervenants.Count();
 
    //Apply sorting
    if (!command.SortDescriptors.Any() && !command.GroupDescriptors.Any())
    {
        command.SortDescriptors.Add(new SortDescriptor() { Member = "IntervenantCode", SortDirection = ListSortDirection.Ascending });
    }
 
    publicationsIntervenants = gridHelper.ApplySorting(publicationsIntervenants, command.GroupDescriptors, command.SortDescriptors);
 
    //Apply paging
    publicationsIntervenants = gridHelper.ApplyPaging(publicationsIntervenants, command.Page, command.PageSize);
 
    var gridView = from publicationIntervenant in publicationsIntervenants.AsEnumerable()
             select Mappers.BesoinIndexGridViewModelMapper.Map(publicationIntervenant);
 
    var gridModel = new GridModel<ViewModels.BesoinIndexGridViewModel>()
    {
        Data = gridView,
        Total = count
    };
 
    return gridModel;
}


ActionResult function (for the first call to the web page)
public ActionResult Index()
{
    var gridCommand = new GridCommand();
    gridCommand.Page = 1;
    gridCommand.PageSize = 10;
 
    var model = new ViewModels.BesoinIndexViewModel()
    {
        BesoinGridData = GetBesoinIndexGridViewModel(gridCommand, string.Empty, 2010),
        AnneeInventaire = 2010
    };
 
    return View(model);
}


ActionResult function (for the grid ajax calls like changing pages, changing the sort order, add a filter, etc...)
[GridAction(EnableCustomBinding = true)]
public ActionResult GetBesoinIndexGridData(GridCommand command, string searchKeyword, int anneeInventaire)
{
    return View(GetBesoinIndexGridViewModel(command, searchKeyword, anneeInventaire));
}


Repository function SearchForBesoin
public IQueryable<PublicationIntervenant> SearchForBesoin(string keyword, int anneeInventaire)
{
    var q = from publicationIntervenant in this.ObjectSet
                .Include(x => x.Intervenant)
                .Include(x => x.Publication)
                .Include(x => x.Publication.PublicationsCommandes.Single().Commande)
                .Include(x => x.Publication.PublicationsCommandes.Single().Bordereaux)
                .Include(x => x.Publication.Transactions)
            where publicationIntervenant.TypeIntervenantId == (int)TypesIntervenant.Client &&
                    publicationIntervenant.Publication.Transactions.Any(x => x.AnneeInventaire == anneeInventaire) &&
                    (publicationIntervenant.Publication.Titre.Contains(keyword) ||
                    publicationIntervenant.Publication.Code.Contains(keyword) ||
                    publicationIntervenant.Intervenant.Nom.Contains(keyword) ||
                    publicationIntervenant.Intervenant.Code.Contains(keyword))
            select publicationIntervenant;
 
    return q;
}
0
Alexandre Jobin
Top achievements
Rank 1
answered on 18 Mar 2011, 02:25 PM
i think i've found the way to make the Grouping feature work with ViewModel. But for this, i had to modify the source code of the Grid. The workflow for this is:
  1. In the Controller, you will do everything on the database side except the grouping logic
  2. You then convert the result from your Model into a ViewModel
  3. You return a typed GridModel
  4. With the modified source code, it will only apply the Grouping feature if needed and then show the result

Heres the modifications for the Telerik source code:

Telerik.Web.Mvc\UI\Grid\GridDataProcessor.cs
(the modified code is in bold)
private void EnsureDataSourceIsProcessed()
{
    if (dataSourceIsProcessed)
    {
        return;
    }
 
    if (bindingContext.DataSource == null)
    {
        dataSourceIsProcessed = true;
        return;
    }
 
    if (!bindingContext.EnableCustomBinding)
    {
        GridModel model;
        var dataTableEnumerable = bindingContext.DataSource as GridDataTableWrapper;
        if (dataTableEnumerable != null)
        {
            model = dataTableEnumerable.ToGridModel(CurrentPage, bindingContext.PageSize, SortDescriptors, FilterDescriptors, GroupDescriptors);                   
        }
        else
        {
            IQueryable dataSource = bindingContext.DataSource.AsQueryable();
            model = dataSource.ToGridModel(CurrentPage, bindingContext.PageSize, SortDescriptors, FilterDescriptors, GroupDescriptors);                   
        }
        totalCount = model.Total;
        processedDataSource = model.Data.AsGenericEnumerable();
    }
    else
    {
        GridModel model;
        IQueryable dataSource = bindingContext.DataSource.AsQueryable();
        model = dataSource.ToGridModel(SortDescriptors, GroupDescriptors);
        processedDataSource = model.Data.AsGenericEnumerable();
 
        totalCount = bindingContext.Total;
    }
 
    dataSourceIsProcessed = true;
}


Telerik.Web.Mvc\Extensions\QueryableExtensions.cs
(add this function)
public static GridModel ToGridModel(this IQueryable queryable, IList<SortDescriptor> sortDescriptors, IEnumerable<GroupDescriptor> groupDescriptors)
{
    IQueryable data = queryable;
 
    GridModel result = new GridModel();
 
    result.Total = data.Count();
    IList<SortDescriptor> temporarySortDescriptors = new List<SortDescriptor>();
 
    if (!sortDescriptors.Any() && queryable.Provider.IsEntityFrameworkProvider())
    {
        // The Entity Framework provider demands OrderBy before calling Skip.
        SortDescriptor sortDescriptor = new SortDescriptor
        {
            Member = queryable.ElementType.FirstSortableProperty()
        };
        sortDescriptors.Add(sortDescriptor);
        temporarySortDescriptors.Add(sortDescriptor);
    }
 
    if (groupDescriptors.Any())
    {
        groupDescriptors.Reverse().Each(groupDescriptor =>
        {
            SortDescriptor sortDescriptor = new SortDescriptor
            {
                Member = groupDescriptor.Member,
                SortDirection = groupDescriptor.SortDirection
            };
 
            sortDescriptors.Insert(0, sortDescriptor);
            temporarySortDescriptors.Add(sortDescriptor);
        });
    }
 
    if (sortDescriptors.Any())
    {
        data = data.Sort(sortDescriptors);
    }
 
    if (groupDescriptors.Any())
    {
        data = data.GroupBy(groupDescriptors);
    }
 
    result.Data = data;
 
    temporarySortDescriptors.Each(sortDescriptor => sortDescriptors.Remove(sortDescriptor));
 
    return result;
}


And for the Controller code, just look at my last post. I have changed some lines to reflect some changes.
I hope someone from the Telerik team will see this post and give us some feedback. Probably there's a better way for doing this!
0
frederic rybkowski
Top achievements
Rank 1
answered on 18 Mar 2011, 04:49 PM
Hi Alexandre,

Not tested, but sounds good.
Hope we will have some feedback from Telerik Dev Team.....
0
Alexandre Jobin
Top achievements
Rank 1
answered on 28 Apr 2011, 03:39 PM
Message to the Telerik Dev Team,

is it possible to answer this thread please? no matter if its possitive or negative but at least give us news of what you think on this one!

thank you and keep the good work!

alex
0
Atanas Korchev
Telerik team
answered on 28 Apr 2011, 05:01 PM
Hi Alexandre Jobin,

Telerik reserves the right not to answer all forum threads posted in the MVC forum. This is outlined in our licensing FAQ (in the "Are the support packages different for the different licenses?" section). 

Having said that  I don't really understand what the discussed problem is. Converting from db model to view model should be as simple as running another select linq method:

var viewModel = queryable.Select(db => new ViewModel { Prop1 = db.Prop1, Prop2 = db.Prop2 });

I have blog post showing how to use view models with the mvc grid.

Our custom binding example also shows how to implement grouping. 

Frankly I don't see a need to modify our source code.

Regards,


Atanas Korchev
the Telerik team
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
0
Alexandre Jobin
Top achievements
Rank 1
answered on 28 Apr 2011, 06:51 PM
hi Atanas! i didnt know about the licensing difference. Sorry! But this thread was created not only to ask you questions but also to try to give you some code to help you with your product since that you are open source. This is a way to give you something in exchange of your great product!

in answer to your message, the problem with ViewModel is still there and heres why:

  • if we take your blog post, you actually retreive all the data from the database and then you apply the filters/sorting/grouping algorithm. The solution work, that's true, but the filters are not executed on the database side. You are returning too much data from the database to maybe only show 10 items from it.
  • on the custom binding example, everything is executed on the database side. Great!! But if you need to use a ViewModel, you will have difficulties to convert the grouped data to your ViewModel before returning it to your GridModel.

so this thread, and also this one from another guy, talk about this problem: by using the CustomBinding attribute, how can i return a ViewModel to the grid where the data have been filtered, sorted and grouped on the database side.

thank a lot!

alex
0
Atanas Korchev
Telerik team
answered on 29 Apr 2011, 08:02 AM
Hello Alexandre Jobin,

 Why do you think that I am retrieving all data in the blog post? This code clearly does not do that:

private IEnumerable<OrderViewModel> GetOrders()
{
    NorthwindDataContext northwind = new NorthwindDataContext();
 
    return from o in northwind.Orders
           select new OrderViewModel
           {
               OrderID = o.OrderID,
               ContactName = o.Customer.ContactName,
               ShipAddress = o.ShipAddress,
               OrderDate = o.OrderDate
           };
}

As you can see I am not calling ToList(), Count() or anything else which will execute the query and fill the result. 

I still don't think there is any problem with using a view model with custom binding. If you think there is please attach a sample project which shows that.

Regards,
Atanas Korchev
the Telerik team
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
0
Alexandre Jobin
Top achievements
Rank 1
answered on 29 Apr 2011, 03:20 PM
You're right when you say that your example will apply the filters/sorting/grouping on the database side. I have verified why it doesnt work on my side and its because i need to apply some logic inside the ViewModel. Heres an example:

private IEnumerable<OrderViewModel> GetOrders()
{
    NorthwindDataContext northwind = new NorthwindDataContext();
  
    return from o in northwind.Orders
           select new OrderViewModel
           {
               OrderID = o.OrderID,
               ContactName = string.Format({0}. {1}. o.Customer.LastName, o.Customer.FirstName),
               ShipAddress = o.ShipAddress,
               OrderDate = o.OrderDate,
               CustomerRating = CalculateCustomerRating(o.Customer)
           };
}

as soon as you are using logic inside the query, you will loose the posibilities to use ViewModel. You will get this kind of errors:
  • LINQ to Entities does not recognize the method 'System.String Format(System.String, System.Object, System.Object)' method, and this method cannot be translated into a store expression.

  • LINQ to Entities does not recognize the method 'Int32 CalculateCustomerRating(Customer)' method, and this method cannot be translated into a store expression


maybe that there's an easy solution to this problem and that im really blind! But for now, the only solution that i've found is to apply the filters/sorting/grouping wihout using the ViewModel (everything is executed on the DB side) and then convert the result in ViewModel. Everything was working find but when i use grouping, i didnt find a solution to convert it in ViewModel!

alex
0
Atanas Korchev
Telerik team
answered on 02 May 2011, 08:35 AM
Hello Alexandre Jobin,

 You need to process the returned result after applying the groups:

private void Convert(IEnumerable<AggregateFunctionsGroup> groups)
{
    foreach (var group in groups)
    {
        if (group.HasSubgroups)
        {
            Convert((IEnumerable<AggregateFunctionsGroup>)group.Items);
        }
        else
        {
            group.Items = group.Items.Cast<Order>().Select(o => new OrderViewModel
            {
                OrderDate = o.OrderDate,
                OrderID = o.OrderID,
                ShipAddress = string.Format("{0}", o.ShipAddress)
            });
        }
    }
}

I created a sample project to demonstrate this approach.

Greetings,
Atanas Korchev
the Telerik team
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
0
Alexandre Jobin
Top achievements
Rank 1
answered on 02 May 2011, 07:40 PM
i had some troubles to make it work since that i was with the version 2011.1.315 and i had an error when i was using the grouping feature. I have updated my project with the 2011.1.414 that came with your example and everything work fine now.

thank you very much for your help on this one! i really appeciate it! it will probably help others too!

alex
0
Alexandre Jobin
Top achievements
Rank 1
answered on 02 May 2011, 07:53 PM
just a little note, with this code, i loose the possibility to initialize the grid like that:

Html.Telerik().Grid<CustomGroupingAndViewModel.Models.OrderViewModel>(Model.GridView.Data)

Because the Model.GridView in your example is not typed with OrderViewModel!
0
Atanas Korchev
Telerik team
answered on 03 May 2011, 07:17 AM
Hello Alexandre Jobin,

 I just checked my example and the grid is declared like this:

<%= Html.Telerik().Grid<CustomGroupingAndViewModel.Models.OrderViewModel>()


which clearly indicates it is of the OrderViewModel type. I really don't understand what the problem is. 

Regards,
Atanas Korchev
the Telerik team
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
0
Alexandre Jobin
Top achievements
Rank 1
answered on 03 May 2011, 02:05 PM
Hello Atanas,

the problem appear when you want that to initialized the grid with data the first time the page is called (server side mode). In my project, we don't want to show the page and then have the grid send an ajax call to get the data. We want the page to show with the grid already initialized with data and then if the user want to change the page/filters/groups, it will be with ajax call.

So in the HomeController/Index, you need to pass the grid data and then use
<%= Html.Telerik().Grid<CustomGroupingAndViewModel.Models.OrderViewModel>(Model.GridModel.Data)

but since that the grid is of type IEnumerable<CustomGroupingAndViewModel.Models.OrderViewModel> and my data is of type IEnumerable, i can't do it that way!

i have attached an example of what i try to do.
0
Atanas Korchev
Telerik team
answered on 03 May 2011, 02:27 PM
Hi Alexandre Jobin,

 Our custom server binding example shows how to deal with this problem:

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<IEnumerable>" %>
<asp:Content contentPlaceHolderID="MainContent" runat="server">
<%= Html.Telerik().Grid<Order>()
        .Name("Grid")
        .BindTo(Model)

Regards,
Atanas Korchev
the Telerik team
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
0
Alexandre Jobin
Top achievements
Rank 1
answered on 04 May 2011, 04:59 PM
everything seems to work now. Thank you very much!
i have updated your helper so we have less duplication code. Here is what i have if it can be useful so someone. I guest that we do it better than this but at least, you don't have to repeat yourself in the AddSortExpression & AddGroupExpression.

Controller
private GridModel GetBesoinIndexGridViewModel(GridCommand command, string searchKeyword, int anneeInventaire, int anneePrevision)
{
    // get the data needed for the grid
    var publicationsIntervenants = this.publicationIntervenantRepository.SearchForBesoin(searchKeyword, anneeInventaire);
 
    var gridHelper = new Helpers.BesoinIndexGridViewModelHelper(command, publicationsIntervenants);
 
    //Convert Entity to ViewModel
    var data = Mappers.BesoinIndexGridViewModelMapper.Map(gridHelper.ProcessedDataSource, anneeInventaire, anneePrevision);
 
    var gridModel = new GridModel();
 
    if (command.GroupDescriptors.Any())
    {
        var groups = gridHelper.ApplyGrouping(data.AsQueryable(), command.GroupDescriptors);
 
        gridModel.Data = groups;
    }
    else
    {
        gridModel.Data = data;
    }
 
    gridModel.Total = gridHelper.Total;
    return gridModel;
}


GridHelper
public class BesoinIndexGridViewModelHelper : GridCustomBindingBase<PublicationIntervenant, BesoinIndexGridViewModel>
{
    public BesoinIndexGridViewModelHelper(GridCommand command, IQueryable<PublicationIntervenant> dataSource)
        : base(command, dataSource)
    {
    }
 
    protected override Func<IEnumerable<BesoinIndexGridViewModel>, IEnumerable<AggregateFunctionsGroup>> AddGroupExpression(IQueryable<BesoinIndexGridViewModel> data, GroupDescriptor groupDescriptor, Func<IEnumerable<BesoinIndexGridViewModel>, IEnumerable<AggregateFunctionsGroup>> selector)
    {
        switch (groupDescriptor.Member)
        {
            case "Publication":
                return AddGroupExpression(data, x => x.Publication, selector);
            case "Intervenant":
                return AddGroupExpression(data, x => x.Intervenant, selector);
            default:
                throw new NotSupportedException();
        }
    }
 
    protected override IQueryable<PublicationIntervenant> AddSortExpression(IQueryable<PublicationIntervenant> data, string memberName, ListSortDirection sortDirection, bool isFirst)
    {
        switch (memberName)
        {
            case "Publication":
                return AddSortExpression(data, x => x.Publication.Titre, sortDirection, isFirst);
            case "Intervenant":
                return AddSortExpression(data, x => x.Intervenant.Nom, sortDirection, isFirst);
            default:
                throw new NotSupportedException();
        }
    }
}


Base class
public abstract class GridCustomBindingBase<TEntity, TViewModel>
{
    private bool dataSourceIsProcessed;
    private int totalCount;
    private IEnumerable<TEntity> processedDataSource;
    private IQueryable<TEntity> dataSource;
    private GridCommand command;
 
    public GridCustomBindingBase(GridCommand command, IQueryable<TEntity> dataSource)
    {
        this.dataSource = dataSource;
        this.command = command;
    }
 
    public int Total
    {
        get
        {
            EnsureDataSourceIsProcessed();
            return this.totalCount;
        }
    }
 
    public IEnumerable<TEntity> ProcessedDataSource
    {
        get
        {
            EnsureDataSourceIsProcessed();
            return processedDataSource;
        }
    }
 
    private void EnsureDataSourceIsProcessed()
    {
        if (dataSourceIsProcessed)
        {
            return;
        }
 
        if (this.dataSource == null)
        {
            dataSourceIsProcessed = true;
            return;
        }
 
        IQueryable<TEntity> data = this.dataSource;
 
        data = this.ApplyFiltering(data, this.command.FilterDescriptors);
 
        this.totalCount = data.Count();
 
        data = this.ApplySorting(data, command.GroupDescriptors, command.SortDescriptors);
        data = this.ApplyPaging(data, command.Page, command.PageSize);
 
        this.processedDataSource = data;
        this.dataSourceIsProcessed = true;
    }
 
    private IQueryable<TEntity> ApplyFiltering(IQueryable<TEntity> data, IList<IFilterDescriptor> filterDescriptors)
    {
        if (filterDescriptors.Any())
        {
            data = data.Where(ExpressionBuilder.Expression<TEntity>(filterDescriptors));
        }
 
        return data;
    }
 
    private IQueryable<TEntity> ApplyPaging(IQueryable<TEntity> data, int currentPage, int pageSize)
    {
        if (pageSize > 0 && currentPage > 0)
        {
            data = data.Skip((currentPage - 1) * pageSize);
        }
 
        data = data.Take(pageSize);
 
        return data;
    }
 
    private IQueryable<TEntity> ApplySorting(IQueryable<TEntity> data, IList<GroupDescriptor> groupDescriptors, IList<SortDescriptor> sortDescriptors)
    {
        if (groupDescriptors.Any())
        {
            foreach (var groupDescriptor in groupDescriptors.Reverse())
            {
                SortDescriptor sortDescriptor = new SortDescriptor
                {
                    Member = groupDescriptor.Member,
                    SortDirection = groupDescriptor.SortDirection
                };
 
                sortDescriptors.Insert(0, sortDescriptor);
            };
        }
 
        if (sortDescriptors.Any())
        {
            bool isFirst = true;
 
            foreach (SortDescriptor sortDescriptor in sortDescriptors)
            {
                data = AddSortExpression(data, sortDescriptor.Member, sortDescriptor.SortDirection, isFirst);
                isFirst = false;
            }
        }
 
        return data;
    }
 
    public IEnumerable ApplyGrouping(IQueryable<TViewModel> data, IList<GroupDescriptor> groupDescriptors)
    {
        Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> selector = null;
 
        foreach (GroupDescriptor groupDescriptor in groupDescriptors.Reverse())
        {
            selector = AddGroupExpression(data, groupDescriptor, selector);
        }
 
        return selector.Invoke(data).ToList();
    }
 
    protected Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> BuildGroup<T>(Func<TViewModel, T> groupSelector, Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> selectorBuilder)
    {
        var tempSelector = selectorBuilder;
 
        return g => g.GroupBy(groupSelector)
                        .Select(c => new AggregateFunctionsGroup
                        {
                            Key = c.Key,
                            HasSubgroups = true,
                            Items = tempSelector.Invoke(c)
                        });
    }
 
    protected IEnumerable<AggregateFunctionsGroup> BuildInnerGroup<T>(IEnumerable<TViewModel> group, Func<TViewModel, T> groupSelector)
    {
        return group.GroupBy(groupSelector)
                .Select(i => new AggregateFunctionsGroup
                {
                    Key = i.Key,
                    Items = i.ToList()
                });
    }
 
    protected Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> AddGroupExpression<T>(IQueryable<TViewModel> data, Func<TViewModel, T> property, Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> selector)
    {
        if (selector == null)
        {
            return items => BuildInnerGroup(items, property);
        }
        else
        {
            return BuildGroup(property, selector);
        }
    }
 
    protected IQueryable<TEntity> AddSortExpression<T>(IQueryable<TEntity> data, Expression<Func<TEntity, T>> property, ListSortDirection sortDirection, bool isFirst)
    {
        if (sortDirection == ListSortDirection.Ascending)
        {
            return isFirst ? data.OrderBy(property) : ((IOrderedQueryable<TEntity>)data).ThenBy(property);
        }
 
        return isFirst ? data.OrderByDescending(property) : ((IOrderedQueryable<TEntity>)data).ThenByDescending(property);
    }
 
    protected abstract Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> AddGroupExpression(IQueryable<TViewModel> data, GroupDescriptor groupDescriptor, Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> selector);
 
    protected abstract IQueryable<TEntity> AddSortExpression(IQueryable<TEntity> data, string memberName, System.ComponentModel.ListSortDirection sortDirection, bool isFirst);
}
0
Stephen
Top achievements
Rank 1
answered on 24 Nov 2011, 12:06 PM
I ran into this exact set of problems myself using Automapper to map from EF 4.2 Code-First entities to view-models. Keeping the query as a query right up to the point where the grid has finished tacking on the sort and grouping expressions is a challenge, but this post saved me a lot of time and effort.

I wanted to say "Thank you!" to everyone that chipped in on this thread!

I got a solution to the immediate problems working from the code here, but I did not want to have to create a separate GridHelper class  for each of my grids (I have a lot of them), especially since the code in each gridhelper would be very repetitive; just variations on setting up sort and group fields for each grid's unique columns.

So... I dug into some territory I'm not all that experienced with. What I came up with was a way to handle all the group and sort setups in a single generic GridHelper, and internally just use reflection and linq expressions to handle the sort and group columns. 

This way, in the controller we can just do this:

In the Controller:
var gridHelper = new Helpers.GridCustomBindingHelper<PublicationIntervenant, BesoinIndexGridViewModel>(command, publicationsIntervenants);


This gives us a single class that works for all grids (in theory).

I have not put this through a lot of testing so far, but I thought I'd share the code. It works in several pilot cases with some simpler grids.
  
GridHelper
public class GridCustomBindingHelper<TEntity, TViewModel>
{
    private bool dataSourceIsProcessed;
    private int totalCount;
    private IEnumerable<TEntity> processedDataSource;
    private IQueryable<TEntity> dataSource;
    private GridCommand command;
 
    public GridCustomBindingHelper(GridCommand command, IQueryable<TEntity> dataSource, string defaultSortMemberName)
    {
        DefaultSortMemberName = defaultSortMemberName ?? "ID";
        this.dataSource = dataSource;
        this.command = command;
 
    }
    public GridCustomBindingHelper(GridCommand command, IQueryable<TEntity> dataSource) : this(command, dataSource, null) { }
 
    public string DefaultSortMemberName { get; set; }
 
    public int Total
    {
        get
        {
            EnsureDataSourceIsProcessed();
            return this.totalCount;
        }
    }
 
    public IEnumerable<TEntity> ProcessedDataSource
    {
        get
        {
            EnsureDataSourceIsProcessed();
            return processedDataSource;
        }
    }
 
    private void EnsureDataSourceIsProcessed()
    {
        if (dataSourceIsProcessed)
        {
            return;
        }
 
        if (this.dataSource == null)
        {
            dataSourceIsProcessed = true;
            return;
        }
 
        IQueryable<TEntity> data = this.dataSource;
 
        data = this.ApplyFiltering(data, this.command.FilterDescriptors);
 
        this.totalCount = data.Count();
        if (command.SortDescriptors.Count == 0)
        {
            command.SortDescriptors.Add(new SortDescriptor() { Member = DefaultSortMemberName, SortDirection = ListSortDirection.Ascending });
        }
        data = this.ApplySorting(data, command.GroupDescriptors, command.SortDescriptors);
        data = this.ApplyPaging(data, command.Page, command.PageSize);
 
        this.processedDataSource = data;
        this.dataSourceIsProcessed = true;
    }
 
    private IQueryable<TEntity> ApplyFiltering(IQueryable<TEntity> data, IList<IFilterDescriptor> filterDescriptors)
    {
        if (filterDescriptors.Any())
        {
            data = data.Where(ExpressionBuilder.Expression<TEntity>(filterDescriptors));
        }
 
        return data;
    }
 
    private IQueryable<TEntity> ApplyPaging(IQueryable<TEntity> data, int currentPage, int pageSize)
    {
        if (pageSize > 0 && currentPage > 0)
        {
            data = data.Skip((currentPage - 1) * pageSize);
        }
 
        data = data.Take(pageSize);
 
        return data;
    }
 
    private IQueryable<TEntity> ApplySorting(IQueryable<TEntity> data, IList<GroupDescriptor> groupDescriptors, IList<SortDescriptor> sortDescriptors)
    {
        if (groupDescriptors.Any())
        {
            foreach (var groupDescriptor in groupDescriptors.Reverse())
            {
                SortDescriptor sortDescriptor = new SortDescriptor
                {
                    Member = groupDescriptor.Member,
                    SortDirection = groupDescriptor.SortDirection
                };
 
                sortDescriptors.Insert(0, sortDescriptor);
            };
        }
 
        if (sortDescriptors.Any())
        {
            bool isFirst = true;
 
            foreach (SortDescriptor sortDescriptor in sortDescriptors)
            {
                var pi = typeof(TEntity).GetProperty(sortDescriptor.Member);
 
                MethodInfo method = this.GetType().GetMethod(
                    "AddSortExpression",
                    BindingFlags.Instance | BindingFlags.NonPublic,
                    Type.DefaultBinder,
                    new Type[] { typeof(IQueryable<TEntity>), typeof(PropertyInfo), typeof(ListSortDirection), typeof(bool) },
                    null
                );
 
                MethodInfo genericMethod = method.MakeGenericMethod(new Type[] { pi.PropertyType });
 
                data = genericMethod.Invoke(this, new object[] { data, pi, sortDescriptor.SortDirection, isFirst }) as IQueryable<TEntity>;
 
                isFirst = false;
            }
        }
 
        return data;
    }
 
    public IEnumerable ApplyGrouping(IQueryable<TViewModel> data, IList<GroupDescriptor> groupDescriptors)
    {
        Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> selector = null;
 
        foreach (GroupDescriptor groupDescriptor in groupDescriptors.Reverse())
        {
            var pi = typeof(TViewModel).GetProperty(groupDescriptor.Member);
 
            MethodInfo method = this.GetType().GetMethod(
                "AddGroupExpression",
                BindingFlags.Instance | BindingFlags.NonPublic,
                Type.DefaultBinder,
                new Type[] { typeof(IQueryable<TViewModel>), typeof(GroupDescriptor), typeof(Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>>) },
                null
            );
 
            MethodInfo genericMethod = method.MakeGenericMethod(new Type[] { pi.PropertyType });
 
            selector = genericMethod.Invoke(this, new object[] { data, groupDescriptor, selector }) as Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>>;
        }
 
        return selector.Invoke(data).ToList();
    }
 
    protected Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> BuildGroup<T>(Func<TViewModel, T> groupSelector, Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> selectorBuilder)
    {
        var tempSelector = selectorBuilder;
 
        return g => g.GroupBy(groupSelector)
                        .Select(c => new AggregateFunctionsGroup
                        {
                            Key = c.Key,
                            HasSubgroups = true,
                            Items = tempSelector.Invoke(c)
                        });
    }
 
    protected IEnumerable<AggregateFunctionsGroup> BuildInnerGroup<T>(IEnumerable<TViewModel> group, Func<TViewModel, T> groupSelector)
    {
        return group.GroupBy(groupSelector)
                .Select(i => new AggregateFunctionsGroup
                {
                    Key = i.Key,
                    Items = i.ToList()
                });
    }
 
 
    protected Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> AddGroupExpression<T>(IQueryable<TViewModel> data, GroupDescriptor groupDescriptor, Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> selector)
    {
        var param = Expression.Parameter(typeof(TViewModel), groupDescriptor.Member);
 
        var exp = Expression.Property(param, groupDescriptor.Member);
 
        var fun = typeof(Func<,>).MakeGenericType(typeof(TViewModel), typeof(T));
 
        var groupExpression = Expression.Lambda(
            fun,
            Expression.Convert(exp, typeof(T)),
            param
        );
 
        return AddGroupExpression(data, groupExpression.Compile() as Func<TViewModel, T>, selector);
    }
 
    protected Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> AddGroupExpression<T>(IQueryable<TViewModel> data, Func<TViewModel, T> property, Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> selector)
    {
        if (selector == null)
        {
            return items => BuildInnerGroup(items, property);
        }
        else
        {
            return BuildGroup(property, selector);
        }
    }
 
    protected IQueryable<TEntity> AddSortExpression<T>(IQueryable<TEntity> data, PropertyInfo property, ListSortDirection sortDirection, bool isFirst)
    {
        var param = Expression.Parameter(typeof(TEntity), property.Name);
 
        var exp = Expression.Property(param, property.Name);
 
        var fun = typeof(Func<,>).MakeGenericType(typeof(TEntity), typeof(T));
 
        var sortExpression = Expression.Lambda(
            fun,
            Expression.Convert(exp, typeof(T)),
            param
        );
 
        return AddSortExpression(data, sortExpression as Expression<Func<TEntity, T>>, sortDirection, isFirst);
    }
 
    protected IQueryable<TEntity> AddSortExpression<T>(IQueryable<TEntity> data, System.Linq.Expressions.Expression<Func<TEntity, T>> property, ListSortDirection sortDirection, bool isFirst)
    {
        if (sortDirection == ListSortDirection.Ascending)
        {
            return isFirst ? data.OrderBy(property) : ((IOrderedQueryable<TEntity>)data).ThenBy(property);
        }
 
        return isFirst ? data.OrderByDescending(property) : ((IOrderedQueryable<TEntity>)data).ThenByDescending(property);
    }
}
  
0
Luis
Top achievements
Rank 1
answered on 02 Jan 2012, 07:22 PM
I came here to thank everyone who contributed to this solution, was of great value to me.
I echo the words of Stephen.

But I want to make my contribution:
I used Stephen's class to make everything even more generic, supporting complex types.

I hope it's useful to someone.

public class GenericGridCustomBindingHelper<TEntity, TViewModel>
    where TEntity : AbstractEntity
    where TViewModel : ViewModelBase
{
    #region Properties
 
    public string DefaultSortMemberName { get; set; }
    public int Total
    {
        get
        {
            EnsureDataSourceIsProcessed();
            return _totalCount;
        }
    }
 
    #endregion
 
    #region Fields
 
    private bool _dataSourceIsProcessed;
    private int _totalCount;
    private IEnumerable<TViewModel> _processedDataSource;
    private readonly IQueryable<TEntity> _dataSource;
    private readonly IQueryable<TEntity> _dataSourceToCount;
    private readonly GridCommand _command;
 
    #endregion
 
    #region Constructors
 
    public GenericGridCustomBindingHelper(GridCommand command, IQueryable<TEntity> dataSource, IQueryable<TEntity> dataSourceToCount, string defaultSortMemberName)
    {
        DefaultSortMemberName = defaultSortMemberName ?? StaticReflection.GetMemberName<TEntity>(x => x.Id);
        _dataSource = dataSource;
        _dataSourceToCount = dataSourceToCount;
        _command = command;
 
    }
 
    public GenericGridCustomBindingHelper(GridCommand command, IQueryable<TEntity> dataSource, IQueryable<TEntity> dataSourceToCount)
        : this(command, dataSource, dataSourceToCount, null) { }
 
    #endregion
 
    public IEnumerable<TViewModel> ProcessedDataSource
    {
        get
        {
            EnsureDataSourceIsProcessed();
            return _processedDataSource;
        }
    }
 
    public GridModel BuildGridModel()
    {
        EnsureDataSourceIsProcessed();
 
        var gridModel = new GridModel();
 
        if (_command.GroupDescriptors.Any())
        {
            var groups = ApplyGrouping(_processedDataSource.AsQueryable(), _command.GroupDescriptors);
 
            gridModel.Data = groups;
        }
        else
        {
            gridModel.Data = _processedDataSource;
        }
 
        gridModel.Total = Total;
 
        return gridModel;
    }
 
    private void EnsureDataSourceIsProcessed()
    {
        if (_dataSourceIsProcessed)
        {
            return;
        }
 
        if (_dataSource == null)
        {
            _dataSourceIsProcessed = true;
            return;
        }
 
        IQueryable<TEntity> data = _dataSource;
 
        data = ApplyFiltering(data, _command.FilterDescriptors);
 
        _totalCount = ApplyFiltering(_dataSourceToCount, _command.FilterDescriptors).Count();
 
        if (_command.SortDescriptors.Count == 0)
            _command.SortDescriptors.Add(new SortDescriptor { Member = DefaultSortMemberName, SortDirection = ListSortDirection.Ascending });
 
        data = ApplySorting(data, _command.GroupDescriptors, _command.SortDescriptors);
        data = ApplyPaging(data, _command.Page, _command.PageSize);
 
        _processedDataSource = Mapper.Map<IEnumerable<TViewModel>>(data);
 
        _dataSourceIsProcessed = true;
    }
 
    #region Filtering
 
    private IQueryable<TEntity> ApplyFiltering(IQueryable<TEntity> data, IList<IFilterDescriptor> filterDescriptors)
    {
        if (filterDescriptors.Any())
        {
            var descriptors = UnwrapFilterDescriptors(filterDescriptors);
 
            foreach (var filterDescriptor in descriptors.Cast<FilterDescriptor>().Reverse())
            {
                var paramm = filterDescriptor.Value.ToString();
                var propertie = filterDescriptor.Member;
                switch (filterDescriptor.Operator)
                {
                    case FilterOperator.IsEqualTo:
                        data = data.Where(string.Concat(propertie, ".Equals(@0)"), paramm);
                        break;
                    case FilterOperator.IsNotEqualTo:
                        data = data.Where(string.Concat(propertie, " != @0"), paramm);
                        break;
                    case FilterOperator.StartsWith:
                        data = data.Where(string.Concat(propertie, ".StartsWith(@0)"), paramm);
                        break;
                    case FilterOperator.Contains:
                        data = data.Where(string.Concat(propertie, ".Contains(@0)"), paramm);
                        break;
                    case FilterOperator.EndsWith:
                        data = data.Where(string.Concat(propertie, ".EndsWith(@0)"), paramm);
                        break;
                }
            }
        }
        return data;
    }
 
    #endregion
 
 
    #region Paging
 
    private IQueryable<TEntity> ApplyPaging(IQueryable<TEntity> data, int currentPage, int pageSize)
    {
        if (pageSize > 0 && currentPage > 0)
        {
            data = data.Skip((currentPage - 1) * pageSize);
        }
 
        data = data.Take(pageSize);
 
        return data;
    }
 
    #endregion
 
 
    #region Sorting
 
    private IQueryable<TEntity> ApplySorting(IQueryable<TEntity> data, IList<GroupDescriptor> groupDescriptors, IList<SortDescriptor> sortDescriptors)
    {
        if (groupDescriptors.Any())
        {
            foreach (var groupDescriptor in groupDescriptors.Reverse())
            {
                var sortDescriptor = new SortDescriptor
                                         {
                                             Member = groupDescriptor.Member,
                                             SortDirection = groupDescriptor.SortDirection
                                         };
 
                sortDescriptors.Insert(0, sortDescriptor);
            }
        }
 
        if (sortDescriptors.Any())
        {
            var isFirst = true;
 
            foreach (var sortDescriptor in sortDescriptors)
            {
                var propertyInfo = (PropertyInfo)StaticReflection.GetMemberInfo(typeof(TEntity), sortDescriptor.Member);
 
                var method = GetType().GetMethod(
                    "AddSortExpression",
                    BindingFlags.Instance | BindingFlags.NonPublic,
                    Type.DefaultBinder,
                    new[] { typeof(IQueryable<TEntity>), typeof(string), typeof(ListSortDirection), typeof(bool) },
                    null
                    );
 
                var genericMethod = method.MakeGenericMethod(new[] { propertyInfo.PropertyType });
 
                data = genericMethod.Invoke(this, new object[] { data, sortDescriptor.Member, sortDescriptor.SortDirection, isFirst }) as IQueryable<TEntity>;
 
                isFirst = false;
            }
        }
 
        return data;
    }
 
    private IQueryable<TEntity> AddSortExpression<TProperty>(IQueryable<TEntity> data, string propertyName, ListSortDirection sortDirection, bool isFirst)
    {
        Expression<Func<TEntity, TProperty>> exp2 = GetPropertyExpression<TEntity, TProperty>(propertyName);
        return AddSortExpression(data, exp2, sortDirection, isFirst);
    }
 
    private IQueryable<TEntity> AddSortExpression<TKey>(IQueryable<TEntity> data, Expression<Func<TEntity, TKey>> property, ListSortDirection sortDirection, bool isFirst)
    {
        if (sortDirection == ListSortDirection.Ascending)
        {
            return isFirst ? data.OrderBy(property) : ((IOrderedQueryable<TEntity>)data).ThenBy(property);
        }
 
        return isFirst
                   ? data.OrderByDescending(property)
                   : ((IOrderedQueryable<TEntity>)data).ThenByDescending(property);
    }
 
    #endregion
 
 
    #region Grouping
 
    private IEnumerable ApplyGrouping(IEnumerable<TViewModel> data, IEnumerable<GroupDescriptor> groupDescriptors)
    {
        Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> selector = null;
 
        foreach (var groupDescriptor in groupDescriptors.Reverse())
        {
            var propertyInfo = (PropertyInfo)StaticReflection.GetMemberInfo(typeof(TEntity), groupDescriptor.Member);
 
            var method = GetType().GetMethod(
                "AddGroupExpression",
                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;
    }
 
    private Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> AddGroupExpression<T>(GroupDescriptor groupDescriptor, Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> selector)
    {
        var exp = GetPropertyExpression<TViewModel, T>(groupDescriptor.Member);
        return AddGroupExpression(exp.Compile(), selector);
    }
 
    private Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> AddGroupExpression<TKey>(Func<TViewModel, TKey> property, Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> selector)
    {
        if (selector == null)
            return items => BuildInnerGroup(items, property);
        return BuildGroup(property, selector);
    }
 
    private Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> BuildGroup<TKey>(Func<TViewModel, TKey> groupSelector, Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> selectorBuilder)
    {
        var tempSelector = selectorBuilder;
 
        return g => g.GroupBy(groupSelector)
                        .Select(c => new AggregateFunctionsGroup
                                         {
                                             Key = c.Key,
                                             HasSubgroups = true,
                                             Items = tempSelector.Invoke(c)
                                         });
    }
 
    private IEnumerable<AggregateFunctionsGroup> BuildInnerGroup<TKey>(IEnumerable<TViewModel> group, Func<TViewModel, TKey> groupSelector)
    {
        return group.GroupBy(groupSelector)
            .Select(i => new AggregateFunctionsGroup
                             {
                                 Key = i.Key,
                                 Items = i.ToList()
                             });
    }
 
    #endregion
 
 
    #region Helper Functions
 
    private IEnumerable<IFilterDescriptor> UnwrapFilterDescriptors(IEnumerable<IFilterDescriptor> filterDescriptors)
    {
        var result = new List<IFilterDescriptor>();
        foreach (var filterDescriptor in filterDescriptors)
        {
            if (filterDescriptor.GetType() == typeof(FilterDescriptor))
                result.Add(filterDescriptor);
 
            var compositeFilterDescriptor = filterDescriptor as CompositeFilterDescriptor;
            if (compositeFilterDescriptor != null)
                result.AddRange(UnwrapFilterDescriptors(compositeFilterDescriptor.FilterDescriptors));
        }
        return result;
    }
 
    private Expression<Func<TSource, TProperty>> GetPropertyExpression<TSource, TProperty>(string sortColumn)
    {
        Expression propConvExp;
        var paramExp = Expression.Parameter(typeof(TSource), typeof(TSource).ToString());
 
        if (sortColumn.Contains('.'))
        {
            string[] keys = sortColumn.Split('.');
 
            propConvExp = Expression.Property(paramExp, keys[0]);
 
            for (int index = 1; index < keys.Length; index++)
            {
                propConvExp = Expression.Property(propConvExp, keys[index]);
            }
 
            propConvExp = Expression.Convert(propConvExp, typeof(TProperty));
        }
        else
        {
            propConvExp = Expression.Convert(Expression.Property(paramExp, sortColumn), typeof(TProperty));
        }
 
        return Expression.Lambda<Func<TSource, TProperty>>(propConvExp, paramExp);
    }
 
    #endregion
 
}

On the line: 
var propertyInfo = (PropertyInfo)StaticReflection.GetMemberInfo(typeof(TEntity), sortDescriptor.Member);

Use this recursive function:
public static MemberInfo GetMemberInfo(Type baseType, string propertyName)
{
    var parts = propertyName.Split('.');
 
    return (parts.Length > 1) ?
        GetMemberInfo(baseType.GetProperty(parts[0]).PropertyType, parts.Skip(1).Aggregate((a, i) => a + "." + i))
        : baseType.GetProperty(propertyName);
}

Oh, I almost forgot, I also used the namespace System.Linq.Dynamic on the ApplyFiltering function because I saw no other way.
The usage is simple this:
public GridModel GetGridModel(GridCommand command)
{
    // get the data needed for the grid
    var dataSource = SomeService.GetAsIQueryable();
    //This is needed to work with NHibernate Linq provider
    var dataSourceToCount = SomeService.CountItens();
 
    var gridHelper = new GenericGridCustomBindingHelper<Produto, ProdutoModel>(command, dataSource, dataSourceToCount);
    return gridHelper.BuildGridModel();
}

Happy new year!!!

Edit - 05/01/2012
Oops, I made a mistake in the line #04:
private IEnumerable ApplyGrouping(IEnumerable<TViewModel> data, IEnumerable<GroupDescriptor> groupDescriptors)
    {
        Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> selector = null;
  
        foreach (var groupDescriptor in groupDescriptors.Reverse())
        {
#04>        var propertyInfo = (PropertyInfo)StaticReflection.GetMemberInfo(typeof(TEntity), groupDescriptor.Member);

Replace "TEntity" to "TViewModel" in this way:
private IEnumerable ApplyGrouping(IEnumerable<TViewModel> data, IEnumerable<GroupDescriptor> groupDescriptors)
    {
        Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> selector = null;
  
        foreach (var groupDescriptor in groupDescriptors.Reverse())
        {
#04>        var propertyInfo = (PropertyInfo)StaticReflection.GetMemberInfo(typeof(TViewModel), groupDescriptor.Member);

0
Alexandre Jobin
Top achievements
Rank 1
answered on 04 Jan 2012, 05:56 PM
thank you Luis for your code. I will give it a try when i will have time for sure!

alex
0
Luis
Top achievements
Rank 1
answered on 04 Jan 2012, 08:33 PM
Actually my problem is very specific, so if anyone needs some tips I'm happy to help in any way.

I have some ideas for making this code into a Nuget package, pluggable into any application, but for that I need to eliminate some problems, such as the mapping between ViewModels and the source objects (DomainModels?) that may have completely different properties names and types.
Do you have an ideia?
0
Luis
Top achievements
Rank 1
answered on 07 Jan 2012, 02:12 AM
Finally I got it!

With the help of you all I'm proud to say that I came to a satisfactory result.

As I said before, I developed a solution pluggable in any type of application without much difficulty and almost none in the settings.

I published the project on Bitbucket and Nuget, please take a look and send me some feedbacks.

* Sorry, I'm out of time now, but of course I'll put all the credits.


https://bitbucket.org/Lunadie/telerikmvcgridcustombindinghelper/wiki/Home 

https://nuget.org/packages/TelerikMvcGridCustomBindingHelper
0
Luis
Top achievements
Rank 1
answered on 21 Jan 2012, 09:41 PM
Everyone's a little quiet here.
Alex, how are your tests?
0
Mattias Altin
Top achievements
Rank 1
answered on 23 Jan 2012, 05:13 PM
Hi,

I have been struggling with something similar. I am using similar code and I have the grouping/sorting etc working fine. What I am now trying to do is load the grouping/sorting from cookies and refresh the grid using Javascript, something like:

function refreshGrid() {
  var grid = $("#Product").data("tGrid");
  var orderBy = 'ExchangeName-asc';
  var groupBy = 'SecurityTypeName-asc';
  grid.orderBy = orderBy;
  grid.groupBy = groupBy;
  grid.ajaxRequest();
}

Obviously I am hard coding the values here, but you get the picture. When this code is executed, the IndexAjax method is called on the controller (which is the same method used when you resort/group the data by clicking on the grid), the query is executed and data is returned. When the grid tries to render it cannot bind to the properties on the model in the Client Template.

Here's the IndexAjax method:

[GridAction(EnableCustomBinding = true)]
public ActionResult IndexAjax(GridCommand command)
{
    IEnumerable data = this.GetData(command);
 
    return this.View(new GridModel
    {
        Data = data,
        Total = this.RecordCount
    });
}

... and the GetData method:

private IEnumerable GetData(GridCommand command)
{
    var data = this.productGridViewRepository.Get();
             
    data = data.ApplyFiltering(command.FilterDescriptors);
             
    this.RecordCount = data.Count();
 
    data = data.ApplySorting(command.GroupDescriptors, command.SortDescriptors);
 
    data = data.ApplyPaging(command.Page, command.PageSize);
             
    if (command.GroupDescriptors.Any())
    {
        return data.ApplyGrouping(command.GroupDescriptors);
    }
 
     return data.ToList();
}

The model on the page is IEnumerable<ProductGridView>, and the setup of the grid is:

@(Html.Telerik().Grid(Model)
    .Name("Product")
    .TableHtmlAttributes(new { @class = "small" })
    .Columns(columns =>
    {
        columns.Bound(c => c.Name)
            .Title("Product")
            .Template(@<text>@Html.ActionLink(item.Name, "Details", new { id = item.Id }, null)</text>)
            .ClientTemplate("<text><a href=\"/ReferenceData/Product/Details/<#= Id #>\"><#= Name #></a></text>");
        columns.Bound(c => c.ExchangeName)
            .Title("Exchange")
            .Template(@<text>@Html.ActionLink(item.ExchangeName, "Details", "Exchange", new { id = item.ExchangeId }, null)</text>)
            .ClientTemplate("<text><a href=\"/ReferenceData/Exchange/Details/<#= ExchangeId #>\"><#= ExchangeName #></a></text>");
        columns.Bound(c => c.SecurityTypeName)
            .Title("Security Type")
            .Template(@<text>@Html.ActionLink(item.SecurityTypeName, "Details", "SecurityType", new { id = item.SecurityTypeId }, null)</text>)
            .ClientTemplate("<text><a href=\"/ReferenceData/Exchange/Details/<#= SecurityTypeId #>\"><#= SecurityTypeName #></a></text>");
 
    })
    .DataBinding(dataBinding =>
    {
        dataBinding.Server().Select("Index", "Product");
        dataBinding.Ajax().Select("IndexAjax", "Product");
    })
    .Pageable(pager =>
    {
        pager.Total(ViewBag.RecordCount);
        pager.PageSize(ViewBag.PageSize);
    })
    .EnableCustomBinding(true)
    .Sortable()
    .Reorderable(reorder => reorder.Columns(true))
    .Resizable(resizing => resizing.Columns(true))
    .Scrollable(s => s.Enabled(true))
    .ColumnContextMenu()
    .Footer(true)
    .Groupable()
)

So, when you use the grid normally the grid renders the grouping fine. If you invoke the Javascript method to refresh the grid based on what you set as the grouping/sorting, it fails to find/bind to the model properties, as if its just trying to directly bind to the IEnumerable object and not the actual model (ProductGridView). Doesn't make sense.

I wondered if it is to do with the fact that model data being returned from GetData() is not strongly typed (i.e. just IEnumerable) but if that was a problem then why does it work when doing normal server/ajax calls, but not when calling ajaxRequest()?

Any assistance greatly appreciated.

(Update: We fixed it... seemed that although we are not using the build in filtering in the grid, you still need to have Filterable() set on the grid)

I hope someone else finds this useful!
0
Stefan
Top achievements
Rank 1
answered on 31 Jan 2012, 08:46 PM
I pulled it from NuGet and am struggling to get it running with the current version (2011.3.1323).

Argument 1: cannot convert from 'Telerik.Web.Mvc.GridCommand [Telerik\Updates\01805Telerik_Extensions_for_ASPNET_MVC_2011_3_1323_hotfix_LIB\Binaries\Mvc3\Telerik.Web.Mvc.dll]' to 'Telerik.Web.Mvc.GridCommand'
 
[GridAction(GridName = "inventory-grid")]
public PartialViewResult Grid(GridCommand command)
        {
            var query = _inventoryService.Get().Include(model => model.House, model => model.Article, model => model.Article.Group);
            var blah = new GridCustomBindingHelper<InventoryItem, InventoryItemViewModel>(command /* <= Error here */, query);
           // Snip



Any ideas?

Also: Any ideas how to make the grid play nice with Server CustomBinding and Aggregates?
0
Luis
Top achievements
Rank 1
answered on 31 Jan 2012, 09:47 PM
Stefan, could you please move your question to the dedicated forum here.

And if necessary, create a new issue here.

For a quick response, this helper will need the latest
TelerikMvcExtensions OpenSource version (2011.3.1115).

Thank you.
Tags
Grid
Asked by
Alexandre Jobin
Top achievements
Rank 1
Answers by
Alexandre Jobin
Top achievements
Rank 1
Graeme
Top achievements
Rank 1
frederic rybkowski
Top achievements
Rank 1
Atanas Korchev
Telerik team
Stephen
Top achievements
Rank 1
Luis
Top achievements
Rank 1
Mattias Altin
Top achievements
Rank 1
Stefan
Top achievements
Rank 1
Share this question
or