Как динамически группировать и вычислять совокупные данные сумм / максимумов с помощью LINQ в c# - PullRequest
0 голосов
/ 13 апреля 2020

У нас есть API для работы с данными, поступающими из разных источников (базы данных, веб-сервисы, простой текст, ...), поэтому мы храним их в структуре списка. У нас есть этот код для выбора некоторых столбцов из них и применения функций для получения новых данных (как сумма двух столбцов, ...)

Func<dynamic[], object>[] leftFunctions = new Func<dynamic[], object>[this.Columns.Count];
IEnumerable<dynamic[]> returnValue = null
j = 0;
foreach (JoinDataColumn dataColum in this.Columns.OrderBy(x => x.Index))
{
     //Obtenemos para cada columna la funcion
     leftFunctions[j++] = dataColum.getLeftFunction(LeftDataQuery);
}
returnValue = (await LeftDataQuery.ExecuteAsync(parameters))
    .Select(x => leftFunctions.Select(f => f.Invoke(x)).ToArray());

В приведенном выше коде: LeftDataquery - это класс для получения списка с данные, выполняющие это и возвращающие List<object[]>

await LeftDataQuery.ExecuteAsync(parameters)

LeftFuncions - это формулы для создания новых данных из исходных данных, "lefdataquery [1] + lefdataquery [2]" или просто "lefdataquery [0]" для получения данные первого столбца.

В классе JoinDataColumn у меня есть описание каждого столбца списка результатов: имя, тип и функция агрегирования.

В конце пользователь Можно выбрать столбцы для группирования и формулу агрегирования для других (сумма, максимум, минимум, количество отличных, ...). Итак, теперь у меня есть возвращаемое значение с подмножеством столбцов исходного источника и списком индексов со столбцами для группировки и списком индексов с их формулой агрегации (например, индекс 1 представляет собой SUM и т. Д.)

public enum DataColumnAggregation { NONE = 0, SUM = 1, MAX = 2, MIN = 3 }

Любая идея о том, как сгруппировать по этим данным и получить SUM / MAX / MIN, ..

Выполняя какой-то тест, я написал этот код, но проблема в том, что Groupby не работает с массивом объектов в качестве параметра. Любая идея, чтобы GroupBy работал с этим подходом? 101

    public enum DataColumnAggregation { NONE = 0, SUM = 1, MAX = 2, MIN = 3 }

    static void Main(string[] args)
    {
        List<object[]> returnValue = new List<object[]> { new object[] { "C1", 1, 2 }, new object[] { "C2", 3, 4 }, new object[] { "C1", 1, 3 } };


        DataColumnAggregation[] config = new DataColumnAggregation[] { DataColumnAggregation.NONE, DataColumnAggregation.SUM, DataColumnAggregation.MAX };
        //TODO agregar por la columna 0 , sumar la columna 1 y maximo de la columna 2 de returnvalue

        var resultado2 = returnValue.GroupBy(x => getGroupByColumns(x,config)).Select(x=>agregar(x, config));

    }

    private static object[] agregar(IGrouping<string[], object[]> x, DataColumnAggregation[] config)
    {
        List<object> result = new List<object>();
        for (int i = 0; i < config.Length; i++)
        {
            if (config[i] == DataColumnAggregation.NONE)
            {
                result.Add(x.Select(xy => xy[i]).FirstOrDefault());
            }
            if (config[i] == DataColumnAggregation.SUM)
            {
                result.Add(x.Sum(xy => Convert.ToInt32(xy[i]))); 
            }
            if (config[i] == DataColumnAggregation.MAX)
            {
                result.Add(x.Max(xy => xy[i])); 
            }
            if (config[i] == DataColumnAggregation.MIN)
            {
                result.Add(x.Min(xy => xy[i])); 
            }
        }
        return result.ToArray();
    }


    private static string[] getGroupByColumns(object[] x, DataColumnAggregation[] config)
    {
        List<string> group = new List<string>();
        for (int i = 0; i< config.Length; i++)
        {
            if (config[i] == DataColumnAggregation.NONE) group.Add(x[i].ToString());
        }
        return group.ToArray();
    }

Спасибо

1 Ответ

0 голосов
/ 14 апреля 2020

Поскольку вы пытаетесь сгруппировать по string[] (или лучше, List<string>), вам нужно IEqualityComparer, чтобы перейти к GroupBy, который сравнивает элементы.

public class IEnumerableSequenceEqualityComparer<T> : IEqualityComparer<IEnumerable<T>> {
    public bool Equals(IEnumerable<T> x, IEnumerable<T> y) =>
        Object.ReferenceEquals(x, y) || (x != null && y != null && (x.SequenceEqual(y)));

    public int GetHashCode(IEnumerable<T> src) {
        // Will not throw an OverflowException
        //unchecked {
        //    return src.Where(e => e != null).Select(e => e.GetHashCode()).Aggregate(17, (a, b) => 23 * a + b);
        //}
        var hc = new HashCode();
        foreach (var v in src)
            hc.Add(v);
        return hc.ToHashCode();
    }
}

Фабрика класс с именем Make помогает создать компаратор:

public static class Make {
    public static IEqualityComparer<IEnumerable<T>> SequenceEqualityComparer<T>() => new IEnumerableSequenceEqualityComparer<T>();
    public static IEqualityComparer<IEnumerable<T>> SequenceEqualityComparer<T>(T _) => new IEnumerableSequenceEqualityComparer<T>();
}

Теперь вы можете вызывать GroupBy с компаратором равенства последовательностей:

var resultado2 = returnValue.GroupBy(x => getGroupByColumns(x,config), Make.SequenceEqualityComparer<string>()).Select(x=>agregar(x, config));

ПРИМЕЧАНИЕ. Это вызывает группу Key быть IEnumerable<string>. Если вы предпочитаете IList<string>, просто замените IEnumerable на IList в классе сравнения равенства и классе фактора. Или добавьте дополнительную версию IList с соответствующими изменениями имени.

...