LINQ Group By Subtotal & Total - linq

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.

Related

Building LINQ Dynamic Order Clause, But Cast Field

The following code works great of I want to dynamically build an orderby:
public static IQueryable<TEntity> OrderByAnyField<TEntity>(this IQueryable<TEntity> source, string orderByProperty, bool desc, Type propertyType)
{
string command = desc ? "OrderByDescending" : "OrderBy";
var type = typeof(TEntity);
var property = type.GetProperty(orderByProperty);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var orderByExpression = Expression.Lambda(propertyAccess, parameter);
var resultExpression = Expression.Call(typeof(Queryable), command, new Type[] { type, property.PropertyType },
source.Expression, Expression.Quote(orderByExpression));
return source.Provider.CreateQuery<TEntity>(resultExpression);
}
So, I want to be able to change the CAST of the sort. So, as an example, I would like to take:
.OrderBy(x => x.Something)
and do this instead:
.OrderBy(x => double.Parse(x.Something))
Any help is greatly appreciated
I am sharing one simpler approach to do the same. You can add generics as per your requirement. You can play on data any way you want
static object GetOrder(Table tb, string propertyName, bool desc)
{
if (desc)
return 0;
PropertyInfo pI = typeof(Table).GetProperty(propertyName);
var val = pI.GetValue(tb);
return val;
}
static object GetOrderDesc(Table tb, string propertyName, bool desc)
{
if (!desc)
return 0;
PropertyInfo pI = typeof(Table).GetProperty(propertyName);
var val = pI.GetValue(tb);
return val;
}
static void Main(string[] args)
{
bool desc = false;
List<Table> table = new List<Table>() {
new Table() { ID = "03", X = "Str1", Y = "C1", P = 10 },
new Table() { ID = "04", X = "Str1", Y = "C1", P = 5 },
new Table() { ID = "05", X = "Str1", Y = "C1", P = 1 },
new Table() { ID = "06", X = "Str1", Y = "C1", P = 2 },
new Table() { ID = "07", X = "Str2", Y = "C1", P = 25 },
new Table() { ID = "08", X = "Str2", Y = "C1", P = 4 },
new Table() { ID = "09", X = "Str1", Y = "C2", P = 411 },
new Table() { ID = "10", X = "Str1", Y = "C2", P = 2356 },
new Table() { ID = "11", X = "Str2", Y = "C2", P = 12 },
new Table() { ID = "12", X = "Str2", Y = "C2", P = 33 },
};
var sortedTable = table.OrderBy(x => GetOrder(x, "P", desc)).OrderByDescending(x => GetOrderDesc(x, "P", desc));
}

LINQ - Group By child property

I have a list of users, each user has string array property called Tags. I am trying to get a unique list of tags and a total count, any idea what I a missing here? I am using LinqPad to write my test query, please see the example code:
void Main()
{
List<User> users = new List<User>(){
new User {Id = 1, Tags = new string[]{"tag1", "tag2"}},
new User {Id = 2, Tags = new string[]{"tag3", "tag7"}},
new User {Id = 3, Tags = new string[]{"tag7", "tag8"}},
new User {Id = 4, Tags = new string[]{"tag1", "tag4"}},
new User {Id = 5 },
};
var uniqueTags = users.Where(m=>m.Tags != null).GroupBy(m=>m.Tags).Select(m=> new{TagName = m.Key, Count = m.Count()});
uniqueTags.Dump();
// RESULT should BE:
// tag1 - Count(2)
// tag2 - Count(1)
// tag3 - Count(1)
// tag4 - Count(1)
// tag7 - Count(2)
// tag8 - Count(1)
}
public class User{
public int Id {get;set;}
public string[] Tags {get;set;}
}
You can flatten to IEnumerable<string> before grouping:
var uniqueTags = users.SelectMany(u => u.Tags ?? new string[0])
.GroupBy(t => t)
.Select(g => new { TagName = g.Key, Count = g.Count() } );
LINQPad C# Expression version:
new[] {
new { Id = 1, Tags = new[] { "tag1", "tag2" } },
new { Id = 2, Tags = new[] { "tag3", "tag7" } },
new { Id = 3, Tags = new[] { "tag7", "tag8" } },
new { Id = 4, Tags = new[] { "tag1", "tag4" } },
new { Id = 5, Tags = (string[])null }
}
.SelectMany(u => u.Tags ?? Enumerable.Empty<string>())
.GroupBy(t => t)
.Select(g => new { TagName = g.Key, Count = g.Count() } )

Linq GroupBy and Aggregate

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)
});

Getting the 1st value in a subset of data

I have a result coming back from a LINQ statement that looks like this:
Id dataId dataVal
A 1 1000
A 2 2000
A 3 3000
A 3 3001
A 3 3002
What I'd like is to just get the 1st item (dataId = 3 and dataVal = 3000)
Here is my query that is generating the above result:
var myIds = myList
.Where(a => ListIds.Contains(a.dataId))
.Select(x=> new
{
Id = x.Id,
DataId = x.dataId,
DataValue = x.DataValue
}).ToList().Distinct();
Do I need to do some grouping or is there an easier way?
Group your items by dataId, and then select first item from each group:
var myIds = (from a in myList
where ListIds.Contains(a.dataId)
group a by a.dataId into g
let firstA = g.OrderBy(x => x.DataValue).First()
select new {
Id = firstA.Id,
DataId = g.Key,
DataValue = firstA.DataValue
}).ToList();
Or with extension methods (it returns first item in original order):
var myIds = myList
.Where(a => ListIds.Contains(a.dataId))
.GroupBy(a => a.dataId)
.Select(g => new
{
Id = g.First().Id,
DataId = g.Key,
DataValue = g.First().DataValue
}).ToList();
Use .FirstOrDefault() after the Select
var myIds = myList
.Where(a => ListIds.Contains(a.dataId))
.Select(x=> new
{
Id = x.Id,
DataId = x.dataId,
DataValue = x.DataValue
}).FirstOrDefault();

Combine properties values from a custom List with LINQ

Lets assume I have a list of Persons, in the List I have the following objects:
Person = { ID= 1, State="CA"}
Person = { ID= 2, State="PA"}
Person = { ID= 1, State="NY"}
Person = { ID= 1, State="OH"}
Person = { ID= 3, State="FL"}
Person = { ID= 2, State="KC"}
How do I get a new List Where I can have new Persons by ID only once and if repeated get the State value and put it in the new Person object separated by comma. For example the new List will be
Person = { ID= 1, State="CA,NY,OH"}
Person = { ID= 2, State="PA,KC"}
Person = { ID= 3, State="FL"}
Is there a way to achieve this?
Use Enumerable.GroupBy and String.Join:
var result = persons.GroupBy(p => p.ID)
.Select(g => new Person{
ID = g.Key,
State = string.Join(",", g.Select(p => p.State))
}).ToList();
here is the extension method version
var persons = Person.GroupBy(p => p.ID).Select(p => new Person() { ID = p.Key, State = String.Join(",", p.Select(p2 => p2.State).ToArray()) }).ToList();

Resources