4

Spent a lot of time, but still cann't understand how to avoid caching in DbContext.

I attached below entity model of some easy case to demonstrate what I mean.

The problem is that dbcontext caching results. For example, I have next code for querying data from my database:

using (TestContext ctx = new TestContext())
{
   var res = (from b in ctx.Buildings.Where(x => x.ID == 1)
             select new
             {
                b,
                flats = from f in b.Flats
                        select new
                        {
                           f,
                           people = from p in f.People
                           where p.Archived == false
                           select p
                        }
             }).AsEnumerable().Select(x => x.b).Single();

}

In this case, everything is fine: I got what I want (Only persons with Archived == false).

But if I add another query after it, for example, query for buildings that have people that have Archived flag set to true, I have next things, that I really cann't understand:

  1. my previous result, that is res, will be added by data (there will be added Persons with Archived == true too)
  2. new result will contain absolutely all Person's, no matter what Archived equals

the code of this query is next:

using (TestContext ctx = new TestContext())
{
   var res = (from b in ctx.Buildings.Where(x => x.ID == 1)
             select new
             {
                b,
                flats = from f in b.Flats
                        select new
                        {
                           f,
                           people = from p in f.People
                           where p.Archived == false
                           select p
                        }
             }).AsEnumerable().Select(x => x.b).Single();


    var newResult = (from b in ctx.Buildings.Where(x => x.ID == 1)
              select new
              {
                  b,
                  flats = from f in b.Flats
                          select new
                          {
                             f,
                             people = from p in f.People
                             where p.Archived == true
                             select p
                           }
               }).AsEnumerable().Select(x => x.b).Single();
            }

By the way, I set LazyLoadingEnabled to false in constructor of TestContext.

Does anybody know how to workaround this problem? How can I have in my query what I really write in my linq to entity?

P.S. @Ladislav may be you can help?

Entity Model

Danil Sabirov
  • 333
  • 2
  • 11
  • what is you read code. That is, what makes you says that you have everything in newResult ? Because you query returns a Building entity and no more. – tschmit007 Jul 23 '13 at 14:44
  • @tschmit007 The Building entity has a navigation property Flats, and each flat has a navigation property People. All of that can be accessed from newResult. –  Jul 23 '13 at 14:49
  • indeed, but the navigation property you get has nothing to see with your select**S** as you only get b (`select(x => x.b)`). So you get the whole building in each case. – tschmit007 Jul 23 '13 at 14:53
  • @tschmit007 Yes, but the building's `Flats` navigation property does not use lazy loading, so only those flats that get loaded into the context are accessible from that. Similarly for each flat's `People` property. All of those entities get created from that same query; `Select(x => x.b)` is not part of the query. –  Jul 23 '13 at 14:56
  • ok, I think I see. Have you tried my suggested query in my response ? Othrwise do not use `select(x => x.b)`. Use just Single() and iterate on newResult.flats. – tschmit007 Jul 23 '13 at 15:04

2 Answers2

9

You can use the AsNoTracking method on your query.

var res = (from b in ctx.Buildings.Where(x => x.ID == 1)
         select new
         {
            b,
            flats = from f in b.Flats
                    select new
                    {
                       f,
                       people = from p in f.People
                       where p.Archived == false
                       select p
                    }
         }).AsNoTracking().AsEnumerabe().Select(x => x.b).Single();

I also want to note that your AsEnumerable is probably doing more harm than good. If you remove it, the Select(x => x.b) will be translated to SQL. As is, you are selecting everything, then throwing away everything but x.b in memory.

cadrell0
  • 17,109
  • 5
  • 51
  • 69
  • AsNoTracking() can't be applied to linq query unfortunatelly. Without AsEnumerable() the details will not be in results, that don't suits me. – Danil Sabirov Jul 23 '13 at 14:38
  • I'm not sure what you mean by a "linq query". Are you getting an error? – cadrell0 Jul 23 '13 at 14:43
  • "If you remove it, the Select(x => x.b) will be translated to SQL" -- Yes, and it should not be. If it's removed, the building's flats won't be part of the query results, because the query provider sees that `flats` is discarded. –  Jul 23 '13 at 14:43
  • AsNoTracking() method cann't be applied to query:`(from b in ctx...)` – Danil Sabirov Jul 23 '13 at 14:59
  • 1
    it did the trick for me in EF 6.1.1 Code First .NET 4.5.2 @ 2:00 a ! ... I googled "entity framework property cache disable" and found this post. – ablaze Oct 17 '14 at 15:36
2

have you tried something like:

ctx.Persons.Where(x => x.Flat.Building.Id == 1 && x.Archived == false);

===== EDIT =====

In this case I think you approach is, imho, really hazardous. Indeed you works on the data loaded by EF to interpret your query rather than on data resulting of the interpretation of your query. If one day EF changes is loading policy (for example with a predictive pre-loading) your approach will "send you in then wall".

For your goal, you will have to eager load the data you need to build your "filterd" entity. That is select the building, then foreach Flat select the non archived persons.

Another solution is to use too separate contexts in an "UnitOfWork" like design.

tschmit007
  • 7,559
  • 2
  • 35
  • 43
  • No, in real cases I need my master object and filtered details and details details and maybe their details and too filtered=) And, of course, this problem of caching is staing on my way, because I don't have correct data – Danil Sabirov Jul 23 '13 at 15:06
  • Really, don't understand why this approach is bad. – Danil Sabirov Jul 23 '13 at 15:49
  • if I write as a second query the query that you write (but only with this condition Archived == true), I have next problem: the first result's (**res**) building's persons will be added by person that have Arhived == true. Why??? How to protect this first result by adding results of next queries? Btw, thans for your answer, I will think about it – Danil Sabirov Jul 23 '13 at 15:54
  • you can't control the caching, except by : use 2 separate contexts, tryin to detach the entity between the two queries. But I repeat, the way you use the cache seems, imho, hazardous. The cache is thinked to optimize/fasten result building. You use it a a result provider. – tschmit007 Jul 23 '13 at 16:07
  • Yeah, I begin to understand what you talking about. Now, I'm reading book of Julie Lerman (Programming EF: DbContext) =) I think it will clarify my gaps in DbContext – Danil Sabirov Jul 23 '13 at 16:22