Best approach to do customized sorting of an EntityCollection?

Posts   
 
    
LarsBerg
User
Posts: 4
Joined: 25-May-2007
# Posted on: 25-May-2007 13:54:18   

I'm testing LLBLGEN at the moment and this is a whole new world wink

I have a question regarding what the best approach is to solve a problem;

I have a DB table (schedule) with this column: (other columns are removed for simplicity) Weekday - TINYINT;

Weekday is a number from 0-6. 0 = Sunday and 6 = Saturday

Our weeks starts with monday, so I want to be able to sort my ScheduleCollection so Monday appears first and sunday last.

What is the best way to do that? 1) Is it to somehow extend the generated code, so it is able do a customized sorting? 2) To make an IComparer at the consumer and send it to the EntityCollectino.Sort()? 3) Other?

Here's my code so far:


                EntityView<ScheduleEntity> scheduleView = new EntityView<ScheduleEntity>(routeLink.Schedule);
                ISortExpression sorter = new SortExpression(ScheduleFields.Weekday | SortOperator.Ascending);
                scheduleView.Sorter = sorter;

This sorts so Sunday appears before monday.

I haven't tried to make a IComparer before, so would like to know if that is the approach before I throw too many hours after that solution.

Thanks simple_smile

jmeckley
User
Posts: 403
Joined: 05-Jul-2006
# Posted on: 25-May-2007 15:28:48   

the simpilest solution is to sort on a different field that is in the order you require. if that's not an option, then the solution can be as complex as it needs to be.

Entity collections sort using an ISortExpression (LLBL specific), not IComparer or Sort() like an ArrayList or List<T>. Also the collection itself is not sorted. An EntityView is used to sort the data which can then be placed into a new collection. Similar to how DataTables and DataViews operate.

Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 25-May-2007 15:58:00   

I'd say go for implementing an IComparer.

Just create a class that inherits from IComparer, and implement the Compare method, which accepts 2 objects to be compared and it returns an int that specifies if these objects are equal, or the first is greater than the second or the opposite. (Check the MSDN)

LarsBerg
User
Posts: 4
Joined: 25-May-2007
# Posted on: 29-May-2007 12:57:07   

I must admit I have some difficulties sorting with an IComparer.

I've made a class CMySort that implements the IComparer interface.

And I use it like this.


IComparer customSort = (IComparer) new CMySort();

ScheduleCollection sortedScheduleLinkCol = routeLink.Schedule.Sort("Weekday", System.ComponentModel.ListSortDirection.Ascending, customSort);


That gives me these errors for the second line: Error 2 The best overloaded method match for 'SD.LLBLGen.Pro.ORMSupportClasses.CollectionCore<xxx.BusinessObjects.yyy. EntityClasses.ScheduleEntity>.Sort(string, System.ComponentModel.ListSortDirection, System.Collections.Generic.IComparer<object>)' has some invalid arguments

Error 3 Argument '3': cannot convert from 'System.Collections.IComparer' to 'System.Collections.Generic.IComparer<object>'

So it seems like I should implement a class implementing a Generic.IComparer instead. I've tried that (and other things) but can't get it to compile.

The sort method want's this as the third parameter: System.Collections.Generic.IComparer<object>

Any help stuck_out_tongue_winking_eye ?

It is LLBLGEN SelfServicing targeted at .NET 2.0

jbb avatar
jbb
User
Posts: 267
Joined: 29-Nov-2005
# Posted on: 29-May-2007 15:26:14   

Hello,

how do you create your Comparer?

do you try to define your comparer as it :

public class DayComparer : System.Collections.Generic.IComparer<ScheduleEntity> {

LarsBerg
User
Posts: 4
Joined: 25-May-2007
# Posted on: 29-May-2007 16:22:43   

I have now. Still doesn't work.

Here's the icomparer:


    class CMySort : System.Collections.Generic.IComparer<ScheduleEntity>
    {
        private SortOrderEnum _SortOrder;

        public CMySort(SortOrderEnum sortOrder)
        {
            _SortOrder = sortOrder;
        }

        public int Compare(ScheduleEntity x, ScheduleEntity y)
        {
            int a = Convert.ToInt32(x.Weekday);
            int b = Convert.ToInt32(y.Weekday);

            if (a == 0) a = 7;
            if (b == 0) b = 7;

            if (_SortOrder == SortOrderEnum.Ascending)
            {
                return a.CompareTo(b);
            }
            else
            {
                return -a.CompareTo(b);
            }

        }
    }

And here's the call:


                System.Collections.Generic.IComparer<ScheduleEntity> customSort = (System.Collections.Generic.IComparer<ScheduleEntity>)new CMySort(SortOrderEnum.Ascending);

                ScheduleCollection sortedScheduleLinkCol = routeLink.Schedule.Sort("Weekday", System.ComponentModel.ListSortDirection.Ascending, customSort);

That gives the error: Error 3 Argument '3': cannot convert from 'System.Collections.Generic.IComparer<xxx.BusinessObjects.yyy.EntityClasses.ScheduleEntity>' to 'System.Collections.Generic.IComparer<object>'

I've tried to specify the 3 parameter as customSort <EntityCollection>, but seems like thats not a valid syntax.

simmotech
User
Posts: 1024
Joined: 01-Feb-2006
# Posted on: 30-May-2007 07:16:06   

LarsBerg wrote:

I have now. Still doesn't work. Error 3 Argument '3': cannot convert from 'System.Collections.Generic.IComparer<xxx.BusinessObjects.yyy.EntityClasses.ScheduleEntity>' to 'System.Collections.Generic.IComparer<object>'

I've tried to specify the 3 parameter as customSort <EntityCollection>, but seems like thats not a valid syntax.

Your cMySortclass needs to implement IComparer<object> for it to be passed as the 3rd parameter argument. 1) Change the interface. (Change '<ScheduleEntity>' to '<object>') 2) Change the parameters in the Compare method to be object 3) Cast the usages of x and y to ScheduleEntity.

Cheers Simon

LarsBerg
User
Posts: 4
Joined: 25-May-2007
# Posted on: 30-May-2007 11:00:14   

Thanks, that put me in the right direction. Note that I cast x and y to bytes - as ScheduleEntity.Weekday is the input to the CMySort.Compare() - not ScheduleEntity.

For reference here is the code:


    class CMySort : System.Collections.Generic.IComparer<object>
    {
        public int Compare(object x, object y)
        {
            byte a = (byte)x;
            byte b = (byte)y;

            if (a == 0) a = 7;
            if (b == 0) b = 7;

            return a.CompareTo(b);
        }
    }

and the call:


    CMySort customSort = new CMySort();

    routeLink.Schedule.Sort("Weekday", System.ComponentModel.ListSortDirection.Ascending, customSort);

This was a very unintuitive experience for me, but it works. Thanks!

BUT, I see some issues with this solution that I don't think I really can overcome.

1) This implementation doesn't sort ScheduleEntities. It sorts ScheduleEntity.Weekday. So I am limited to only sorting on 1 column. That means that I probably can't use this implementation, but have to come up with something else.

2) And I can not use IComparer in combination with EntityViews - so I have to do the sorting on the "base" collection

simmotech
User
Posts: 1024
Joined: 01-Feb-2006
# Posted on: 30-May-2007 12:43:42   

LarsBerg wrote:

Thanks, that put me in the right direction. Note that I cast x and y to bytes - as ScheduleEntity.Weekday is the input to the CMySort.Compare() - not ScheduleEntity.

For reference here is the code:


    class CMySort : System.Collections.Generic.IComparer<object>
    {
        public int Compare(object x, object y)
        {
            byte a = (byte)x;
            byte b = (byte)y;

            if (a == 0) a = 7;
            if (b == 0) b = 7;

            return a.CompareTo(b);
        }
    }

and the call:


    CMySort customSort = new CMySort();

    routeLink.Schedule.Sort("Weekday", System.ComponentModel.ListSortDirection.Ascending, customSort);

This was a very unintuitive experience for me, but it works. Thanks!

BUT, I see some issues with this solution that I don't think I really can overcome.

1) This implementation doesn't sort ScheduleEntities. It sorts ScheduleEntity.Weekday. So I am limited to only sorting on 1 column. That means that I probably can't use this implementation, but have to come up with something else.

2) And I can not use IComparer in combination with EntityViews - so I have to do the sorting on the "base" collection

To be honest, I'm not sure how sorting works within LLBLGen Pro (I leave my DevExpress Grid to do sorting mainly) but I did have a quick look in CollectionCore<T>. Seems that only a single column is supported and it uses the QuickSort of List<T> to do the sorting (after putting null values first).

I see this as a problem since QuickSort is not ideal for sorting items that have multiple properties (I think the term is "non-stable sorting algorithm). If your single sort column/property descriptor has duplicate values then those rows/entities with that duplicate value will be sorted amongst themselves in random order. So if you have a grid and change the sort direction then change it back again, the rows displayed will not necessarily be in the same order you started with.

Ideally, you want to be able to take control of the whole sorting process by being able to use your custom comparer on the whole object rather than just the single sort field. That way you can write custom comparers with any level of comparison. Maybe the support team can advise of a way of using a custom IComparer in a View.

Here is some sample code (similar to what you had originally) for a ScheduleSorter to show how to sort by multiple fields. Taking this further, it should be possible to write a generic Comparer for multiple fields along the same principle, ie keep going through the list of sort fields until a non-zero result is found. (I think I have one somewhere that used reflection to sort by any number of properties - not fast but could be speeded up using .NET20 features and/or LLBLGen metadata)


public int Compare(object x, object y)
{
    int result = 0;
    ScheduleEntity left = (ScheduleEntity) x;
    ScheduleEntity right = (ScheduleEntity) y;
    
    // Sort by adjusted weekday first
    int a = Convert.ToInt32(x.Weekday);
    int b = Convert.ToInt32(y.Weekday);
    if (a == 0) a = 7;
    if (b == 0) b = 7;
    result = a.CompareTo(b);
    
    if (result != 0)
    {
        if (_weekDaySortOrder == SortOrder.Descending) result = -result;
    }
    else
    {
        // Both the same, so do another comparison here
        result = left.OtherField.CompareTo(right.OtherField);
    
        if (result != 0)
        {
            if (_otherFieldSortOrder == SortOrder.Descending) result = -result;
        }
        else {
            // Repeat further comparisons here
        }
    
    }
    
    return result;

}

Cheers Simon

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39914
Joined: 17-Aug-2003
# Posted on: 30-May-2007 16:09:41   

You can sort this using an Entity view I think.

((Weekday + 7) ExOp.Mod 8 ) is the expression you need to sort on (ascending).

this will place monday first and sunday last simple_smile

So instead of using solely ScheduleFields.Weekday, you should do something like: new SortExpression(new EntityField("WeekDayMod", new Expression((ScheduleFields.Weekday + 7), ExOp.Mod, 8 )) | SortOperator.Ascending);

Haven't tested it, but something in that direction should do it.

Frans Bouma | Lead developer LLBLGen Pro