Paging List of Objects using LINQ - c#

I have a list of objects List<Parent> Parents.
The Parent class has a List<Child> Children.
Until now, I have applied paging to Parents using LINQ:
List<Parent> PageX = Parents.Skip(PageIndex * PageSize).Take(PageSize);
For instance, if PageSize=2, I have the following result:
--------------- Page 1 ----------------------
Parent 1
Child 1
Child 2
Child 3
Parent 2
Child 1
Child 2
--------------- Page 2 ----------------------
Parent 3
Child 1
Parent 4
Child 1
Child 2
What I want to achieve is the following:
--------------- Page 1 ----------------------
Parent 1
Child 1
Child 2
--------------- Page 2 ----------------------
Child 3
Parent 2
Child 1
--------------- Page 3 ----------------------
Child 2
Parent 3
Child 1
How can I achieve this?

You could use SelectMany:
var page = parents.SelectMany(p => p.Children)
.Skip(PageIndex * PageSize).Take(PageSize);
See a working fiddle here.
Update:
I did some research as this did interest me also. Using the following assumptions:
you are using EF
you have only one level of this parent->children relationship
your entities have all a Position property
you should be able to execute the following against the DB to get the items for a page with the correct order "in one query":
var pageItems = db.Parents.SelectMany(p => p.Children).Include(c => c.Parent)
.OrderBy(c => c.Parent.Position).ThenBy(c => c.Position)
.Skip(PageIndex * PageSize).Take(PageSize);
I did not test this code, as I have no DB here at the moment to test it, so please report back, if you can test it, if the assumtions are correct for your case.

I am presuming that both Parent and Child have a common property (say, Name) which is used to identify them?
In that case, you would have to flatten the hierarchy to include both parents and children info at the same list level.
So, presuming something like:
class Parent
{
public string Name { get; set; }
private readonly List<Child> _children = new List<Child>();
public List<Child> Children
{
get { return _children; }
}
}
class Child
{
public string Name { get; set; }
}
You would flatten it to a single IEnumerable<string> using:
var flattened = Parents
.Select(p => new [] { p.Name }.Concat(p.Children.Select(c => c.Name)))
.SelectMany(x => x);
And then you page it the way you did so far:
var results = flattened.Skip(PageIndex * PageSize).Take(PageSize);
foreach (var x in results)
Console.WriteLine(x);
It would probably be cooler if your Parent and Child both inherited from the same class or interface.

Related

Entity Framework Core compare two child collection properties

I am using EF Core and stuck in a scenario where I need to fetch all the Parent table records that has child records matching the given child records.
Example:
Parent Table
Id
Name
1
P1
2
P2
3
P3
Child Table
Id
ParentId
Name
Age
Address
1
1
C1
20
abc
2
1
C2
25
xyz
3
2
C1
20
qqq
4
2
C2
25
wer
5
3
C3
30
tyu
I need Linq to get all parents which matches below search parameters.
All prents with Child records same as:
Child: [ {C1,20}, {C2,25}]
So, it should return the Parent P1 and P2 as result.
I am trying EqualityComparer but getting not translated error from EF.
Any help is appreciated.
I don't think EF supports this. I didn't find a direct solution when I was in the same situation (multi-column primary key on the child table). My workaround was to add artificial (computed+stored) string column to the child table that combined all the PK parts, after that you should be able to do:
var childrenKeys = new List<string>();
/* .... fill it with children keys you are interested in ... */
var parents = dbContext.Parents
.Where(x => x.Children.Any(xx => childrenKeys.Contains(xx.NameAndAge))
.ToList();
Only other option I know of is to switch to client side evaluation and filter it yourself.
EDIT: I've changed my answer as your comment provided me information I didn't have. I believe you might be searching for this:
var childrenConditions = new List<Child>()
{
new Child()
{
Name = "C1",
Age = 20
},
new Child()
{
Name = "C2",
Age = 25
},
}
var children = context.Children.Include(child => child.Parent);
var groupedChildren = children.GroupBy(child => child.Parent.Id);
var rightParents = new List<Parent>();
foreach(var group in groupedChildren)
{
var respectsConditions = true;
foreach(var condition in childrenConditions)
{
if ((group.Select(child =>
child.Name != condition.Name ||
child.Age != condition.Age)).Any())
{
respectsConditions = false;
break;
}
}
if (respectsCondition)
{
rightParents.Add(group.First().Parent);
}
}
At the end of the algorithm, 'rightParents' will be a collection containing the parents you're searching for.

Get nested elements in hierarchical manner C# linq

I have list of objects which is map as Parent- child relationship.
so parent have many children, grand children and great grandchildren and so on.
I have structured table as below
UserId UserNme
1 Jack
2 William
3 asha
4 winston
ParentId ChildId
2 3
3 4
So I want to list the users in following hierarchical manner:
->User not as child (Parent/Non-parent)
----> Child Users
-------->Grand child users
-----------> Great Grand child users
I have tried below code, but not completed it:
public List<MarketingUserDto> GetChildAgents(List<MarketingUserDto> agents,List<MarketingUserDto> resultAgents)
{
if (agents.Count == 0)
{
var parentagents = _userRegistrationRepository.GetMany(x => ((x.IsParentAgent ?? false) == true && x.UserTypeId == (short)Enums.UserTypes.Agent) || (x.UserTypeId == (short)Enums.UserTypes.Super_Manager && (x.IsParentAgent ?? false) == false));
this.GetChildAgents(Mapper.Map<List<UserRegistration>, List<MarketingUserDto>>(parentagents.ToList()), resultAgents);
}
else
{
foreach (var agent in agents)
{
var childagents = _agentMappingRepository.GetMany(x => x.ParentId == agent.UserId, y => y.UserRegistration);
}
}
return resultAgents;
}
Could you please provide any suggestion how to achieve this?
If you don't want to reinvent the flattening wheel, take a look at MoreLinq's Flatten() extension method.
If you grab the NuGet package, you get that and a lot more useful LINQ extensions.

Delete records with multiple ids based on condition

Below is my class :
public partial class Ads
{
public int Id { get; set; }
public int RegionId { get; set; }
public string Name { get; set; }
public int Group { get; set; }
}
Records :
Id Name Group
1 abc 1
2 xyz 1
3 lmn 1
4 xxx 2
5 ppp 2
6 ttt 3
7 ggg 3
Now I want to remove all records/only that record with particular id of same group for some ids.
Code :
public void Delete(int[] ids,bool flag = false)
{
using (var context = new MyEntities())
{
context.Ads.RemoveRange(
context.Ads.Where(t => (flag ?
(context.Ads.Any(x => ids.Contains(x.Id) && x.Group == t.Group)) : false)));
context.SaveChanges();
}
}
What I am trying to do is something like below :
If flag is false with ids=3,5 then
I want to delete only records with Id=3,5
Else if flag is true with ids=3,5 then
I want to delete records with Id=3,5 but all other records too of the group to which ids=3,5 belong to.
Here id=3 belongs to group 1 so I want to delete all records of group1 i.e id=1,2 like wise ids=5 belongs to
group 2 so I want to delete all records of group 2 i.e id=4.
Expected output for this last case(flag=true) :
Id Name Group
6 ttt 3
7 ggg 3
But I think that I haven't done this is a proper way, and there is some source of improvement in the query.
Note : ids[] will always contains ids from different group and that too highest ids from different group.
How can I to improve my query for both the cases(flag=true and false)?
What about
var removeRecs=context.Ads.where(t => ids.contains(t.id))
if(flag)
removeRecs.AddRange(context.Ads.where(t=> removeRecs.Any(r =>t.groupId==r.Id)))
Ads.RemoveRange(removeRecs);
Do not make it too hard for your self, not everything must/can be done in the where statement of a query. Also a general rule of thumb in a loop try to factor out all the constant values and checks. So try this:
public static void Delete(int[] ids, bool flag = false)
{
using (var context = new MyEntities())
{
var query = context.Ads.AsQueryable();
query = flag
? query.Where(x => context.Ads
.Where(i => ids.Contains(i.Id))
.Select(i => i.Group)
.Contains(x.Group))
: query.Where(x => ids.Contains(x.Id));
context.Ads.RemoveRange(query);
context.SaveChanges();
}
}
public void Delete(int[] ids, bool flag = false)
{
using (var context = new MyEntities())
{
var items = context.Ads.Where(x => ids.Any(a => x.Id == a));
if (!flag)
{
//flag=false --> delete items with Id in ids[]
context.Ads.RemoveRange(items);
}
else
{
var groups = items.GroupBy(a => a.Group).Select(a => a.Key);
//flag=true --> delete all items in selected groups
context.Ads.RemoveRange(context.Ads.Where(x => groups.Any(a => x.Group == a)));
}
context.SaveChanges();
}
You should separate your tasks...
if (flag)
{
groupIds = db.Ads.Where(x => ids.Contains(x.Id)).Select(x => x.Group).ToList();
db.Ads.RemoveRange(db.Ads.Where(x => groupIds.Contains(x.Group)).ToList());
}
else
{
db.Ads.RemoveRange(db.Ads.Where(x => ids.Contains(x.Id)).ToList());
}
To me it looks like you have two different deletes here.
In first case you are only deleting the ads with given ID and this is pretty straight forward.
In second case you are deleting the ads with given ID and all other ads that contain the group of the recently deleted Ads. So in this case instead of deleting the ads with given Id first why not actualy get distinct groups for these ID-s and than just delete the groups.
EDIT
You can do it like this.
using (var context = new TestEntities())
{
if (!flag)
context.Ads.RemoveRange(context.Ads.Where(a => ids.Contains(a.Id)));
else
context.Ads.RemoveRange(context.Ads.Where(a => context.Ads.Where(g => ids.Contains(g.Id)).Select(x => x.Group).Distinct().Contains(a.Group)));
context.SaveChanges();
}
For the more complicated case I am trying to get distinct groups for given id-s. So for ID-s 3 and 5 I am selecting the groups and than I am doing distinct on the groups since it might happen that the id-s have the same group. Than I am fetching all the ads that have these groups. So for passed values of 3 and 5 I would get groups 1 and 2 which I would than use to get all the ads that have that group. That in turn would yield id-s 1, 2, 3, 4, and 5 which I would than delete.
EDIT 2
If the complexity of second Linq query bothers you than write a SQL query.
context.Database.ExecuteSqlCommand(
"DELETE Ads WHERE Group IN (SELECT Group FROM Ads WHERE Id IN(#p1, #p2))", new SqlParameter("#p1", ids[0]), new SqlParameter("#p2", ids[1]));
This should be extra performant rather than rely on EF which will delete it one by one.

Linq select 10 children for each parent item

I am using a nested set model and want to be able to select 10 parent items, as well as 10 child items for each parent, which are ordered.
I am trying to write a nested comment system, whereby a reply to a comment will be signified by having left and right values within its 'parent' comment.
I am having a real headache reducing the load times of large lists of items being retrieved and believe if I could do the above in a single Linq statement then I might be able to save a lot more time than making repeated calls to the db to get 10 children for each parent.
I use a this statement to get the parent(or root items) from the datacontext.
var query = context.Items
.Where(x => !context.Items.Any(y => y.LeftPos < x.LeftPos
&& y.RightPos > x.RightPos))
.OrderBy(x => x.Id)
.Take(pageSize)
.ToList();
The above code is getting the outermost (parent) items in the nested set model by checking there are not any other items with left and right values outside of theirs.
So, rather than looping through the parentItems with a foreach (which I am doing currently) and making 10 or (pageSize) calls to the db on each iteration, how can I take 10 children of each parent node with a Linq statement?
EDIT:
I am using the nested set model. My items have left and right positions (leftPos and rightPos). So a child is an object with left and right values within the left and right values of another object, which in turn would be the parent.
1 a 4
2 b 3
so if I have a lot of items
1 a 4
2 b 3
5 c 10
6 d 7
8 e 9
11 f 14
12 g 13
....
Is there a way I can select x amount of children from each parent using Linq?
Any help appreciated
class Program
{
static void Main(string[] args)
{
List<A> lst = new List<A>();
for (int j = 1; j < 4; j++)
{
var tmp = new A() { Value = j * 1000 };
for (int i = 0; i < 150; i++)
{
tmp.SubItems.Add(new B { Value = i + 1, Parent = tmp });
}
lst.Add(tmp);
}
List<B> result = lst.SelectMany(x => x.SubItems.Take(10)).ToList();
}
}
public class A
{
public A()
{
SubItems = new List<B>();
}
public int Value { get; set; }
public List<B> SubItems { get; set; }
}
public class B
{
public int Value { get; set; }
public A Parent { get; set; }
}
not sure if this is what you want. this way you get a collection of subitems. 10 subitems of each parent. you can access the parents with the .Parent property of each subitem...

LINQ and how to return a list of a specific type

I have 2 tables (Document and DocumentClass) that have the following columns:
DocumentClass: DocClassID, Name, ParentID
Document: DocID, Name, DocClassID
The DocumentClass table contains parent and child records and the relationship between a parent and a child is the ParentID column. A Document record is linked with a child record in the DocumentClass by the DocClassID foreign key.
Parent records in DocumentClass have ParentID = 0 and Child records in the DocumentClass have ParentID != 0
I want to retrieve a childs Name and that child's Parents Name from the DocumentClass table.
I Have managed to create a function that does that for me. I send in a list of Document ids, find those DocumentClass records (the child records) that the document is linked to and then find the parent to those child records. Then I put that information I want into a Child class.
public List<Child> GetDocClassInfo(List<int> docIds)
{
var result = from dc in _context.DocClasses
from d in _context.Documents
where dc.DocClassID == d.DocClassID
where dc.DocClassID != 0
where docIds.Contains(d.DocID)
select new
{
children = from p in _context.DocClasses
where dc.ParentID == p.DocClassID
select new Child
{
ChildId = dc.DocClassID,
ChildDocClassName = dc.DocClassName,
ParentId = p.DocClassID,
ParentDocClassName = p.DocClassName
}
};
return result.ToList();
}
My problem is that I want to have a List to return from the function, but the compiler doesn't like this at all. I get an error saying that
Cannot implicitly convert type System.Collections.Generic.List``<AnonymousType#1> to System.Collection.Generic.List<Child>.
How can I write that LINQ query to return a List?
Best regards,
OKB
result is a list of anonymous types, each with a member (children) that is an enumerable set of Child records. You should be able to use SelectMany here:
var list = (from item in result
from child in item.children
select child).ToList();
or (identical):
var list = result.SelectMany(item => item.children).ToList();
You are returning an anonymous type when you say select new { children ...
var result = (from dc in _context.DocClasses
join d in _context.Documents
on dc.DocClassID equals d.DocClassID
where dc.DocClassID != 0 && docIds.Contains(d.DocID)
let children = from p in _context.DocClasses
where dc.ParentID == p.DocClassID
select new Child {
ChildId = dc.DocClassID,
ChildDocClassName = dc.DocClassName,
ParentId = p.DocClassID,
ParentDocClassName = p.DocClassName
}
select children).SelectMany(c=>c).ToList();

Categories