How to change value of an object using linq - c#

I have the following statment that if isdefault is true to this collection i need to set each object isDefault property to false.
custHead.lstCustomziation.Where(x => x.IsDefaultSelected == true).Select(x=>{x.IsDefaultSelected=false});
lstCustomziation is a collection.

LINQ is for querying. You should use a foreach loop to make changes:
foreach (var item in custHead.lstCustomziation.Where(x => x.IsDefaultSelected))
{
item.IsDefaultSelected = false;
}
That said, if IsDefaultSelected is false for the other items anyway, it may be simpler just to unconditionally set it:
foreach (var item in custHead.lstCustomziation)
{
item.IsDefaultSelected = false;
}

Linq is for querying, not updating. You can get a list of the items you want to change and then update using a normal loop:
var list = custHead.lstCustomziation.Where(x => x.IsDefaultSelected == true)
foreach(var item in list)
item.IsDefaultSelected=false;

As the Q of LINQ says, LINQ is designed for queries, not updates.
Just enumerate the LINQ result and apply your update.

Linq may have been initially created for querying but it has evolved and is used as functional programming methods, equivalents to "map", "reduce", and "filter" used in other languages.
In your example I would suggest:
var list = custHead.lstCustomziation.Where(x => x.IsDefaultSelected == true)
.Select(x=> TransformItem(x));
private XType TransformItem(XType item){
item.IsDefaultSelected=false;
return item;
}

Related

How to set properties using Linq statement

Instead of doing this horrible loop which does achieve the desired result :
foreach (var mealsViewModel in mealsListCollection)
{
foreach (var VARIABLE in mealsViewModel.Items)
{
foreach (var d in VARIABLE.ArticlesAvailable)
{
d.ArticleQty = 0;
}
}
}
I'm trying to achieve the same result but with this linQ statement :
mealsListCollection.ForEach(u =>
u.Items.Select(o => o.ArticlesAvailable.Select(c =>
{
c.ArticleQty = 0;
return c;
})));
But the linQ statement does not reset ArticleQty to zero
What I am doing wrong? and why ?
Change your linq to ForEach cause Select does not iterate through collection in the way you want.
MSDN definition:-
Select Projects each element of a sequence into a new form.
ForEach Performs the specified action on each element of the List.
mealsListCollection.ForEach(u =>
u.Items.ForEach(o =>
o.ArticlesAvailable.ForEach(c =>
{
c.ArticleQty = 0;
})));
Use SelectMany to work through trees of nested lists. Use the ForEach function last to do the work:
mealsListCollection
.SelectMany(m => m.Items)
.SelectMany(i => i.ArticlesAvailable)
.ToList()
.ForEach(a => { a.ArticleQty = 0; });
What you are doing wrong is: select is returning your same collection, but has no effect until the objects are iterated over. Sitting in the foreach call, the selects are outside of the execution path. (Review comments for more information).
.select() in a call by itself does nothing special but determine what the returned list will look like.
.select().ToList() iterates over the collection, applying the projection.
If you were to set a variable equal to the .select call, but never access the data inside it, then the values would essentially still be what they started as. As soon as you iterate over, or select a specific element, it would then apply the projections.
Changing the selects to foreachs per vasily's comments will give you the desired results.
Can I perhaps suggest that you look to set the value equal to 0 further up your stack ( or down)? - Without knowing your use case, maybe there Is a better place to default it back to 0 than where you have chosen?
(automapper, Initializer, etc )

Iterate over an IQueryable without calling ToList()

I have a DB used for a production line. It has an Orders table, and Ordertracker table, an Item table, and an Itemtracker table.
Both Orders and Items have many-to-many relationships with status. The tracker tables resolves these relationships in such a way that an item can have multiple entries in the tracker - each with a particular status.
I tried to upload a picture of the tables to make things clearer but alas, I don't have enough points yet :C
I need to find items whose last status in the Itemtracker table meets a condition, either '3' or '0'.
I then need to get the first one of these items.
The steps I am using to accomplish this are as follows:
Get all the Orders which have a certain status.
Get all the Items in that Order.
Get all the Items whose last status was = 0 or 3.
Get the first of these items.
My code is as follows:
public ITEM GetFirstItemFailedOrNotInProductionFromCurrentOrder()
{
var firstOrder = GetFirstOrderInProductionAndNotCompleted();
var items = ERPContext.ITEM.Where(i => i.OrderID == firstOrder.OrderID) as IQueryable<ITEM>;
if (CheckStatusOfItems(items) != null)
{
var nextItem = CheckStatusOfItems(items);
return nextItem ;
}
else
{
return null;
}
}
private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
{
List<ITEM> listOfItemsToProduce = new List<ITEM>();
foreach (ITEM item in items.ToList())
{
var lastStatusOfItem = ERPContext.ITEMTRACKER.Where(it => it.ItemID == item.ItemID)
.OrderByDescending(it => it.ItemTrackerID).FirstOrDefault();
if (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
{
listOfItemsToProduce.Add(item);
}
}
return listOfItemsToProduce.FirstOrDefault();
}
Now, this all works fine and returns what I need but I'm aware that this might not be the best approach. As it is now my IQueryable collection of items will never contain more than 6 items - but if it could grow larger, then calling ToList() on the IQueryable and iterating over the results in-memory would probably not be a good idea.
Is there a better way to iterate through the IQueryable items to fetch out the items that have a certain status as their latest status without calling ToList() and foreaching through the results?
Any advice would be much appreciated.
Using LINQ query syntax, you can build declaratively a single query pretty much the same way you wrote the imperative iteration. foreach translates to from, var to let and if to where:
private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
{
var query =
from item in items
let lastStatusOfItem = ERPContext.ITEMTRACKER
.Where(it => it.ItemID == item.ItemID)
.OrderByDescending(it => it.ItemTrackerID)
.FirstOrDefault()
where (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
select item;
return query.FirstOrDefault();
}
or alternatively using from instead of let and Take(1) instead of FirstOrDefault():
private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
{
var query =
from item in items
from lastStatusOfItem in ERPContext.ITEMTRACKER
.Where(it => it.ItemID == item.ItemID)
.OrderByDescending(it => it.ItemTrackerID)
.Take(1)
where (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
select item;
return query.FirstOrDefault();
}

Update all objects except one in a collection using Linq

Is there a way to do the following using Linq:
foreach (var c in collection)
{
if (c.Condition == condition)
{
c.PropertyToSet = value;
// I must also check I only set this value to one minimum and only one element.
}
else
{
c.PropertyToSet = otherValue;
}
}
To clarify, I want to iterate through each object in a collection and then update a property on each object except for one element of my collection that should updated to another value.
At this moment I use a counter to check I set my value to one and only one element of my collection. I removed it from this example to let people suggest other solutions.
The original question without exception in collection is here
EDIT
I ask this question because I'm not sure it's possible to do it with LinQ. so your answers comfort my opinion about LinQ. Thank you.
You can use .ForEach to make the change, and .Single to verify only one element matches the condition:
// make sure only one item matches the condition
var singleOne = collection.Single(c => c.Condition == condition);
singleOne.PropertyToSet = value;
// update the rest of the items
var theRest = collection.Where(c => c.Condition != condition);
theRest.ToList().ForEach(c => c.PropertyToSet = otherValue);
I don't suggest you to implement this with Linq. Why? Because Linq is for querying, not for modification. It can return you objects which match some condition, or objects which don't match. But for updating those objects you still need to use foreach or convert query results to list and use ForEach extension. Both will require enumerating sequence twice.
So, simple loop will do the job:
foreach (var c in collection)
{
c.PropertyToSet = (c.Condition == condition) ? value : otherValue;
}
collection.Where(x => <condition>).ToList().ForEach(x => <action>);
Hacky way to use LINQ if you persist to use:
var result = collection.Select(c =>
{
c.PropertyToSet = c.Condition == condition ? value : otherValue;
return c;
});
But my recommendation, don't do this, you code actually get the best approach, for more readability, you can change:
foreach (var c in collection)
c.PropertyToSet = c.Condition == condition ? value : otherValue;
You can use a ternary operator in conjunction with a linq statement:
collection.ToList().ForEach(c => c.PropertyToSet = c.Condition == condition ? value : otherValue);
However I woud just use a regular foreach here to avoid converting the collection to a list.
Well, you could do:
var itemToSetValue = collection.FirstOrDefault(c => c.Condition == condition);
if(itemToSetValue != null)
itemToSetValue.PropertyToSet = value;
// Depending on what you mean, this predicate
// might be c => c != itemToSetValue instead.
foreach (var otherItem in collection.Where(c => c.Condition != condition))
{
otherItem.PropertyToSet = otherValue;
}
Now of course that's not a pure LINQ solution, but pure LINQ solutions are not appropriate for modifying existing collections.

LINQ, Unable to create a constant value of type XXX. Only primitive types or enumeration types are supported in this context

In my application I have Lecturers and they have list of Courses they can teach and when I'm deleting a course I want to remove connection to lecturers. Here's the code:
public void RemoveCourse(int courseId)
{
using (var db = new AcademicTimetableDbContext())
{
var courseFromDb = db.Courses.Find(courseId);
var toRemove = db.Lecturers
.Where(l => l.Courses.Contains(courseFromDb)).ToList();
foreach (var lecturer in toRemove)
{
lecturer.Courses.Remove(courseFromDb);
}
db.SaveChanges();
}
}
but it doesn't work. I get
NotSupportedException: Unable to create a constant value of type Course. Only primitive types or enumeration types are supported in this context.
What am I doing wrong?
You can't use Contains with non-primitive values. Do
Where(l => l.Courses.Select(c => c.CourseId).Contains(courseId)
(or the Id field you use).
If you are using a DbContext, you can query the .Local collection, and the == operator will work also with objects:
public void RemoveCourse(int courseId)
{
using (var db = new AcademicTimetableDbContext())
{
var courseFromDb = db.Courses.Find(courseId);
db.Lecturers.Load() //this is optional, it may take some time in the first load
//Add .Local to this line
var toRemove = db.Lecturers.Local
.Where(l => l.Courses.Contains(courseFromDb)).ToList();
foreach (var lecturer in toRemove)
{
lecturer.Courses.Remove(courseFromDb);
}
db.SaveChanges();
}
}
The .Local is an ObservableCollection, so you can compare anything you like inside it (not limited to SQL queries which don't support object comparison). Just to make sure you get all your objects in the .Local collection you can call the db.Lecturers.Load() method before calling .Local, which brings all database entries into the Local collection.
The Courses collection of below line should be null or empty.
var toRemove = db.Lecturers
.Where(l => l.Courses.Contains(courseFromDb)).ToList();
This can also happen when you pass a Func<T, bool> to Where() as a way to write a dynamic condition like here here
For some reason the delegate can't be translated to SQL.
You cannot compare complex type, if you have not specified what you mean for equality.
As exception detail says, you need to check primitive values (like Integer in your case).
And better to use Any() method instead.
var toRemove = db.Lecturers
.Where(l => l.Courses.Any(p=>p.Id == courseFromDb.Id)).ToList();

Selecting ListBox values against a List

I have a list:
var _books = new List<int> {233,5,20};
And a ListBox with a lot of books (their value is an item id)
How do I take this working code:
var t = from ListItem n in lbBooks.Items
where _books.Contains(int.Parse(n.Value))
select n;
foreach(ListItem i in t)
{
i.Selected = true;
}
and convert it to lambda:
lbBooks.Items.Cast<ListItem>()
.Where(n => _books.Contains(int.Parse(n.Value)))
.Select(n => n.Selected = true);
The best option? Don't. LINQ queries are supposed to be just that – queries. And queries aren't supposed to have side effects. Your approach using foreach is perfectly fine.
If you really wanted, you could create your own extension method ForEach(), similar to the one on List<T>, but I don't think that's a good idea.
Your not far off, you need to return the whole object and set Selected to true. Something like:
lbBooks.Items.Cast<ListItem>()
.Where(n => _books.Contains(int.Parse(n.Value)))
.Select(n => SetSelected(n));
The above .Select can be shortend to .Select(SetSelected); if you prefer
private ListItem SetSelected(ListItem listItem)
{
listItem.Selected = true;
return listItem
}
Additionally to save casting you could utilise the SetSelected to handle your casting once you have just the records you want. Your query would then become:
lbBooks.Items.Where(n => _books.Contains(int.Parse(n.Value)))
.Select(n => SetSelected(n));
private ListItem SetSelected(Item item)
{
ListItem result = item as ListItem;
result.Selected = true;
return result;
}
There is no build in ForEach extension method. You can create your own, but I do not see any problem with the code you have.
Cast to .ToList() and use the `ForEach' operator as so:
lbBooks.Items.Cast<ListItem>()
.Where(n => _books.Contains(int.Parse(n.Value)))
.Select(n => n).ToList().ForEach(n=> n.Selected=true);

Categories

Resources