Skip to content

Commit

Permalink
Add QueryTracking behavior to DbContextOptionsBuilder
Browse files Browse the repository at this point in the history
Allows one time configuration in places like Startup.cs.

Issue #5057
  • Loading branch information
ajcvickers committed Jun 21, 2016
1 parent b23e45e commit f34adde
Show file tree
Hide file tree
Showing 14 changed files with 231 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,29 @@ public virtual void Can_disable_and_reenable_query_result_tracking()
}
}

[Fact]
public virtual void Can_disable_and_reenable_query_result_tracking_starting_with_NoTracking()
{
using (var context = Fixture.CreateContext(QueryTrackingBehavior.NoTracking))
{
Assert.Equal(QueryTrackingBehavior.NoTracking, context.ChangeTracker.QueryTrackingBehavior);

var query = context.Employees.OrderBy(e => e.EmployeeID);

var results = query.Take(1).ToList();

Assert.Equal(1, results.Count);
Assert.Equal(0, context.ChangeTracker.Entries().Count());

context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.TrackAll;

results = query.Skip(1).Take(1).ToList();

Assert.Equal(1, results.Count);
Assert.Equal(1, context.ChangeTracker.Entries().Count());
}
}

[Fact]
public virtual void Can_disable_and_reenable_query_result_tracking_query_caching()
{
Expand All @@ -256,6 +279,30 @@ public virtual void Can_disable_and_reenable_query_result_tracking_query_caching
}
}

[Fact]
public virtual void Can_disable_and_reenable_query_result_tracking_query_caching_using_options()
{
using (var context = CreateContext())
{
Assert.Equal(QueryTrackingBehavior.TrackAll, context.ChangeTracker.QueryTrackingBehavior);

var results = context.Employees.ToList();

Assert.Equal(9, results.Count);
Assert.Equal(9, context.ChangeTracker.Entries().Count());
}

using (var context = Fixture.CreateContext(QueryTrackingBehavior.NoTracking))
{
Assert.Equal(QueryTrackingBehavior.NoTracking, context.ChangeTracker.QueryTrackingBehavior);

var results = context.Employees.ToList();

Assert.Equal(9, results.Count);
Assert.Equal(0, context.ChangeTracker.Entries().Count());
}
}

[Fact]
public virtual void Can_disable_and_reenable_query_result_tracking_query_caching_single_context()
{
Expand All @@ -279,6 +326,18 @@ public virtual void Can_disable_and_reenable_query_result_tracking_query_caching
}
}

[Fact]
public virtual void AsTracking_switches_tracking_on_when_off_in_options()
{
using (var context = Fixture.CreateContext(QueryTrackingBehavior.NoTracking))
{
var results = context.Employees.AsTracking().ToList();

Assert.Equal(9, results.Count);
Assert.Equal(9, context.ChangeTracker.Entries().Count());
}
}

[Fact]
public virtual void Precendence_of_tracking_modifiers()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public virtual void OnModelCreating(ModelBuilder modelBuilder)
modelBuilder.Entity<OrderDetail>(e => { e.HasKey(od => new { od.OrderID, od.ProductID }); });
}

public abstract NorthwindContext CreateContext();
public abstract NorthwindContext CreateContext(
QueryTrackingBehavior queryTrackingBehavior = QueryTrackingBehavior.TrackAll);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ public class NorthwindContext : DbContext
{
public static readonly string StoreName = "Northwind";

public NorthwindContext(DbContextOptions options)
: base(options)
public NorthwindContext(
DbContextOptions options,
QueryTrackingBehavior queryTrackingBehavior = QueryTrackingBehavior.TrackAll)
: base(queryTrackingBehavior == QueryTrackingBehavior.TrackAll
? options
: new DbContextOptionsBuilder(options).UseQueryTrackingBehavior(queryTrackingBehavior).Options)
{
}

Expand Down
22 changes: 17 additions & 5 deletions src/Microsoft.EntityFrameworkCore/ChangeTracking/ChangeTracker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore.ChangeTracking
Expand All @@ -22,16 +23,23 @@ public class ChangeTracker : IInfrastructure<IStateManager>
private IStateManager _stateManager;
private IChangeDetector _changeDetector;
private IEntityEntryGraphIterator _graphIterator;
private QueryTrackingBehavior _queryTrackingBehavior;

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public ChangeTracker([NotNull] DbContext context)
{
Check.NotNull(context, nameof(context));

Context = context;
_queryTrackingBehavior = context
.GetService<IDbContextOptions>()
.Extensions
.OfType<CoreOptionsExtension>()
.FirstOrDefault()
.QueryTrackingBehavior;
}

/// <summary>
Expand Down Expand Up @@ -67,7 +75,11 @@ public ChangeTracker([NotNull] DbContext context)
/// keep track of changes for all entities that are returned from a LINQ query.
/// </para>
/// </summary>
public virtual QueryTrackingBehavior QueryTrackingBehavior { get; set; }
public virtual QueryTrackingBehavior QueryTrackingBehavior
{
get { return _queryTrackingBehavior; }
set { _queryTrackingBehavior = value; }
}

/// <summary>
/// Gets an <see cref="EntityEntry" /> for each entity being tracked by the context.
Expand Down Expand Up @@ -184,13 +196,13 @@ public virtual void TrackGraph(
});
}

private IStateManager StateManager
private IStateManager StateManager
=> _stateManager ?? (_stateManager = Context.GetService<IStateManager>());

private IChangeDetector ChangeDetector
private IChangeDetector ChangeDetector
=> _changeDetector ?? (_changeDetector = Context.GetService<IChangeDetector>());

private IEntityEntryGraphIterator GraphIterator
private IEntityEntryGraphIterator GraphIterator
=> _graphIterator ?? (_graphIterator = Context.GetService<IEntityEntryGraphIterator>());
}
}
26 changes: 24 additions & 2 deletions src/Microsoft.EntityFrameworkCore/DbContextOptionsBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
Expand Down Expand Up @@ -129,6 +130,27 @@ public virtual DbContextOptionsBuilder UseInternalServiceProvider([CanBeNull] IS
public virtual DbContextOptionsBuilder EnableSensitiveDataLogging()
=> SetOption(e => e.IsSensitiveDataLoggingEnabled = true);

/// <summary>
/// <para>
/// Sets the tracking behavior for LINQ queries run against the context. Disabling change tracking
/// is useful for read-only scenarios because it avoids the overhead of setting up change tracking for each
/// entity instance. You should not disable change tracking if you want to manipulate entity instances and
/// persist those changes to the database using <see cref="DbContext.SaveChanges()" />.
/// </para>
/// <para>
/// This method sets the default behavior for all contexts created with these options, but you can override this
/// behavior for a context instance using <see cref="ChangeTracker.QueryTrackingBehavior" /> or on individual
/// queries using the <see cref="EntityFrameworkQueryableExtensions.AsNoTracking{TEntity}(IQueryable{TEntity})" />
/// and <see cref="EntityFrameworkQueryableExtensions.AsTracking{TEntity}(IQueryable{TEntity})" /> methods.
/// </para>
/// <para>
/// The default value is <see cref="EntityFrameworkCore.QueryTrackingBehavior.TrackAll" />. This means the change tracker will
/// keep track of changes for all entities that are returned from a LINQ query.
/// </para>
/// </summary>
public virtual DbContextOptionsBuilder UseQueryTrackingBehavior(QueryTrackingBehavior queryTrackingBehavior)
=> SetOption(e => e.QueryTrackingBehavior = queryTrackingBehavior);

/// <summary>
/// Configures the runtime behavior of warnings generated by Entity Framework. You can set a default behavior and behaviors for
/// each warning type.
Expand Down Expand Up @@ -183,8 +205,8 @@ private DbContextOptionsBuilder SetOption(Action<CoreOptionsExtension> setAction

var extension
= existingExtension != null
? new CoreOptionsExtension(existingExtension)
: new CoreOptionsExtension();
? new CoreOptionsExtension(existingExtension)
: new CoreOptionsExtension();

setAction(extension);

Expand Down
41 changes: 32 additions & 9 deletions src/Microsoft.EntityFrameworkCore/DbContextOptionsBuilder`.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.Extensions.Caching.Memory;
Expand Down Expand Up @@ -59,28 +61,28 @@ public DbContextOptionsBuilder([NotNull] DbContextOptions<TContext> options)
=> (DbContextOptionsBuilder<TContext>)base.UseModel(model);

/// <summary>
/// Sets the <see cref="ILoggerFactory"/> used for logging information from the context.
/// Sets the <see cref="ILoggerFactory" /> used for logging information from the context.
/// </summary>
/// <param name="loggerFactory"> The <see cref="ILoggerFactory"/> to be used. </param>
/// <param name="loggerFactory"> The <see cref="ILoggerFactory" /> to be used. </param>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
public new virtual DbContextOptionsBuilder<TContext> UseLoggerFactory([CanBeNull] ILoggerFactory loggerFactory)
=> (DbContextOptionsBuilder<TContext>)base.UseLoggerFactory(loggerFactory);

/// <summary>
/// Sets the <see cref="IMemoryCache"/> used to cache information such as query translations. If none is specified, then
/// Entity Framework will maintain its own internal <see cref="IMemoryCache"/>.
/// Sets the <see cref="IMemoryCache" /> used to cache information such as query translations. If none is specified, then
/// Entity Framework will maintain its own internal <see cref="IMemoryCache" />.
/// </summary>
/// <param name="memoryCache"> The <see cref="IMemoryCache"/> to be used. </param>
/// <param name="memoryCache"> The <see cref="IMemoryCache" /> to be used. </param>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
public new virtual DbContextOptionsBuilder<TContext> UseMemoryCache([CanBeNull] IMemoryCache memoryCache)
=> (DbContextOptionsBuilder<TContext>)base.UseMemoryCache(memoryCache);

/// <summary>
/// Sets the <see cref="IServiceProvider"/> that the context will resolve its internal services from. If none is specified, then
/// Entity Framework will maintain its own internal <see cref="IServiceProvider"/>. By default, we recommend allowing Entity Framework
/// to create and maintain its own <see cref="IServiceProvider"/> for internal services.
/// Sets the <see cref="IServiceProvider" /> that the context will resolve its internal services from. If none is specified, then
/// Entity Framework will maintain its own internal <see cref="IServiceProvider" />. By default, we recommend allowing Entity Framework
/// to create and maintain its own <see cref="IServiceProvider" /> for internal services.
/// </summary>
/// <param name="serviceProvider"> The <see cref="IServiceProvider"/> to be used. </param>
/// <param name="serviceProvider"> The <see cref="IServiceProvider" /> to be used. </param>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
public new virtual DbContextOptionsBuilder<TContext> UseInternalServiceProvider([CanBeNull] IServiceProvider serviceProvider)
=> (DbContextOptionsBuilder<TContext>)base.UseInternalServiceProvider(serviceProvider);
Expand All @@ -94,6 +96,27 @@ public DbContextOptionsBuilder([NotNull] DbContextOptions<TContext> options)
public new virtual DbContextOptionsBuilder<TContext> EnableSensitiveDataLogging()
=> (DbContextOptionsBuilder<TContext>)base.EnableSensitiveDataLogging();

/// <summary>
/// <para>
/// Sets the tracking behavior for LINQ queries run against the context. Disabling change tracking
/// is useful for read-only scenarios because it avoids the overhead of setting up change tracking for each
/// entity instance. You should not disable change tracking if you want to manipulate entity instances and
/// persist those changes to the database using <see cref="DbContext.SaveChanges()" />.
/// </para>
/// <para>
/// This method sets the default behavior for all contexts created with these options, but you can override this
/// behavior for a context instance using <see cref="ChangeTracker.QueryTrackingBehavior" /> or on individual
/// queries using the <see cref="EntityFrameworkQueryableExtensions.AsNoTracking{TEntity}(IQueryable{TEntity})" />
/// and <see cref="EntityFrameworkQueryableExtensions.AsTracking{TEntity}(IQueryable{TEntity})" /> methods.
/// </para>
/// <para>
/// The default value is <see cref="EntityFrameworkCore.QueryTrackingBehavior.TrackAll" />. This means the change tracker will
/// keep track of changes for all entities that are returned from a LINQ query.
/// </para>
/// </summary>
public new virtual DbContextOptionsBuilder<TContext> UseQueryTrackingBehavior(QueryTrackingBehavior queryTrackingBehavior)
=> (DbContextOptionsBuilder<TContext>)base.UseQueryTrackingBehavior(queryTrackingBehavior);

/// <summary>
/// Configures the runtime behavior of warnings generated by Entity Framework. You can set a default behavior and behaviors for
/// each warning type.
Expand Down
12 changes: 12 additions & 0 deletions src/Microsoft.EntityFrameworkCore/Internal/CoreOptionsExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class CoreOptionsExtension : IDbContextOptionsExtension
private IMemoryCache _memoryCache;
private bool _isSensitiveDataLoggingEnabled;
private WarningsConfiguration _warningsConfiguration;
private QueryTrackingBehavior _queryTrackingBehavior = QueryTrackingBehavior.TrackAll;

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
Expand All @@ -44,6 +45,7 @@ public CoreOptionsExtension([NotNull] CoreOptionsExtension copyFrom)
_memoryCache = copyFrom.MemoryCache;
_isSensitiveDataLoggingEnabled = copyFrom.IsSensitiveDataLoggingEnabled;
_warningsConfiguration = copyFrom.WarningsConfiguration;
_queryTrackingBehavior = copyFrom.QueryTrackingBehavior;
}

/// <summary>
Expand Down Expand Up @@ -112,6 +114,16 @@ public virtual WarningsConfiguration WarningsConfiguration
[param: CanBeNull] set { _warningsConfiguration = value; }
}

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual QueryTrackingBehavior QueryTrackingBehavior
{
get { return _queryTrackingBehavior; }
set { _queryTrackingBehavior = value; }
}

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ public override DbContextOptions BuildOptions(IServiceCollection serviceCollecti
.AddSingleton<ILoggerFactory>(_testLoggerFactory)
.BuildServiceProvider()).Options;

public override NorthwindContext CreateContext()
=> new NorthwindContext(_options);
public override NorthwindContext CreateContext(
QueryTrackingBehavior queryTrackingBehavior = QueryTrackingBehavior.TrackAll)
=> new NorthwindContext(_options, queryTrackingBehavior);
}

public class TestLoggerFactory : ILoggerFactory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@ protected virtual void ConfigureOptions(SqlServerDbContextOptionsBuilder sqlServ
{
}

public override NorthwindContext CreateContext()
=> new SqlServerNorthwindContext(_options);
public override NorthwindContext CreateContext(
QueryTrackingBehavior queryTrackingBehavior = QueryTrackingBehavior.TrackAll)
=> new SqlServerNorthwindContext(_options, queryTrackingBehavior);

public void Dispose() => _testStore.Dispose();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@ public override DbContextOptions BuildOptions(IServiceCollection additionalServi
.BuildServiceProvider())
.UseSqlServer(_testStore.ConnectionString).Options;

public override NorthwindContext CreateContext()
public override NorthwindContext CreateContext(
QueryTrackingBehavior queryTrackingBehavior = QueryTrackingBehavior.TrackAll)
{
var context = new SqlServerNorthwindContext(_options);
var context = new SqlServerNorthwindContext(_options, queryTrackingBehavior);

context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ public class SqlServerNorthwindContext : NorthwindContext
public static readonly string DatabaseName = StoreName;
public static readonly string ConnectionString = SqlServerTestStore.CreateConnectionString(DatabaseName);

public SqlServerNorthwindContext(DbContextOptions options)
: base(options)
public SqlServerNorthwindContext(DbContextOptions options,
QueryTrackingBehavior queryTrackingBehavior = QueryTrackingBehavior.TrackAll)
: base(options, queryTrackingBehavior)
{
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@ protected virtual DbContextOptionsBuilder ConfigureOptions(DbContextOptionsBuild
protected virtual SqliteDbContextOptionsBuilder ConfigureOptions(SqliteDbContextOptionsBuilder sqliteDbContextOptionsBuilder)
=> sqliteDbContextOptionsBuilder;

public override NorthwindContext CreateContext()
=> new SqliteNorthwindContext(_options);
public override NorthwindContext CreateContext(
QueryTrackingBehavior queryTrackingBehavior = QueryTrackingBehavior.TrackAll)
=> new SqliteNorthwindContext(_options, queryTrackingBehavior);

public void Dispose() => _testStore.Dispose();

Expand Down
Loading

0 comments on commit f34adde

Please sign in to comment.