Including tables without using ToList when GroupBy - c#

The tables I included in my query are not included in the query after the groupby.
but if I do a tolist before groupby, the problem is solved, but it didn't seem like a very good solution. Is it possible for me to solve this problem in a different way?
[HttpGet]
public async Task<IActionResult> GetMostPurchased()
var mostRequestUsers = await dbContext.UserProducts
.Include(x => x.Products).Include(x => x.User)
.GroupBy(x => new { x.UserId, x.ProductId })
.Select(g => new
{
MostPurchased = g.Key.ProductId,
UserId = g.Key.UserId,
Count = g.Count(),
// how can i get the following data fields? After grouping, I cannot access the
"UserProducts" and "Products" table.
ProductName = g.Select(x => x.Product.Name),
ProductPrice = g.Select(x => x.Product.Price),
ProductDesc = g.Select(x => x.Product.Desc),
UserFirstName = g.Select(x => x.User.UserFirstName),
UserLastName = g.Select(x => x.User.UserLastName ),
UserPhoneNumber = g.Select(x => x.User.UserPhoneNumber)
//
})
.GroupBy(x => x.UserId)
.Select(g => g.OrderByDescending(t => t.Count).First());
return mostRequestUsers;
// UserProducts
ID UserId ProductId
1 10 1
2 10 2
3 10 3
4 11 4
5 11 5
6 11 6
6 11 4
6 11 3
6 11 2
6 12 1
6 12 4
6 12 5
// User
ID FirstName LastName
1 Tom Jack
2 Brad Pitt
3 John Rock
// Product
ID ProductName Price
1 Apple 20
2 Dell 30
3 Lenovo 40
my purpose: I want to list the most purchased products by users by user ID.
UserId: 10, MostPurchased: 1, Count: 2, ProductName: null, ProductPrice : null, UserFirstName: null, UserLastName : null
UserId: 11, MostPurchased: 2, Count: 3, ProductName: null, ProductPrice : null, UserFirstName: null, UserLastName : null
UserId: 12, MostPurchased: 3, Count: 2, ProductName: null, ProductPrice : null, UserFirstName: null, UserLastName : null

Include do not work with GroupBy and can be omitted. Just add needed field to grouping key:
var users = dbContext.User
.GroupBy(x => new { x.UserId, x.CarId, x.Car.Name })
.Select(g => new
{
UserId = g.Key.UserId,
CarId = g.Key.CarId,
CarName = g.Key.Name
};
Or rewrite query to use Distinct()
var users = dbContext.User
.Select(x => new
{
x.UserId,
x.CarId,
CarName = x.Car.Name
})
.Distinct();

Related

Linq query to select records based on most recent sum

I'm writing a method that selects customer records based on a group by with the most recent purchase total equal (passed to the method).
I did some search and nothing showed up that helps me to achieve the desired output.
customerList.GroupBy(x=> x.CustomerID },
(key, result) =>
{
var orderedList = result.OrderByDescending(c => c.Date).ToList();
return new Customer
{
CustomerID = orderedList.First().CustomerID,
PurchaseID = orderedList.First().PurchaseID,
Price = orderedList.First().Price,
};
});
CUSTOMERID
PURCHACEID
PRICE
DATE
1
11
235
01-03-2021
1
12
230
01-03-2021
1
14
235
05-04-2021
1
15
535
06-04-2021
1
16
230
07-04-2021
If I'm looking for most recent total purchase price of 1000, I should get
CUSTOMERID
PURCHACEID
PRICE
DATE
1
14
235
05-04-2021
1
15
535
06-04-2021
1
16
230
07-04-2021
You probably need to produce a separate list with the cumulative sums. You can then use TakeWhile to take items until some condition is reached
var list = new[] { 1, 2, 3, 4, 5 };
var sum = 0;
var cummulativeSums = list.Select(v => sum += v).ToList();
var result= list.TakeWhile((_, index) => cummulativeSums[index] < 7).ToList();
// 1, 2, 3
Or you could do it all in one go by creating intermediate pair of values.
var list = new[] { 1, 2, 3, 4, 5 };
var sum = 0;
var result = list.Select(value => (sum += value, value))
.TakeWhile(pair => pair.Item1 < 7)
.Select(pair => pair.value)
.ToList();

C# Lambda / extension methods flatten results between 4 entities?

I have a Tag entity in EF6 that has a one-to-many relationship to 3 other entities:
Tag
/ | \
Definition Variant Machine
Tag
{
Id : 1,
Name : New York,
Definition
{
Id : 1,
Title: EquipA
},
Definition
{
Id : 2,
Title: EquipB
},
Variant
{
Id : 1,
Name: EquipA11
},
Variant
{
Id : 2,
Name: EquipB11
},
Machine
{
Id : 1,
Plant : New York,
Line : 1A
}
Machine
{
Id : 2,
Plant : New York,
Line : 2B
}
}
I want to return the flattened results for all 4 entities so I get results like this:
Tag.Id, Tag.Name, Tag.Definition.Id, Tag.Definition.Title, Tag.Variant.Id, Tag.Variant.Name, Tag.Machine.Id, Tag.Machine.Plant, Tag.Machine.Line
1, New York, 1, EquipA, 1, EquipA11, 1, New York, 1A
1, New York, 1, EquipA, 1, EquipA11, 2, New York, 2B
1, New York, 1, EquipA, 2, EquipB11, 1, New York, 1A
1, New York, 1, EquipA, 2, EquipB11, 2, New York, 2B
1, New York, 2, EquipB, 1, EquipA11, 1, New York, 1A
1, New York, 2, EquipB, 1, EquipA11, 2, New York, 2B
1, New York, 2, EquipB, 2, EquipB11, 1, New York, 1A
1, New York, 2, EquipB, 2, EquipB11, 2, New York, 2B
I am able to do this, but I can only get the Definitions, not sure how to select from all 4 entities:
var temp = db.Tags.Include(c => c.Definition)
.Include(v => v.Variant)
.Include(p => p.PaperMachine)
.SelectMany(t => t.Definition)
.Select(t => new { t.Id, t.Title } )
//.SelectMany(c => c.Definition, v => v.Variant, )
//.SelectMany(v => v.)
.ToList();
It sounds like you want to produce a Cartesian across all of the associated entities.
Something like this should net the results you are looking for:
var temp = db.Tags
.SelectMany(t => t.Definitions
.SelectMany(d => d.Tag.Variants
.SelectMany(v => v.Tag.PaperMachines
.Select(p => new
{
TagId = t.Id,
TagName = t.Name,
DefinitionId = d.Id,
DefinitionName = d.Name,
VariantId = v.Id,
VariantName = v.Name,
PaperMachineId = p.Id,
PaperMachineName = p.Name
})))).ToList();
This populates an anonymous type with the requested details. You can define a DTO/ViewModel to populate and return if this needs to go back to a caller/view. This requires bi-directional navigation properties to get from the tag to each child and back to the tag. Alternatively you could use Join though I suspect that will be a bit messier to read.
There may be a more succinct way to get it. Normally I'm helping people avoid Cartesians, not purposefully making them. :)
Edit: For a many-to-many relationship you can use the above query without the reverse navigation property...
var temp = db.Tags
.SelectMany(t => t.Definitions
.SelectMany(d => t.Variants
.SelectMany(v => t.PaperMachines
.Select(p => new
{
TagId = t.Id,
TagName = t.Name,
DefinitionId = d.Id,
DefinitionName = d.Name,
VariantId = v.Id,
VariantName = v.Name,
PaperMachineId = p.Id,
PaperMachineName = p.Name
})))).ToList();
This looks a bit weird, but does appear to do the trick. Note that for the SelectMany joins we join back in on the t. references, but this allows us to still join across combinations of our (t)ag, (d)efinition, (v)ariant, and (p)aperMachine reference in the final Select.
Be cautious because this will get exponentially bigger and more expensive with the more combinations you introduce.

Count Group By Many-to-Many table

I am using ASP.NET MVC and have a many-to-many table that as follows:
custID | objID
================
1 2
1 3
2 5
2 2
3 2
Both userID and objID are Foreign Keys linking to other tables. What I would like to do is get the highest count based on objID. The table above will yield the following results:
objID | objName | Price
=======================
2 | Chicken | 10
The custID does not matter in this scenario as I just want to get the objID with the highest count.
I've tried the following but i'm stuck here:
//retrieve many-to-many table
var retrieved = db.Customer.Include(c => c.Objects)
var topID = retrieved.GroupBy(q => q.objID)
.OrderByDescending(g => g.Count())
Should do the trick.
var topID = retrieved.GroupBy(q => q.objID)
.Select(g => new { Id = g.Key, Total = g.Count() })
.OrderByDescending(g => g.Total).First().Id;
List<int> lst = new List<int>() { 1, 3, 4, 5, 1, 2, 3, 4, 5, 2, 3, 2 };
var count = lst.GroupBy(q => q).OrderByDescending(s => s.Count())
.First().Key;
var lstKeyWithCount lst.GroupBy(i => i).Select(g => new { Key=g.Key,Count=g.Count() });
in lstKeyWithCount variable you get key with count.
in count variable you get most times repeated value.

Linq for rank() equivalent in SQL Server -

How can order my list using Linq equals rank() over in SQL ?
For example rank is my List<Player>
class Player
{
public int Id;
public int RankNumber;
public int Points;
public int Name;
}
Original Rank list:
RankNumber Points Name Id
1 100 James 01
2 80 Mike 50
3 80 Jess 22
4 50 Jack 11
5 50 Paul 03
6 10 Monik 13
I need this Rank:
RankNumber Points Name Id
1 100 James 01
2 80 Mike 50
2 80 Jess 22
4 50 Jack 11
4 50 Paul 03
6 10 Monik 13
I don't think there is a good way to convert this directly to Linq to SQL but you could do this:
var rankedPlayers = players
.OrderByDescending(p => p.Points)
.Select((p, r) => new Player
{
Id = p.Id,
RankNumber = players.Where(pl => pl.Points > p.Points).Count() + 1,
Points = p.Points,
Name = p.Name
});
It gives you the correct output but will convert horribly and inefficiently to SQL. So I would suggest this modification which materialises the data to a list before creating the ranks:
var rankedPlayers = players
.OrderByDescending(p => p.Points)
.ToList() //<--- Add this
.Select((p, r) => new Player
{
Id = p.Id,
RankNumber = players.Where(pl => pl.Points > p.Points).Count() + 1,
Points = p.Points,
Name = p.Name
});
You can try below expression:
var newData = players
.OrderByDescending(x => x.Points)
.GroupBy(x => x.Points)
.SelectMany((x, index) => x.Select(y => new Player
{
Name = y.Name,
Points = y.Points,
RankNumber = index + 1,
Id = y.Id
}));
players contains IEnumerable of objects of type Player and newData contains ordered data with rank.

How to merge/sum records by group using LINQ?

For example, how can I group the following records by GroupId using LINQ, and sum all other columns in each group? (thus merging all rows in each group into one)
var list = new List<Foo>()
{
new Foo() { GroupId = 0, ValueA = 10, ValueB = 100 },
new Foo() { GroupId = 1, ValueA = 30, ValueB = 700 },
new Foo() { GroupId = 1, ValueA = 40, ValueB = 500 },
new Foo() { GroupId = 2, ValueA = 80, ValueB = 300 },
new Foo() { GroupId = 2, ValueA = 20, ValueB = 200 },
new Foo() { GroupId = 2, ValueA = 20, ValueB = 200 }
};
Expected result is :
| GroupId | ValueA | ValueB |
|---------|--------|--------|
| 0 | 10 | 100 |
| 1 | 70 | 1200 |
| 2 | 120 | 700 |
list.GroupBy(i => i.GroupId)
.Select(g => new { GroupId = g.Key,
ValueA = g.Sum(i => i.ValueA),
ValueB = g.Sum(i => i.ValueB)});
or just for fun you can do it within one GroupBy call using its overload:
list.GroupBy(i => i.GroupId,
(key, groupedItems) => new {
GroupId = key,
ValueA = groupedItems.Sum(i => i.ValueA),
ValueB = groupedItems.Sum(i => i.ValueB),
});
or you can use Aggregate to avoid iterating each group many times:
list.GroupBy(i => i.GroupId)
.Select(g => g.Aggregate((i1, i2) => new Foo{ GroupId = i1.GroupId,
ValueA = i1.ValueA + i2.ValueA,
ValueB = i1.ValueB + i2.ValueB,
}));
var query = list.GroupBy(x=> x.GroupId)
.Select(g=> new
{
GroupId = g.Key,
ValueA = g.Sum(x => x.ValueA),
ValueB = g.Sum(x => x.ValueB)
});
Use GroupBy to collect Foos into groups based on their GroupId, and then create a new object for each "row" of the result (that object can be a Foo itself based on the code you give, but it could just as easily be anything else, including an anonymous type). At that point you will also sum up the other values.
var sums = list.GroupBy(f => f.GroupId)
.Select(g => new Foo
{
GroupId = g.Key,
ValueA = g.Sum(f => f.ValueA),
ValueB = g.Sum(f => f.ValueB)
}).ToList();
Avoiding the lambda expressions (almost) and using a "purely" LINQ way:
var sums = from foo in list
group foo by foo.GroupId into groupings
orderby groupings.Key ascending
select new
{
GroupId = groupings.Key,
ValueA = groupings.Sum(g => g.ValueA),
ValueB = groupings.Sum(g => g.ValueB)
};
I just think LINQ is a bit more natural-language looking, as compared to lambda (not that there's anything wrong with that...)

Categories

Resources