Global setting for AsNoTracking()?

C#.NetEntity FrameworkChange Tracking

C# Problem Overview


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?

C# Solutions


Solution 1 - C#

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();
}

Solution 2 - C#

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

Solution 3 - C#

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);
});

Solution 4 - C#

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.

Solution 5 - C#

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.

Solution 6 - C#

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));
    }

Solution 7 - C#

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;
    }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionVindbergView Question on Stackoverflow
Solution 1 - C#Amr ElgarhyView Answer on Stackoverflow
Solution 2 - C#Ladislav MrnkaView Answer on Stackoverflow
Solution 3 - C#Alexander I.View Answer on Stackoverflow
Solution 4 - C#Gabriel G. RoyView Answer on Stackoverflow
Solution 5 - C#RhyousView Answer on Stackoverflow
Solution 6 - C#Ahmed IGView Answer on Stackoverflow
Solution 7 - C#George MView Answer on Stackoverflow