Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"sdk": {
"version": "9.0.100"
"version": "9.0.100",
"rollForward": "latestMinor"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ public static Expression ExpandQuaryables(this Expression expression)
/// Replaces all calls to properties and methods that are marked with the <C>Projectable</C> attribute with their respective expression tree
/// </summary>
public static Expression ExpandProjectables(this Expression expression)
=> new ProjectableExpressionReplacer(new ProjectionExpressionResolver()).Replace(expression);
=> new ProjectableExpressionReplacer(new ProjectionExpressionResolver(), false).Replace(expression);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,34 @@ public sealed class CustomQueryCompiler : QueryCompiler
readonly IQueryCompiler _decoratedQueryCompiler;
readonly ProjectableExpressionReplacer _projectableExpressionReplacer;

public CustomQueryCompiler(IQueryCompiler decoratedQueryCompiler, IQueryContextFactory queryContextFactory, ICompiledQueryCache compiledQueryCache, ICompiledQueryCacheKeyGenerator compiledQueryCacheKeyGenerator, IDatabase database, IDiagnosticsLogger<DbLoggerCategory.Query> logger, ICurrentDbContext currentContext, IEvaluatableExpressionFilter evaluatableExpressionFilter, IModel model) : base(queryContextFactory, compiledQueryCache, compiledQueryCacheKeyGenerator, database, logger, currentContext, evaluatableExpressionFilter, model)
public CustomQueryCompiler(IQueryCompiler decoratedQueryCompiler,
IQueryContextFactory queryContextFactory,
ICompiledQueryCache compiledQueryCache,
ICompiledQueryCacheKeyGenerator compiledQueryCacheKeyGenerator,
IDatabase database,
IDbContextOptions contextOptions,
IDiagnosticsLogger<DbLoggerCategory.Query> logger,
ICurrentDbContext currentContext,
IEvaluatableExpressionFilter evaluatableExpressionFilter,
IModel model) : base(queryContextFactory,
compiledQueryCache,
compiledQueryCacheKeyGenerator,
database,
logger,
currentContext,
evaluatableExpressionFilter,
model)
{
_decoratedQueryCompiler = decoratedQueryCompiler;
_projectableExpressionReplacer = new ProjectableExpressionReplacer(new ProjectionExpressionResolver());
var trackingByDefault = (contextOptions.FindExtension<CoreOptionsExtension>()?.QueryTrackingBehavior ?? QueryTrackingBehavior.TrackAll) ==
QueryTrackingBehavior.TrackAll;

_projectableExpressionReplacer = new ProjectableExpressionReplacer(new ProjectionExpressionResolver(), trackingByDefault);
}

public override Func<QueryContext, TResult> CreateCompiledAsyncQuery<TResult>(Expression query)
public override Func<QueryContext, TResult> CreateCompiledAsyncQuery<TResult>(Expression query)
=> _decoratedQueryCompiler.CreateCompiledAsyncQuery<TResult>(Expand(query));
public override Func<QueryContext, TResult> CreateCompiledQuery<TResult>(Expression query)
public override Func<QueryContext, TResult> CreateCompiledQuery<TResult>(Expression query)
=> _decoratedQueryCompiler.CreateCompiledQuery<TResult>(Expand(query));
public override TResult Execute<TResult>(Expression query)
=> _decoratedQueryCompiler.Execute<TResult>(Expand(query));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using EntityFrameworkCore.Projectables.Extensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Query;

Expand All @@ -15,14 +16,16 @@ public sealed class ProjectableExpressionReplacer : ExpressionVisitor
private readonly ExpressionArgumentReplacer _expressionArgumentReplacer = new();
private readonly Dictionary<MemberInfo, LambdaExpression?> _projectableMemberCache = new();
private IQueryProvider? _currentQueryProvider;
private bool _disableRootRewrite;
private bool _disableRootRewrite = false;
private readonly bool _trackingByDefault;
private IEntityType? _entityType;

private readonly MethodInfo _select;
private readonly MethodInfo _where;

public ProjectableExpressionReplacer(IProjectionExpressionResolver projectionExpressionResolver)
public ProjectableExpressionReplacer(IProjectionExpressionResolver projectionExpressionResolver, bool trackByDefault = false)
{
_trackingByDefault = trackByDefault;
_resolver = projectionExpressionResolver;
_select = typeof(Queryable).GetMethods(BindingFlags.Static | BindingFlags.Public)
.Where(x => x.Name == nameof(Queryable.Select))
Expand Down Expand Up @@ -59,7 +62,7 @@ bool TryGetReflectedExpression(MemberInfo memberInfo, [NotNullWhen(true)] out La
[return: NotNullIfNotNull(nameof(node))]
public Expression? Replace(Expression? node)
{
_disableRootRewrite = false;
_disableRootRewrite = _trackingByDefault;
_currentQueryProvider = null;
_entityType = null;

Expand Down Expand Up @@ -163,6 +166,15 @@ protected override Expression VisitMethodCall(MethodCallExpression node)
_disableRootRewrite = true;
}

if (methodInfo.Name == nameof(EntityFrameworkQueryableExtensions.AsTracking))
{
_disableRootRewrite = true;
}
if (methodInfo.Name is nameof(EntityFrameworkQueryableExtensions.AsNoTracking) or nameof(EntityFrameworkQueryableExtensions.AsNoTrackingWithIdentityResolution))
{
_disableRootRewrite = false;
}

if (TryGetReflectedExpression(methodInfo, out var reflectedExpression))
{
for (var parameterIndex = 0; parameterIndex < reflectedExpression.Parameters.Count; parameterIndex++)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using System.Linq;
using System.Threading.Tasks;
using EntityFrameworkCore.Projectables.FunctionalTests.Helpers;
using EntityFrameworkCore.Projectables.Infrastructure;
using Microsoft.EntityFrameworkCore;
using Xunit;

namespace EntityFrameworkCore.Projectables.FunctionalTests;

public class ChangeTrackerTests
{
public class SqliteSampleDbContext<TEntity> : DbContext
where TEntity : class
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite("Data Source=test.sqlite");
optionsBuilder.UseProjectables();
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TEntity>();
}
}

public record Entity
{
private static int _nextId = 1;
public const int Computed1DefaultValue = -1;
public int Id { get; set; } = _nextId++;
public string? Name { get; set; }

[Projectable(UseMemberBody = nameof(InternalComputed1))]
public int Computed1 { get; set; } = Computed1DefaultValue;
private int InternalComputed1 => Id;

[Projectable]
public int Computed2 => Id * 2;
}

[Fact]
public async Task CanQueryAndChangeTrackedEntities()
{
using var dbContext = new SqliteSampleDbContext<Entity>();
await dbContext.Database.EnsureDeletedAsync();
await dbContext.Database.EnsureCreatedAsync();
dbContext.Add(new Entity());
await dbContext.SaveChangesAsync();
dbContext.ChangeTracker.Clear();

var entity = await dbContext.Set<Entity>().AsTracking().FirstAsync();
var entityEntry = dbContext.ChangeTracker.Entries().Single();
Assert.Same(entityEntry.Entity, entity);
dbContext.Set<Entity>().Remove(entity);
await dbContext.SaveChangesAsync();
}

[Fact]
public async Task CanSaveChanges()
{
using var dbContext = new SqliteSampleDbContext<Entity>();
await dbContext.Database.EnsureDeletedAsync();
await dbContext.Database.EnsureCreatedAsync();
dbContext.Add(new Entity());
await dbContext.SaveChangesAsync();
dbContext.ChangeTracker.Clear();

var entity = await dbContext.Set<Entity>().AsTracking().FirstAsync();
entity.Name = "test";
await dbContext.SaveChangesAsync();
dbContext.ChangeTracker.Clear();
var entity2 = await dbContext.Set<Entity>().FirstAsync();
Assert.Equal("test", entity2.Name);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="ScenarioTests.XUnit" />
<PackageReference Include="Verify.Xunit" />
<PackageReference Include="xunit" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ public class SampleDbContext<TEntity> : DbContext
where TEntity : class
{
readonly CompatibilityMode _compatibilityMode;
readonly QueryTrackingBehavior _queryTrackingBehavior;

public SampleDbContext(CompatibilityMode compatibilityMode = CompatibilityMode.Full)
public SampleDbContext(CompatibilityMode compatibilityMode = CompatibilityMode.Full, QueryTrackingBehavior queryTrackingBehavior = QueryTrackingBehavior.TrackAll)
{
_compatibilityMode = compatibilityMode;
_queryTrackingBehavior = queryTrackingBehavior;
}

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
Expand All @@ -26,6 +28,7 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
optionsBuilder.UseProjectables(options => {
options.CompatibilityMode(_compatibilityMode); // Needed by our ComplexModelTests
});
optionsBuilder.UseQueryTrackingBehavior(_queryTrackingBehavior);
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SELECT [e].[Id], [e].[Id] * 5
FROM [Entity] AS [e]
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SELECT [e].[Id], [e].[Id] * 5
FROM [Entity] AS [e]
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SELECT [e].[Id]
FROM [Entity] AS [e]
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SELECT [e].[Id]
FROM [Entity] AS [e]
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SELECT [e].[Id]
FROM [Entity] AS [e]
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SELECT [e].[Id]
FROM [Entity] AS [e]
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,17 @@ public record Entity
[Fact]
public Task UseMemberPropertyQueryRootExpression()
{
using var dbContext = new SampleDbContext<Entity>();
using var dbContext = new SampleDbContext<Entity>(queryTrackingBehavior: QueryTrackingBehavior.NoTracking);

var query = dbContext.Set<Entity>();

return Verifier.Verify(query.ToQueryString());
}

[Fact]
public Task DontUseMemberPropertyQueryRootExpression()
{
using var dbContext = new SampleDbContext<Entity>(queryTrackingBehavior: QueryTrackingBehavior.TrackAll);

var query = dbContext.Set<Entity>();

Expand All @@ -47,5 +57,25 @@ public Task EntityRootSubqueryExpression()

return Verifier.Verify(query.ToQueryString());
}

[Fact]
public Task AsTrackingQueryRootExpression()
{
using var dbContext = new SampleDbContext<Entity>(queryTrackingBehavior: QueryTrackingBehavior.NoTracking);

var query = dbContext.Set<Entity>().AsTracking();

return Verifier.Verify(query.ToQueryString());
}

[Fact]
public Task AsNoTrackingQueryRootExpression()
{
using var dbContext = new SampleDbContext<Entity>(queryTrackingBehavior: QueryTrackingBehavior.TrackAll);

var query = dbContext.Set<Entity>().AsNoTracking();

return Verifier.Verify(query.ToQueryString());
}
}
}