How to display grandparent when child is given - linq

How to display grandparent when 'childItemNo' is given.
Below is my model: Any record (ItemNo) can become a grandparent, parent or child.
public partial class item
{
public string ItemNo { get; set; }
public string ParentItemNo { get; set; }
}
Below query returns parent when childItemNo is given: Would like to display grandparent as well.
var result = DbContext.ItemTables.Where(p => p.ItemNo== childItemNo).Select(p => p.ParentItemNo).ToListAsync();
Thank you.

If you have navigation properties, this is trivial:
Select(c => c.Parent.ParentItemNo)
Without them, you could go slightly more dirty:
Select(c => DbContext.ItemTables.First(p => p.ItemNo == c.ParentItemNo).ParentItemNo)
Or use a join
(from ch in db.ItemTables
join pa in db.ItemTables
on ch.ParentItemNo equals pa.ItemNo
where ch.ItemNo == childItemNo
select pa.ParentItemNo).First()
Or in method syntax:
db.ItemTables.Where(ch => ch.ItemNo == childItemNo).Join(
db.ItemTables,
l => l.ParentItemNo, //left is the child
r => r.ItemNo, //right is the parent
(l, r) => r.ParentItemNo //want the parent's parent
).First();

Related

EF Core query with GroupBy and Count not working as expected

I have a .NET Core 3.1 project with EF Core 3.1.8. Lets say I have two entitys:
public class Card
{
public int CardId { get; set; }
public int Stage { get; set; }
public int SectionId { get; set; }
public Section Section { get; set; }
}
public class Section
{
public int SectionId { get; set; }
public string Title { get; set; }
public List<Card> Cards { get; set; }
}
Now I want a query that gives me the sections and for each section the information of how many Cards with Stage=1, Stage=2, Stage=3 etc. are in there.
I tried this:
var q = _dbContext.Sections
.Include(s => s.Cards)
.Select(s => new
{
s.SectionId,
cards = s.Cards
.Select(c => c.Stage)
.GroupBy(c => c)
.Select(c => new { c.Key, count = c.Count() })
})
.ToList();
But in the result is always only one section with only one card. How can I do this?
I made slight tweak on Group by
var q = _dbContext.Sections
.Include(s => s.Cards)
.GroupBy(s => s.SectionId)
.Select(s => new
{
s.Key,
cards = s.SelectMany(t => t.Cards)
.GroupBy(c => c.Stage)
.Select(c => new { c.Key, count = c.Count() })
})
.ToList();
When I run into issues where EntityFramework isn't quite behaving as I would expect, I tend to fall back to thinking about how I would do this in SQL directly. Mimicking that usually makes EF work.
//Create a query to group and count the cards
//In SQL:
// SELECT SectionId, Stage, COUNT(CardId)
// FROM Cards
// GROUP BY SectionId, Stage
//In EF (note, not executing just building up the query):
var cardCountQuery = context.Cards
.Select(c => new
{
c.SectionId,
c.Stage
})
.GroupBy(c => c)
.Select(c => new
{
SectionAndStage = c.Key,
Count = c.Count()
});
//Now use that as a subquery and join to sections
//In SQL
// SELECT s.SectionId, s.Title, c.Stage, c.CardCount
// FROM Sections s
// INNER JOIN (
// SELECT SectionId, Stage, COUNT(CardId) AS CardCount
// FROM Cards
// GROUP BY SectionId, Stage
// ) c ON c.SectionId = s.SectionId
//In EF:
var sectionsWithCardCountByStage = context.Sections
.Join(cardCountQuery,
s => s.SectionId,
c => c.SectionAndStage.SectionId,
(s, g) => new
{
s.SectionId,
g.SectionAndStage.Stage,
CardCount = g.Count
})
.ToList();
Edit: Reshaping the data per comment
From what is above we can then reshape the data to what you are looking for.
//If you don't mind bring back the Section data multiple times (this will increase the result set size) you can alter the above to bring back the entire Section in the query and then re-shape it in memory.
//NOTE: This will only bring back Sections that have cards
var sectionsWithCardCountByStage = context.Sections
.Join(cardCountQuery,
s => s.SectionId,
c => c.SectionAndStage.SectionId,
(s, g) => new
{
Section = s,
g.SectionAndStage.Stage,
CardCount = g.Count
})
.ToList()
.GroupBy(g => g.Section.SectionId)
.Select(g => new
{
g.First().Section,
Cards = g.ToDictionary(c => c.Stage, c => c.CardCount)
})
.ToList();
//Or you can bring back Sections only once to reduce result set size. This extends the query from the first response section above.
var sections = context.Sections
.Where(s => s.Cards.Count > 0) //Only bring back sections with cards. Remove it to bring back all sections and have an empty dictionary of card counts.
.ToList()
.Select(s => new
{
Section = s,
Cards = sectionsWithCardCountByStage
.Where(c => c.SectionId == s.SectionId)
.ToDictionary(c => c.Stage, c => c.CardCount)
})
.ToList();
EDIT: I try minimize my queries and bring back only the data necessary to do the job. But if you aren't dealing with a lot of data then this might offer a more compact single query option at the expense of possibly bringing back more data then you need and thus a larger result set.
var sections = context.Sections
.Include(s => s.Cards)
.ToList()
.Select(s => new
{
Section = s,
CardCount = s.Cards.GroupBy(c => c.Stage)
.Select(g => new { Stage = g.Key, CardCount = g.Count() })
.ToDictionary(c => c.Stage, c => c.CardCount)
})
.ToList();

enumerable group field using Linq?

I've written a Linq sentence like this:
var fs = list
.GroupBy(i =>
new {
X = i.X,
Ps = i.Properties.Where(p => p.Key.Equals("m")) <<<<<<<<<<<
}
)
.Select(g => g.Key });
Am I able to group by IEnumerable.Where(...) fields?
The grouping won't work here.
When grouping, the runtime will try to compare group keys in order to produce proper groups. However, since in the group key you use a property (Ps) which is a distinct IEnumerable<T> for each item in list (the comparison is made on reference equality not on sequence equality) this will result in a different collection for each element; in other words if you'll have two items:
var a = new { X = 1, Properties = new[] { "m" } };
var b = new { X = 1, Properties = new[] { "m" } };
The GroupBy clause will give you two distinct keys as you can see from the image below.
If your intent is to just project the items into the structure of the GroupBy key then you don't need the grouping; the query below should give the same result:
var fs = list.Select(item => new
{
item.X,
Ps = item.Properties.Where(p => p.Key == "m")
});
However, if you do require the results to be distinct, you'll need to create a separate class for your result and implement a separate IEqualityComparer<T> to be used with Distinct clause:
public class Result
{
public int X { get; set; }
public IEnumerable<string> Ps { get; set; }
}
public class ResultComparer : IEqualityComparer<Result>
{
public bool Equals(Result a, Result b)
{
return a.X == b.X && a.Ps.SequenceEqual(b.Ps);
}
// Implement GetHashCode
}
Having the above you can use Distinct on the first query to get distinct results:
var fs = list.Select(item => new Result
{
X = item.X,
Ps = item.Properties.Where( p => p.Key == "m")
}).Distinct(new ResultComparer());

Select a group of n objects into a list of a list of objects

I have Object A in which I have lengths. I would like to order by length descending, then I would like to group them by threes and return that list of a list of objects.
I can get the grouping to work, but all i get is the key of the grouping and not the items.
public class a
{
public string Id { get; set; }
public int Length { get; set; }
}
List<a> c = Instantiate a list
c.OrderByDescending(x => x.Length)
.Select((e, i) => new { Item = e, Grouping = (i / 3) })
.GroupBy(x => x.Grouping)
.Select(x => x.Key)
.ToList()
I think it has something to do with my group by but I cant seem to get it to work. What I would like is a List<List<a>> that have at most three items.
Use this .Select(grp => grp.ToList()) instead of .Select(x => x.Key).
This will return the group as a List<a>.
Following query will generate a list of lists where the inner list contains three items:
var listOfTriplets = c.OrderByDescending(x => x.Length)
.Select((x, i) => new { Index = i, Value = x })
.GroupBy(x => x.Index / 3)
.Select(x => x.Select(v => v.Value).ToList())
.ToList();

Filtering out values from a list of object in a List

I have an IEnumerable collection of UnitGroup: IEnumerable<UnitGroup>,
class UnitGroup
{
string key { get; set; }
List<UnitType> NameList { get; set; }
}
class UnitType
{
String UnitName{ get; set; }
Description { get; set; }
}
Now I want to filterIEnumerable<UnitGroup> based on UnitType's UnitName.
For example I want to get only the records of UnitName that contains a string and remove remaining.
something like this:
IEnumerable<UnitGroup> Groups;
IEnumerable<UnitGroup> filteredResult = Groups.NameList(o => o.UnitName.contains("test"));
And get IEnumerable<UnitGroup> with only filtered UnitNames under UnitType under UnitGroup.
What is the best way of acheiving this?
I'm not 100% sure what you're trying to achieve. Could you provide some sample data, to make it more clear?
Although, I think it may fit into your goal:
IEnumerable<UnitGroup> Groups;
var filteredResult = Groups.Select(g => new UnitGroup {
key = g.key,
NameList = g.NameList
.Where(n => n.UnitName == "test")
.ToList()
})
.Where(g => g.NameList.Count > 0);
Here is another way that should do what #MarcinJuraszek answers does. (I am guessing the intent of the question as well.)
IEnumerable<UnitGroup> Groups;
var filteredResult = Groups.Where (g => g.NameList.Count() > g.NameList.RemoveAll(nl => nl.UnitName != "Name1"));
If the number of removed items was less than the original count, then we have items that are of interest, so select the parent.
Note: This will modify the original collection, so if you need to filter it more than once then this is not the answer you are looking for.
Try this:
var filteredList = from g in Groups
where g.NameList.Exists(i=>i.UnitName=="test")
select g;

Linq, force an item to be first?

I have a List<> of MyPersonObjects. The list is a list of people who I can assign something to. In this list, I include myself (As the task can be assigned to me - and usually is).
So, I want to make sure myself is at the top of the list. I know my personId, which is a property of my person object - so is there a way to order the list to make sure I am first, and then the rest, alphabetically by surname (which is another property of my object)
You can just order the list by two separate criteria, the OrderBy will be the main sort order, if the values are equal (1 for all except you), ThenBy is used.
personList.OrderBy(x => x.Id == myId ? 0 : 1)
.ThenBy(x => x.Surname);
One way is to sort the list by surname with Linq and then remove yourself and put back at the first place:
var personList = db.MyPersonObjects.OrderBy(p => p.Surname).ToList();
var myself = personList.Single(p => p.Id == myselfId);
personList.Remove(myself);
personList.Insert(0, myself);
Or create a custom comparer IComparer<MyPersonObject> where the implementation is something like:
public class PersonCompaper : IComparer<MyPersonObject>
{
private readonly int personId;
public PersonCompaper(int personId)
{
this.personId = personId;
}
public int Compare(MyPersonObject x, MyPersonObject y)
{
if (x.Id == personId && y.Id == personId)
return 0;
if (x.Id == personId)
return 1;
if (y.Id == personId)
return -1;
return string.Compare(x.Surname, y.Surname);
}
}
Then you use it in the OrderBy
db.MyPersonObjects.OrderBy(p => p, new PersonCompaper(myselfId))

Resources