rwkiii
rwkiii

Reputation: 5846

How to change values while looping through an IEnumerable

I keep getting stuck on this issue and have done a lot of searching/reading to try to understand the problem, but I just don't get it. Am wondering if someone can provide me with an example of how to update object values while looping an IEnumerable<>.

Example:

public class MyClass
{
    private IEnumerable<MyData> _MyData = null;

    public MyClass() {}
    public MyClass(string parm1, int parm2) { Load(parm1, parm2); }

    public IEnumerable<MyData> Load(string parm1, int parm2)
    {
        _MyData = _dbContext.MyData.Where(m => m.Parm1 == parm1 && m.Parm2 == parm2);

        foreach(MyData mydata in _MyData)
        {
            if(mydata.Parm2 == 1)
            {
                mydata.Parm1 = "Some value";
            }
        }
        return _MyData;
    }
}

The above code doesn't work as any changes made to mydata.Parm1 within the loop are not retained. I read an SO article that suggested to make changes like this:

for (int i = 0; i < _MyData.Count(); i++)
{
    _MyData.Skip(i).FirstOrDefault().Parm1 = "Some Value";
}

This works, but it's really ugly and hard for me to believe there isn't a cleaner way. For a single change it might be acceptable, but I have several changes that need to be made. So I've attempted this:

for (int i = 0; i < _MyData.Count(); i++)
{
    MyData md = _MyData.ElementAt(i);
    md.Parm1 = "Some Value";
}

But I don't understand how to put the modified md back into _MyData to save changes.

I think the main reason I'm going through all of these hoops is that I don't understand how to make changes to the elements and have them retained. Any changes I make seem to revert back to their original values unless I go through these hoops. Maybe someone can point out where I'm going wrong. I would like to use this class like this:

IEnumerable<MyData> MyDataResult = new MyClass("Parm1", 4);

Using the above line of code, I can trace through the Load() function and see that the desired changes are being made, but once finished MyDataResult contains the original values instead of those I saw being changed.

It is more then obvious that my code is wrong, but I hope I've been clear enough to illustrate what I'm wanting to do and where I'm having difficulty.

Upvotes: 1

Views: 3013

Answers (2)

iandayman
iandayman

Reputation: 4467

Not sure why you are returning a value which never gets used. You just need to set _MyData in your Load method as it is a class level variable. Change Load to not return a value and ToList() your query on the db context and then set your _MyData to your concrete list object in Load.

Something like this will work - I've simplified your example slightly but same principals:

public class MyClass
{
    public IEnumerable<string> _MyData = null;
    public IEnumerable<string> _dbContext = new List<string> {"a", "b", "aa", "bb"};
    public MyClass(){}
    public MyClass(string parm1, int parm2){Load(parm1, parm2);}

    public void Load(string parm1, int parm2)
    {
        var somedata = _dbContext.Where(m => m.Length == 2).ToList();

        var myData = somedata.Select(s => s == parm1 ? "something else" : s).ToList();

        _MyData = myData;
    }
}

Use can use

var a = new MyClass("aa",1)._MyData;

and "aa" will get replaced with "something else" and be returned to "a".

You can't use a class like this:

IEnumerable<MyData> MyDataResult = new MyClass("Parm1", 4);

If you ask for an MyClass you'll get a MyClass not an IEnumerable.

If your call to dbContext is to an underlying data source you may be better off not doing that in the class constructor and always call Load explicitly following class construction. You then have options to avoid thread blocking on a potentially long running I/O operation.

Upvotes: 1

Vikas Gupta
Vikas Gupta

Reputation: 4465

Try this -

_MyData = _dbContext.MyData.Where(m => m.Parm1 == parm1 && m.Parm2 == parm2).ToList();

To be clear, essentially what you have in your current code is a query, which you are enumerating in your Load method, trying to update some of its properties, but then in the end, you end up returning the query, and not the updated values in the query.

The caller of Load method, will end up running the query a second time, only to see direct values from the database.

Adding .ToList to the end of the query in Load method, materializes the query, and then you are doing rest of the operations in memory.

EDIT: Just in case, if there would be performance concern over the large amount of data this query may return, then you could also consider this alternate approach -

public IEnumerable<MyData> Load(string parm1, int parm2)
{
    _MyData = _dbContext.MyData.Where(m => m.Parm1 == parm1 && m.Parm2 == parm2);

    foreach(MyData mydata in _MyData)
    {
        if(mydata.Parm2 == 1)
        {
            mydata.Parm1 = "Some value";
        }

        yield return myData;
    }
}

This would keep the query lazy, and still return the modified values, as caller of Load iterates over the IEnumerable<MyData> returned by Load.

Upvotes: 3

Related Questions