IQueryable Parent-Child - linq-to-sql

I need to cast: IQueryable<PARENT> to IQueryable<Child>.
IQueryable<PARENT> query = GetParents();
IQueryable<CHILD> result = query.Select(t => t.Children);
This doesn't work, unable to convert EntitySet to IQueryable. Thanks

Use:
IQueryable<CHILD> result = query.SelectMany(t => t.Children);
The query returns all cild items of the parent, childs referenced by multiple parents will be returned multiple times.
To get the distinct rows use - you will have to implement a class that Implements IComparer though, to do a custom filtering. Distinct() will look at the instances and not the values.
IQueryable<CHILD> result = query.SelectMany(t => t.Children)
.Distict(new Comparer<CHILD>());
Distinct() removes duplicats from the query.
Aternatively you can use grouping to create a grouping for the children:
IEnumerable<CHILD> result = query.SelectMany(t => t.Children)
.GroupBy(x => new CHILD {
id = x.id
// add other properties here
})
.Select(g => g.Key);
Instead of new CHILD { } you could also use new { } to create an anonymous result, in this case you should replace IEnumerable<CHILD> with var.
To perform a type cast in Linq you can use - although I would assume that this will not work in you case:
someQuery.OfType<TypeToCastTo>().Cast<TypeToCastTo>();
OfType<T>() filters the items so that Cast<T>() can perform the type cast.

Related

Yii2 Model search query

How can I add where condition to my Articles model so that slug(From category model) is equal to $slug?
And this is a function that Gii generated:
public function getCategory()
{
return $this->hasOne(Categories::className(), ['id' => 'category_id']);
}
Here's my code:
public function specificItems($slug)
{
$query = Articles::find()->with('category');
$countQuery = clone $query;
$pages = new Pagination(['totalCount' => $countQuery->count(),'pageSize' => 12]);
$articles = $query->offset($pages->offset)
->limit($pages->limit)
->all();
return ['articles' => $articles,'pages' => $pages];
}
Your SQL query should contain columns from both article and category table. For that you need to use joinWith().
$result = Articles::find()
->joinWith('category')
->andWhere(['category.slug' => $slug])
->all();
Where 'category' is then name of your category table.
However, in your code you deviate from certain best practices. I would recommend the following:
Have both table name and model class in singular (Article and article). A relation can be in plural, like getCategories if an article has multiple categories.
Avoid functions that return result sets. Better return ActiveQuery class. If you have a query object, all you need to get the actual models is ->all(). However, you can further manipulate this object, add more conditions, change result format (->asArray()) and other useful stuff. Returning array of results does not allow that.
Consider extending ActiveQuery class into ArticleQuery and implementing conditions there. You'll then be able to do things like Article::find()->joinWith('category')->byCategorySlug('foo')->all().

Using mssql and node-sql to UPDATE

I'm using mssql together with node-sql to build SELECT queries but I can't find any example how to use it to build UPDATE queries. I have an object where properties corresponds to table fields and I would like to update all of them.
Assume:
child: sql.define({
name: 'children',
columns: ['id', 'name', 'surname', 'group']
})
and:
var data = {/*new child data*/};
var query = child.update(data).where(child.id.equals(data.id)).toQuery().text;
How can I use this with mssql without knowing values and count of data properties?
Right now I have this:
connection.query(query, [data.id, data.name, data.surname, data.group], function(err, result) {
res.redirect('/index');
});
that can be achieved by using lodash's values:
_.values(data);
which returns array of object properties but it does not guarantee correct order which is deal breaker.
How can I tackle that problem?
This will return an array of values based on the order of table columns:
child.columns.map(function(col){return data[col.name]})
It might be possible to compact the above in shorter form with lodash.
Few days later I figured node-sql's query object also has .values property besides .text property so above update can be written as
var data = {/*new child data*/};
var query = child.update(data).where(child.id.equals(data.id)).toQuery();
connection.query(query.text, query.values, function(err, result) {
res.redirect('/index');
});

Complex AR query, where clause one column equal to another column's value?

I've written up the AR query below, but have one problem with a where clause. On the Payout model there is a comp_builder_id and there's also a comp_builder_id on a ContractLevel.
I want to add a where clause to this query to get the data where the Payout model's comp_builder_id is equal to the ContractLevel model's comp_builder_id.
payouts = Payout.includes(:comp_builder => {
:contract_levels => {
:transmittal => [
{:appointment_hierarchies => :child},
:appointment_cases
]
}
})
.includes(:product)
.includes(:payor)
.where(products: { :id => self.product.id })
.where(appointment_cases: { :case_id => self.id })
.where(transmittals: { :id => appointment_case.transmittal_id })
.where(contract_levels: { :transmittal_id => appointment_case.transmittal_id,
:appointment_id => Transmittal.find(appointment_case.transmittal_id).low,
:comp_builder_id => # ? payout's comp builder id ? })
How can this be done in Active Record? In the last few where clauses I'm referencing different variables, those can be ignored as this query is just a snippet.
You can pass a string into the where statement:
User.include(:parent).where('users.parent_id = parents.id')
Basically if you know what you want the where statement to look like in raw SQL then you can just pass it in as a string into the where statement.
That is the only way I know of to make complex where statements in ActiveRecord.
Just make sure to remember that you need to reference the column names, not the model names.

Magento JoinLeft() in custom orders grid causing SQL integrity constrain violation for non-admin user in multi-website setup

I have extended the Mage_Adminhtml_Block_Sales_Order_Grid class with a custom module to add several customer attributes (Magento EE 1.10) to the grid.
I added the custom attributes to the collection in my MyCompany_MyModule_Block_Adminhtml_Order_Grid class in the _prepareCollection() method using three joins like this:
protected function _prepareCollection()
{
$collection = Mage::getResourceModel($this->_getCollectionClass());
//get the table names for the customer attributes we'll need
$customerEntityVarchar = Mage::getSingleton('core/resource')
->getTableName('customer_entity_varchar');
$customerEntityInt = Mage::getSingleton('core/resource')
->getTableName('customer_entity_int');
// add left joins to display the necessary customer attribute values
$collection->getSelect()->joinLeft(array(
'customer_entity_int_table'=>$customerEntityInt),
'`main_table`.`customer_id`=`customer_entity_int_table`.`entity_id`
AND `customer_entity_int_table`.`attribute_id`=148',
array('bureau'=>'value'));
$collection->getSelect()->joinLeft(array(
'customer_entity_varchar_table'=>$customerEntityVarchar),
'`main_table`.`customer_id`=`customer_entity_varchar_table`.`entity_id`
AND `customer_entity_varchar_table`.`attribute_id`=149',
array('index_code'=>'value'));
$collection->getSelect()->joinLeft(array(
'customer_entity_varchar_2_table'=>$customerEntityVarchar),
'`main_table`.`customer_id`=`customer_entity_varchar_2_table`.`entity_id`
AND `customer_entity_varchar_2_table`.`attribute_id`=150',
array('did_number'=>'value'));
$this->setCollection($collection);
return parent::_prepareCollection();
}
UPDATE: While everything displays fine when viewing orders, things are not fine when I try to search / filter orders by any of the text join fields (index_code or did_number). The result is a SQL error: "SQLSTATE[23000]: Integrity constraint violation: 1052 Column 'store_id' in where clause is ambiguous."
This problem also exists if I remove all but one of the leftJoin() statements, so something is going wrong with both (either) of the joins with the customer_entity_varchar table.
As now there are two columns with the name store_id, you have to specify filter_index when you add the column to the grid:
$this->addColumn('store_id', array(
...
'filter_index'=>'main_table.store_id',
));
So that it knows which one you are referring while filtering.
I hope it helps!
More than likely it is because you are joining customer_entity_varchar_table twice.
$collection->getSelect()->joinLeft(array(
'customer_entity_varchar_table'=>$customerEntityVarchar),
'`main_table`.`customer_id`=`customer_entity_varchar_table`.`entity_id`
AND `customer_entity_varchar_table`.`attribute_id`=149',
array('index_code'=>'value'));
$collection->getSelect()->joinLeft(array(
'customer_entity_varchar_2_table'=>$customerEntityVarchar),
'`main_table`.`customer_id`=`customer_entity_varchar_2_table`.`entity_id`
AND `customer_entity_varchar_2_table`.`attribute_id`=150',
array('did_number'=>'value'));
You may want to combine those, you can also try and print the SQL to see what the Query looks like:
$collection->getSelect()->getSelectSql();
More info on collections: http://blog.chapagain.com.np/magento-collection-functions/
The problem appears to exist in two different places. One case is if logged in as a user with a single store, the other as a user who can filter various stores.
Single store user
The solution I went with was to override the addAttributeToFilter method on the collection class. Not knowing exactly what changing the Enterprise_AdminGws_Model_Collections::addStoreAttributeToFilter method would affect other behavior I wanted to avoid that, and I found adding a filter index in Mage_Adminhtml_Block_Sales_Order_Grid as Javier suggested did not work.
Instead I added the following method to Mage_Sales_Model_Resource_Order_Grid_Collection:
/**
* {#inheritdoc}
*/
public function addAttributeToFilter($attribute, $condition = null)
{
if (is_string($attribute) && 'store_id' == $attribute) {
$attribute = 'main_table.' . $attribute;
}
return parent::addFieldToFilter($attribute, $condition);
}
A patch can be found here: https://gist.github.com/josephdpurcell/baf93992ff2d941d02c946aeccd48853
Multi-store user
If a user can filter orders by store at admin/sales_order, the following change is also needed to Mage_Adminhtml_Block_Sales_Order_Grid around line 75:
if (!Mage::app()->isSingleStoreMode()) {
$this->addColumn('store_id', array(
'header' => Mage::helper('sales')->__('Purchased From (Store)'),
'index' => 'store_id',
'type' => 'store',
'store_view'=> true,
'display_deleted' => true,
'filter_index' => 'main_table.store_id',
));
}
A patch can be found here: https://gist.github.com/josephdpurcell/c96286a7c4d2f5d1fe92fb36ee5d0d5a
I had the same bug, after grepping the code, I finally found the troublemaker which is in the Enterprise_AdminGws_Model_Collections class at line ~235:
/**
* Add store_id attribute to filter of EAV-collection
*
* #param Mage_Eav_Model_Entity_Collection_Abstract $collection
*/
public function addStoreAttributeToFilter($collection)
{
$collection->addAttributeToFilter('store_id', array('in' => $this->_role->getStoreIds()));
}
You have to replace 'store_id' by 'main_table.store_id', of course you'll have to extend that particular method in your own rewrite to stick into Magento guidelines :p
Hope it helps!

LINQ to SQL select all fields in a table but with a distinct column

I need to return a list of counties, but I need to filter out duplicate phone code values. For some reason I'm having trouble with the syntax. Can anyone show me how to do this? Should I be using the group by instead?
Group by would work if you need the actual entity.
var query = db.Counties.GroupBy( c => new { c.CountyName, c.PhoneCode } )
.Select( g => g.FirstOrDefault() );
Or if you are constructing it for a view model and only need the data, you could use Distinct. The following creates an anonymous type that could be used to populate the model.
var query = db.Counties.Select( c => new { c.CountyName, c.PhoneCode } )
.Distinct();