简体   繁体   中英

Using LINQ in C# to find and update values in a list of lists

I have three classes listed below:

class OuterObjects
{
    string name;
    List<InnerObjects> myList;
}

class InnerObjects
{
    float small;
    float big;
}

class CorrectValues
{
    string name;
    float correctValue;
}

So my code runs through everything and gets to a point where I have all the OuterObjects and InnerObjects mapped and then seperately I load up CorrectValues with a database call.

So I have:

List<OuterObjects> theList = [loaded up with many names and InnerObjects]

So I need to write something that will go through the OuterObjects and into the InnerObjects and find all small > big and fix them. The (float) big will be replaced by the correctValue where the name in CorrectValues and OuterObjects match. Name is can also appear more than once in each of it's objects, but the correctValue will always match the same name (ie if there are 2 names of "Bob" in CorrectValues then the correctValue will be the same).

I have something like:

outerObjects.Select(q => q.InnerObjects).Where(q => (q.small > q.big) ... then update the big with the correctValue

Is there a good, elegant way to do this? I don't want to use forEach statements either.

I understand you don't want to use forEach loops for this, but in this case, it might be appropriate.

For example, you could do the following:

public void UpdateTheList(List<OuterObjects> theList) {
    theList.ForEach(x => {
       x.name = "John";
       x.myList.ForEach(x => {
       x.big = updateBigDbCall();
       x.small = updateSmallDbCall();
       });
    });
}

where you are updating big and small in separate database calls and updating all names to "John".

The other way this could be done is using a nested select statement:

theList = theList.Select(e => {
   e.name = "John";
   e.myList.Select(x => {
      x.big = updateBigDbCall();
      x.small = updateSmallDbCall();
      return x;
    }).ToList();
    return e;
}).ToList();

To be honest I prefer to break these operations down into separate short methods doing one thing each.

So getting the list of objects to update could be a method ie selecting out of your collection only those items that you need to fix.

The second method would iterate the collection and make any updates.

The third method would be the work required to do the update.

You could wrap these three methods in an upper method UpdateList for example and it would be clear for any other developer to understand the purpose of the code.

If you're going to be modifying objects within a Linq statement (such as within a Select() that the blog you link to shows), you're going to have a bad time.

Select() is to project objects, which means that you're going to map them into new objects.

var customerNames = allCustomers.Select(c => c.Firstname + " " + c.Lastname).ToList();

If you're going to abuse that method to modify the objects that pass by it, you're not doing pure, functional, set-oriented programming anymore, and that's not what Linq is meant for. This will also modify the items in the source collection, which usually is not what you want, and is going to cause headaches later on.

Where() is to filter objects from a collection. You're going to reduce them to a new collection, like so:

var activeCustomers = allCustomers.Where(c => c.IsActive).ToList();

A rough, idiomatic way to do what you want using foreach() would look like this:

foreach (var outerObject in theList)
{
    var correction = correctValues.FirstOrDefault(c => c.Name == outerObject.Name);
    if (correction == null)
    {
        // We can't fix this outerObject
        continue;
    }
    
    // Look, Linq!
    foreach (var innerObject in outerObject.InnerObjects.Where(o => o.Small > o.Big))
    {
        // Here you're assuming that the correction will have the correct value
        innerObject.Big = correction.CorrectValue;
    }
}

Now sure, you could translate this to something with a .Where() and a .Join() , but that won't make the code any more readable or performant, and you will still have to do object manipulation to modify each outerObject.InnerObject where Small > Big , and you don't want to do that within a Select() , so you're going to have to call .Where(...).ToList().ForEach(/* do the update */) anyway, and the code within that ForEach() action will more or less be exactly the same as in the inner foreach() in my code above.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM