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

Custom Aggregate Function Help

5 Answers 885 Views
GridView
This is a migrated thread and some comments may be shown as answers.
John
Top achievements
Rank 1
John asked on 04 Sep 2012, 08:58 PM
Hello,

I am using the RadGridView version 2012.2.607.40 for WPF and am having a heck of a time trying to determine how to write a custom aggregrate function to sum a column of System.TimeSpan values. My XAML definition of my grid view looks like this:

<telerik:RadGridView x:Name="scanSessionList" Grid.Row="0" Grid.Column="0" ItemsSource="{Binding ElementName=_this, Path=ScanSessions}" Margin="0,0,0,6" ShowGroupPanel="False" ShowGroupFooters="True">
    <telerik:RadGridView.GroupDescriptors>
        <telerik:GroupDescriptor Member="ScanSession.VehicleId" SortDirection="Ascending">
            <telerik:GroupDescriptor.AggregateFunctions>
                <telerik:CountFunction Caption="Missions: "/>
            </telerik:GroupDescriptor.AggregateFunctions>
        </telerik:GroupDescriptor>
    </telerik:RadGridView.GroupDescriptors>
    <telerik:RadGridView.Columns>
        <telerik:GridViewDataColumn Header="Description" DataMemberBinding="{Binding Path=ScanSession.Tag}"/>
        <telerik:GridViewDataColumn Header="Start Time" DataMemberBinding="{Binding Path=ScanSession.StartTimestamp, StringFormat=d MMM yyyy HH:mm}"/>
        <telerik:GridViewDataColumn Header="Elapsed Time" DataMemberBinding="{Binding Path=ScanSession.Duration, Converter={StaticResource TimeSpanConverter}}">
            <telerik:GridViewDataColumn.AggregateFunctions>
                <telerik:SumFunction/>
 
            </telerik:GridViewDataColumn.AggregateFunctions>
        </telerik:GridViewDataColumn>
        <telerik:GridViewDataColumn Header="Distance" DataMemberBinding="{Binding Path=ScanSession.DistanceTravelled, Converter={StaticResource DistanceConverter}}"/>
        <telerik:GridViewDataColumn Header="Avg Speed" DataMemberBinding="{Binding Path=ScanSession.AverageSpeed, Converter={StaticResource SpeedConverter}}"/>
        <telerik:GridViewDataColumn Header="Max Speed" DataMemberBinding="{Binding Path=ScanSession.MaximumSpeed, Converter={StaticResource SpeedConverter}}"/>
    </telerik:RadGridView.Columns>
</telerik:RadGridView>


Please note that ScanSession.Duration is of type System.TimeSpan.

I first tried the naive approach of just using the <telerik:SumFunction/> as shown above and got an InvalidOperationException with the error:

No generic method 'Sum' on type 'System.Linq.Enumerable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic.

So I assumed I needed to write a custom aggregrate function and looked at the standard deviation example Telerik has and came up with substituting <local:TimeSpanSumFunction SourceField="ScanSession.Duration"/>  for <telerik:SumFunction/> and added the following:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Telerik.Windows.Data;
 
namespace Exponent.Analyzer.Performance
{
    public class TimeSpanSumFunction : EnumerableSelectorAggregateFunction
    {
        protected override string AggregateMethodName
        {
            get { return "TimeSpanSum"; }
        }
 
        protected override Type ExtensionMethodsType
        {
            get { return typeof(Aggregates); }
        }
    }
 
    public static class Aggregates
    {
        public static TimeSpan TimeSpanSum<TSource>(IEnumerable<TSource> source, Func<TSource, TimeSpan> selector)
        {
            TimeSpan sum = TimeSpan.FromSeconds(0);
 
            foreach (TimeSpan ts in source.Select(s => selector(s)))
            {
                sum = sum.Add(ts);
            }
 
            return sum;
        }
    }
}


I then got an InvalidOperationException with the error:

No generic method 'TimeSpanSum' on type 'Exponent.Analyzer.Performance.Aggregates' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic.

Finally, I wondered if I could just extend EnumerableAggregateFunction so I changed the XAML to use simply <local:TimeSpanSumFunction/> and came up with:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Telerik.Windows.Data;
 
namespace Exponent.Analyzer.Performance
{
    public class TimeSpanSumFunction : EnumerableAggregateFunction
    {
        protected override string AggregateMethodName
        {
            get { return "TimeSpanSum"; }
        }
 
        protected override Type ExtensionMethodsType
        {
            get { return typeof(Aggregates); }
        }
    }
 
    public static class Aggregates
    {
        public static TimeSpan TimeSpanSum<TSource>(IEnumerable<TimeSpan> source)
        {
            TimeSpan sum = TimeSpan.FromSeconds(0);
 
            foreach (TimeSpan ts in source)
            {
                sum = sum.Add(ts);
            }
 
            return sum;
        }
    }
}

and I still get an InvalidOperationException with the message:

No generic method 'TimeSpanSum' on type 'Exponent.Analyzer.Performance.Aggregates' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic.

Obviously, I do not know how to write a custom aggregate function and I am finding what little documentation there is very unhelpful. I have more custom aggregate functions I need to write using other non-simple types.

Could somebody please give me some advise on how to write these custom aggregate functions?

And one other thing, can you also suggest how I can invoke my TimeSpanConverter (as seen in the XAML for the "Elapsed TIme" grid column") when displaying the results of the aggregte function?

Thank you,

-- john

5 Answers, 1 is accepted

Sort by
0
Dimitrina
Telerik team
answered on 05 Sep 2012, 06:59 AM
Hello John,

As the custom aggregates do not work in your case, then I would recommend you to try the approach suggested in this forum thread.

Kind regards,
Didie
the Telerik team

Explore the entire Telerik portfolio by downloading Telerik DevCraft Ultimate.

0
John
Top achievements
Rank 1
answered on 05 Sep 2012, 03:54 PM
Hello Didie,

Thank you for the quick reply.

Are you saying the custom aggregate mechanism will not work in my case? In my original post, I did not mean to imply that they do not work; I meant to imply that I do not understand how to use them properly. I would hope that the custom aggregate mechanism would let me "sum" (or perform some other function) on all values of a column regardless of the data type of those values. Is this not possible?

Thank you for any more guidance you can provide on how to properly extend EnumerableAggregateFunction and EnumerableSelectorAggregateFunction.

-- john
0
John
Top achievements
Rank 1
answered on 05 Sep 2012, 10:04 PM
Hi Didie,

I've had more time to play with this and found one solution that works. I had to change my custom aggregate code to the following:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Telerik.Windows.Data;
using Exponent.Analyzer.ViewModels;
 
namespace Exponent.Analyzer.Performance
{
    public class TimeSpanSumFunction : EnumerableAggregateFunction
    {
        protected override string AggregateMethodName
        {
            get { return "TimeSpanSum"; }
        }
 
        protected override Type ExtensionMethodsType
        {
            get { return typeof(Aggregates); }
        }
    }
 
    public static class Aggregates
    {
        public static TimeSpan TimeSpanSum<TSource>(IEnumerable<TSource> source)
        {
            TimeSpan sum = TimeSpan.FromSeconds(0);
 
            foreach (TSource ts in source)
            {
                sum = sum.Add((ts as ScanSessionViewModel).ScanSession.Duration);
            }
 
            return sum;
        }
    }
}

I assumed that my aggregate code would be receiving an IEnumerable of the type of the data being displayed in the particular grid column. That was a bad assumption. Instead, the IEnumerable contains data of the type of the ItemsSource for the grid.

Now I am going to see if I can figure out how to use the EnumerableSelectorAggregateFunction.

-- john
0
Dimitrina
Telerik team
answered on 06 Sep 2012, 02:32 PM
Hi,

 I am glad to hear that you have resolve the problem. You can use the EnumerableSelectorAggregateFunction as demonstrated on Custom Aggregate Function Demo.

All the best,
Didie
the Telerik team

Explore the entire Telerik portfolio by downloading Telerik DevCraft Ultimate.

0
Tomáš
Top achievements
Rank 1
Iron
Iron
answered on 15 Feb 2023, 04:01 PM
@John



To use the EnumerableSelectorAggregateFunction, you have to create function that takes two parameters (Similar to LINQ .Sum function - items and selector).


public class CheckedCountFunction : SumFunction //EnumerableSelectorAggregateFunction
{
    // inheriting SumFunction, so I don't have to create my own Merge function

    protected override string AggregateMethodName => nameof(CheckedCount);

    protected override Type ExtensionMethodsType => typeof(CheckedCountFunction);

    public override object Merge(object item, AggregateResultMergeAction mergeAction,
        AggregateResult originalAggregateResult, QueryableCollectionViewGroup group)
    {
        /*
         * DECOMPILED

        object obj = originalAggregateResult.Value;
        if (obj == null)
          return (object) null;
        Type type1 = obj.GetType();
        object aggregationValue = this.GetAggregationValue(item);
        if (aggregationValue == null)
          return obj;
        Type type2 = aggregationValue.GetType();
        Func<object, object, object> func = (Func<object, object, object>) null;
        switch (mergeAction)
        {
          case AggregateResultMergeAction.Add:
            func = ArithmeticFunctionsCache.GetAdditionFunction(type1, type2);
            break;
          case AggregateResultMergeAction.Remove:
            func = ArithmeticFunctionsCache.GetSubtractionFunction(type1, type2);
            break;
        }
        return func(obj, aggregationValue);

         */

        return base.Merge(item, mergeAction, originalAggregateResult, group);
    }

    public static int CheckedCount<TItem, TProperty>(IEnumerable<TItem> items, Func<TItem, TProperty> selector)
    {
        if (typeof(TProperty) != typeof(bool))
            throw new ArgumentException($@"You must select bool property, to use {nameof(CheckedCountFunction)}.");

        var selectedProperties = items.Where(s => Convert.ToBoolean(selector(s)));
        var count = selectedProperties.Count();
        return count;
    }
}

This function of mine for example does Count of `true` values.

I suppose, you have your problem resolved more than 10 years ago, but maybe someone might find this usefull.
Martin Ivanov
Telerik team
commented on 16 Feb 2023, 01:52 PM

Hey Tomáš, thanks for sharing your solution.
Tags
GridView
Asked by
John
Top achievements
Rank 1
Answers by
Dimitrina
Telerik team
John
Top achievements
Rank 1
Tomáš
Top achievements
Rank 1
Iron
Iron
Share this question
or