Linq GroupBy TakeWhile? - linq

Using EF4 and Linq
I have a data structure which looks like this:
parentid childid version
1 2 1
1 3 1
1 4 1
1 2 2
1 3 2
1 5 2
...
That is, I want to select the parentid, childid but only for the highest version, for every parentid there is in the database.
My first attempt was this:
var links = data
.GroupBy (link => link.parentid)
.Select(ig => ig.OrderByDescending(link => link.version).First())
.Select ( link => new ....... );
However, this obviously only selects one of the child id's for each parent id..
In the sample data above I want to get the child ids 2,3,5 from version 2 for parent 1 that is..

This should meet your requirement..
var links = data.Where(x => x.version == data.Max(y => y.version))
.OrderBy(x => x.childid)
.Select(x => new {parentid = x.parentid, childid = x.childid});
if you need only for parent 1 then
var links = data.Where(x => x.version == data.Max(y => y.version))
.Where(x => x.parentid == 1)
.OrderBy(x => x.childid)
.Select(x => new {parentid = x.parentid, childid = x.childid});

I ended up using a subquery like this:
var links = data
.OrderBy(link => link.parentid)
.ThenBy(link => link.childid)
.Where(o => o.version == data
.Where(i => i.version == o.version).Max(l => l.version))
.Select(link => new ....);
This way, I get all the parentid's in the db with only the children with the max version for that specific parent.

Related

LINQ : How to set value of Take() from a field in current query

I have a table of Widgets with columns "Name" and "Count"
The "Count" field contains count of Posts each Widget would show:
Name | Count
---------------------
RecentNews | 6
SpecialNews | 5
NewsGallery | 10
The second table associated with Widgets Table and Posts Table :
PostID | WidgetID
------------------
100 | 6
101 | 5
102 | 10
For more performance, I just fetch last needed posts for each Widget by this query:
var postInWidgets = db.PostWidgets
.GroupBy(pw => pw.WidgetID)
.SelectMany(g => g.OrderByDescending(p => p.Post.DateCreated).Take(500))
.ToList();
and then get posts in each widget :
var postsInGalery = postInWidgets
.Where(wid => wid.WidgetID == 1).Take(6)
.ToList();
var postsInSpecialNews=postInWidgets
.Where(wid => wid.WidgetID == 2).Take(5)
.ToList();
var postsInRecentNews=postInWidgets
.Where(wid => wid.WidgetID == 5).Take(10)
.ToList();
and in each Widget Partial View :
foreach(var p in Model.PostsInRecentNews)
{
<li>#Html.ActionLink(p.Post.Title,"Index","Home")</li>
}
My Question : How to Set the int value of Take(count) Dynamically for each widget instead of Take(6) , Take(5) , Take(10) ...
I think I need to use TakeWhile() instead the Take()
Thanks for any assistance...
It sounds like you just need to fetch the counts first (you may want to cache them):
var counts = db.Counts.ToDictionary<string, int>(c => c.Name, c => c.Count);
Then:
var postsInGallery = postInWidgets
.Where(wid => wid.WidgetID == 1)
.Take(counts["NewsGallery"])
.ToList();
var postsInSpecialNews = postInWidgets
.Where(wid => wid.WidgetID == 2)
.Take(counts["SpecialNews"])
.ToList();
var postsInRecentNews = postInWidgets
.Where(wid => wid.WidgetID == 5)
.Take(counts["RecentNews"])
.ToList();
You could potentially use an enum instead of a string, to avoid the use of easily-typoed string constants. If the enum had a value of the related widget ID, you could wrap that up in a single method:
List<Widget> GetWidgets(WidgetType widgetType)
{
return postInWidgets.Where(wid => wid.WidgetID == (int) widgetType)
.Take(counts[widgetType])
.ToList();
}
Then call it as:
var postsInGallery = GetWidgets(WidgetType.NewsGallery);
var postsInSpecialNews = GetWidgets(WidgetType.SpecialNews);
var postInRecentNews = GetWidgets(WidgetType.RecentNews);
(This assumes counts is a field somewhere, of course - adjust as per your requirements.)

Linq GROUP & SUM the same columns from different tables

I'm trying to combine these 2 Linq queries into 1:
var query = from s in _context.Set<StockInventoryItem>()
where s.StockCatalogueItemId == id
group s by s.StockType into g
select new
{
inStock = g.Sum(x => x.QtyInStock),
};
var query2 = from p in _context.Set<PurchaseOrderItem>()
where p.StockCatalogueItemId == id
group p by p.StockType into g2
select new
{
onOrder = g2.Sum(x => x.QtyStillDue)
};
Note that the filtering, grouping and output is the same from both tables, and I want the results to look like this:
StockType inStock onOrder
+----------+--------+--------+
Type 1 4 3
+----------+--------+--------+
Type 2 0 1
i.e. Quantities grouped by StockType
This is EF code first and there is no direct relationship between these tables, which is why I'm trying this query in the service layer so I can access both entities.
You should be able to "shoehorn" both groups into the same sequence with anonymous types and Concat, and then count the results separately, like this:
var query = _context.Set<StockInventoryItem>()
.Where(ii => ii.StockCatalogueItemId == id)
.Select(ii => new {
II = ii, PO = (PurchaseOrderItem)null
}).Concat(_context.Set<PurchaseOrderItem>()
.Where(po => po.StockCatalogueItemId == id)
.Select(po => new {
II = (StockInventoryItem)null, PO = po
})).GroupBy(p => II != null ? ii.StockType : PO.StockType)
.Select(g => new {
InStock = g.Sum(p => p.II != null ? p.II.QtyInStock : 0)
, OnOrder = g.Sum(p => p.PO != null ? p.PO.QtyStillDue: 0)
});

How to find all rows of items that have a part in common using LINQ?

I need to return all records (items) that has a part (X) so I can use that in a group or .GroupBy afterwards
Using this summary data:
ItemName PartName
1 A
1 B
2 A
3 C
So Item1 has two parts (A,B), etc...
I need a LINQ query that will
- find all items that have part A (i.e items 1 and 2)
- return all rows for all these items
1 A
1 B
2 A
Notice that the end result returned the row (1 B) because Item1 has PartA and so I need to get back all rows for Item1.
I was looking at something like:
let items = from data in summary where data.PartName == A select new { data.ItemName } // to get all the items I need
But then, now that I have that list I need to use it to get all the rows for all items listed, and I can't seem to figure it out ...
Actual Source Code (for reference):
NOTE:
Recipe = ITEM
Ingredient = PART
(I was just trying to make it simpler)
ViewFullRecipeGrouping = (
from data in ViewRecipeSummary
group data by data.RecipeName into recipeGroup
let fullIngredientGroups = recipeGroup.GroupBy(x => x.IngredientName)
select new ViewFullRecipe()
{
RecipeName = recipeGroup.Key,
RecipeIngredients = (
from ingredientGroup in fullIngredientGroups
select new GroupIngredient()
{
IngredientName = ingredientGroup.Key
}
).ToList(),
ViewGroupRecipes = (
from data in ViewRecipeSummary
// this is where I am looking to add the new logic to define something I can then use within the next select statement that has the right data based on the information I got earlier in this query.
let a = ViewRecipeSummary.GroupBy(x => x.RecipeName)
.Where(g => g.Any(x => x.IngredientName == recipeGroup.Key))
.Select(g => new ViewRecipe()
{
RecipeName = g.Key,
IngredientName = g.Select(x => x.IngredientName)
})
select new GroupRecipe()
{
// use the new stuff here
}).ToList(),
}).ToList();
Any help would be much appreciated.
Thanks,
I believe this does what you want:
var data = /* enumerable containing rows in your table */;
var part = "X";
var items = new HashSet<int>(data
.Where(x => x.PartName == part)
.Select(x => x.ItemName));
var query = data.Where(x => items.Contains(x.ItemName));
If I understand your comment at the end, I believe this also does what you want:
var query = data
.GroupBy(x => x.ItemName)
.Where(g => g.Any(x => x.PartName == part))
.Select(g => new
{
ItemName = g.Key,
PartNames = g.Select(x => x.PartName)
});

Reordering the linq statement after group by

Basicly, i have a statement which is like that
Contents
.Where(x=>x.CategoryId==5 && x.Status==1)
.GroupBy(q=>q.VersionId)
.OrderByDescending(q=>q.Key)
.Take(100)
.Select(q => new { VersionId = q.Key, MajorVersion = q.Max(x => x.MajorVersion) })
At the moment, it looks like below. But i want to reorder MajorVersion field as a descending...
VersionId MajorVersion
387276 2
365015 1
355427 3
369865 1
How do i do that?
Select returns an IEnumerable so you can do further sorting/ordering etc after.
Contents
.Where(x => x.CategoryId == 5 && x.Status == 1)
.GroupBy(q => q.VersionId)
.OrderByDescending(q => q.Key)
.Take(100)
.Select(q => new { VersionId = q.Key, MajorVersion = q.Max(x => x.MajorVersion) })
.OrderByDescending(x => x.MajorVersion);
Just move the OrderByDescending after the Select.
You will then be able to sort by the fields in the select. (since you'll be calling OrderByDescending on the IQueryable of anonymous types returned by Select())

Linq GroupBy filter

I have the following linq expression pulling all data from my database:
var items = response.Select(a => a.SessionLocationID).ToArray();
mdl = _meetingRepository.Select<SessionLocation>()
.OrderBy(a => a.SessionDT).ThenBy(a => a.SessionEndTime);
Now I want to group by the field ActualRoom and only the ones with ActualRoom count > 3
Is that possible?
You can use GroupBy, just keep in mind that you are losing the ordering you already did so I would start off before you do the sorting:
var groups = _meetingRepository.Select<SessionLocation>()
.GroupBy(x => x.ActualRoom)
.Where(g => g.Count() > 3)
To have sorted groups - assuming preserving the count as a separate property is not neccessary you can just project to an IEnumerable of IEnumerable<SessionLocation>:
var groups = _meetingRepository.Select<SessionLocation>()
.GroupBy(x => x.ActualRoom)
.Where(g => g.Count() > 3)
.Select(g => g.OrderBy(x => x.SessionDT).ThenBy(x => x.SessionEndTime));

Resources