SilverStripe 3: Loop greatgrandchildren pages WITH OUT grouping by parent - sorting

In my template I'm looping my child pages as 2 separate lists and then their grandchild pages under these. But I want these greatgrandchild pages to be sorted as an entire list and not divided / grouped by their parents.
Simply loop after loop does not work as all greatgrandchild pages should be put in order and not grouped by parent then sorted.
I know this could be done by a function to get all the greatgrandchildpages and sort them by a chosen method (e.g. alphabetically). But I don't want to get ALL of them as I need them just the grandchildren for each child.
PAGE - to show list
Child - List Heading
GrandChild - not shown in list
GreatGrandChild - Listed and ALL sorted Alphabetically

On Page you can get the IDs of all children (which are parents of the grand children) using getIDList():
$ids = Page::get()->filter(array('ParentID' => $this->ID))->getIDList();
Now you can get all Pages (or whatever pagetype you want) by ParentID:
$grandChildren = Page::get()->filter(array('ParentID' => $ids));
Same for grand-grandchildren....
$grandGrandChildren = Page::get()->filter(array('ParentID' => $grandChildren->getIDList()));
You can sort this DataList by whatever you want, e.g.
$sortedgrandGrandChildren = $grandGrandChildren->sort('Title', 'ASC');
edit:
$this->Children() does return an ArrayList, not a DataList, so we have to get all children manually. Be aware, this doesn't respect canView(), so we get all pages, if the current user has permissions or not. For a simple website this should be ok.
Using $this->Children()->column('ID') you get the filtered result.
A ready to use method could be:
public function getGrandGrandChildren() {
$ids = Page::get()->filter(array('ParentID' => $this->ID))->getIDList();
$grandChildren = Page::get()->filter(array('ParentID' => $ids));
$grandGrandChildren = Page::get()->filter(array('ParentID' => $grandChildren->getIDList()));
return $grandGrandChildren->sort('Title', 'ASC');
}
And in your template:
<% loop $GrandGrandChildren %>
$Title
//do whatever you want
<% end_loop %>

SiteTree::get()->filter('ParentID',SiteTree::get()->filter('ParentID',$this->Children()->column('ID')))->filterByCallback(function($page){return $page->canView()})

Related

How to query a child element, present inside a list item

I want to automate the below scenario using Xamarin.UITest:
The List has several rows and each row has different elements inside it.
I want to iterate through the list and find elements inside each row using Xamarin.
IList CellList = app.Query(thisObject => thisObject.Id("MyList_ID"));
foreach(var element in CellList)
{
//Here I want to go inside each row and find an element inside each row. So what needs to be changed in the below line of code?
bool isNTextPresent = (app.Query(thisObject => thisObject.Id("NText_ID")).Length) == 1 ? true : false;
Assert.AreEqual(isNTextPresent , true);
}

Take one and skip other duplicate item in a child table

I have a list of Items and every item have some list, Now I wants to select Distinct items of child. I have tried like below but it's not working.
var items = await _context.Items.
Include(i => i.Tags.Distinct()).
Include(i => i.Comments).
OrderBy(i => i.Title).ToListAsync();
//Tag items
TagId - tag
------------------
1 --- A
2 --- B
3 --- B
4 --- C
5 --- D
6 --- D
7 --- F
//Expected Result
Item.Tags -> [A,B,C,D,F]
how can I do this in EF Core? Thanks.
You can use the MoreLinq library to get DistinctBy or write your own using this post.
Then use this:
var items = await _context.Items.
Include(i => i.Tags).
Include(i => i.Comments).
OrderBy(i => i.Title).
DistinctBy(d => d.Tags.tag).
ToListAsync();
You want to get distinct records based on one column; so that should do it.
Apparently you have a table of Items, where every Item has zero or more Tags. Furthermore the Items have a property Comments, of which we do not know whether it is one string, or a collection of zero or more strings. Furthermore every Item has a Title.
Now you want all properties of Items, each with its Comments, and a list of unique Tags of the items. Ordered by Title
One of the slower parts of database queries is the transport of the selected data from the database management system to your local process. Hence it is wise to limit the amount of data to the minimum you are really using.
It seems that the Tags of the Items are in a separate table. Every Item has zero or more Tags, every Tag belongs to exactly one item. A simple one-to-many relation with a foreign key Tag.ItemId.
If Item with Id 300 has 1000 Tags, then you know that every one of these 1000 Tags has a foreign key ItemId of which you know that it has a value of 300. What a waste if you would transport all these foreign keys to your local process.
Whenever you query data to inspect it, Select only the properties
you really plan to use. Only use Include if you plan to update the
included item.
So your query will be:
var query = myDbContext.Items
.Where(item => ...) // only if you do not want all items
.OrderBy(item => item.Title) // if you Sort here and do not need the Title
// you don't have to Select it
.Select(item => new
{ // select only the properties you plan to use
Id = item.Id,
Title = item.Title,
Comments = item.Comments, // use this if there is only one item, otherwise
Comments = item.Comments // use this version if Item has zero or more Comments
.Where(comment => ...) // only if you do not want all comments
.Select(comment => new
{ // again, select only the Comments you plan to use
Id = comment.Id,
Text = comment.Text,
// no need for the foreign key, you already know the value:
// ItemId = comment.ItemId,
})
.ToList();
Tags = item.Tags.Select(tag => new
{ // Select only the properties you need
Id = tag.Id,
Type = tag.Type,
Text = tag.Text,
// No need for the foreign key, you already know the value
// ItemId = tag.ItemId,
})
.Distinct()
.ToList(),
});
var fetchedData = await query.ToListAsync();
I haven't tried it, but I'd say you put .Distinct() in the wrong place.
var items = await _context.Items
.Include(i => i.Tags)
.Include(i => i.Comments).
.OrderBy(i => i.Title)
.Select(i => { i.Tags = i.Tags.GroupBy(x => x.Tag).Select(x => x.First()); return i; })
.ToListAsync();

Linq2Entities Equivalent Query for Parent/Child Relationship, With All Parents and Children, Filtering/Ordering Children

So the question is ridiculously long, so let's go to the code. What's the linq2entities equivalent of the following Sql, given entities (tables) that look like:
Parent
---
parent_id
parent_field1
Child
--
child_id
parent_id
child_field1
child_field2
The sql:
select p.*, c.*
from parent p
inner join p on
p.parent_id = child.parent_id
where
c.child_field1 = some_appropriate_value
order by
p.parent_field1
c.child_field2
L2E let's you do .include() and that seems like the appropriate place to stick the ordering and filtering for the child, but the include method doesn't accept an expression (why not!?). So, I'm guessing this can't be done right now, because that's what a lot of articles say, but they're old, and I'm wondering if it's possible with EF6.
Also, I don't have access to the context, so I need the lambda-syntax version.
I am looking for a resultant object hierarchy that looks like:
Parent1
|
+-- ChildrenOfParent1
|
Parent2
|
+-- ChildrenOfParent2
and so forth. The list would be end up being an IEnumerable. If one iterated over that list, they could get the .Children property of each parent in that list.
Ideally (and I'm dreaming here, I think), is that the overall size of the result list could be limited. For example, if there are three parents, each with 10 children, for a total of 33 (30 children + 3 parents) entities, I could limit the total list to some arbitrary value, say 13, and in this case that would limit the result set to the first parent, with all its children, and the second parent, with only one of its children (13 total entities). I'm guessing all of this would have to be done manually in code, which is disappointing because it can be done quite easily in SQL.
when you get a query from db using entityframewrok to fetch parents, parent's fields are fetched in single query. now you have a result set like this:
var parentsQuery = db.Parents.ToList();
then, if you have a foreign key on parent, entityframework creates a navigation property on parent to access to corresponding entity (for example Child table).
in this case, when you use this navigation property from parent entities which already have been fetched, to get childs, entityframework creates another connection to sql server per parent.
for example if count of parentsQueryis 15, by following query entityframework creates 15 another connection, and get 15 another query:
var Childs = parentsQuery.SelectMany(u => u.NavigationProperty_Childs).ToList();
in these cases you can use include to prevent extra connections to fetch all childs with its parent, when you are trying to get parents in single query, like this:
var ParentIncludeChildsQuery = db.Parents.Include("Childs").ToList();
then by following Query, entityframework doesn't create any connection and doesn't get any query again :
var Childs = ParentIncludeChildsQuery.SelectMany(u => u.NavigationProperty_Childs).ToList();
but, you can't create any condition and constraint using include, you can check any constraint or conditions after include using Where, Join, Contains and so forth, like this:
var Childs = ParentIncludeChildsQuery.SelectMany(u => u.NavigationProperty_Childs
.Where(t => t.child_field1 = some_appropriate_value)).ToList();
but by this query, all child have been fetched from database before
the better way to acheieve equivalent sql query is :
var query = parent.Join(child,
p => p.ID
c => c.ParentID
(p, c) => new { Parent = p, Child = c })
.Where(u => u.Child.child_field1 == some_appropriate_value)
.OrderBy(u => u.Parent.parent_field1)
.ThenBy(u => u.Child.child_field2)
.ToList();
according to your comment, this is what you want:
var query = parent.Join(child,
p => p.ID,
c => c.ParentID,
(p, c) => new { Parent = p, Child = c })
.Where(u => u.Child.child_field1 == some_appropriate_value)
.GroupBy(u => u.Parent)
.Select(u => new {
Parent = u.Key,
Childs = u.OrderBy(t => t.Child.child_field2).AsEnumerable()
})
.OrderBy(u => u.Parent.parent_field1)
.ToList();

Magento - cms/page collection - apply filter to return only pages which are unique to a given store id (ie not also assigned to other stores)

I can use:
Mage::getModel('cms/page')->getCollection()->addStoreFilter($store_id);
to retrieve a collection of CMS pages filtered by Store Id.
But how do I get it to remove ones which are also assigned to other stores?
ie: I do not want it to return items which have 'All Store Views' as the Store View. (Or any other additional store id assigned to that CMS page.) It has to only return pages unique to that one store.
I am extending the Aitoc permissions module, so that Store Admins cant view or edit CMS pages and static blocks which might impact other stores. This involves filtering those items from the grid.
There's no native collection method to do this, so you'll need to
Query the cms_page_store table for pages unique to a given store
Use the results from above in your filter
I didn't fully test the following, but it should work (and if it doesn't, it'll give you a good start on your own query)
$page = Mage::getModel('cms/page');
$resource = $page->getResource();
$read = $resource->getReadConnection();
#$select = $read->query('SELECT page_id FROM ' . $resource->getTable('cms/page_store') . ' GROUP BY store_id');
//set total count to look for. 1 means the page only appears once.
$total_stores_count_to_look_for = '1';
//get the table name. Need to pass through getTable to ensure any prefix used is added
$table_name = $resource->getTable('cms/page_store');
//aggregate count select from the cmd_page_store database
//greater than 0 ensures the "all stores" pages aren't selected
$select = $read->query('SELECT page_id as total
FROM '.$table_name.'
WHERE store_id > 0
GROUP BY page_id
HAVING count(page_id) = ?',array($total_stores_count_to_look_for));
//fetch all the rows, which will be page ids
$ids = $select->fetchAll();
//query for pages using IDs from above
$pages = Mage::getModel('cms/page')->getCollection()->addFieldToFilter('page_id',array('in'=>$ids));
foreach($pages as $page)
{
var_dump($page->getData());
}
If you have thousands and thousands of CMS pages it may be worth it to alter the cms/page collection's select to join in aggregate table data. I'll leave that as an exercise for the reader, as those sorts of joins can get tricky.
$collection = Mage::getModel('cms/page')->getCollection();
$collection->getSelect()
->join(
array('cps' => $collection->getTable('cms/page_store')),
'cps.page_id = main_table.page_id AND cps.store_id != 0',
array('store_id')
)
->columns(array('stores_count' => new Zend_Db_Expr('COUNT(cps.store_id)')))
->group('main_table.page_id')
->having('stores_count = ?', 1)
->having('cps.store_id = ?', $storeId)
;
Fusing some elements of the solutions proposed by Alan and Vitaly with my own cumbersome understanding, I achieved what I needed with the following code.
To put into context, I was extending the Aitoc permissions module, so that Store Admins cant view or edit CMS pages and static blocks which might impact other stores. This involved filtering those items from the grid.
$collection = Mage::getModel('cms/page')->getCollection();
$collection->addStoreFilter(Mage::helper('aitpermissions')->getStoreIds());
$conn = Mage::getSingleton('core/resource')->getConnection('core_read');
$page_ids = array();
foreach($collection as $key=>$item) {
$page_id = $item->getId();
$results = $conn->fetchAll("SELECT * FROM cms_page_store
WHERE page_id = ".$page_id.";");
$count = 0;
$arr_stores = array();
foreach($results as $row) {
$arr_stores[] = $row['store_id'];
$count++;
}
//We dont want to show the item if any of the following are true:
//The store id = 0 (Means its assigned to All Stores)
//There is more than one store assigned to this CMS page
if( in_array('0',$arr_stores) || $count>1) {
//This removes results from the grid (but oddly not the paging)
$collection->removeItemByKey($key);
}
else {
//build an array which we will use to remove results from the paging
$page_ids[] = $page_id;
}
}
//This removes results from paging (but not the grid)
$collection->addFieldToFilter('page_id',array('in'=>$page_ids));
I'm not sure why I needed to use two different methods to filter from the paging and the grid.
The site uses magento 1.5 so perhaps there is an issue related to that.
Either way, this solution worked for me.
My solution to add field store_id to pages collection via join, and use collection method addFieldToFilter().
$pages = Mage::getModel('cms/page')->getCollection();
$pages->getSelect()->joinInner(
array('cms_page_store' => 'cms_page_store'),
'main_table.page_id = cms_page_store.page_id',
array()
);
$pages->addFieldToFilter('store_id', ['in' => [1, 2]]);

LINQ get parent with most children

I have a list of Parent objects that has a list of Children objects. I need to write a query that would give me the parent that has the most children. The ORM is entity framework, so it should work with that.
Code to start with:
parents.FirstOrDefault(c => c.Children.Max());
Something like that.
I think it should look more like this:
parents.OrderByDescending(p => p.Children.Count()).FirstOrDefault();
Your query is not correct, because c.Children.Max() will try to iterate over children of one parent, and if they support comparison (e.g. children are Ints), will simply return biggest of them. And most probably your Children objects are not bool, so you won't be able to even compile the code, because FirstOrDefault takes
Expression<T, bool>
You don't need sorting for this:
int maxChildCount = parents.Max(x => x.Children.Count());
var maxParent = parents.FirstOrDefault(p => p.Children.Count() == maxChildCount);
Or as query expression:
var maxParent = (from p in parents
let max = parents.Max(x => x.Children.Count())
where p.Children.Count() == max).FirstOrDefault();

Resources