Originally I believed that
context.Configuration.AutoDetectChangesEnabled = false;
would disable change tracking. But no. Cur
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();
}
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
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));
}
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.
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.
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);
});