7

I have an IEnumerable of a POCO type containing around 80,000 rows and a db table (L2E/EF4) containing a subset of rows where there was a "an error/a difference" (about 5000 rows, but often repeated to give about 150 distinct entries)

The following code gets the distinct VSACode's "in error" and then attempts to update the complete result set, updating JUST the rows that match...but it doesn't work!

var vsaCodes = (from g in db.GLDIFFLs
  select g.VSACode)
  .Distinct();

foreach (var code in vsaCodes)
 {
  var hasDifference = results.Where(r => r.VSACode == code);
  foreach (var diff in hasDifference)
   diff.Difference = true;
 }

 var i = results.Count(r => r.Difference == true);

After this code, i = 0

I've also tried:

foreach (var code in vsaCodes)
    {
        results.Where(r => r.VSACode == code).Select(r => { r.Difference = true; return r; }).ToList();
    }

How can I update the "results" to set only the matching Difference property?

Cœur
  • 37,241
  • 25
  • 195
  • 267
BlueChippy
  • 5,935
  • 16
  • 81
  • 131

3 Answers3

16

Assuming results is just a query (you haven't shown it), it will be evaluated every time you iterate over it. If that query creates new objects each time, you won't see the updates. If it returns references to the same objects, you would.

If you change results to be a materialized query result - e.g. by adding ToList() to the end - then iterating over results won't issue a new query, and you'll see your changes.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • My (clearly not quite right) understanding was to avoid using ToList() unless you really needed it...I guess this is a "really needed it" situation? Quick Q? is it better to call ToList() early on in my chain of statements/queries to materialise the list? If I don't, will it query the DB every time it is used? – BlueChippy Sep 05 '11 at 09:29
  • @BlueChippy: You should usually use ToList as late as you can, so that most of the query (in particular parts which filter the output) executes at the database. – Jon Skeet Sep 05 '11 at 09:53
  • Thanks Jon, guess I had left this ToList() a little TooLate() ;) – BlueChippy Sep 05 '11 at 10:10
  • @JonSkeet: Slight tweak, if you have a query over an underlying "materialized" source, you can update the source objects via the resulting query. The query will be re-evaluated each time and if your query contains a filter, you may filter out items based on the update but the source will remained updated. If you query contains a projection (Select()) into a class (as opposed to an anonymous type), that will break the link back to the source objects. – andleer Apr 09 '14 at 14:45
  • 1
    @andleer: I'm afraid it's not really clear to me what you mean. Perhaps you should add your own answer? – Jon Skeet Apr 09 '14 at 14:46
  • @jonskeet, posted. Total edge case and I really didn't address the OP's question but as you pointed out, it is a bit vague. – andleer Apr 09 '14 at 16:35
2

I had the same kind of error some time ago. The problem is that linq queries are often deferred and not executed when it appears you are calling them.

Quotation from "Pro LINQ Language Integrated Query in C# 2010":

"Notice that even though we called the query only once, the results of the enumeration are different for each of the enumerations. This is further evidence that the query is deferred. If it were not, the results of both enumerations would be the same. This could be a benefit or detriment. If you do not want this to happen, use one of the conversion operators that do not return an IEnumerable so that the query is not deferred, such as ToArray, ToList, ToDictionary, or ToLookup, to create a different data structure with cached results that will not change if the data source changes."

Here you have a good explanation with examples of it:

http://blogs.msdn.com/b/charlie/archive/2007/12/09/deferred-execution.aspx

Regards

GoRoS
  • 5,183
  • 2
  • 43
  • 66
1

Parsing words pretty closely on @jonskeet's answer...

If your query is simply a filter and the underlying source objects are updated, the query will be reevaluated and may exclude these objects based on the filter condition in which case your query results will change on subsequent enumerations but the underlying objects will still have been updated.

The key is a lack of a projection to a new type as far as updating and persisting the changed objects.

ToList() is the usual solution to this issue and it will solve the problem if there is a projection to a new type but things gets cloudy in event your query filters but does not project. Updates to the query still affect the original source objects given everything is referencing the same object.

Again, parsing words but these edge cases can trip you up.

public class Widget
{
    public string Name { get; set; }
}

var widgets1 = new[]
{
    new Widget { Name = "Red", },
    new Widget { Name = "Green", },
    new Widget { Name = "Blue", },
    new Widget { Name = "Black", },
};

// adding ToList() will result in 'static' query result but
// updates to the objects will still affect the source objects
var query1 = widgets1
    .Where(i => i.Name.StartsWith("B"))
    //.ToList()
    ;

foreach (var widget in query1)
{
    widget.Name = "Yellow";
}

// produces no output unless you uncomment out the ToList() above
// query1 is reevaluated and filters out "yellow" which does not start with "B"
foreach (var name in query1)
    Console.WriteLine(name.Name);

// produces Red, Green, Yellow, Yellow
// the underlying widgets were updated
foreach (var name in widgets1)
    Console.WriteLine(name.Name);
andleer
  • 22,388
  • 8
  • 62
  • 82