59

Originally I believed that

context.Configuration.AutoDetectChangesEnabled = false;

would disable change tracking. But no. Currently I need to use AsNoTracking() on all my LINQ queries (for my read only layer). Is there a global setting to disable tracking on the DbContext?

Alexander I.
  • 1,769
  • 3
  • 12
  • 31
Vindberg
  • 1,294
  • 2
  • 14
  • 25

7 Answers7

34

What about simply exposing method like this on your derived context and use it for queries:

public IQueryable<T> GetQuery<T>() where T : class {
    return this.Set<T>().AsNoTracking();
}

Setting AsNoTracking globally is not possible. You must set it per each query or per each ObjectSet (not DbSet). The latter approach requires using ObjectContext API.

var objectContext = ((IObjectContextAdapter)dbContext).ObjectContext;
var set = objectContext.CreateObjectSet<T>();
set.MergeOption = MergeOption.NoTracking;
// And use set for queries
Bernd Ott
  • 4,075
  • 1
  • 23
  • 42
Ladislav Mrnka
  • 349,807
  • 56
  • 643
  • 654
  • 1
    How would do a join between Entities when only exposing a single entity like GetQuery? Thanks for the reply though. – Vindberg Oct 04 '12 at 12:50
  • you can join results of two different `GetQuery` calls – Ladislav Mrnka Oct 04 '12 at 16:25
  • Its possible, but then I need to redo my generic repository setup :/ But thanks for the suggestion. – Vindberg Oct 05 '12 at 09:08
  • @LadislavMrnka I have queries that return instances of a class not captured by the DbContext. In this case, I don't think this.Set would work. On the other hand, maybe AsNoTracking is not necessary? – Candy Chiu Oct 22 '14 at 13:03
29

Since this question is not tagged with a specific EF version, I wanted to mention that in EF Core the behavior can be configured at the context level.

You can also change the default tracking behavior at the context instance level:

using (var context = new BloggingContext())
{
    context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;

    var blogs = context.Blogs.ToList();
}
Amr Elgarhy
  • 59,046
  • 67
  • 178
  • 291
  • If you have additional settings (e.g. connection string), then the DbContextOptions helps. For example: `var dbContextOptionsBuilder = new DbContextOptionsBuilder().UseSqlServer("SQL connection to Blogging database").UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);`. Then create context with `var bloggingContext = new BloggingContext(dbContextOptionsBuilder.Options)`. – Jeremy Ray Brown Dec 16 '20 at 13:25
12

In EntityFramework.Core it is very easy.

For this purpose you can use UseQueryTrackingBehavior method.

Code snippet is here:

services.AddDbContext<DatabaseContext>(options =>
{
    options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
    options.UseSqlServer(databaseSettings.DefaultConnection);
});
Alexander I.
  • 1,769
  • 3
  • 12
  • 31
2

You could do something like this in your DbContext:

public void ObjectContext_OnObjectMaterialized(Object objSender, ObjectMaterializedEventArgs e)
{
    Entry(e.Entity).State = EntityState.Detached;
}

Every time an object is materialized by your context, it will be detached and no longer tracked.

Gabriel G. Roy
  • 2,494
  • 1
  • 24
  • 38
  • I think this works but maybe not the best way to do this. AsNoTracking() as far as I know do not Attach and Detach objects. – Jone Polvora Apr 02 '15 at 12:24
  • 1
    The answer here elaborates on the difference between the two. http://stackoverflow.com/a/20163424/219072 It sounds like AsNoTracking() is definitely the preferred approach. – emragins Oct 26 '15 at 22:34
1

Update: This didn't really work. See comments!

I hate it when I search on StackOverflow and the answer is: "You can't!" or "You could, but only if you completely change every single call you've ever made."

Reflection anyone? I was hoping this would be a DbContext setting. But since it is not, I made one using reflection.

This handy little method will set AsNoTracking on all properties of type DbSet.

    private void GloballySetAsNoTracking()
    {
        var dbSetProperties = GetType().GetProperties();
        foreach (PropertyInfo pi in dbSetProperties)
        {
            var obj = pi.GetValue(this, null);
            if (obj.GetType().IsGenericType && obj.GetType().GetGenericTypeDefinition() == typeof(DbSet<>))
            {
                var mi = obj.GetType().GetMethod("AsNoTracking");
                mi.Invoke(obj, null);
            }
        }
    }

Add it to an overloaded DbContext constructor.

    public ActivationDbContext(bool proxyCreationEnabled, bool lazyLoadingEnabled = true, bool asNoTracking = true)
    {
        Configuration.ProxyCreationEnabled = proxyCreationEnabled;
        Configuration.LazyLoadingEnabled = lazyLoadingEnabled;
        if (asNoTracking)
            GloballySetAsNoTracking();
    }

It uses reflection, which means someone will quickly comment that this is a performance hit. But is it really that much of a hit? Depends on your use case.

Rhyous
  • 5,919
  • 1
  • 37
  • 47
  • 4
    I haven't tested this, but as far as I know, `AsNoTracking()` only returns the current set as a not-tracked `IQueryable`. Calling it on the `DbSet` doesn't make the next queries non-tracked. What I understand from this code is that you are calling `AsNoTracking()` on all your sets, but that doesn't do anything unless you are using the returned queryables for anything – Jcl Dec 06 '15 at 17:50
  • @Jcl, I'll have to check on that. It appears to be working for me. – Rhyous Dec 07 '15 at 17:08
  • 1
    I don't have time right now to test it, but it looks dubious to me at first glance. If this works, it'd mean that anytime you call `AsNoTracking()` on a `DbSet` in a context, all subsequent queries on that `DbSet` in the same context would be non-tracked... and that'd be some weird behaviour (specially considering there's no `AsTracking()` to compensate). If this indeed works, I'd say it's a bug... or undocumented feature :-) – Jcl Dec 07 '15 at 20:28
  • 1
    @Rhyous I dont think setting AsNoTracking() once will set it forever, as per this link. http://www.c-sharpcorner.com/UploadFile/ff2f08/entity-framework-and-asnotracking/ I see this is almost an year old comment.. please let me know know if you have any alternative for setting Globally AsNoTracking – Raghav Aug 08 '16 at 13:31
  • Forgive me for forgetting to post my findings. As @jcl stated, it is per query. A new query does not honor the AsNoTracking() command. I implemented the a wrapper around EF (the Repository pattern) and now the calls that need AsNoTracking have it. – Rhyous Aug 08 '16 at 14:55
0

In my case since I needed the whole context to be readonly rather than Read/Write.

So I did a change to the tt file, and changed all the DbContext properties to return DbQuery instead of DbSet, removed the sets from all properties, and for the gets, I returned the Model.AsNoTracking()

For example:

public virtual DbQuery<Campaign> Campaigns { get{ return Set<Campaign>().AsNoTracking();} }

The way I did this in the tt template is:

public string DbQuery(EntitySet entitySet)
    {
        return string.Format(
            CultureInfo.InvariantCulture,
            "{0} virtual DbQuery<{1}> {2} {{ get{{ return Set<{1}>().AsNoTracking();}} }}",
            Accessibility.ForReadOnlyProperty(entitySet),
            _typeMapper.GetTypeName(entitySet.ElementType),
            _code.Escape(entitySet));
    }
Ahmed IG
  • 514
  • 7
  • 17
0

If using Entity Framework core you could also add the following code in the constructor of the class that is inheriting DbContext.

public NPCContext()
        : base()
{
     base.ChangeTracker.AutoDetectChangesEnabled = false;
}

or the following

    public NPCContext(DbContextOptions<NPCContext> options)
        : base(options)
    {
        base.ChangeTracker.AutoDetectChangesEnabled = false;
    }
George M
  • 43
  • 2
  • 5