Yii2 - custom order in the models get relationship - yii

What is the correct way to make the custom order in the models get relationship? Is it possible to do this? This is my current code:
public function getBoardPosts()
{
return $this->hasMany(BoardPosts::className(), ['topic_id' => 'id'])->orderBy('order ASC');
}

Yes it is. Here is an example from the guide:
class Customer extends ActiveRecord
{
public function getBigOrders($threshold = 100)
{
return $this->hasMany(Order::className(), ['customer_id' => 'id'])
->where('subtotal > :threshold', [':threshold' => $threshold])
->orderBy('id');
}
}
Please note that you may have to quote the field (or better yet name your columns using non-SQL words):
return $this->hasMany(BoardPosts::className(), ['topic_id' => 'id'])->orderBy('`order` ASC');

Related

Yii2 - hasMany relation with multiple columns

I have a table message_thread:
id
sender_id
recipient_id
I want to declare a relation in my User model that will fetch all message threads as follows:
SELECT *
FROM message_thread
WHERE sender_id = {user.id}
OR recipent_id = {user.id}
I have tried the following:
public function getMessageThreads()
{
return $this->hasMany(MessageThread::className(), ['sender_id' => 'id'])
->orWhere(['recipient_id' => 'id']);
}
But it generates an AND query. Does anyone know how to do this?
You cannot create regular relation in this way - Yii will not be able to map related records for eager loading, so it not supporting this. You can find some explanation int this answer and related issue on GitHub.
Depending on use case you may try two approach to get something similar:
1. Two regular relations and getter to simplify access
public function getSenderThreads() {
return $this->hasMany(MessageThread::className(), ['sender_id' => 'id']);
}
public function getRecipientThreads() {
return $this->hasMany(MessageThread::className(), ['recipient_id' => 'id']);
}
public function getMessageThreads() {
return array_merge($this->senderThreads, $this->recipientThreads);
}
In this way you have two separate relations for sender and recipient threads, so you can use them directly with joins or eager loading. But you also have getter which will return result ofboth relations, so you can access all threads by $model->messageThreads.
2. Fake relation
public function getMessageThreads()
{
$query = MessageThread::find()
->andWhere([
'or',
['sender_id' => $this->id],
['recipient_id' => $this->id],
]);
$query->multiple = true;
return $query;
}
This is not real relation. You will not be able to use it with eager loading or for joins, but it will fetch all user threads in one query and you still will be able to use it as regular active record relation - $model->getMessageThreads() will return ActiveQuery and $model->messageThreads array of models.
Why orOnCondition() will not work
orOnCondition() and andOnCondition() are for additional ON conditions which will always be appended to base relation condition using AND. So if you have relation defined like this:
$this->hasMany(MessageThread::className(), ['sender_id' => 'id'])
->orOnCondition(['recipient_id' => new Expression('id')])
->orOnCondition(['shared' => 1]);
It will generate condition like this:
sender_id = id AND (recipent_id = id OR shared = 1)
As you can see conditions defined by orOnCondition() are separated from condition from relation defined in hasMany() and they're always joined using AND.
For this query
SELECT *
FROM message_thread
WHERE sender_id = {user.id}
OR recipent_id = {user.id}
You Can use these
$query = (new \yii\db\Query)->from("message_thread")
$query->orFilterWhere(['sender_id'=>$user_id])->orFilterWhere(['recipent_id '=>$user_id]);

Yii 2 can rewrite WHERE condition from model?

I have some where condition in my model .
Its check is field active or no.
Now I need to write a join relation. But I need to remove where condition. Is it possible?
My model.
...
public static function find() {
return (new AssetgroupsQuery(get_called_class()))->active();
}
My relation
public function getAssetgroup(): \app\models\AssetgroupsQuery {
return $this->hasOne(Assetgroups::class, ['asg_id' => 'ass_group'])->andOnCondition(['asg_active' => '1'])
->viaTable('assets', ['ass_id' => 'log_ass_id',]);
}
I need to got all active assets and join, if asset is empty I need to got null fields, but
model where condition added to my current sql query and remove all fields which assets are null.
I try to add some where Condition to remove old where, but it don't work.
Can you help me?
You can reset existing conditions by using where(null).
On relation level:
public function getAssetgroup(): \app\models\AssetgroupsQuery {
return $this->hasOne(Assetgroups::class, ['asg_id' => 'ass_group'])
->andOnCondition(['asg_active' => '1'])
->where(null)
->viaTable('assets', ['ass_id' => 'log_ass_id',]);
}
Or directly on join:
$query = MyModel::find()
->joinWith([
'assetgroup' => function (ActiveQuery $query) {
$query->where(null);
},
])

How to generate mysql query from relations using via or viaTable relations

I have the following relations in yii2 model
public function getStreamsFormations()
{
return $this->hasMany(StreamsFormations::className(), ['stream_id' => 'id']);
}
public function getFormations()
{
return $this->hasMany(Formations::className(), ['id' => 'formation_id'])->via('streamsFormations');
}
How to create complete mysql query from the relation "getFormations()"?
I need it to use it some where else...
I used ...
$query = $model->getFormations()->createCommnad()->rawSql;
But it skipped the via relation tables from the query.
Relations not have where condition ! they are just join 2 table to theme ! you have to put your condition in find() queries!
if you need more information just let me know .

NHibernate accumulate queryOver conditions

I want to do a sort of filtering chain to filter Receipt objects using queryOver functionality.
The chain can differ in length, according to the parameters user chooses on the screen.
Eventually, I want the chain to run somehow like this:
public IList<Receipt> RunFilters()
{
IQueryOver<Receipt, Receipt> currQuery = NHibernateHelper.Session.QueryOver<Receipt>();
foreach (var item in filters)
{
currQuery = item.RunFilter(currQuery);
}
return currQuery.List();
}
So, the question is - how RunFilter should be defined? I thought it should be
public IQueryOver<Receipt, Receipt> RunFilter(IQueryOver<Receipt, Receipt> prevFilter)
and they I can do filters like
return prevFilter.Where(receipt => receipt.TotalSum > 0);
But I can't do
return prevFilter.JoinQueryOver(v => v.Store).Where(vv => vv.Name.Equals(m_storeName));
Any ideas?
Thanks in advance
Victor
return prevFilter.JoinQueryOver(v => v.Store).Where(vv => vv.Name.Equals(m_storeName));
the above can be written as
Store storeAlias = null;
return prevFilter.JoinAlias(v => v.Store, () => storeAlias).Where(() => storeAlias.Name == m_storeName);
EDIT: fixed equation

NHibernate 3.1 ignoring computed column formula

I have a class with two computed columns. The formulas are select statements that grab counts from other tables, like so:
private const string VOTES_FORMULA = "(select count(v.id) from Votes v where v.BusinessID = Id)";
private const string SURVEY_FORMULA = "(select cast((case when exists (select * from surveys s where s.businessid = Id) then 1 else 0 end) as bit))";
// in my bootstrap code...
mappings.Override<Business>(map =>
{
map.IgnoreProperty(x => x.IsNewRecord);
map.IgnoreProperty(x => x.IdString);
map.Map(x => x.UserPassword).CustomType<EncryptedStringType>();
map.Map(x => x.HasTakenSurvey).Formula(SURVEY_FORMULA).Not.Insert().Not.Update();
map.Map(x => x.Votes).Formula(VOTES_FORMULA).Not.Insert().Not.Update();
});
This was all working fine with Fluent NHibernate 1.1 (using NHibernate 2.1), but I just upgraded to 1.2 (using NH 3.1) and it appears that Fluent NHibernate is ignoring the formulas. I'm getting an "invalid column name" exception for the two fields HasTakenSurvey and Votes because its' trying to query the columns directly rather than executing the formulas as directed. An example query:
exec sp_executesql N'select TOP (#p0) business0_.Id as Id0_, business0_.UserPassword as UserPass2_0_, business0_.HasTakenSurvey as HasTaken3_0_, business0_.Votes as Votes0_, business0_.Origin as Origin0_, business0_.SecurityToken as Security6_0_, business0_.BusinessName as Business7_0_, business0_.BusinessType as Business8_0_, business0_.BusinessImageUrl as Business9_0_, business0_.BusinessDescription as Busines10_0_, business0_.EmployeeCount as Employe11_0_, business0_.OwnerFirstName as OwnerFi12_0_, business0_.OwnerLastName as OwnerLa13_0_, business0_.UserPosition as UserPos14_0_, business0_.BusinessAddress1 as Busines15_0_, business0_.BusinessAddress2 as Busines16_0_, business0_.BusinessCity as Busines17_0_, business0_.BusinessState as Busines18_0_, business0_.BusinessPostal as Busines19_0_, business0_.BusinessCountry as Busines20_0_, business0_.UserBusinessPhone as UserBus21_0_, business0_.UserMobilePhone as UserMob22_0_, business0_.UserEmailAddress as UserEma23_0_, business0_.UserIpAddress as UserIpA24_0_, business0_.OptInReminders as OptInRe25_0_, business0_.OptInOffers as OptInOf26_0_, business0_.OptInSms as OptInSms0_, business0_.Created as Created0_, business0_.Modified as Modified0_ from dbo.Businesses business0_ order by business0_.BusinessName asc',N'#p0 int',#p0=25
Did the implementation change? What am I doing wrong?
As noted in the comments ConventionBuilder.Property.Always(x => x.Column(x.Property.Name)) was adding the column to all properties (and overriding the formula).
Adding .Columns.Clear() to the mapping should remove the column, so:
mappings.Override<Business>(map =>
{
map.IgnoreProperty(x => x.IsNewRecord);
map.IgnoreProperty(x => x.IdString);
map.Map(x => x.UserPassword).CustomType<EncryptedStringType>();
map.Map(x => x.HasTakenSurvey).Formula(SURVEY_FORMULA).Not.Insert().Not.Update().Columns.Clear();
map.Map(x => x.Votes).Formula(VOTES_FORMULA).Not.Insert().Not.Update().Columns.Clear();
});
Columns.Clear() solution provided by #david duffet also didn't work for me. The getter of Formula is private and so you can't filter on the Formula property (you get method-group cannot be converted to value or something like that). NH3.3, FNH 1.3.
My solution - create a custom Attribute in my Model project - IsNHibernateFormulaPropertyAttribute, apply it to my formula properties, and then check for that attribute in my naming convention logic using reflection:
private bool IsFormula(IPropertyInstance instance)
{
var propInfo = instance.Property.DeclaringType.GetProperty(instance.Property.Name);
if (propInfo != null)
{
return Attribute.IsDefined(propInfo, typeof(IsNHibernateFormulaPropertyAttribute));
}
return false;
}
public void Apply(IPropertyInstance instance)
{
if (!IsFormula(instance))
{
instance.Column(Convert(instance.Property.Name));
}
}