Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Changing value works with eager loading but not lazing loading in linq and ef

this is not 'a problem' question , but 'why this happens' question.

var chapters = story.Chapters.Select(
                    ch => new ChapterDisplayViewModel { 
                                   Id = ch.Id,
                                   Number = ch.Number});

first i want to get some data. story is an entity type of type Story and it has One To Many relation with Chapter i want to change some data in the chapters collection that i got so i write some condition if so change the value

if(chapters.Any(c => c.Number == chapterNum))
   chapters.Where(c => c.Number == chapterNum).Single().IsSelected = true;

and then i send the data to the view but the problem is :

nothing is changed due to lazy loading , the change that i made wasn't triggerd even after sending the data to the view , Why ? i made an assigment statment and shouldn't passing the data to the view trigger it ?

the solution was of course using ToList to execute the query immediately

var chapters = story.Chapters.Select(
                    ch => new ChapterDisplayViewModel { 
                                   Id = ch.Id,
                                   Number = ch.Number}).ToList();

i just want an explanation to the behavior

like image 461
Nadeem Khedr Avatar asked Dec 02 '25 06:12

Nadeem Khedr


1 Answers

Because you say that you have a lazily loaded collection Chapters on your Story class I assume that Chapters is actually a collection of dynamic proxy objects. If you look what happens in the database with a profiler you will see that this line ...

var chapters = story.Chapters.Select(
               ch => new ChapterDisplayViewModel { 
                               Id = ch.Id,
                               Number = ch.Number});

... executes a query in the database which queries all Chapter objects (the projection into ChapterDisplayViewModel doesn't happen in the database). And that's the only database query. The following ...

if (chapters.Any(c => c.Number == chapterNum))
    chapters.Where(c => c.Number == chapterNum).Single().IsSelected = true;

... is executed in memory on the already lazily loaded collection of Chapters. The projection happens at this point.

But this means that the Single operator materializes a ChapterDisplayViewModel object, it means: There happens a new ChapterDisplayViewModel somewhere internally. Simple check for this:

var viewModel1 = chapters.Where(c => c.Number == chapterNum).Single();
var viewModel2 = chapters.Where(c => c.Number == chapterNum).Single();

bool sameObjects = object.ReferenceEquals(viewModel1, viewModel2);

sameObjects is false which means Single doesn't simply return references to ViewModel objects which are already in memory but creates new instances of them.

When you apply ToList in the first query the ViewModels are materialized at once into a in-memory collection of ViewModels and Single will simply return a reference to the matching, but already existing object. sameObjects will be true.

So, without ToList you are setting the IsSelected property on a just materialized object which you don't reference anymore and therefore disappears in garbage collection immediately. With ToList you are setting the property on the unique object inside of the in-memory collection. When you use this collection in your view the flag is still there.

like image 144
Slauma Avatar answered Dec 04 '25 21:12

Slauma



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!