Yii2: delete records from junction table beforeSave/afterSave - activerecord

I'm trying to delete records from a junction table.
When frequentie is 2 (daily) I use a junction table to connect the planning_id's with the dagen_id's. But when the frequentie get's updated to weekly by example, I need to delete all those planning_id's from the junction table.
This is what I tried so far:
public function beforeSave($insert)
{
if (parent::beforeSave($insert)) {
$this->gewijzigd_op = new \yii\db\Expression('NOW()');
$this->gewijzigd_door = Yii::$app->user->id;
if ($this->getOldAttribute('frequentie') != $this->frequentie) {
if ($this->getOldAttribute('frequentie') == 2) {
PlanningDagen::deleteAll(['planning_id' => $this->planning_id]);
}
}
return true;
} else {
return false;
}
}
Using:
$query = new \yii\db\Query();
$query->createCommand()->delete('planning_dagen', ['planning_id' => $this->planning_id)->execute();
instead of:
PlanningDagen::deleteAll(['planning_id' => $this->planning_id]);
Doesn't work either.

Solved it using afterSave:
public function afterSave($insert, $changedAttributes) {
parent::afterSave($insert, $changedAttributes);
if (!$insert) { // since $insert is false, a database update happend instead of an insertion
if ($changedAttributes['frequentie'] == 2 && $this->frequentie != 2) { // $changedAttributes is an array which holds the attribute-values from BEFORE the update, so you can compare them with $this (active record).
PlanningDagen::deleteAll('planning_id = :id', [':id' => $this->planning_id]);
}
}
}

Related

is it possible to update parent table through child table

I want to update the column status of my Goal table if the percentage column on the Accomplishment table reach 100
$accomplishment = new Accomplishment();
$accomplishment->emp_id = Auth::user()->employees->first()->id;
$accomplishment->goal_id = $request->goals;
$accomplishment->date = $request->date;
$accomplishment->accomplishment = $request->accomplishment;
// $accomplishment->no_of_hours = $request->hours;
$accomplishment->remarks = $request->remarks;
$accomplishment->percentage_of_goal = $request->percentage;
$accomplishment->save();
Of course, if You've added relations you can run
(if you have more than one relation to the parent, run)
foreach($accomplishment->goals as $goal) {
$goal->some_field = true;
$goal->save();
}
(if you have only one parent)
$accomplishment->goal->some_field = true;
$accomplishment->goal->save();
Inside model Accomplishment.php you should have
public function goal()
{
return $this->belongsTo(Goal::class);
}
Or add boot method for the model and it will run each time when Accomplishment.php is creating
protected static function boot()
{
parent::boot();
self::creating(function ($accomplishment) {
if($accomplishment->percentage_of_goal === 100) {
$accomplishment->goal->some_field = true;
$accomplishment->goal->save()
}
});
}
Read more about Laravel relations here https://laravel.com/docs/7.x/eloquent-relationships

How to check if the user doesn't select any item in the list?

I have a list view that list the activities. I have a way to get the selected values. What I don't know is how can I check if the user doesn't select any items from the list. How can I do this?
This is how I populate my list this will return at least 5-20 activities:
public void Get_Activities()
{
try
{
var db = DependencyService.Get<ISQLiteDB>();
var conn = db.GetConnection();
var getActivity = conn.QueryAsync<ActivityTable>("SELECT * FROM tblActivity WHERE Deleted != '1' ORDER BY ActivityDescription");
var resultCount = getActivity.Result.Count;
if (resultCount > 0)
{
var result = getActivity.Result;
lstActivity.ItemsSource = result;
lstActivity.IsVisible = true;
}
else
{
lstActivity.IsVisible = false;
}
}
catch (Exception ex)
{
//Crashes.TrackError(ex);
}
}
And this is how I get the values of the selected items on my list:
foreach(var x in result)
{
if (x.Selected)
{
// do something with the selected items
}
}
My question is like this
if(list.selecteditemcount == 0){
DisplayAlert("Error","Please select atleast 1 item","Ok");
}
In xamarin projects you can use Linq (using System.Linq). With Linq it's really easy to filter your list like that:
if(!list.Any(x => x.Selected == true))
{
// DisplayAlert...
}
This basically checks if any of your items has the value Selected='true'
Or without Linq you can do something like this:
if(list.FindAll(x => x.Selected).Count() == 0)
{
//DisplayAlert...
}
Use this
if (result.Any(x => x.Selected))
{
}
else
{
await DisplayAlert("Application Error", "Please choose at least one(1) activity", "Ok");
}

Error:You must specify an orderBy clause when using this function

I am getting the following error while trying to migrate. Actually, I have a lot of data so I used the chunk in laravel.
I am using MongoDB not SQL
** You must specify an orderBy clause when using this function.**
My migration file's up function is is
public function up()
{
ini_set('max_execution_time', 250);
ini_set('memory_limit', '1024M');
$product_array = DB::collection('product')->orderBy('created_at','asc');
$product_array->chunk(20, function($product_array)
{
$cotationPercentForecast=0;
$cotationPercent=0;
$quote_tpl = array();
foreach ($product_array as $key => $value) {
if (isset($value['quote_tpl'])) {
$quote_tpl = $value['quote_tpl'];
$defaultScoreCount = 0;
$realScoreCount = 0;
$forecastScoreCount = 0;
foreach ($quote_tpl as $idxPerimetre => $perimetre) {
if (isset($perimetre['families'])) {
foreach ($perimetre['families'] as $idxFamilies => $family) {
if (isset($family['subfamilies'])) {
foreach ($family['subfamilies'] as $idxSubFamilies => $subfamily) {
if (isset($subfamily['items'])) {
foreach ($subfamily['items'] as $idxitem => $item) {
if (isset($item['criterias'])) {
foreach ($item['criterias'] as $idxcriteria => $criteria) {
if (!isset($criteria['inactive']) || $criteria['inactive'] != 'true') {
$defaultScoreCount++;
if (isset($criteria['real_score'])) {
$realScoreCount++;
}
if (isset($criteria['forecast_score'])) {
$forecastScoreCount++;
}
}
} // FOREACH CRITERIA
} // ISSET CRITERIA ###
} // FOREACH ITEM
} // ISSET ITEM ###
} // FOREACH SUBFAMILY
} // ISSET SUBFAMILY ###
} // FOREACH FAMILY
} // ISSET FAMILY ###
}// FOREACH PERIMETER ##
if ($realScoreCount > 0 && $defaultScoreCount > 0) {
$cotationPercentUn = ($realScoreCount / $defaultScoreCount) * 100;
$cotationPercent = round($cotationPercentUn, 2);
}
if ($forecastScoreCount > 0 && $defaultScoreCount > 0) {
$cotationPercentUnForecast = ($forecastScoreCount / $defaultScoreCount) * 100;
$cotationPercentForecast = round($cotationPercentUnForecast, 2);
}
$results = DB::collection('product')->update(['cotation_percent' => $cotationPercent,'forecast_cotation_percent' => $cotationPercentForecast]);
}
}
});
}
I have gone through many answers here and almost all suggest to use orderBy so I used it. But still getting the error.
i have used
Product::chunk(5, function($product_array){}
Any help is appreciated. Thanks in advance.
This warning is added when Laravel internally uses enforceOrderBy which is defined inside Illuminate/Database/Query/Builder.
Whenever you use chunk on a query builder instantiated by DB facade Illuminate\Database\Query\Builder directly, it would ask you :
You must specify an orderBy clause when using this function.
So it will happen if you are doing :
\DB::table('product')->chunk(10, function($product){
...
});
If you manually add an order by to this, it will not throw the error and will work as expected :
\DB::table('product')->orderBy('created_at')->chunk(10, function($product){
...
});
However, its better to use the Eloquent Model directly like which will not enforce you to add an order by clause manually :
Product::chunk(10, function($product){
...
});
Also there is no method DB::collection(), you can use DB::table() instead if you wish, unless you are using mongodb and not MySQL

Comparing objects before and after update retrieved by LINQ query

I am using LINQtoSQL and I need to compare object before and after update.
In the example below Student 'before' and Student 'curStudent' are the same, because they was retrieved by the same query.
using(DataContext db = new DataContext())
{
Student before = db.Student.Where(q=>q.id == 1).SingleOrDefault();
Student curStudent = db.Student.Where(q=>q.id == 1).SingleOrDefault();
curStudent.Name = "NewName";
db.SubmitChanges();
}
if(before.Name != curStudent.Name) // this condition will never be true
{
//do something
}
The context will only keep one object per key value, so one way would be to use two separate contexts:
Student before;
using(DataContext db1 = new DataContext())
{
before = db.Student.Where(q=>q.id == 1).SingleOrDefault();
}
Student curStudent;
using(DataContext db2 = new DataContext())
{
curStudent = db.Student.Where(q=>q.id == 1).SingleOrDefault();
curStudent.Name = "NewName";
db.SubmitChanges();
}
if(before.Name != curStudent.Name)
{
//do something
}
Or if you're just interested in the name change, keep the previous value:
string before;
using(DataContext db = new DataContext())
{
Student curStudent = db.Student.Where(q=>q.id == 1).SingleOrDefault();
before = curStudent.Name; // cache the name
curStudent.Name = "NewName";
db.SubmitChanges();
}
if(before != curStudent.Name)
{
//do something
}

LINQ in Business Layer paging / sorting gridview in User layer

I have a gridview in my user layer that uses a business layer method as its datasource, and I want the gridview to support paging and sorting. When I returned an Ienumerable from the method, it will bring back all of the data. If I use Take/Skip to bring back only a page's worth, the gridview doesn't realize that there are many pages of data.
When I change the Ienumerable to an IQueryable, the DataBind() fails because the data has already been disposed of. I think this problem has something to do with when the query actually executes and it may have to do with the filters I am applying to the query (see below).
User Layer Code:
grdSelectedQuestionaires.DataSource = assessment.FilteredAssessmentList(filter);
grdSelectedQuestionaires.DataBind(); <-- Fails - "Cannot access a disposed object"
Business Layer Code:
public IQueryable<Assessment> FilteredAssessmentList(AssessmentSearchFilter filter)
{
using (PAQcDataLayerDataContext dc = new PAQcDataLayerDataContext())
{
var rows = (
from a in dc.vPAQSummaries
select new Assessment()
{
PAQNumber = a.PAQNumber.Trim(),
CustomerID = (int)a.CustomerID,
Department = a.Department.Trim(),
CustomerName = a.CustomerName.Trim(),
DOTNumber = a.DOTNumber.Trim(),
OrgName = a.OrgName.Trim(),
DateEntered = a.DateEntered,
GroupNumber = a.GroupNumber.Trim(),
JobTitle = a.JobTitle.Trim(),
FileNames = a.FileNames.Trim(),
AnalystType = a.AnalystType.ToString(),
Incumbents = a.Incumbents,
});
// Filter by Customer ID
if (filter.CustomerID > 0)
{
rows = rows.Where(r => r.CustomerID == filter.CustomerID);
}
else
{
rows = rows.Where(r => r.CustomerID != null);
}
// Filter by DOT Number
if (!string.IsNullOrEmpty(filter.DOTNumberFrom))
{
if (string.IsNullOrEmpty(filter.DOTNumberTo))
{
rows = rows.Where(r => r.DOTNumber == filter.DOTNumberFrom);
}
else
{
rows = rows.Where(r => r.DOTNumber.CompareTo(filter.DOTNumberFrom) >= 0
&& r.DOTNumber.CompareTo(filter.DOTNumberTo) <= 0);
}
}
// Filter by OrgName
if (!string.IsNullOrEmpty(filter.OrgName))
{
rows = rows.Where(r => r.OrgName.StartsWith(filter.OrgName));
}
// Filter by Group
if (!string.IsNullOrEmpty(filter.GroupNumberFrom))
{
if (!string.IsNullOrEmpty(filter.GroupNumberTo))
{
rows = rows.Where(r => r.GroupNumber == filter.GroupNumberFrom);
}
else
{
rows = rows.Where(r => r.GroupNumber.CompareTo(filter.GroupNumberFrom) >= 0
&& r.GroupNumber.CompareTo(filter.GroupNumberTo) <= 0);
}
}
if (filter.Skip > 0)
{
rows = rows.Skip(filter.Skip);
}
if (filter.Take > 0)
{
rows = rows.Take(filter.Take);
}
else
{
rows = rows.Take(100);
}
return rows.Distinct();
}
}
}
The paging problem was caused by the Using statement. Changing this:
using (PAQcDataLayerDataContext dc = new PAQcDataLayerDataContext())
{
...
}
to this:
PAQcDataLayerDataContext dc = new PAQcDataLayerDataContext();
Made paging work. Now I have to figure out how sorting works.

Resources