Linq to update a collection with values from another collection? - c#

I have IQueryable<someClass> baseList
and List<someOtherClass> someData
What I want to do is update attributes in some items in baseList.
For every item in someData, I want to find the corresponding item in baselist and update a property of the item.
someOtherClass.someCode == baseList.myCode
can I do some type of join with Linq and set baseList.someData += someOtherClass.DataIWantToConcantenate.
I could probably do this by iteration, but is there a fancy Linq way I can do this in just a couple lines of code?
Thanks for any tips,
~ck in San Diego

To pair elements in the two lists you can use a LINQ join:
var pairs = from d in someData
join b in baseList.AsEnumerable()
on d.someCode equals b.myCode
select new { b, d };
This will give you an enumeration of each item in someData paired with its counterpart in baseList. From there, you can concatenate in a loop:
foreach(var pair in pairs)
pair.b.SomeData += pair.d.DataIWantToConcantenate;
If you really meant set concatenation rather than +=, take a look at LINQ's Union, Intersect or Except methods.

LINQ is for querying - not for updating. That means it'll be fine to use LINQ to find the corresponding item, but for the modification you should be using iteration.
Admittedly you might want to perform some appropriate query to get baseList into an efficient form first - e.g. a Dictionary<string, SomeClass> based on the property you'll be using to find the corresponding item.

You can convert the IQueryable<SomeClass> into a List<SomeClass>, use the ForEach method to loop over it and update the elements, then convert back to IQueryable:
List<SomeClass> convertedList = baseList.ToList();
convertedList.ForEach(sc =>
{
SomeOtherClass oc = someData.First(obj => obj.SomeCode == sc.MyCode);
if (oc != null)
{
sc.SomeData += oc.DataIWantToConcatenate;
}
});
baseList = convertedList.AsQueryable(); // back to IQueryable
But it may be more efficient during this using non-LINQ constructs.

As mentioned before, it should be a combination of loop and LINQ
foreach (var someDataItem in someData)
{
someDataItem.PropertyToUpdate = (baseList.FirstOrDefault(baseListItem => baseListItem .key == someDataItem.key) ?? new SomeClass(){OtherProperty = "OptionalDefaultValue"}).OtherProperty;
}

You can't simply find objects that are in one list but not the other, because they are two different types. I'll assume you're comparing a property called OtherProperty that is common to the two different classes, and shares the same type. In that case, using nothing but Linq queries:
// update those items that match by creating a new item with an
// updated property
var updated =
from d in data
join b in baseList on d.OtherProperty equals b.OtherProperty
select new MyType()
{
PropertyToUpdate = d.PropertyToUpdate,
OtherProperty = d.OtherProperty
};
// and now add to that all the items in baseList that weren't found in data
var result =
(from b in baseList
where !updated.Select(x => x.OtherProperty).Contains(b.OtherProperty)
select b).Concat(updated);

Related

Get values from Grouping result except values from list

it looks fairly simple but I can not get my mind over it.
I have a list:
List<IGrouping<byte, MyClass>>
MyClass object has a timestamp property, and I also have list of timestamp, now I want to know if there is elegant way to get all values from grouping list where Timestamp property is in my timestamp list?
I have solved the problem, but i think it can be solved in more efficient way. code would look like:
var loadedValues = new List<MyClass>();
foreach (IGrouping<byte, MyClass> value in Values)
{
loadedValues.AddRange(value.Select(c => c).Where(c => Timestamps.Any(point => c.Timestamp == point)));
}
You could use Join which uses a set based approach, hence it's more efficient than Where:
IEnumerable<MyClass> query =
from grp in Values
from cls in grp
join ts in Timestamps on cls.Timestamp equals ts
select cls;
List<MyClass> loadedValues = query.ToList();

Select inside a select query (lists)

I have this code:
HashSet<string> MyHash = new HashSet<String>();
foreach (MyType a in myCollection)
{
foreach (string b in a.mylist)
{
MyHash.Add(b);
}
}
I tried to make it easier to read like this but it doesn't work:
myCollection.MyType.select(x => x.mylist.select(y => MyHash.add(y)));
Any suggestions?
Select will return a collection of items instead of modifying it. Thus you have to assign its return-value into a variable or member or pass it into a method. Furthermore you´d need to flatten the results to add the members of your inner list into the hashset.
Thus when you want to add the result into your list use HashSet.UnionWith:
myHash.UnionWith(myCollection.SelectMany(x => x.MyList));
Alternativly you can also use the constructor of HashSet that accepts a collection of items:
var myHash = new HashSet<string>(...);
However IMHO this isn´t any more readable than using some foreach-based approach.
Here's how I would do it, separating query definition and state modification.
IEnumerable<string> items = myCollection.SelectMany(a => a.myList);
foreach(string b in items)
{
MyHash.Add(b);
}
If you want to one-line it for some reason:
myCollection.SelectMany(a => a.myList).ToList().ForEach(b => MyHash.Add(b));

Finding the list of common objects between two lists

I have list of objects of a class for example:
class MyClass
{
string id,
string name,
string lastname
}
so for example: List<MyClass> myClassList;
and also I have list of string of some ids, so for example:
List<string> myIdList;
Now I am looking for a way to have a method that accept these two as paramets and returns me a List<MyClass> of the objects that their id is the same as what we have in myIdList.
NOTE: Always the bigger list is myClassList and always myIdList is a smaller subset of that.
How can we find this intersection?
So you're looking to find all the elements in myClassList where myIdList contains the ID? That suggests:
var query = myClassList.Where(c => myIdList.Contains(c.id));
Note that if you could use a HashSet<string> instead of a List<string>, each Contains test will potentially be more efficient - certainly if your list of IDs grows large. (If the list of IDs is tiny, there may well be very little difference at all.)
It's important to consider the difference between a join and the above approach in the face of duplicate elements in either myClassList or myIdList. A join will yield every matching pair - the above will yield either 0 or 1 element per item in myClassList.
Which of those you want is up to you.
EDIT: If you're talking to a database, it would be best if you didn't use a List<T> for the entities in the first place - unless you need them for something else, it would be much more sensible to do the query in the database than fetching all the data and then performing the query locally.
That isn't strictly an intersection (unless the ids are unique), but you can simply use Contains, i.e.
var sublist = myClassList.Where(x => myIdList.Contains(x.id));
You will, however, get significantly better performance if you create a HashSet<T> first:
var hash = new HashSet<string>(myIdList);
var sublist = myClassList.Where(x => hash.Contains(x.id));
You can use a join between the two lists:
return myClassList.Join(
myIdList,
item => item.Id,
id => id,
(item, id) => item)
.ToList();
It is kind of intersection between two list so read it like i want something from one list that is present in second list. Here ToList() part executing the query simultaneouly.
var lst = myClassList.Where(x => myIdList.Contains(x.id)).ToList();
you have to use below mentioned code
var samedata=myClassList.where(p=>p.myIdList.Any(q=>q==p.id))
myClassList.Where(x => myIdList.Contains(x.id));
Try
List<MyClass> GetMatchingObjects(List<MyClass> classList, List<string> idList)
{
return classList.Where(myClass => idList.Any(x => myClass.id == x)).ToList();
}
var q = myClassList.Where(x => myIdList.Contains(x.id));

why ToList() doesnt work in linq?

i am having a issue while sorting data with more than a field
here is sample code i have used :
var Item = from itm in DB.Items
select new Item
{
};
return Item.ToList().OrderBy(e => e.ExpiryDate).ToList();
above code does'nt show any error but also doesn sort with expirydate field :(
if i use like this
return Item.OrderBy(e => e.ExpiryDate).ToList();
this gives a error that sql doesnt contain translation for orderby
any help
thanks in advance
I think there are a couple things going on here. First, you've named your result variable the same as the class you're creating in the select. Probably doesn't make much difference, but it's confusing. Let's change it:
var items = from itm in DB.Items
select new Item
{
};
Next, your DB.Items context is already built of Item objects. You don't need to create new ones. In the above code, your new Item objects are empty anyway.
var items = from itm in DB.Items
select itm;
If you want to order them right away, you can do that too:
var items = from itm in DB.Items
orderby itm.ExpiryDate
select itm;
If you need that as a List, you can do that in the same line:
var items = (from itm in DB.Items
orderby itm.ExpiryDate
select itm).ToList();
Now items will be a concrete List of Item objects (List<Item>).
Your code creates instances of Item using the default constructor, without passing anything that you get from the DB to it. That is why you get a list of identical empty items; sorting them has no effect.
Remove the first Select to make it work:
var Item = DB.Items; // No "Select ..."
return Item.ToList().OrderBy(e => e.ExpiryDate).ToList();
Your first ToList forces the data into memory, where OrderBy can be applied. You can replace it with an AsEnumerable() call as well, to avoid creating a list in memory for the second time:
return Item.AsEnumerable().OrderBy(e => e.ExpiryDate).ToList();

Adding IEnumerable<Type> to IList<Type> where IList doesn't contain primary key - LAMBDA

I have an IList<Price> SelectedPrices. I also have an IEnumerable<Price> that gets retrieved at a later date. I would like to add everything from the latter to the former where the former does NOT contain the primary key defined in the latter. So for instance:
IList<Price> contains Price.ID = 1, Price.ID = 2, and IEnumerable<Price> contains Price.ID = 2, Price.ID = 3, and Price.ID = 4. What's the easiest way to use a lambda to add those items so that I end up with the IList containing 4 unique Prices? I know I have to call ToList() on the IList to get access to the AddRange() method so that I can add multiple items at once, but how do I select only the items that DON'T exist in that list from the enumerable?
I know I have to call ToList() on the IList to get access to the AddRange() method
This is actually not safe. This will create a new List<T>, so you won't add the items to your original IList<T>. You'll need to add them one at a time.
The simplest option is just to loop and use a contains:
var itemsToAdd = enumerablePrices.Where(p => !SelectedPrices.Any(sel => sel.ID == p.ID));
foreach(var item in itemsToAdd)
{
SelectedPrices.Add(item);
}
However, this is going to be quadratic in nature, so if the collections are very large, it may be slow. Depending on how large the collections are, it might actually be better to build a set of the IDs in advance:
var existing = new HashSet<int>(SelectedPrices.Select(p => p.ID));
var itemsToAdd = enumerablePrices.Where(p => !existing.Contains(p.ID));
foreach(var item in itemsToAdd)
{
SelectedPrices.Add(item);
}
This will prevent the routine from going quadratic if your collection (SelectedPrices) is large.
You can try that:
var newPrices = prices.Where(p => !SelectedPrices.Any(sp => sp.ID == p.ID));
foreach(var p in newPrices)
SelectedPrices.Add(p);
I know I have to call ToList() on the IList to get access to the AddRange() method so that I can add multiple items at once
ToList will create a new instance of List<Price>, so you will be modifying another list, not the original one... No, you need to add the items one by one.
Try yourEnumerable.Where(x => !yourList.Any(y => y.ID == x.ID)) for the selection part of your question.
If you want to add new elements to the existing list and do that in a most performant way you should probably do it in a conventional way. Like this:
IList<Price> selectedPrices = ...;
IEnumerable<Price> additionalPrices = ...;
IDictionary<int, Price> pricesById = new Dictionary<int, Price>();
foreach (var price in selectedPrices)
{
pricesById.Add(price.Id, price);
}
foreach (var price in additionalPrices)
{
if (!pricesById.ContainsKey(price.Id))
{
selectedPrices.Add(price);
}
}

Categories