Update one object in collection using LINQ - c#

I have object and collection in this object.
myObject.myCollection.Where(a => a.Id == Id).FirstOrDefault() = newMyCollection;
Unfortunately this didn't works
If I change single element, then work. For example:
myObject.myCollection.Where(a => a.Id == Id).FirstOrDefault().Id = newMyCollection.Id;
How to update all of object? Thanks for any help
I can do something like this:
myObject.myCollection.Remove(objectOfMyCollection);
myObject.MyCollection.Add(myNewCollection);
But: If my objectOfMyCollection is firstly, then my new object be last.

Try this
var customListItem2 = myObject.myCollection.Where(a => a.Id == Id).FirstOrDefault();
var index = myObject.myCollection.IndexOf(customListItem2);
if(index != -1)
myObject.myCollection[index] = newCustomListItem;

You can just query for a list and iterate over that:
foreach (var obj in myObject.myCollection.Where(a => a.Id == Id).ToList()) {
obj.Id = newMyCollection.Id;
}

To change only first index you can do something like this:
myObject.myCollection.Where(a => a.Id == Id).FirstOrDefault(a => { a.Id = newMyCollection.Id; return true; });
Which in plain English does :
- From myCollection get items Where item's Id is equal to Id
- From selected values get FirstItem and set it's Id to the new one
- Do not check the rest
Or even make it simplier :
myObject.myCollection.FirstOrDefault(a => { if(a.Id == Id) { a.Id = newMyCollection.Id; return true; } return false; });

You would have to replace the underlying references in your list which isn't possible with LINQ (which is designed for querying, not updating objects).
To do this with a list use an old-school for-loop:
for(int i = 0; i < myList.Count; i++)
{
if(myList[i].Id == Id)
{
myList[i] = newElement;
break;
}
}
Using a for- instead of a foreach is necessary here because you re-reference the instances in your list which is not possible using the latter.
Another approach was to create a temporary copy of your list, get the matching element there and it´s index in the list and replace the element at that index in the original list:
var tmp = myList;
foreach(var elem in tmp)
{
if(elem.Id == Id)
{
var index = myList.IndexOf(x => x.Id == Id);
myList[index] = newElement;
break;
}
}

If you want to replicate SQL UPDATE with JOIN and update one or more objects, you can use LINQ as below, this time using CollectionBase-derived objects. But you can use List<T>, etc
(from Type1 o1 in collection1
join Type2 o2 in collection2 on o1.Id equals o2.RelatedId
select new { O1 = o1, O2 = o2 }).
Count(item => (item.O1.SomeProperty = item.O2.SomeOtherProperty) == null);
A little hacky but it acts similar to SQL and does not mutate collections. YeA, it creates light objects that only hold reference to original collection items.

Related

C# using aggregate method on nopcommerce shipment items list not working

Ok so I'm trying to get all items which have been shipped and add them to a list if the shipment item is not already in that list. But if the shipment item is found in the list then I want to combine those two items in the list.
Here is the code I'm working with:
var shippedItems = _orderService.GetOrderById(shipment.OrderId).Shipments.Where(x => x.ShippedDateUtc != null && x.OrderId == shipment.OrderId && x.Id != shipment.Id).ToList();
List<ShipmentItem> shipmentItemsList = new List<ShipmentItem>();
for (int i = 0; i <= shippedItems.Count - 1; i++)
{
var si = shippedItems[i];
var sii = si.ShipmentItems.ToList();
foreach (var item in sii)
{
if (!shipmentItemsList.Contains(item))
{
shipmentItemsList.Add(item);
}
else
{
var foundId = shipmentItemsList.Select(x => x.Id == item.Id);
shipmentItemsList.Aggregate((foundId, item) => foundId + item);
}
}
}
For these two variables (foundId, item) i get errors:
A local variable named the variable name cannot be declared in this
scope because that name is used in an enclosing local scope to define
a local or parameter
UPDATE
I also thought I could try the following, but it's not joining the results.
if (i == 0)
{
shipmentItemsList = si.ShipmentItems.ToList();
}
else
{
shipmentItemsList.Concat(si.ShipmentItems.ToList());
}
Anyone able to point me on the right track.
Cheers
Thanks for the clarification. Essentially, the way that I understand your problem is that you need to take an object map that is grouped by Shipment and look at it from the point of Item instead. Linq can deal with this for you by using SelectMany to flatten the list and the GroupBy to shape the flattened list into your new groupings. I've made some assumptions about property names for the nopCommerce objects, but the following code sample should get you close enough to tweak with the correct property names:
var shipmentItemsList = shippedItems // This is logically grouped by shipment id
.SelectMany(s => s.ShipmentItems) // First flatten the list
.GroupBy(i => i.ItemId) // Now group it by item id
.Select(g => new
{
ItemId = g.Key,
Quantity = g.Sum(item => item.Quantity)
}) // now get the quantity for each group
.ToList();

Select a sub List<T> with filtering condition List<int> using Linq

I am struggling with producing a sublist of objects with Linq.
var assignedObject = new List<int>();
foreach (var obj in myObjects) {
if (IsAssigned(obj)) {
assignedObject.add(obj.Id);
}
}
What I want to do now is remove assigned objects from myObjects with Linq .Where(Func<T,bool> predicate).
myObjects = myObject.Where(item => item.Id != assignedObj.All()).ToList();
item.Id != assignedObj.All() is the problem part.
If it is achievable with Linq please educate me.
Otherwise, I am going to implement a loop.
var remainingObjects = new List<MyClass>();
foreach(var obj in myObjects) {
if (!(assignedObjects.IndexOf(obj.Id) > -1)) {
remainingObjects.Add(obj);
}
}
PS: In case you're wondering List<MyClass> myObjects = GetMyObjects(params); which retrieved from somewhere else.
var toRemove = myObjects.Where(item=>assignedObj.Any(a=>a.Id == item.Id)).ToArray()
Does that work?
You can rewrite your All() with a condition, like this:
myObjects = myObject
.Where(item => assignedObj.All(assigned => item.Id != assigned.Id))
.ToList();
However, it is easier to use Contains:
var assignedIds = new HashSet<int>(assignedObject);
myObjects = myObject.Where(item => assignedIds.Contains(item.Id)).ToList();
Using HashSet<int> speeds up the lookup in situations when assignedObject is relatively large - say, hundreds of objects or more.
It can be like this: var list = from item in myObjects where IsAssigned(item) select item
Try this
var assignedObjects = myObjects.Where(m => IsAssigned(m)).ToList()
var remainedObjects = myObjects.Where(m => !IsAssigned(m)).ToList()

LINQ Lambda, Group by with list

I'm having some trouble with finding the right syntax to accomplish the following:
Is it possible with LINQ (Lambda Expression) to .GroupBy data and instead of using the usual .Sum() or .Count() I want the resulting data to be a List of Int.
I defined my own class named: Filter_IDs. Its constructor needs two parameters:
public int? type; // Represents the object_type column from my database
public List<int?> objects; // Represents the object_id column from my database
I want to load data from my database into this object. The following LINQ query should result in a List of Filter_IDs:
The following LINQ query should result in a List of Filter_IDs:
List<Filter_IDs> filterids = ef.filterLine
.GroupBy(fl => fl.objectType)
.Select(fl => new Filter_IDs { type = fl.Key, objects = fl.Select(x => x.object_id).ToList() })
.ToList();
Using this query gives no building error but gives an 'NotSupportedException' on RunTime.
The database looks like this to give you a better understanding of the data:
http://d.pr/i/mnhq+ (droplr image)
Thanks in advance,
Gerben
I think the problem is the DB is not able to call ToList in the select, nor to create a new Filter_ID.
Try something like this :
List<Filter_IDs> filterids = ef.filterLine.Select(o => new { objectType = o.objectType, object_id=o.object_id})
.GroupBy(fl => fl.objectType).ToList()
.Select(fl => new Filter_IDs { type = fl.Key, objects = fl.Select(x => x.object_id).ToList() })
.ToList();
Maybe you want
IList<Filter_IDs> filterIds = ef.filterline
.Select(fl => fl.objectType).Distinct()
.Select(ot => new Filter_IDs
{
type = ot,
objects = ef.filterline
.Where(fl => fl.objectType == ot)
.Select(fl =>objectType)
.ToList()
}).ToList();
Get the distinct list objectType and use that to subquery for each list of object_id.
However, it seems more efficient to me to just enumerate the values in order,
var results = new List<Filter_IDs>();
var ids = new List<int>();
var first = true;
int thisType;
foreach (var fl in ef.filterLines
.OrderBy(fl => fl.objectType)
.ThenBy(fl => fl.object_Id))
{
if (first)
{
thisType = fl.objectType;
first = false;
}
else
{
if (fl.objectType == thisType)
{
ids.Add(fl.object_Id);
}
else
{
results.Add(new Filter_IDs
{
Type = thisType,
objects = ids
});
thisType = fl.objectType;
ids = new List<int>();
}
}
}
You can use GroupBy on client side:
List<Filter_IDs> filterids = ef.filterLine
.Select(fl=>new {fl.ObjectType, fl.object_id})
.AsEnumerable()
.GroupBy(fl => fl.objectType)
.Select(fl => new Filter_IDs { type = fl.Key, objects = fl.Select(x => x.object_id).ToList() })
.ToList();

what is the most elegant way to update an item in one list from another list in C#?

I have 2 collections and i have the following code to loop through one collection and see if it exists in another collection. If it does exist, then update a property of that item.
foreach (var favorite in myFavoriteBooks)
{
var book = allBooks.Where(r => r.Name == favorite.Name).FirstOrDefault();
if (book != null)
{
book.IsFavorite = true;
}
}
Is there a more elegant or faster way to achieve this code above?
You can use Join for this, either in extension method syntax or LINQ syntax:
Extension method:
foreach(var favorite in myFavoriteBooks.Join(allBooks,
f => f.Name,
a => a.Name,
(f, a) => a))
{
a.IsFavorite = true;
}
LINQ:
var favorites = from f in myFavoriteBooks
join a in allBooks on f.Name equals a.Name
select a
foreach(var favorite in favorites)
{
favorite.IsFavorite = true;
}
These solutions are functionally identical; they differ only by syntax and they are faster than your original solution, since LINQ will build a hashtable on both sides and use that for matching rather than scanning the other list for every item in the original list.
You can more easily find the books in one collection that match another collection with Any
var booksInCommon = allBooks.Where(b => myFavoriteBooks.Any(bi => bi.Name == b.Name));
foreach(book b in booksInCommon)
b.IsFavorite = true;
Or, if you don't mind "tricky" code
allBooks.Where(b => myFavoriteBooks.Any(bi => bi.Name == b.Name)).ToList()
.ForEach(b => b.IsFavorite = true);
EDIT
As Adam Robinson points out, this is an O(N2) algorithm, so avoid it if you have thousands of books in both of your collections and opt for his Join answer.
If the books collections were stored in dictionaries with the key of Name, then this becomes an easy and very efficient operation:
var myFavoriteBooks = new System.Collections.Generic.Dictionary<string, book>();
var allBooks = new System.Collections.Generic.Dictionary<string, book>();
foreach (var bookName in myFavoriteBooks.Keys)
{
if (allBooks.ContainsKey(bookName))
{
allBooks[bookName].IsFavorite = true;
}
}
var favTitles = new HashSet<string>(favorite.Select(f => f.Name));
//favTitles now a hash-based O(1) name lookup.
foreach(var book in allBooks.Where(b => favTitles.Contains(b.Name)))
book.IsFavorite = true;

How much can this be simplified to a select statement in LINQ

I am trying to gransp wether I can get big refactoring advantages out of learning LINQ.
How can LINQ improve this code, which is a real-world example that is representative for a lot of code in a project I work on:
foreach (SchemeElement elem in mainDiagram.Elements)
{
if (elem.SubType == EElemSubType.BusBar)
{
if (connPts.Busbars.ContainsKey(elem.ConnectionPointId))
{
if (!taAddrList.TAAddressList.ContainsKey(elem.Key))
{
taAddrList.TAAddressList.Add(elem.Key, new TAAddress());
}
taAddrList.TAAddressList[elem.Key] = connPts.Busbars[elem.ConnectionPointId];
}
} // if busbar
} // foreach element
For Clarity:
taAddrList.TAAddressList is of type Dictionary<ElemKey, TAAddress>
where ElemKey is a two-component type that consists of two int ID's.
connPts.Busbars is of type Dictionary<int, TAAddress>
See for yourself:
var query = from element in mainDiagram.Elements
where element.SubType == EElemSubType.BusBar
where connPts.Busbars.ContainsKey(element.ConnectionPointId)
select element;
foreach (var element in query)
{
// by accessing immidiatly in a dictionary (assuming you are using one), you can either insert or update
taAddrList.TAAddressList[element.Key] = connPts.Bushbars[elem.ConnectionPointId];
}
Well this depends, its certainly alot easier to write that sort of stuff in LINQ, but the depends part is on whether TAddressList is just a Dictionary... if it were you can get that dictionary easily:
var dictionary = mainDiagram.Elements.Where(e => e.SubType == EElemSubType.BusBar)
.ToDictionary(k => k.Key, e => connPts.BusBars[e.ConnectionPointId])
If you have to add to TAddressList in exactly the manner you gave in your example, you simply need to ForEach over the list
mainDiagram.Elements.Where(e => e.SubType == EElemSubType.BusBar && !taAddrList.TAAddressList.Contains(e.Key))
.ToList()
.ForEach(e => taAddrList.TAAddressList.Add(elem.Key, connPts.BusBars[e.ConnectionPointId]));
You can use linq for selecting a list of SchemeElement:
var query = from elem in mainDiagram.Elements
where elem.SubType == EElemSubType.BusBar
&& connPts.Busbars.ContainsKey(elem.ConnectionPointId)
select elem;
foreach (SchemeElement elem in query){
if (!taAddrList.TAAddressList.ContainsKey(elem.Key))
{
taAddrList.TAAddressList.Add(elem.Key, new TAAddress());
}
taAddrList.TAAddressList[elem.Key] = connPts.Busbars[elem.ConnectionPointId];
}
var elements =
mainDiagram.Elements
.Where(e => e.SubType == EElemSubType.BusBar &&
connPts.Busbars.ContainsKey(e.ConnectionPointId))
foreach (var elem in elements)
{
if (!taAddrList.TAAddressList.ContainsKey(elem.Key))
{
taAddrList.TAAddressList.Add(elem.Key, new TAAddress());
}
taAddrList.TAAddressList[elem.Key] = connPts.Busbars[elem.ConnectionPointId];
}
var items = mainDiagram.Elements
.Where(el => el.SubType == EElemSubType.BusBar
&& connPts.Busbars.ContainsKey(el.ConnectionPointId));
items.ForEach(item =>
{
if (!taAddrList.TAAddressList.ContainsKey(item.Key))
{
taAddrList.TAAddressList.Add(item.Key, new TAAddress());
}
});
foreach (SchemeElement elem in mainDiagram.Elements.Where(r => r.SubType == EElemSubType.BusBar
&& connPts.Busbars.ContainsKey(r.ConnectionPointId)))
{
if (!taAddrList.TAAddressList.ContainsKey(elem.Key))
{
taAddrList.TAAddressList.Add(elem.Key, new TAAddress());
}
taAddrList.TAAddressList[elem.Key] = connPts.Busbars[elem.ConnectionPointId];
} // foreach element
Code bellow is not tested, I assumed finally addresses want to go a specific Dictionary of Address objects, and address class contains two property, Key and value:
addressDic = mainDiagram.Elements.Where(x=>x.SubType == EElemSubType.BusBar)
.Where(x=>connPts.Busbars.ContainsKey(x.ConnectionPointId))
.GroupBy(x=>x.Key)
.Select(x=>new {Key = x.Key,
Value = connPts.Busbars[x.Last().ConnectionPointId]})
.ToDictionary(x=>x.Key);
but as you can see, It's not very readable in linq, but depend on your power in linq, may be it's simpler than for loop.

Categories

Resources