c# - multiple - Obtenga la suma de dos columnas en una consulta LINQ
sum linq c# datatable (8)
digamos que tengo una tabla llamada Items (ID int, Done int, Total int)
Puedo hacerlo por dos consultas:
int total = m.Items.Sum(p=>p.Total)
int done = m.Items.Sum(p=>p.Done)
Pero me gustaría hacerlo en una consulta, algo como esto:
var x = from p in m.Items select new { Sum(p.Total), Sum(p.Done)};
Seguramente hay una forma de llamar a funciones agregadas desde la sintaxis LINQ ...
Averiguar dónde extraer las sumas u otro agregado en el resto de mi código me confundió, hasta que recordé que la variable que construí era ilegible. Supongamos que tenemos una tabla en nuestra base de datos compuesta de Órdenes, y queremos producir un resumen para la compañía ABC:
var myResult = from g in dbcontext.Ordertable
group p by (p.CUSTNAME == "ABC") into q // i.e., all of ABC company at once
select new
{
tempPrice = q.Sum( x => (x.PRICE ?? 0m) ), // (?? makes sure we don''t get back a nullable)
tempQty = q.Sum( x => (x.QTY ?? 0m) )
};
Ahora la parte divertida - tempPrice y tempQty no están declarados en ningún lado pero deben ser parte de myResult, ¿no? Acceda a ellos de la siguiente manera:
Console.Writeline(string.Format("You ordered {0} for a total price of {1:C}",
myResult.Single().tempQty,
myResult.Single().tempPrice ));
También se podrían usar otros métodos Queryable.
Con una clase auxiliar de tupla, ya sea tu propia o en .NET 4, las estándar puedes hacer esto:
var init = Tuple.Create(0, 0);
var res = m.Items.Aggregate(init, (t,v) => Tuple.Create(t.Item1 + v.Total, t.Item2 + v.Done));
Y res.Item1
es el total de la columna Total
y res.Item2
de la columna Done
.
Cuando usa group by Linq crea una nueva colección de artículos para que tenga dos colecciones de artículos.
Aquí hay una solución para ambos problemas:
- sumando cualquier cantidad de miembros en una iteración y
- evite duplicar la colección de su artículo
Código:
public static class LinqExtensions
{
/// <summary>
/// Computes the sum of the sequence of System.Double values that are obtained
/// by invoking one or more transform functions on each element of the input sequence.
/// </summary>
/// <param name="source">A sequence of values that are used to calculate a sum.</param>
/// <param name="selectors">The transform functions to apply to each element.</param>
public static double[] SumMany<TSource>(this IEnumerable<TSource> source, params Func<TSource, double>[] selectors)
{
if (selectors.Length == 0)
{
return null;
}
else
{
double[] result = new double[selectors.Length];
foreach (var item in source)
{
for (int i = 0; i < selectors.Length; i++)
{
result[i] += selectors[i](item);
}
}
return result;
}
}
/// <summary>
/// Computes the sum of the sequence of System.Decimal values that are obtained
/// by invoking one or more transform functions on each element of the input sequence.
/// </summary>
/// <param name="source">A sequence of values that are used to calculate a sum.</param>
/// <param name="selectors">The transform functions to apply to each element.</param>
public static double?[] SumMany<TSource>(this IEnumerable<TSource> source, params Func<TSource, double?>[] selectors)
{
if (selectors.Length == 0)
{
return null;
}
else
{
double?[] result = new double?[selectors.Length];
for (int i = 0; i < selectors.Length; i++)
{
result[i] = 0;
}
foreach (var item in source)
{
for (int i = 0; i < selectors.Length; i++)
{
double? value = selectors[i](item);
if (value != null)
{
result[i] += value;
}
}
}
return result;
}
}
}
Así es como tienes que hacer la suma:
double[] result = m.Items.SumMany(p => p.Total, q => q.Done);
Aquí hay un ejemplo general:
struct MyStruct
{
public double x;
public double y;
}
MyStruct[] ms = new MyStruct[2];
ms[0] = new MyStruct() { x = 3, y = 5 };
ms[1] = new MyStruct() { x = 4, y = 6 };
// sum both x and y members in one iteration without duplicating the array "ms" by GROUPing it
double[] result = ms.SumMany(a => a.x, b => b.y);
como puedes ver
result[0] = 7
result[1] = 11
Esto hará el truco:
from p in m.Items
group p by 1 into g
select new
{
SumTotal = g.Sum(x => x.Total),
SumDone = g.Sum(x => x.Done)
};
Esto ya se ha respondido, pero las otras respuestas seguirán realizando múltiples iteraciones sobre la colección (múltiples llamadas a Sum) o crearán muchos objetos intermedios / Tuples que pueden estar bien, pero si no lo está, entonces puede crear una extensión método (o múltiple) que lo hace a la antigua usanza pero encaja bien en una expresión LINQ.
Tal método de extensión se vería así:
public static Tuple<int, int> Sum<T>(this IEnumerable<T> collection, Func<T, int> selector1, Func<T, int> selector2)
{
int a = 0;
int b = 0;
foreach(var i in collection)
{
a += selector1(i);
b += selector2(i);
}
return Tuple.Create(a, b);
}
Y puedes usarlo así:
public class Stuff
{
public int X;
public int Y;
}
//...
var stuffs = new List<Stuff>()
{
new Stuff { X = 1, Y = 10 },
new Stuff { X = 1, Y = 10 }
};
var sums = stuffs.Sum(s => s.X, s => s.Y);
Para sumar la tabla, agrupe por una constante:
from p in m.Items
group p by 1 into g
select new {
SumTotal = g.Sum(x => x.Total),
SumDone = g.Sum(x => x.Done)
}
Qué tal si
m.Items.Select(item => new { Total = item.Total, Done = item.Done })
.Aggregate((t1, t2) => new { Total = t1.Total + t2.Total, Done = t1.Done + t2.Done });
//Calculate the total in list field values
//Use the header file:
Using System.Linq;
int i = Total.Sum(G => G.First);
//By using LINQ to calculate the total in a list field,
var T = (from t in Total group t by Total into g select g.Sum(t => t.First)).ToList();
//Here Total is a List and First is the one of the integer field in list(Total)