Linq GroupBy and Aggregate - linq

Given the following list:
var data = new[]
{
new {category = "Product", text = "aaaa"},
new {category = "Product", text = "bbbb"},
new {category = "Product", text = "bbbb"},
};
how do I group it by category and return an object with category and a description of the different text put together ??
ie. would like to end yp with:
{
categroy="Product"
description = "aaaa,bbbb,cccc"
}
tried the following GroupBy and Aggregate, but something is not right
data.GroupBy(x => x.category).Select(g => new
{
category = g.Key,
description = g.Aggregate((s1, s2) => s1 + "," + s2)
});
TIA

Why don't you use String.Join(IEnumerable) method?
data.GroupBy(x => x.category).Select(g => new
{
category = g.Key,
description = String.Join(",", g.Select(x => x.text))
});
With Aggregate you should do following:
description = g.Aggregate(string.Empty, (x, i) => x + "," + i.text)
First parameter sets seed start value to String.Empty. Second parameter defines method to concatenate current seed value (string) with current element (anonymous_type).

data.GroupBy(x => x.category).Select(g => new
{
category = g.Key,
description = g.Select(x => x.text).Aggregate((s1, s2) => s1 + "," + s2)
});

Related

How to get all the fields after groupby in LINQ

I have this object
With Fecha="Julio2017" I have 2 items and I need to group these three items by SubcontratistaId and Fecha.
item1.Realizado=4060.000 and item2.Realizado=-4060.000 So I need to show in Julio2017 the value of 0
So I try this
private IEnumerable<SubcontratacionesEnFecha> GetRealizadosEnFecha(string proyectoId)
.....
return realizadosAbonadosEnFechaAcumulados
.GroupBy(x => new { x.SubcontratistaId, x.Fecha })
But now I don't know how to get the values of all the items grouped
If I try this I get error
If I try this
return realizadosAbonadosEnFechaAcumulados
.GroupBy(x => new { x.SubcontratistaId, x.Fecha })
.Select(x=>x.First())
.ToList();
I get this
That is, I get the first value of the items grouped
Any idea Please?
Thanks
GroupBy returns IGrouping<,>, which has Key and itself is IEnumerable<> of grouped items. So more probable usage is:
return realizadosAbonadosEnFechaAcumulados
.GroupBy(x => new { x.SubcontratistaId, x.Fecha })
.Select(g => new
{
g.Key.SubcontratistaId,
g.Key.Fecha,
Items = g.ToList()
})
.ToList();
I Found this way
var agrupacion = from p in realizadosAbonadosEnFechaAcumulados
group p by new { p.SubcontratistaId, p.Fecha } into grupo
select grupo;
foreach (var grupo in agrupacion)
{
foreach (var objetoAgrupado in grupo)
{
resultAgrupado.Add(new SubcontratacionesEnFecha
{
SubcontratistaId = objetoAgrupado.SubcontratistaId,
NombreSubcontratista= objetoAgrupado.NombreSubcontratista,
ProyectoId = objetoAgrupado.ProyectoId,
Fecha = objetoAgrupado.Fecha,
Mes = objetoAgrupado.Mes,
Año = objetoAgrupado.Año,
Realizado = objetoAgrupado.Realizado,
Planificado = objetoAgrupado.Planificado
});
}
}
return resultAgrupado;

Linq Implementation With Dynamic GroupBy Clause

In the bellow Linq Statement, i am facing an error in "Select" while trying to fetch, datatable field like "ID" and assign it to row["ID"].
//row["ID"] = g.Field<decimal>("ID");
Error Message:
System.Linq.IGrouping<string,System.Data.DataRow> does not contain a definition for 'Field'
var x = groupedDataRow
.AsEnumerable()
.Select(g =>
{
var row = dataTable.NewRow();
//row["ID"] = g.Field<decimal>("ID");
row["AMT"] = g.Sum(r => r.Field<decimal>("AMT"));
row["PERCENTAGE"] = g.Sum(r => r.Field<decimal>("PERCENTAGE"));
return row;
}).CopyToDataTable();
How retrieve datatable field like "ID" so that it could be assigned to row["ID"] in the Select statement?
Linq Example
public class Program
{
static StringBuilder stringBuilder = new StringBuilder();
public static String GroupData(DataRow dataRow)
{
String[] columnNames = new[] {"ID","COL1", "COL2"};
stringBuilder.Remove(0, stringBuilder.Length);
foreach (String column in columnNames)
{
stringBuilder.Append(dataRow[column].ToString());
}
return stringBuilder.ToString();
}
public static void Main()
{
DataTable dataTable = new DataTable("MyTable");
DataColumn dc2 = dataTable.Columns.Add("ID", typeof(decimal));
dataTable.Columns.Add("AMT", typeof(decimal));
dataTable.Columns.Add("PERCENTAGE", typeof(decimal));
dataTable.Columns.Add("COL1", typeof(String));
dataTable.Columns.Add("COL2", typeof(String));
dataTable.Rows.Add(000, 400,100,"sss","vvv");
dataTable.Rows.Add(888, 400, 100,"qqq","fff");
dataTable.Rows.Add(000, 300, 100,"eee","aaa");
dataTable.Rows.Add(000, 300, 100,"eee","aaa");
dataTable.Rows.Add(000,400,100,"sss","vvv");
EnumerableDataRowList<DataRow> enumerableRowCollection = new EnumerableDataRowList<DataRow>(dataTable.Rows);
Func<DataRow, String> groupingFunction = GroupData;
var groupedDataRow = enumerableRowCollection.GroupBy(groupingFunction);
var x = groupedDataRow.AsEnumerable()
.Select(g =>
{
var row = dataTable.NewRow();
//row["ID"] = g.Field<decimal>("ID");
row["AMT"] = g.Sum(r => r.Field<decimal>("AMT"));
row["PERCENTAGE"] = g.Sum(r => r.Field<decimal>("PERCENTAGE"));
return row;
}).CopyToDataTable();
foreach(DataRow row in x.Rows)
{
Console.WriteLine(row["ID"].ToString() + " " + row["COL1"].ToString() + " " + row["COL2"].ToString() + " " + row["AMT"].ToString() + " " + row["PERCENTAGE"].ToString()) ;
}
}
class EnumerableDataRowList<T> : IEnumerable<T>, IEnumerable
{
IEnumerable dataRows;
internal EnumerableDataRowList(IEnumerable items)
{
dataRows = items;
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
foreach (T dataRow in dataRows)
yield return dataRow;
}
IEnumerator IEnumerable.GetEnumerator()
{
IEnumerable<T> iEnumerable = this;
return iEnumerable.GetEnumerator();
}
}
It's because g is an IGrouping which doesn't have a Field member.
You could maybe try something like:
var x = groupedDataRow.AsEnumerable()
.Select(g =>
{
var row = dataTable.NewRow();
row["ID"] = g.Select(r => r.Field<decimal>("ID")).FirstOrDefault();
row["AMT"] = g.Sum(r => r.Field<decimal>("AMT"));
row["PERCENTAGE"] = g.Sum(r => r.Field<decimal>("PERCENTAGE"));
return row;
}).CopyToDataTable();
or
var x = groupedDataRow.AsEnumerable()
.Select(g =>
{
var row = dataTable.NewRow();
row["ID"] = g.First().Field<decimal>("ID");
row["AMT"] = g.Sum(r => r.Field<decimal>("AMT"));
row["PERCENTAGE"] = g.Sum(r => r.Field<decimal>("PERCENTAGE"));
return row;
}).CopyToDataTable();
It should be safe to use g.First() because a group is only a group if it contains any items.

How to create an identity column within a lambda?

How can I make the expression add a counter (ID) that is 1,2,3,4 etc... ? I just want to have some unqiue key to identify the data
var query = data.Select(x =>
new DemoItemV1
{
Id = x.Field<double>("ID"),
AreaId = x.Field<int>("1Area ID"),
CategoryTitle = x.Field<string>("2Table Title")
}).ToList();
I don't think you can do this purely in a Linq-to-Sql or Linq-to-Entities query. But, since you're already materializing it to a list, you can do this:
var query = data.Select(x =>
new DemoItemV1
{
AreaId = x.Field<int>("1Area ID"),
CategoryTitle = x.Field<string>("2Table Title")
})
.AsEnumerable()
.Select((x, i) => { x.ID = i + 1; return x })
.ToList();

Trying to set property in a LINQ GroupBy Select using a loop

I have the following code:
public class Report
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Sales { get; set; }
}
var result = myItems.GroupBy(x => new { Id = x.Id, Name = x.Name }).Select(x => new Report { Id = x.Key.Id, Name = x.Key.Name });
foreach (var item in result)
{
item.Sales = anotherColletion.FirstOrDefault(x => x.Id == item.Id).Sales;
}
I am unable to set the sales property to any value this way. Even if I try:
foreach (var item in result)
{
item.Sales = 50;
}
However, if I set the property using the following code it works:
var result = myItems.GroupBy(x => new { Id = x.Id, Name = x.Name }).Select(x => new Report { Id = x.Key.Id, Name = x.Key.Name, Sales = 50 });
Is this by design?
The problem is that LINQ queries are lazy ("deferred execution"). You are setting the property on each result of the query in the foreach loop, but these results will essentially disappear into thin air.
When you enumerate the results of the query again after your foreach (which you haven't shown us), the query is re-executed and the results recreated, effectively undoing your changes. Remember that the query is just a specification for how to produce the results, not the results themselves.
A simple fix is to materialize the query into a collection first.
var result = myItems.GroupBy(x => new { Id = x.Id, Name = x.Name })
.Select(x => new Report { Id = x.Key.Id, Name = x.Key.Name })
.ToList();
Your foreach will then end up mutating the elements of an in-memory collection rather than the results of a lazy query, and will therefore be visible downstream.
Personally though, consider setting the property in the query itself:
var result = myItems.GroupBy(x => new { Id = x.Id, Name = x.Name })
.Select(x => new Report
{
Id = x.Key.Id,
Name = x.Key.Name,
Sales = anotherCollection.First(a => a.Id == x.KeyId)
.Sales
});
Thomas,
The var results you are using is just a query, when you iterate over it, in the foreach loop, you are generating a new report object but it is not 'stored' anywhere.
Add the ToArray() or ToList() to the end of the query to fix the issue:
var result = myItems.GroupBy(x => new { Id = x.Id, Name = x.Name }).Select(x => new Report { Id = x.Key.Id, Name = x.Key.Name }).ToList();
Amir.

LINQ Group By Subtotal & Total

I have a Batch with BatchItems entered by multiple users. I'm trying to not only get the subtotal per user for a single batch, but also grand total for that same batch regardless of the user grouping. Its this last part that I can't figure out. How might I get that total in order to return it as a list?
from b in context.BatchItem
where b.BatchId == batchId
group b by b.CreatedByUser into g
select new
{
BatchName = g.FirstOrDefault<BatchItem>().Batch.Name,
User = g.Key,
UserBatchCount = g.Count<BatchItem>(),
// something like this is what I can't figure out
TotalBatchCount = b.Count<BatchItem>()
}
Not sure, but try this:
from b in context.BatchItem
let cnt = context.BatchItem.Count()
b.BatchId == batchId
group b by b.CreatedByUser into g
select new
{
BatchName = g.FirstOrDefault<BatchItem>().Batch.Name,
User = g.Key,
UserBatchCount = g.Count<BatchItem>(),
// something like this is what I can't figure out
TotalBatchCount = cnt
}
var batch1 = new { Name = "Batch A", BatchId = 1, CreatedByUser = "David" };
var batch2 = new { Name = "Batch A", BatchId = 1, CreatedByUser = "Mike" };
var batch3 = new { Name = "Batch B", BatchId = 2, CreatedByUser = "Cathy" };
var batch4 = new { Name = "Batch B", BatchId = 2, CreatedByUser = "Cathy" };
var batch5 = new { Name = "Batch B", BatchId = 2, CreatedByUser = "David" };
var batch6 = new { Name = "Batch C", BatchId = 3, CreatedByUser = "Henry" };
var batchItem = new[] { batch1, batch2, batch3, batch4, batch5, batch6 }.ToList();
var result =
batchItem.Where(b => b.BatchId == batchId)
.GroupBy(b => b.BatchId, b => b)
.SelectMany(g =>
g.GroupBy(c => c.CreatedByUser, c => c)
.SelectMany(sg =>
sg.Select(c => new
{
BatchName = g.First().Name,
UserName = c.CreatedByUser,
UserBatchCount = sg.Count(),
TotalBatchCount = g.Count()
})
)
);
Audit Log: Removed previous two code blocks.

Resources