Skip to content

Commit

Permalink
Query: Disallow tracking queries for owned entites without their owners
Browse files Browse the repository at this point in the history
Resolves #13036
  • Loading branch information
smitpatel committed Aug 7, 2019
1 parent f0dd703 commit c8e2522
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 2 deletions.
28 changes: 27 additions & 1 deletion src/EFCore/Query/ShapedQueryCompilingExpressionVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ private static readonly MethodInfo _startTrackingMethodInfo

private readonly IEntityMaterializerSource _entityMaterializerSource;
private readonly bool _trackQueryResults;
private readonly ISet<IEntityType> _visitedEntityTypes = new HashSet<IEntityType>();
private int _currentEntityIndex;

public EntityMaterializerInjectingExpressionVisitor(
Expand All @@ -188,7 +189,30 @@ public EntityMaterializerInjectingExpressionVisitor(
_trackQueryResults = trackQueryResults;
}

public Expression Inject(Expression expression) => Visit(expression);
public Expression Inject(Expression expression)
{
var result = Visit(expression);
if (_trackQueryResults)
{
bool containsOwner(IEntityType owner)
=> owner != null && (_visitedEntityTypes.Contains(owner) || containsOwner(owner.BaseType));

foreach (var entityType in _visitedEntityTypes)
{
if ((entityType.HasDefiningNavigation()
&& !containsOwner(entityType.DefiningEntityType))
|| (entityType.FindOwnership() is IForeignKey ownership
&& !containsOwner(ownership.PrincipalEntityType)))
{
throw new InvalidOperationException("A tracking query projects owned entity without corresponding owner in result. " +
"Owned entities cannot be tracked without their owner. " +
"Either include the owner entity in the result or make query non-tracking using AsNoTracking().");
}
}
}

return result;
}

protected override Expression VisitExtension(Expression extensionExpression)
=> extensionExpression is EntityShaperExpression entityShaperExpression
Expand Down Expand Up @@ -379,6 +403,8 @@ var discriminatorValue

if (_trackQueryResults)
{
_visitedEntityTypes.Add(entityType);

expressions.Add(
Expression.Assign(
entryVariable, Expression.Call(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,5 +180,10 @@ public override void Member_pushdown_chain_3_levels_deep()
public override void Member_pushdown_with_collection_navigation_in_the_middle()
{
}

// Invalid test due to projecting owned entity without owner in tracking query
public override void Entries_for_detached_entities_are_removed()
{
}
}
}
20 changes: 19 additions & 1 deletion test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,23 @@ public virtual void Query_loads_reference_nav_automatically_in_projection()
}
}

[ConditionalFact]
public virtual void Throw_for_owned_entities_without_owner_in_tracking_query()
{
using (var context = CreateContext())
{
var query = context.Set<OwnedPerson>().Select(e => e.PersonAddress);

var result = query.AsNoTracking().ToList();
Assert.Equal(4, result.Count);
Assert.Equal(0, context.ChangeTracker.Entries().Count());

Assert.Throws<InvalidOperationException>(() => query.AsTracking().ToList());

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

protected virtual DbContext CreateContext() => Fixture.CreateContext();

public abstract class OwnedQueryFixtureBase : SharedStoreFixtureBase<PoolableDbContext>
Expand Down Expand Up @@ -687,7 +704,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
b.HasData(
new Barton
{
Id = 1, Simple = "Simple"
Id = 1,
Simple = "Simple"
});

});
Expand Down

0 comments on commit c8e2522

Please sign in to comment.