Yii2 Adding SearchModel Many-to-Many relationships produces duplicative results - search

I have a model that has six many-to-many relationships. When I add the joinWith entries in the SearchModel and, then, look at what gets returned in the index.php view (with no search parameters applied), it shows many more rows than are actually in the base model. Upon further inspection, I find that multiple duplicative rows are being returned. It also throws pagination way off. For instance, if I add just one joinWith in the SearchModel and I've got pagination set to 10 rows per page, here's what happens. In the base model which has 175 rows, the first page will show "1-4 of 425 items". The second page will show "111-16 of 425 items." and the last item of the first page is duplicated at the top of the second page. To give some background:
The base model has the following relationships:
/**
* #return \yii\db\ActiveQuery
*/
public function getHerbalHerbsHerbalPreparations()
{
return $this->hasMany(\common\models\HerbalHerbsHerbalPreparations::className(),
['herbal_preparation_id' => 'id']);
}
/**
* #return \yii\db\ActiveQuery
*/
public function getHerbalHerbs()
{
return $this->hasMany(\common\models\HerbalHerbs::className(), ['id' => 'herbal_herb_id'])
->viaTable('herbal_herbs_herbal_preparations', ['herbal_preparation_id' => 'id']);
} */
In the SearchModel, I've got this entry:
$query = HerbalPreparations::find()->joinWith('herbalHerbs');
Please note that it makes no difference if I add the optional joinWith parameters for eager loading and join type. `$query = HerbalPreparations::find()->joinWith('herbalHerbs', true, 'LEFT JOIN'); produces the same results.
Without any joinWith, the index.php view looks like this:
With the joinWith described above, page 1 of index.php looks like this:
And page 2 of the index.php view looks like:
Note the duplication of the last row on page 1 and the first row of page 2.
Needless to say, this problem becomes exponentially worse if I specify the additional many-to-many relations with joinWith.
If I actually enter a search parameter using the GridView filter or a search form, it works perfectly. The problem is that the index.php view needs to work properly with no search parameter.
This seems like it would be a common problem for anyone using Yii2 in any kind of advanced application. I'm sure I must be doing something wrong, but I searched Google until I'm exhausted and haven't been able to find anything helps.
`

I can't take responsibility for this answer. Actually, Newbie on the Yii forum gave this answer. It's actually rather simple — just add a groupBy('id'). For instance
$query = HerbalPreparations::find()->joinWith('herbalHerbs', true, 'LEFT JOIN')->groupBy(['id']);
I suppose I should have been able to figure this one out from a basic knowledge of SQL, but I've never had to use groupBy in this way.
This seems like such a common need that I would think it would be documented and found just about everyplace on a simple search. Unfortunately, it doesn't appear to be.

Related

Is there a way to get the related pages from a left side of the page relationship that is not the CurrentDocument in Kentico

I need to access to the Main Page information from any of the Secondary Pages (1,2,3,4...) using a page relationship, and then retrieve the relationship list from the main page, in this case SecondaryPage1,SecondaryPage2,SecondaryPage3, etc..
I have the following structure
MainPage (Page Type e.g. Article) isRelatedTo:
SecondaryPage1 (Page Type: e.g. Item)
SecondaryPage2 (Page Type: e.g. Item)
SecondaryPage3 (Page Type: e.g. Item)
SecondaryPage4 (Page Type: e.g. Item)
SecondaryPage5 (Page Type: e.g. Item)
Is there an easy way to do it? I am using a CMSRepeater to display the items. I am thinking on creating a custom CMSRepeater for this specific scenario but I would like to know if there is a different approach.
So, to recap, The SecondaryPage1 isRelatedTo MainPage on the right side.
MainPage -> isRelatedTo -> SecondaryPage1
I am trying the display whole list from the MainPage, I need to access to that information on any of the secondary pages.
I created this code, it works pretty well I am just trying to discover is there is simpler solution or if there is an alternative.
....
List<CMS.DocumentEngine.TreeNode> mainRelatedItems = new List<CMS.DocumentEngine.TreeNode>();
mainRelatedItems.AddRange(DocumentHelper.GetDocuments().Types(PageTypes.Split(';')).InRelationWith(CurrentDocument.NodeGUID, PageRelationship, relationshipSide));
List<CMS.DocumentEngine.TreeNode> secondaryRelatedItems = new List<CMS.DocumentEngine.TreeNode>();
foreach (CMS.DocumentEngine.TreeNode item in mainRelatedItems)
{
if(ExcludeCurrentDocument)
secondaryRelatedItems.AddRange(DocumentHelper.GetDocuments().Types(PageTypes.Split(';')).InRelationWith(item.NodeGUID, SecondLevelPageRelationship, secondaryRelationshipSide).Where(x => x.NodeGUID != CurrentDocument.NodeGUID));
else
secondaryRelatedItems.AddRange(DocumentHelper.GetDocuments().Types(PageTypes.Split(';')).InRelationWith(item.NodeGUID, SecondLevelPageRelationship, secondaryRelationshipSide));
}
....
CustomRepeater.ItemTemplate = TransformationHelper.LoadTransformation(CustomRepeater, TransformationName);
CustomRepeater.DataSource = secondaryRelatedItems;
CustomRepeater.DataBind();
....
In your document query, you can specify the relationship GUID as well as the side and relationship name. It doesn't have to be the current document.
DocumentHelper.GetDocuments().InRelationWith(guid, "reltionshipname", side)
** UPDATE **
Based on the most recent update of your question, you'd need to get the Main document id/guid by URL parameter or session or some other parameter, then look that node up using something like DocumentHelper.GetDocument(MainDocID). After you have that Main document, you can perform your lookup using the code I provided above.
It doesn't sound like you have a great way to get this info simply because one of those Secondary pages could be related to possibly many other pages or page types. One suggestion may be to create a very specific relationship name and simply query your Main document by that very specific relationship name and the Secondary page ID to get any Main pages that may be related to it.

Pass the current Model down to a more specific shape

TL;DR: How do I pass the model down into the next shape?
I have modified the Orchard.Search modules search results view in my theme (Views\Orchard.Search\Search\Index.cshtml), and split it out into several scenarios. I test for one result and just redirect straight through and test for no search terms and multiple search search results.
I would like to put these scenarios in separate .cshtml views but these specialised shapes need access to the main search result model (#model Orchard.Search.ViewModels.SearchViewModel).
I tried using Arguments.From to make a dynamic object which works but then I have to cast everything in the view back to its correct data type because the next shape down thinks every property on its model is of type dynamic.
Instead I've tried to figure out how to just pass the whole Model to the next shape:
#if (HasMultipleResults())
{
var shapeFactory = (IShapeFactory)New;
// this doesn't work
var multipleResultsShape = (dynamic)shapeFactory.Create("Search_MultipleResults", Model);
#Display(multipleResultsShape);
return;
}
I haven't found a way to get it working yet, can anyone educate me please?
If the first argument of the dynamic method is a type, then this will be used as the model.
New.YourShape(typeof(YourType))
See this video for the details: https://www.youtube.com/watch?v=gKLjtCIs4GU&feature=youtu.be&t=1127

Xpages: how to have a custom function return type ahead values

I have a SSJS object/function that does a custom full text search, as the results need to be in a specific order defined by my client. This function works fine in the search results view. It has a method to build the search query (add the * and the AND and all that), a property that returns the sorted results and I just added a method that returns the HTML needed for the type ahead.
My object is called SortedSearchResults. It needs to be instantiated with the search query in order to get the results and the type ahead HTML. How would I code that in the the type ahead values, so I don'T end up creating one SortedSearchResults object each time a letter is added to the type ahead field?
Would I be better off with a session managed bean? Would that make it easier? Would it make it faster? The search is limited to a maxiumum of 15 results.
Sor far, I only used SSJS code, but I am not sure how what I should do to avoid a memory hole. Here is the current code:
//TODO Memory management???
var results = new SortedSearchResults( getComponent("inputSearch").getValue());
return results.typeAheadValues;
How can I optimize this code so I don'T create unecessary "var results"? Or is it OK that way???
Thanks :)

In Orchard CMS, how can I get all items of a particular content type based in the values of an attached part's properties

I'm using Orchard 1.7.2.
I have created a new content type called PropertyImage of stereotype Media. I also created a part called PropertyPart and attached that part to my PropertyImage content type. This allows a user to pick a product when uploading a PropertyImage (ie to say 'This image is of this property').
So far so good.
Now what I'd like to do is query for all PropertyImages that have a PropertyPart attached to them where the associated property is x, y, or z.
This is what I have so far:
var images = _orchardServices.ContentManager
.Query<PropertyPart, PropertyRecord>()
.Where(p => p.PropertyId == id)
.ForType(new[] { "PropertyImage" });
This however will only return a collection of PropertyParts, which is not what I want, because I want the whole PropertyImage Content Item. How can I do this?
I should point out that properties come from an external source, and are therefore not content items.
Edit
As soon as I asked this question, I realised I could just append my query with this:
.List().Select(p=>p.ContentItem)
Sometimes it just helps to talk your problem through!
As soon as I asked this question, I realised I could just append my query with this:
.List().Select(p=>p.ContentItem)
Sometimes it just helps to talk your problem through!

Breadcrumbs error in Kohana

I am implementing breadcrumbs on my application using Kohana framework using https://github.com/RaymondCrandall/kohana-breadcrumbs
I have a Category section which internally has many other sub categories n so on. One controller called Category.php having two action:
1. index($cat) (called when I click on each category until it reaches the last sub category)
2. category($cat) (called when I click on last sub category i.e. on leaf node )
The way I wrote my code into both action is:
Breadcrumbs::add(Breadcrumb::factory()->set_title("Home")->set_url(url::site()));
Breadcrumbs::add(Breadcrumb::factory()->set_title("Categories")->set_url(url::site('categories')));
if($cat != NUll) {
Breadcrumbs::add(Breadcrumb::factory()->set_title($cat)->set_url(url::site('categories/' .$cat )));
}
$actual = Breadcrumbs::get();
$view->breadcrumbs = $actual;
The problem is it shows me only three levels. How can I extend it to 4th level or more.
Eg. home>category>stationary>dress. How can I save my previous values of $actual?
So when I click on dress, index action is called and replaces my array with
home>category>dress since parameter '$cat= dress'.
I don't think this is much related to the mentioned plugin.
You are just using the category name string, a simple string doesn't even know what is a category, event less what is it's name, and hierarchy.
I see three options:
Get all your parent relations from the DB, or wherever you store them, run a loop that prints breadcrumbs, until it reaches the leaf.
Map your actions so they accept full path, for example "index/stationary/dress", and extract all the values from the path parameters (no need for DB call in this case)
Store an array with previous values in session, and recreate breadcrumb from that array. Also no DB calls here. Note that, in this case you have to be careful when to empty the array. Depends on your logic. You would have to recognize when a leaf has been hit, and empty the array then.

Resources