1

My foreach loop is not iterating over the entire collection, just the first element and I can't figure out why. The only similar question I found was this one: IEnumerable not enumerating in foreach

But the only answer was posted by the question author and he talks about IEnumerable having a timeout which doesn't really make sense to me and I've never heard of before.

IEnumerable<Document> documentsToAdd = dbEvent.Documents.Where(
    dbd => !eventToSave.Documents.Select(d => d.DocumentId)
    .Contains(dbd.DocumentId));
foreach (Document documentToAdd in documentsToAdd) {
    documentToAdd.DocumentType = null;
    documentToAdd.DeletedByUser = null;
    documentToAdd.DocumentOwnerTeam = null;
    documentToAdd.UploadedByUser = null;
    documentToAdd.UploadedInStage = null;
    hcDbContext.Documents.Add(documentToAdd);
}

In the debugger I can see that documentsToAdd has 3 elements in the collection. But when I step through the loop it only goes through once and then moves on, so only the first document is saved. Since I've verified the contents of documentsToAdd, I know the problem isn't in the Where clause. Why won't foreach go through the entire collection?

EDIT

To be clear, no exception is thrown. It goes through the loop once and then moves on to the next line after the foreach.

ManoDestra
  • 6,325
  • 6
  • 26
  • 50
Legion
  • 3,922
  • 8
  • 51
  • 95
  • 2
    Are you getting an exception? Wrap code in a try clause. – jdweng Apr 08 '16 at 15:18
  • No exception. It just goes through the loop once and moves on to the next line after the foreach. – Legion Apr 08 '16 at 15:19
  • 1
    How do you **know** there is more than one item in the enumeration? – CodingGorilla Apr 08 '16 at 15:21
  • 4
    What is `eventToSave`? Just as a first punt, try adding a `ToList()` on the documentsToAdd and see if that makes a difference. – Chris Apr 08 '16 at 15:23
  • 1
    @CodingGorilla I put a breakpoint on the line `documentToAdd.DocumentType = null`. Then I did a quckwatch on `documentsToAdd` which shows a count of 3. I also went into each of the 3 elements and examined them and their properties are fully populated. – Legion Apr 08 '16 at 15:24
  • please try what Chris said, call ToList or ToArray first, store it in temporary variable, then iterate over the array/list. – Thariq Nugrohotomo Apr 08 '16 at 15:25
  • Maybe it is just me but those ( ) just don't make sense to me. What is documentsToAdd.Count(); ? – paparazzo Apr 08 '16 at 15:28
  • @Chris Yes, that works. Can you, or anyone else, explain why? I mean, what's the point of `IEnumerable` if you can't use enumeration? – Legion Apr 08 '16 at 15:29
  • when you enumerate once it will probably change something inside enumeration and thus the second time will be different than first time. – M.kazem Akhgary Apr 08 '16 at 15:30
  • 8
    It is a Heisenbug. You change what you observe, the debugger emptied the enumerable. – Hans Passant Apr 08 '16 at 15:30
  • Does adding to `hcDbContext.Documents` possible effect `eventToSave.Documents`? More importantly does the problem only occur when you Debug, or also when you just run the code? – juharr Apr 08 '16 at 15:30
  • @juharr I've never heard of an entity framework dbcontext changing a collection like that, especially on an add. – Legion Apr 08 '16 at 15:31
  • @Legion: LINQ is lazy-loading, meaning that certain methods that is uses (the Where method in particular) don't actually execute until you try to get the results. If you run the code, the first time you access the IEnumerable is in the foreach... but if you put a breakpoint right before the foreach is evaluated to check what `documentsToAdd` is in the debugger, it is evaluated at that time instead (prior to the foreach)... perhaps that's what was causing the issue? – Mage Xy Apr 08 '16 at 15:32
  • @HansPassant That would be true if the behavior only manifested itself during debugging. But I was in the debugger because not all my documents were being saved. So the behavior occurs even without the debugger. – Legion Apr 08 '16 at 15:32
  • 1
    @Legion you are wrong. thats why ToList fixed the problem. – M.kazem Akhgary Apr 08 '16 at 15:35
  • @Legion I have a vague memory of getting tripped up by doing something with EF a long time ago, has any code at all changed within the last few days (when you say this has stopped working), or has everything remained static and the problem has manifested? I suspect it's something related to mutating the objects you're adding. – Chris Apr 08 '16 at 15:38
  • @M.kazemAkhgary Can you make your comment more useful by explaining what I'm wrong about? – Legion Apr 08 '16 at 15:38
  • @Chris Things have changed, but nothing that appears related to this. I think the lesson here is IEnumerable is unreliable, convert everything to Lists. – Legion Apr 08 '16 at 15:42
  • @Legion Potentially irrelevant but it might be worth looking into EF loading strategies. If memory serves, some navigation properties, child objects won't be loaded when using Lazy loading, a ToList() may force the load (I could be wrong about this, I haven't dabbled with EF for a while). E.g. http://stackoverflow.com/questions/34627865/eager-lazy-and-explicit-loading-in-ef6 – Chris Apr 08 '16 at 15:44
  • @Legion i suggest you after experimenting with `IEnumerable` then go and study what it really is. dont judge on experiments. its not good to always convert everything to list. there are many articles that explain how IEnumerable works. read them too ;) – M.kazem Akhgary Apr 08 '16 at 15:44
  • @Legion You are kind of ranting rather than politely asking for an explanation. – paparazzo Apr 08 '16 at 15:46
  • @Legion Also, be wary of converting everything to lists everywhere, this may force loading many unrequired objects/children when doing very simple operations (potentially an issue with large datasets). Again, my EF knowledge is rusty, so this may not be entirely accurate. – Chris Apr 08 '16 at 15:48
  • please remember that `IEnumerable` is not `IEnumerator`. Learn the difference. – Thariq Nugrohotomo Apr 08 '16 at 15:53

1 Answers1

2

An IEnumerable is an iterator, so it returns one result at a time.

Each time foreach loops, it asks the iterator for the next result. Sometimes results can disappear from the collection being enumerated, so you get unexpected behaviour.

To avoid this, make sure you have all the results before starting the foreach, by calling .ToList()

  // Make a list of all documents
  List<Document> documentsToAdd;

  documentsToAdd = dbEvent.Documents
                          .Where(dbd => !eventToSave.Documents
                                                    .Select(d => d.DocumentId)
                                                    .Contains(dbd.DocumentId))
                          .ToList();   // load all results

  // Now this will loop through the whole list
  foreach (Document documentToAdd in documentsToAdd) 
  {
      documentToAdd.DocumentType = null;
      documentToAdd.DeletedByUser = null;
      documentToAdd.DocumentOwnerTeam = null;
      documentToAdd.UploadedByUser = null;
      documentToAdd.UploadedInStage = null;

      hcDbContext.Documents.Add(documentToAdd);
  }
  • Can you explain why this works? As I asked Chris above, why can't I reliably enumerate an IEnumerable? I say reliably because it has been working fine for the last few days and only started doing this today. Why is the ToList() conversion necessary? – Legion Apr 08 '16 at 15:30
  • @Legion try to remove ToList(), start the execution with a debugger, trace to the end of the foreach cycle and THEN enumerate the enumerable – enkryptor Apr 08 '16 at 15:36