Skip to content

Commit

Permalink
Set reference navigation properties as loaded when attaching
Browse files Browse the repository at this point in the history
Fixes #18142
  • Loading branch information
ajcvickers committed Oct 17, 2019
1 parent 0fd7de0 commit 5984ea2
Show file tree
Hide file tree
Showing 3 changed files with 338 additions and 0 deletions.
15 changes: 15 additions & 0 deletions src/EFCore/ChangeTracking/Internal/EntityGraphAttacher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ public virtual Task AttachGraphAsync(
private static bool PaintAction(
EntityEntryGraphNode<(EntityState TargetState, EntityState StoreGenTargetState, bool Force)> node)
{
SetReferenceLoaded(node);

var internalEntityEntry = node.GetInfrastructure();
if (internalEntityEntry.EntityState != EntityState.Detached)
{
Expand All @@ -103,6 +105,8 @@ private static async Task<bool> PaintActionAsync(
EntityEntryGraphNode<(EntityState TargetState, EntityState StoreGenTargetState, bool Force)> node,
CancellationToken cancellationToken)
{
SetReferenceLoaded(node);

var internalEntityEntry = node.GetInfrastructure();
if (internalEntityEntry.EntityState != EntityState.Detached)
{
Expand All @@ -123,5 +127,16 @@ await internalEntityEntry.SetEntityStateAsync(

return true;
}

private static void SetReferenceLoaded(
EntityEntryGraphNode<(EntityState TargetState, EntityState StoreGenTargetState, bool Force)> node)
{
var inboundNavigation = node.InboundNavigation;
if (inboundNavigation != null
&& !inboundNavigation.IsCollection())
{
node.SourceEntry.GetInfrastructure().SetIsLoaded(inboundNavigation);
}
}
}
}
182 changes: 182 additions & 0 deletions test/EFCore.Specification.Tests/LazyLoadProxyTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,188 @@ public abstract class LazyLoadProxyTestBase<TFixture> : IClassFixture<TFixture>

protected TFixture Fixture { get; }

[ConditionalTheory]
[InlineData(EntityState.Unchanged, false)]
[InlineData(EntityState.Modified, false)]
[InlineData(EntityState.Added, false)]
[InlineData(EntityState.Unchanged, true)]
[InlineData(EntityState.Modified, true)]
[InlineData(EntityState.Added, true)]
public virtual void Attached_references_to_principal_are_marked_as_loaded(EntityState state, bool lazy)
{
using (var context = CreateContext(lazy))
{
var parent = context.CreateProxy<Parent>();
parent.Id = 707;
parent.AlternateId = "Root";

var singlePkToPk = context.CreateProxy<SinglePkToPk>();
singlePkToPk.Id = 707;
parent.SinglePkToPk = singlePkToPk;

var single = context.CreateProxy<Single>();
single.Id = 21;
parent.Single = single;

var singleAk = context.CreateProxy<SingleAk>();
singleAk.Id = 42;
parent.SingleAk = singleAk;

var singleShadowFk = context.CreateProxy<SingleShadowFk>();
singleShadowFk.Id = 62;
parent.SingleShadowFk = singleShadowFk;

var singleCompositeKey = context.CreateProxy<SingleCompositeKey>();
singleCompositeKey.Id = 62;
parent.SingleCompositeKey = singleCompositeKey;

context.Attach(parent);

if (state != EntityState.Unchanged)
{
context.ChangeTracker.LazyLoadingEnabled = false;

context.Entry(parent.SinglePkToPk).State = state;
context.Entry(parent.Single).State = state;
context.Entry(parent.SingleAk).State = state;
context.Entry(parent.SingleShadowFk).State = state;
context.Entry(parent.SingleCompositeKey).State = state;
context.Entry(parent).State = state;

context.ChangeTracker.LazyLoadingEnabled = true;
}

Assert.True(context.Entry(parent).Reference(e => e.SinglePkToPk).IsLoaded);
Assert.True(context.Entry(parent).Reference(e => e.Single).IsLoaded);
Assert.True(context.Entry(parent).Reference(e => e.SingleAk).IsLoaded);
Assert.True(context.Entry(parent).Reference(e => e.SingleShadowFk).IsLoaded);
Assert.True(context.Entry(parent).Reference(e => e.SingleCompositeKey).IsLoaded);
}
}

[ConditionalTheory]
[InlineData(EntityState.Unchanged, false)]
[InlineData(EntityState.Modified, false)]
[InlineData(EntityState.Added, false)]
[InlineData(EntityState.Unchanged, true)]
[InlineData(EntityState.Modified, true)]
[InlineData(EntityState.Added, true)]
public virtual void Attached_references_to_dependents_are_marked_as_loaded(EntityState state, bool lazy)
{
using (var context = CreateContext(lazy))
{
var parent = context.CreateProxy<Parent>();
parent.Id = 707;
parent.AlternateId = "Root";

var singlePkToPk = context.CreateProxy<SinglePkToPk>();
singlePkToPk.Id = 707;
parent.SinglePkToPk = singlePkToPk;

var single = context.CreateProxy<Single>();
single.Id = 21;
parent.Single = single;

var singleAk = context.CreateProxy<SingleAk>();
singleAk.Id = 42;
parent.SingleAk = singleAk;

var singleShadowFk = context.CreateProxy<SingleShadowFk>();
singleShadowFk.Id = 62;
parent.SingleShadowFk = singleShadowFk;

var singleCompositeKey = context.CreateProxy<SingleCompositeKey>();
singleCompositeKey.Id = 62;
parent.SingleCompositeKey = singleCompositeKey;

context.Attach(parent);

if (state != EntityState.Unchanged)
{
context.ChangeTracker.LazyLoadingEnabled = false;

context.Entry(parent.SinglePkToPk).State = state;
context.Entry(parent.Single).State = state;
context.Entry(parent.SingleAk).State = state;
context.Entry(parent.SingleShadowFk).State = state;
context.Entry(parent.SingleCompositeKey).State = state;
context.Entry(parent).State = state;

context.ChangeTracker.LazyLoadingEnabled = true;
}

Assert.True(context.Entry(parent.SinglePkToPk).Reference(e => e.Parent).IsLoaded);
Assert.True(context.Entry(parent.Single).Reference(e => e.Parent).IsLoaded);
Assert.True(context.Entry(parent.SingleAk).Reference(e => e.Parent).IsLoaded);
Assert.True(context.Entry(parent.SingleShadowFk).Reference(e => e.Parent).IsLoaded);
Assert.True(context.Entry(parent.SingleCompositeKey).Reference(e => e.Parent).IsLoaded);
}
}

[ConditionalTheory]
[InlineData(EntityState.Unchanged, false)]
[InlineData(EntityState.Modified, false)]
[InlineData(EntityState.Added, false)]
[InlineData(EntityState.Unchanged, true)]
[InlineData(EntityState.Modified, true)]
[InlineData(EntityState.Added, true)]
public virtual void Attached_collections_are_not_marked_as_loaded(EntityState state, bool lazy)
{
using (var context = CreateContext(lazy))
{
var parent = context.CreateProxy<Parent>();
parent.Id = 707;
parent.AlternateId = "Root";

var child1 = context.CreateProxy<Child>();
child1.Id = 11;
var child2 = context.CreateProxy<Child>();
child2.Id = 12;
parent.Children = new List<Child> { child1, child2 };

var childAk1 = context.CreateProxy<ChildAk>();
childAk1.Id = 31;
var childAk2 = context.CreateProxy<ChildAk>();
childAk2.Id = 32;
parent.ChildrenAk = new List<ChildAk> { childAk1, childAk2 };

var childShadowFk1 = context.CreateProxy<ChildShadowFk>();
childShadowFk1.Id = 51;
var childShadowFk2 = context.CreateProxy<ChildShadowFk>();
childShadowFk2.Id = 52;
parent.ChildrenShadowFk = new List<ChildShadowFk> { childShadowFk1, childShadowFk2 };

var childCompositeKey1 = context.CreateProxy<ChildCompositeKey>();
childCompositeKey1.Id = 51;
var childCompositeKey2 = context.CreateProxy<ChildCompositeKey>();
childCompositeKey2.Id = 52;
parent.ChildrenCompositeKey = new List<ChildCompositeKey> { childCompositeKey1, childCompositeKey2 };

context.Attach(parent);

if (state != EntityState.Unchanged)
{
context.ChangeTracker.LazyLoadingEnabled = false;

foreach (var child in parent.Children.Cast<object>()
.Concat(parent.ChildrenAk)
.Concat(parent.ChildrenShadowFk)
.Concat(parent.ChildrenCompositeKey))
{
context.Entry(child).State = state;
}
context.Entry(parent).State = state;

context.ChangeTracker.LazyLoadingEnabled = true;
}

Assert.False(context.Entry(parent).Collection(e => e.Children).IsLoaded);
Assert.False(context.Entry(parent).Collection(e => e.ChildrenAk).IsLoaded);
Assert.False(context.Entry(parent).Collection(e => e.ChildrenShadowFk).IsLoaded);
Assert.False(context.Entry(parent).Collection(e => e.ChildrenCompositeKey).IsLoaded);
}
}

[ConditionalTheory]
[InlineData(EntityState.Unchanged, false, false)]
[InlineData(EntityState.Modified, false, false)]
Expand Down
141 changes: 141 additions & 0 deletions test/EFCore.Specification.Tests/LoadTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,147 @@ public abstract class LoadTestBase<TFixture> : IClassFixture<TFixture>

protected TFixture Fixture { get; }

[ConditionalTheory]
[InlineData(EntityState.Unchanged, false)]
[InlineData(EntityState.Modified, false)]
[InlineData(EntityState.Added, false)]
[InlineData(EntityState.Unchanged, true)]
[InlineData(EntityState.Modified, true)]
[InlineData(EntityState.Added, true)]
public virtual void Attached_references_to_principal_are_marked_as_loaded(EntityState state, bool lazy)
{
using (var context = CreateContext(lazy))
{
var parent = new Parent
{
Id = 707,
AlternateId = "Root",
SinglePkToPk = new SinglePkToPk { Id = 707 },
Single = new Single { Id = 21 },
SingleAk = new SingleAk { Id = 42 },
SingleShadowFk = new SingleShadowFk { Id = 62 },
SingleCompositeKey = new SingleCompositeKey { Id = 62 }
};

context.Attach(parent);

if (state != EntityState.Unchanged)
{
context.ChangeTracker.LazyLoadingEnabled = false;

context.Entry(parent.SinglePkToPk).State = state;
context.Entry(parent.Single).State = state;
context.Entry(parent.SingleAk).State = state;
context.Entry(parent.SingleShadowFk).State = state;
context.Entry(parent.SingleCompositeKey).State = state;
context.Entry(parent).State = state;

context.ChangeTracker.LazyLoadingEnabled = true;
}

Assert.True(context.Entry(parent).Reference(e => e.SinglePkToPk).IsLoaded);
Assert.True(context.Entry(parent).Reference(e => e.Single).IsLoaded);
Assert.True(context.Entry(parent).Reference(e => e.SingleAk).IsLoaded);
Assert.True(context.Entry(parent).Reference(e => e.SingleShadowFk).IsLoaded);
Assert.True(context.Entry(parent).Reference(e => e.SingleCompositeKey).IsLoaded);
}
}

[ConditionalTheory]
[InlineData(EntityState.Unchanged, false)]
[InlineData(EntityState.Modified, false)]
[InlineData(EntityState.Added, false)]
[InlineData(EntityState.Unchanged, true)]
[InlineData(EntityState.Modified, true)]
[InlineData(EntityState.Added, true)]
public virtual void Attached_references_to_dependents_are_marked_as_loaded(EntityState state, bool lazy)
{
using (var context = CreateContext(lazy))
{
var parent = new Parent
{
Id = 707,
AlternateId = "Root",
SinglePkToPk = new SinglePkToPk { Id = 707 },
Single = new Single { Id = 21 },
SingleAk = new SingleAk { Id = 42 },
SingleShadowFk = new SingleShadowFk { Id = 62 },
SingleCompositeKey = new SingleCompositeKey { Id = 62 }
};

context.Attach(parent);

if (state != EntityState.Unchanged)
{
context.ChangeTracker.LazyLoadingEnabled = false;

context.Entry(parent.SinglePkToPk).State = state;
context.Entry(parent.Single).State = state;
context.Entry(parent.SingleAk).State = state;
context.Entry(parent.SingleShadowFk).State = state;
context.Entry(parent.SingleCompositeKey).State = state;
context.Entry(parent).State = state;

context.ChangeTracker.LazyLoadingEnabled = true;
}

Assert.True(context.Entry(parent.SinglePkToPk).Reference(e => e.Parent).IsLoaded);
Assert.True(context.Entry(parent.Single).Reference(e => e.Parent).IsLoaded);
Assert.True(context.Entry(parent.SingleAk).Reference(e => e.Parent).IsLoaded);
Assert.True(context.Entry(parent.SingleShadowFk).Reference(e => e.Parent).IsLoaded);
Assert.True(context.Entry(parent.SingleCompositeKey).Reference(e => e.Parent).IsLoaded);
}
}

[ConditionalTheory]
[InlineData(EntityState.Unchanged, false)]
[InlineData(EntityState.Modified, false)]
[InlineData(EntityState.Added, false)]
[InlineData(EntityState.Unchanged, true)]
[InlineData(EntityState.Modified, true)]
[InlineData(EntityState.Added, true)]
public virtual void Attached_collections_are_not_marked_as_loaded(EntityState state, bool lazy)
{
using (var context = CreateContext(lazy))
{
var parent = new Parent
{
Id = 707,
AlternateId = "Root",
Children = new List<Child> { new Child { Id = 11 }, new Child { Id = 12 } },
ChildrenAk = new List<ChildAk> { new ChildAk { Id = 31 }, new ChildAk { Id = 32 } },
ChildrenShadowFk = new List<ChildShadowFk> { new ChildShadowFk { Id = 51 }, new ChildShadowFk { Id = 52 } },
ChildrenCompositeKey = new List<ChildCompositeKey>
{
new ChildCompositeKey { Id = 51 }, new ChildCompositeKey { Id = 52 }
}
};

context.Attach(parent);

if (state != EntityState.Unchanged)
{
context.ChangeTracker.LazyLoadingEnabled = false;

foreach (var child in parent.Children.Cast<object>()
.Concat(parent.ChildrenAk)
.Concat(parent.ChildrenShadowFk)
.Concat(parent.ChildrenCompositeKey))
{
context.Entry(child).State = state;
}
context.Entry(parent).State = state;

context.ChangeTracker.LazyLoadingEnabled = true;
}

Assert.False(context.Entry(parent).Collection(e => e.Children).IsLoaded);
Assert.False(context.Entry(parent).Collection(e => e.ChildrenAk).IsLoaded);
Assert.False(context.Entry(parent).Collection(e => e.ChildrenShadowFk).IsLoaded);
Assert.False(context.Entry(parent).Collection(e => e.ChildrenCompositeKey).IsLoaded);
}
}

[ConditionalTheory]
[InlineData(EntityState.Unchanged)]
[InlineData(EntityState.Modified)]
Expand Down

0 comments on commit 5984ea2

Please sign in to comment.