Do not ignore SorterToUse on llblgenprodatasource2 with LivePersistence="True" SortingMode="ServerSide"

Posts   
1  /  2
 
    
hotchill avatar
hotchill
User
Posts: 180
Joined: 22-Jan-2007
# Posted on: 12-Sep-2013 12:30:46   

Hi.

I have a GridView hooked up to a llblgenprodatasource2 with live persistence and server side sorting. I need to sort on fields of related entity like this:

SorterToUse = new SortExpression(RoleFields.Title.SetObjectAlias("ParentRole") | GetSortOperator(RoleGridView.SortDirection));
                        FilterToUse.Relations.Add(RoleEntity.Relations.RoleEntityUsingParentRoleId, "ParentRole", JoinHint.Left);

The llblgenprodatasource2 does not use the SorterToUse that I have set. I would like it to. Perhaps you could add another mode to sorting so that one could say SortingMode="ServerSideCustom".

Is there an easy way for me to get around this issue?

Thanks, Tore.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 13-Sep-2013 10:03:39   

It does use the sorter set in fetches. SortingMode on the datasource has to be set to ServerSide, and it is then passed to the entity collection fetch on the adapter. Though it still might be possible it's not using the sorter on the DB side, because it might be you're sorting on fields which are not in the select list. It might be it needs to switch to client-side sorting because it otherwise would cause an exception on the DB side.

Also, which version are we talking about?

Frans Bouma | Lead developer LLBLGen Pro
hotchill avatar
hotchill
User
Posts: 180
Joined: 22-Jan-2007
# Posted on: 13-Sep-2013 16:55:36   

I am using '4.0 Final, April 11th, 2013'. I set the SorterToUse in pre render and the default sort expression on first page load works fine.

However when user sorts the grid by clicking the column header, SorterToUse is ignored. It calculates its own sort expression given the SortExpression property on the sorted column. It still uses the FilterToUse that I have set though.

<asp:TemplateField HeaderText="Forelder-rolle" SortExpression="ParentRoleTitle" >
                                    <EditItemTemplate>
                                        <asp:DropDownList ID="EditParentRole" runat="server" Width="200px" CssClass="sm-dropdown" ></asp:DropDownList>
                                    </EditItemTemplate>
                                    <ItemTemplate>
                                        <asp:Label runat="server" ID="ParentRoleTitle" Text='<%# Bind("ParentRoleTitle") %>' ></asp:Label>
                                    </ItemTemplate>
                                    <FooterTemplate>
                                        <asp:DropDownList ID="FooterParentRole" runat="server" Width="200px" CssClass="sm-dropdown" ></asp:DropDownList>
                                    </FooterTemplate>
                                    <FooterStyle Wrap="False" />
                                </asp:TemplateField>

public partial class RoleEntity {
        public string ParentRoleTitle {
            get { return ParentRole != null ? ParentRole.Title : string.Empty; }
        }
}

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 14-Sep-2013 17:14:47   

It might be the sorter is not set when the page posts back. PLease set it earlier than pre-render. The sorter to use for fetching is build from the sorter set on the datasource control and what's passed in by the bound control (grid).

I've moved this thread to a normal support forum as it's a feature which is already there and should work properly.

Frans Bouma | Lead developer LLBLGen Pro
hotchill avatar
hotchill
User
Posts: 180
Joined: 22-Jan-2007
# Posted on: 20-Sep-2013 17:26:29   

The GridView does not have value for SortExpression in Onload. It does have the value in OnLoadComplete so I tried setting SorterToUse there, but same issue.

If the sorter to use for fetching is built from what's passed in by the bound control (grid), the fetch will fail. The table does not have a 'ParentRoleTitle' column. This is a just a property on the entity class:

public string ParentRoleTitle {
    get { return ParentRole != null ? ParentRole.Title : string.Empty; }
}
Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 21-Sep-2013 10:06:20   

I can't reproduce it. Just set the SorterToUse in the Page_Load, and it was clearly used.

Are you handling the Sorting event? Could you please attach a simple repro project?

hotchill avatar
hotchill
User
Posts: 180
Joined: 22-Jan-2007
# Posted on: 21-Sep-2013 10:46:33   

Yes, if you set SorterToUse in page load or pre-render for that matter, it is used if you do not have a SortExpression active on the GridView. If there is a SortExpression however, SorterToUse is ignored.

Sure, but I'll have to get back to you later with the repro project. I'll try to handle the sorting event too.

Thanks.

Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 22-Sep-2013 20:34:16   

I'm not sure I understand what you mean by "have a SortExpression active on the GridView". Please elaborate.

hotchill avatar
hotchill
User
Posts: 180
Joined: 22-Jan-2007
# Posted on: 23-Sep-2013 10:30:55   

The GridView has a SortExpression property. This property is the empty string on first load of the page. When empty, SorterToUse takes effect.

On click of a column header in the GridView, the GridView SortExpression property is set to the value of the SortExpression property on the corresponding Field. This is what I mean by "have a SortExpression active on the GridView". The SorterToUse is now ignored.

Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 23-Sep-2013 19:25:03   

This seems logical, otherwise, you should disable sorting on the grid, which will disable columns clicking.

hotchill avatar
hotchill
User
Posts: 180
Joined: 22-Jan-2007
# Posted on: 23-Sep-2013 19:58:01   

Please read the entire thread. I initially posted this under feature requests and Otis moved it to the General section.

If this is the intended behavior, the post belongs under feature requests.

The problem is that the sorting done by the data source does not handle relations. I need the sort to look like this:

SorterToUse = new SortExpression(RoleFields.Title.SetObjectAlias("ParentRole") | GetSortOperator(RoleGridView.SortDirection));
FilterToUse.Relations.Add(RoleEntity.Relations.RoleEntityUsingParentRoleId, "ParentRole", JoinHint.Left);

If there is a SorterToUse set, the data source should not build its own.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 24-Sep-2013 10:18:53   

It indeed ignores the sorter on the datasource, when a sort is specified by the bound control (which is what is happening in your case). This is visible in the sourcecode, LLBLGenProDataSourceView2.cs, line 859.

The problem however is in LLBLGenProDataSourceViewBase.cs, line 635 and further: the field specified by the grid isn't a field of the entity viewed, so it checks whether the sorting has to take place client side (which will take into properties of classes) or server side (which will only work on db fields obviously).

In LivePersistence, it will sort on one side only: the one set on the datasource control. This means that if serverside is specified, it won't sort on the client.

I now finally understand what you want simple_smile

The thing is though: to sort on the client, it has to have its sorter specification set as 'client' so it won't sort on the server. This is because it has to generate a sort expression from the column clicked and then decide where to execute it: client or server.

It comes down to: is the column clicked a known field? If so, sort on the server (which will work), or if it's not a known field, it will ignore it, but you now want to sort it on the client.

This is odd behavior though: the client sort will be on the data in the grid, but when the sort is done on a field which is in the DB, it will be done on the server so it will read in new data into the grid.

I don't see a solution to this. Please correct me if I missed something though!

Frans Bouma | Lead developer LLBLGen Pro
hotchill avatar
hotchill
User
Posts: 180
Joined: 22-Jan-2007
# Posted on: 24-Sep-2013 13:37:08   

I want it to always sort on the server using the SorterToUse that I set and never calculate its own. I have posted all relevant code here. The data source will always use the FilterToUse and the PrefetchPathToUse that I set, but the SorterToUse is ignored when a sort is specified by the bound control.

I would like to see one of two solutions: 1. When SorterToUse is set, always use that. 2. Introduce a new sort mode: ServerSideCustom. With this sort mode, never calculate SorterToUse and and always use any SorterToUse that is set.

protected override void OnLoadComplete(EventArgs e) {
    base.OnLoadComplete(e);
    RoleGridView.PagerTemplate = null;
    RoleGridView.CssClass = "mGrid";
    CalculateFilterAndSorterToUse();
    RoleDS.SorterToUse = SorterToUse;
    RoleDS.FilterToUse = FilterToUse;
    RoleDS.PrefetchPathToUse = new PrefetchPath2(EntityType.RoleEntity) { RoleEntity.PrefetchPathParentRole };
    if (!IsPostBack) {
        DatabindRoleTypes(RoleType);
    }
}

private void CalculateFilterAndSorterToUse() {
    if (FilterToUse != null) {
        return;
    }
    FilterToUse = new RelationPredicateBucket();
    ApplySecurityFilter();
    ApplyPostbackFilterAndSort();
    if (!string.IsNullOrEmpty(FilterRoles.Text) && FilterRoles.Text != RoleFilterWatermarkText) {
        FilterToUse.PredicateExpression.AddWithAnd(Util.GetFieldLikePredicateExpressionForSearchTerm(FilterRoles.Text, new IEntityField2[] {RoleFields.Title, RoleFields.Description}));
    }
}

private void ApplySecurityFilter() {
    if (!SecurityRepository.IsActionAllowed(SecurityRepository.Action.ManageCompanyRoles)) {
        FilterToUse.PredicateExpression.AddWithAnd(RoleFields.RoleTypeId != (int) RoleEntity.RoleTypeEnum.Company);
    }
}

private void ApplyPostbackFilterAndSort() {
    if (!string.IsNullOrEmpty(RoleGridView.FilterFieldName)) {
        switch (RoleGridView.FilterFieldName) {
            case "RoleTypeReaderAndSortFriendly":
                FilterToUse.PredicateExpression.AddWithAnd(RoleFields.RoleTypeId == (int)RoleEntity.GetRoleTypeFromReaderAndSortFriendly(RoleGridView.FilterFieldValue));
                break;
            case "ParentRoleTitle":
                if (RoleGridView.FilterFieldValue == "(Tom)") {
                    FilterToUse.PredicateExpression.AddWithAnd(new FieldCompareNullPredicate(RoleFields.ParentRoleId, null, false));
                } else {
// this workaround works because there is a db unique constraint on the Title field
                    var role = AccessLevelEntityService.GetRole(RoleGridView.FilterFieldValue);
                    FilterToUse.PredicateExpression.AddWithAnd(RoleFields.ParentRoleId == role.RoleId);
                    // does not work...probable bug in llblgen, alias is ignored
                    //FilterToUse.PredicateExpression.AddWithAnd(RoleFields.Title.SetObjectAlias("ParentRole") == RoleGridView.FilterFieldValue);
                    //FilterToUse.Relations.Add(RoleEntity.Relations.RoleEntityUsingParentRoleId, "ParentRole", JoinHint.Left);
                }
                break;
            default:
                FilterToUse.PredicateExpression.AddWithAnd(GetRoleEntityField2(RoleGridView.FilterFieldName) == RoleGridView.FilterFieldValue);
                break;
            
        }
    }
    if (!string.IsNullOrEmpty(RoleGridView.SortExpression)) {
        //bool addSortRelation = RoleGridView.SortExpression != RoleGridView.FilterFieldName || string.IsNullOrEmpty(RoleGridView.FilterFieldValue) || RoleGridView.FilterFieldValue == "(Tom)";
        switch (RoleGridView.SortExpression) {
            case "ParentRoleTitle":
                // does not work..issue with llblgen, entire SortExpression is ignored
                SorterToUse = new SortExpression(RoleFields.Title.SetObjectAlias("ParentRole") | GetSortOperator(RoleGridView.SortDirection));
                //if (addSortRelation) {
                    FilterToUse.Relations.Add(RoleEntity.Relations.RoleEntityUsingParentRoleId, "ParentRole", JoinHint.Left);
                //}
                break;
            default:
                SorterToUse = new SortExpression(GetRoleEntityField2(RoleGridView.SortExpression) | GetSortOperator(RoleGridView.SortDirection));
                break;
        }
    } else {
        SorterToUse = DefaultSortExpression;
    }
}

public static SortExpression DefaultSortExpression { get { return new SortExpression { RoleFields.IsActive | SortOperator.Descending, RoleFields.Title | SortOperator.Ascending }; } }
private static SortOperator GetSortOperator(SortDirection sortDirection) { return (sortDirection == SortDirection.Ascending ? SortOperator.Ascending : SortOperator.Descending); }
private EntityField2 GetRoleEntityField2(string fieldName) { return (EntityField2)(EntityFieldFactory.Create(EntityType.RoleEntity.ToString(), fieldName)); }
Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 25-Sep-2013 03:15:10   

I want it to always sort on the server using the SorterToUse that I set and never calculate its own.

To clear out any misunderstanding, do you mean by the above quote that you don't want the user to click on column headers to re-sort the grid?!

hotchill avatar
hotchill
User
Posts: 180
Joined: 22-Jan-2007
# Posted on: 25-Sep-2013 10:24:48   

Sorry for the confusion guys.

No, I do want the user to click on column headers to re-sort the grid. I want to do my own sorting though. I don't want llblgen to do it for me.

I put together a screen cast to show the issue. Sorry for the slow progress in parts of it: http://www.screencast.com/t/Lrf4VXokWZ

Thanks.

Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 25-Sep-2013 18:35:34   

I believe the only way you get around this is by handling the grid's sorting event, and then decide what you need to do, either sort on the server side using the llblgenDataSource SorterToUse, or by sorting the fetched collection in memory if the sorted upon field is a custom property.

hotchill avatar
hotchill
User
Posts: 180
Joined: 22-Jan-2007
# Posted on: 25-Sep-2013 19:10:00   

Then I'd like to request a new feature: ServerSideCustom as new option on DataSourceSortingMode. In this mode, the SorterToUse set on the data source would always be used.

Please let me know if you will implement this. If not, I need to find another way.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 26-Sep-2013 11:53:43   

What I have a problem with is how to handle a click on a column which is a custom property so it can't be handled in the DB: you then can't sort on the DB side, or you have to supply the sorting for that column manually to the engine, but this is of course not trivial and requires you to effectively abandon LivePersistence. It's not trivial because you have to re-implement the custom property in DB logic (e.g. concatenate two fields together) and sort on the result of it.

You state you want to sort when the user clicks a column. I understand that, though it requires that you define the sorter to use on the DB side, we can't do that for you: as we don't know what to pass when a column with a custom property is clicked. The sorter set on the datasource control isn't sufficient here, because it won't sort on the column specified by the user (the column clicked).

Frans Bouma | Lead developer LLBLGen Pro
hotchill avatar
hotchill
User
Posts: 180
Joined: 22-Jan-2007
# Posted on: 26-Sep-2013 19:08:05   

though it requires that you define the sorter to use on the DB side, we can't do that for you: as we don't know what to pass when a column with a custom property is clicked.

I do define the sorter on every page request.

The sorter set on the datasource control isn't sufficient here, because it won't sort on the column specified by the user (the column clicked).

Yes it will. I calculate the sort expression based on the column clicked.

switch (RoleGridView.SortExpression) {

case "ParentRoleTitle":
    SorterToUse = new SortExpression(RoleFields.Title.SetObjectAlias("ParentRole") | GetSortOperator(RoleGridView.SortDirection));
    FilterToUse.Relations.Add(RoleEntity.Relations.RoleEntityUsingParentRoleId, "ParentRole", JoinHint.Left);
    break;

default:
    SorterToUse = new SortExpression(GetRoleEntityField2(RoleGridView.SortExpression) | GetSortOperator(RoleGridView.SortDirection));
    break;
}

private static SortOperator GetSortOperator(SortDirection sortDirection) { return (sortDirection == SortDirection.Ascending ? SortOperator.Ascending : SortOperator.Descending); }

private EntityField2 GetRoleEntityField2(string fieldName) { return (EntityField2)(EntityFieldFactory.Create(EntityType.RoleEntity.ToString(), fieldName)); }
Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 27-Sep-2013 00:47:31   

Yes it will. I calculate the sort expression based on the column clicked.

Code: switch (RoleGridView.SortExpression) {

case "ParentRoleTitle": SorterToUse = new SortExpression(RoleFields.Title.SetObjectAlias("ParentRole") | GetSortOperator(RoleGridView.SortDirection)); FilterToUse.Relations.Add(RoleEntity.Relations.RoleEntityUsingParentRoleId, "ParentRole", JoinHint.Left); break;

default: SorterToUse = new SortExpression(GetRoleEntityField2(RoleGridView.SortExpression) | GetSortOperator(RoleGridView.SortDirection)); break; }

private static SortOperator GetSortOperator(SortDirection sortDirection) { return (sortDirection == SortDirection.Ascending ? SortOperator.Ascending : SortOperator.Descending); }

private EntityField2 GetRoleEntityField2(string fieldName) { return (EntityField2)(EntityFieldFactory.Create(EntityType.RoleEntity.ToString(), fieldName)); }

So apart from the feature request, which we will revisit in the future, did you manage to solve this?

hotchill avatar
hotchill
User
Posts: 180
Joined: 22-Jan-2007
# Posted on: 27-Sep-2013 09:28:40   

Thanks for that.

No. I got my hopes up when I saw that I could override the data source GetView method, but then I realized that the data source internally does not use this method.

public class DataSource : LLBLGenProDataSource2 {
        private LLBLGenProDataSourceView2 _defaultView;
        protected override LLBLGenProDataSourceViewBase GetView() { return _defaultView ?? (_defaultView = new DataSourceView(this, DefaultDataSourceViewName)); }
    }
public class DataSourceView : LLBLGenProDataSourceView2 {
        public DataSourceView(LLBLGenProDataSourceBase owner, string viewName)
            : base(owner, viewName) { }

        public bool UseCustomSorting { get; set; }

        public override bool CanSort {
            get { return !UseCustomSorting; }
        }
    }

So I'll just disable sorting on this column till you implement this feature or tell me how I can get the data source to use my own view.

Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 27-Sep-2013 11:13:25   

Didn't you try my suggestion on handling the GridView's OnSorting event?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 27-Sep-2013 11:44:47   

You can set the datasource's EntityCollection property to an entity collection you fetch yourself, if that's what you're asking. There's currently no way to tap into the pipeline which is called by the bound grid. ExecuteSelect is virtual, but it's in the datasourceview, which is an object hold by the datasourcecontrol itself, and thus adding your own is a pain. If this is server side sorting anyway, I'd indeed go the route Walaa suggested, and simply refetch the data when the user clicks a column, and do this manually.

Frans Bouma | Lead developer LLBLGen Pro
hotchill avatar
hotchill
User
Posts: 180
Joined: 22-Jan-2007
# Posted on: 27-Sep-2013 11:51:42   

I did try to handle the sorting event.

Thanks guys, but I will just disable sorting on the column pending the feature.

It looks like a setter on LLBLGenProDataSourceViewBase.CanSort would suffice.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 27-Sep-2013 12:51:32   

No, that setter is simply to enable/disable sorting, but it's always controlled through the datasourcecontrol. Datasourcecontrols are not simple: what you work with on a page object is not what performs the actual work: the datasourceview does the work, but you can't access that object, you have to go through the datasourceecontrol. That's also why this isn't a trivial problem: if I could simply make a method protected virtual and you could simply create a subclass of the datasourceview and override the method, everything would be great, but that's not the case. In fact, the only way to solve this is to add an event to which you bind to which allows you to overrule the sorter in the event args. (and the filter as well), so basically, an event which allows you to manipulate the elements passed to the fetch method. Which is actually what one does with the non-live persistence situation, with one difference: the arguments passed into the ExecuteSelect method are not known by code in the page. That is: not directly.

However, it seems to me, it could be done though: In the OnSorting (or similar) event of the grid, you grab the field / column clicked. In the PerformSelect method, you use that info to build the sort clauses and pass it instead of the sorter specified in the event args of PerformSelect.

IMHO there are no other ways to solve this.

I'm also not going to lie to you: webforms in ASP.NET are not the technique of choice for many people going forward. Adding features in that area doesn't have a high priority, as what the datasourcecontrols do today is sufficient to create webforms applications. We do realize webforms with datasource controls have an advantage due to their 2-way databinding, but a lot of new webapplications are written with javascript on the client and MVC on the server. So even if we'll look at it in the future, your current project will likely not use it for quite some time.

Frans Bouma | Lead developer LLBLGen Pro
1  /  2