46

Perhaps I am misunderstanding the caching that DbContext and DbSet does but I was under the impression that there was some caching that would go on. I'm seeing behavior that I wouldn't expect when I run the following code:

var ctx = CreateAContext();
var sampleEntityId = ctx.SampleEntities.Select(i => i.Id)
                                       .Single(i => i == 3); //Calls DB as expected
var cachedEntityId = ctx.SampleEntities.Select(i => i.Id)
                                       .Single(i => i == 3); //Calls DB unexpectedly

What's going on here? I thought that part of what you get from DbSet is that it would first check the local cache to see if that object exists before querying the database. Is there just some sort of configuration option I am missing here?

Adam Modlin
  • 2,994
  • 2
  • 22
  • 39

6 Answers6

49

What @emcas88 is trying to say is that EF will only check the cache when you use the .Find method on DbSet.

Using .Single, .First, .Where, etc will not cache the results unless you are using second-level caching.

Community
  • 1
  • 1
danludwig
  • 46,965
  • 25
  • 159
  • 237
  • 5
    Do you know of any resources for implementing second level caching in EF6 or any projects that have already done it? – Adam Modlin Feb 11 '14 at 17:13
  • If you google it you should find some. I vaguely recall one on codeproject, though it was not for EF6, it may work and is definitely a good place to start. A better solution would involve not having to execute the same query a second time. – danludwig Feb 11 '14 at 17:14
  • My question on this stems from a concern that if I query my `DbContext` and load some entities in a `DbSet`, then run a stored procedure that has side effects on that `DbSet`, I want to make sure my "stale" entities aren't there anymore. Sounds like I don't need to worry about that. – Adam Modlin Feb 11 '14 at 17:37
  • 5
    EF **WILL CACHE** the results of `.First` due to [Performance Considerations for Entity Framework 4, 5, and 6](https://msdn.microsoft.com/en-us/data/hh949853.aspx#2). warm query uses cached data. Also i have a code that uses `.Where` and it reads from cache. Likely EF caches `.Single` too – mjalil Mar 29 '16 at 12:01
  • 4
    @mjalil I think you are confusing results caching with metadata, view, and materializer caching. Only `.Find` will cache actual query results in the first-level cache. – danludwig Mar 29 '16 at 14:11
  • It is still basically caching. Mindblowing, confusing things can happen because of it. For instance, if you `Add()` a new entity that wasn't created using `Create()`, then select out that entity, you will get back the exact same object you added, and it will be missing dynamic proxies and all that. So, sharing a DbContext across multiple bits of code that add and select things can become quite confusing. The solution is **always use `Create`**, but it is extremely confusing behavior. – John Hargrove Apr 05 '18 at 16:27
  • @AdamModlin a second level cache resource I used for EF6 is http://entityframework-plus.net/tutorial-query has worked quite well for my needs; read up on it and test drive it. – Geovani Martinez Apr 25 '18 at 13:20
36

This is because the implementation of the extensor methods, use the Find method of the context

contextName.YourTableName.Find()

to verify first the cache. Hope it helps.

emcas88
  • 881
  • 6
  • 15
26

Sometimes I use my extension method:

using System.Linq;
using System.Linq.Expressions;

namespace System.Data.Entity
{
    public static class DbSetExtensions
    {
        public static TEntity FirstOrDefaultCache<TEntity>(this DbSet<TEntity> queryable, Expression<Func<TEntity, bool>> condition) 
            where TEntity : class
        {
            return queryable
                .Local.FirstOrDefault(condition.Compile()) // find in local cache
                   ?? queryable.FirstOrDefault(condition); // if local cache returns null check the db
        }
    }
}

Usage:

db.Invoices.FirstOrDefaultCache(x => x.CustomerName == "Some name");

You can replace FirstOrDefault with SingleOrDetfault also.

Giuseppe Romagnuolo
  • 3,362
  • 2
  • 30
  • 38
cryss
  • 4,130
  • 1
  • 29
  • 34
  • 3
    I like this solution. Just a little feedback: you missed the "this" before "DbSet queryable", and witting "Where(condition).FirstOrDefault()" is the same as ".FirstOrDefault(condition)". Thanks for sharing cryss. – Augusto Barreto Mar 04 '15 at 22:03
  • Very interesting. But would this work with Lazy Loading too? – tocqueville May 23 '16 at 11:46
12

Take a look at EF Docs, you will find answer there:

Note that DbSet and IDbSet always create queries against the database and will always involve a round trip to the database even if the entities returned already exist in the context. A query is executed against the database when:

  • It is enumerated by a foreach (C#) or For Each (Visual Basic) statement.
  • It is enumerated by a collection operation such as ToArray, ToDictionary, or ToList.
  • LINQ operators such as First or Any are specified in the outermost part of the query.
  • The following methods are called: the Load extension method on a DbSet, DbEntityEntry.Reload, and Database.ExecuteSqlCommand.
cin
  • 351
  • 3
  • 14
Vladislav Kostenko
  • 1,155
  • 11
  • 18
7

EF6 doesn't do results caching out of the box. In order to cache results, you need to use a second level cache. See this promising project on CodePlex:

Second Level Caching for EF 6.1

Keep in mind that if data changes on the db, you won't immediately know about it. Sometimes this is important depending upon the project. ;)

John Cummings
  • 1,949
  • 3
  • 22
  • 38
Dave
  • 897
  • 8
  • 8
  • 5
    You are talking about *2nd-level* cache. What he is asking is *1st-level* cache, I believe. :) – Alen Siljak Feb 05 '15 at 10:34
  • This answer appears to be wrong. See http://codethug.com/2016/02/19/Entity-Framework-Cache-Busting/, for example. – Zero3 Jun 01 '17 at 11:43
  • 1st-level cache is the default if you require the second level cache then you could use something like http://entityframework-plus.net/tutorial-query – Geovani Martinez Apr 25 '18 at 13:21
0

It is very clear on MSDN. Please note what is find

[https://learn.microsoft.com/en-us/ef/ef6/querying/][1]


using (var context = new BloggingContext())
{
    // Will hit the database
    var blog = context.Blogs.Find(3);

    // Will return the same instance without hitting the database
    var blogAgain = context.Blogs.Find(3);

    context.Blogs.Add(new Blog { Id = -1 });

    // Will find the new blog even though it does not exist in the database
    var newBlog = context.Blogs.Find(-1);

    // Will find a User which has a string primary key
    var user = context.Users.Find("johndoe1987");
}
Jin Thakur
  • 2,711
  • 18
  • 15