Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MemberEntry support for skip navigations #21989

Merged
merged 2 commits into from
Aug 11, 2020
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
105 changes: 105 additions & 0 deletions src/EFCore/ChangeTracking/CollectionEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,111 @@ private void LocalDetectChanges()
[param: CanBeNull] set => base.CurrentValue = value;
}

/// <summary>
/// Gets or sets a value indicating whether any of foreign key property values associated
/// with this navigation property have been modified and should be updated in the database
/// when <see cref="DbContext.SaveChanges()" /> is called.
/// </summary>
public override bool IsModified
{
get
{
var stateManager = InternalEntry.StateManager;

if (Metadata is ISkipNavigation skipNavigation)
{
if (InternalEntry.EntityState != EntityState.Unchanged
&& InternalEntry.EntityState != EntityState.Detached)
{
return true;
}

var joinEntityType = skipNavigation.JoinEntityType;
var foreignKey = skipNavigation.ForeignKey;
var inverseForeignKey = skipNavigation.Inverse.ForeignKey;
foreach (var joinEntry in stateManager.Entries)
ajcvickers marked this conversation as resolved.
Show resolved Hide resolved
{
if (joinEntry.EntityType == joinEntityType
&& stateManager.FindPrincipal(joinEntry, foreignKey) == InternalEntry
&& (joinEntry.EntityState == EntityState.Added
|| joinEntry.EntityState == EntityState.Deleted
|| foreignKey.Properties.Any(joinEntry.IsModified)
|| inverseForeignKey.Properties.Any(joinEntry.IsModified)
|| (stateManager.FindPrincipal(joinEntry, inverseForeignKey)?.EntityState == EntityState.Deleted)))
{
return true;
}
}
}
else
{
var navigationValue = CurrentValue;
if (navigationValue != null)
{
var targetEntityType = Metadata.TargetEntityType;
var foreignKey = ((INavigation)Metadata).ForeignKey;

foreach (var relatedEntity in navigationValue)
{
var relatedEntry = stateManager.TryGetEntry(relatedEntity, targetEntityType);

if (relatedEntry != null
&& (relatedEntry.EntityState == EntityState.Added
|| relatedEntry.EntityState == EntityState.Deleted
|| foreignKey.Properties.Any(relatedEntry.IsModified)))
{
return true;
}
}
}
}

return false;
}
set
{
var stateManager = InternalEntry.StateManager;

if (Metadata is ISkipNavigation skipNavigation)
{
var joinEntityType = skipNavigation.JoinEntityType;
var foreignKey = skipNavigation.ForeignKey;
foreach (var joinEntry in stateManager
.GetEntriesForState(added: !value, modified: !value, deleted: !value, unchanged: value).Where(
e => e.EntityType == joinEntityType
&& stateManager.FindPrincipal(e, foreignKey) == InternalEntry)
.ToList())
{
joinEntry.SetEntityState(value ? EntityState.Modified : EntityState.Unchanged);
}
}
else
{
var foreignKey = ((INavigation)Metadata).ForeignKey;
var navigationValue = CurrentValue;
if (navigationValue != null)
{
foreach (var relatedEntity in navigationValue)
{
var relatedEntry = InternalEntry.StateManager.TryGetEntry(relatedEntity, Metadata.TargetEntityType);
if (relatedEntry != null)
{
var anyNonPk = foreignKey.Properties.Any(p => !p.IsPrimaryKey());
foreach (var property in foreignKey.Properties)
{
if (anyNonPk
&& !property.IsPrimaryKey())
{
relatedEntry.SetPropertyModified(property, isModified: value, acceptChanges: false);
}
}
}
}
}
}
}
}

/// <summary>
/// <para>
/// Loads the entities referenced by this navigation property, unless <see cref="NavigationEntry.IsLoaded" />
Expand Down
7 changes: 5 additions & 2 deletions src/EFCore/ChangeTracking/EntityEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,8 @@ public virtual MemberEntry Member([NotNull] string propertyName)
return new PropertyEntry(InternalEntry, propertyName);
}

var navigation = InternalEntry.EntityType.FindNavigation(propertyName);
var navigation = (INavigationBase)InternalEntry.EntityType.FindNavigation(propertyName)
?? InternalEntry.EntityType.FindSkipNavigation(propertyName);
if (navigation != null)
{
return navigation.IsCollection
Expand Down Expand Up @@ -170,7 +171,9 @@ public virtual NavigationEntry Navigation([NotNull] string propertyName)
{
Check.NotEmpty(propertyName, nameof(propertyName));

var navigation = InternalEntry.EntityType.FindNavigation(propertyName);
var navigation = (INavigationBase)InternalEntry.EntityType.FindNavigation(propertyName)
?? InternalEntry.EntityType.FindSkipNavigation(propertyName);

if (navigation != null)
{
return navigation.IsCollection
Expand Down
4 changes: 2 additions & 2 deletions src/EFCore/ChangeTracking/Internal/NavigationFixer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -847,7 +847,7 @@ private void InitialFixup(
try
{
_inFixup = false;
joinEntry.SetEntityState(EntityState.Added);
joinEntry.SetEntityState(setModified ? EntityState.Added : EntityState.Unchanged);
}
finally
{
Expand Down Expand Up @@ -892,7 +892,7 @@ private void DelayedFixup(
try
{
_inFixup = false;
joinEntry.SetEntityState(EntityState.Added);
joinEntry.SetEntityState(setModified ? EntityState.Added : EntityState.Unchanged);
}
finally
{
Expand Down
106 changes: 7 additions & 99 deletions src/EFCore/ChangeTracking/NavigationEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
Expand Down Expand Up @@ -47,14 +46,16 @@ protected NavigationEntry([NotNull] InternalEntityEntry internalEntry, [NotNull]
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
[EntityFrameworkInternal]
protected NavigationEntry([NotNull] InternalEntityEntry internalEntry, [NotNull] INavigation navigation)
protected NavigationEntry([NotNull] InternalEntityEntry internalEntry, [NotNull] INavigationBase navigation)
: base(internalEntry, navigation)
{
}

private static INavigation GetNavigation(InternalEntityEntry internalEntry, string name, bool collection)
private static INavigationBase GetNavigation(InternalEntityEntry internalEntry, string name, bool collection)
{
var navigation = internalEntry.EntityType.FindNavigation(name);
var navigation = (INavigationBase)internalEntry.EntityType.FindNavigation(name)
?? internalEntry.EntityType.FindSkipNavigation(name);

if (navigation == null)
{
if (internalEntry.EntityType.FindProperty(name) != null)
Expand Down Expand Up @@ -177,103 +178,10 @@ public virtual bool IsLoaded
private IEntityFinder TargetFinder
=> InternalEntry.StateManager.CreateEntityFinder(Metadata.TargetEntityType);

/// <summary>
/// Gets or sets a value indicating whether any of foreign key property values associated
/// with this navigation property have been modified and should be updated in the database
/// when <see cref="DbContext.SaveChanges()" /> is called.
/// </summary>
public override bool IsModified
{
get
{
if (Metadata.IsOnDependent)
{
return AnyFkPropertiesModified(InternalEntry);
}

var navigationValue = CurrentValue;

return navigationValue != null
&& (Metadata.IsCollection
? ((IEnumerable)navigationValue).OfType<object>().Any(CollectionContainsNewOrChangedRelationships)
: AnyFkPropertiesModified(navigationValue));
}
set
{
if (Metadata.IsOnDependent)
{
SetFkPropertiesModified(InternalEntry, value);
}
else
{
var navigationValue = CurrentValue;
if (navigationValue != null)
{
if (Metadata.IsCollection)
{
foreach (var relatedEntity in (IEnumerable)navigationValue)
{
SetFkPropertiesModified(relatedEntity, value);
}
}
else
{
SetFkPropertiesModified(navigationValue, value);
}
}
}
}
}

private bool CollectionContainsNewOrChangedRelationships(object relatedEntity)
{
var relatedEntry = InternalEntry.StateManager.TryGetEntry(relatedEntity, Metadata.TargetEntityType);

return relatedEntry != null
&& (relatedEntry.EntityState == EntityState.Added
|| relatedEntry.EntityState == EntityState.Deleted
|| Metadata.ForeignKey.Properties.Any(relatedEntry.IsModified));
}

private bool AnyFkPropertiesModified(object relatedEntity)
{
var relatedEntry = InternalEntry.StateManager.TryGetEntry(relatedEntity, Metadata.TargetEntityType);

return relatedEntry != null
&& (relatedEntry.EntityState == EntityState.Added
|| relatedEntry.EntityState == EntityState.Deleted
|| Metadata.ForeignKey.Properties.Any(relatedEntry.IsModified));
}

private void SetFkPropertiesModified(object relatedEntity, bool modified)
{
var relatedEntry = InternalEntry.StateManager.TryGetEntry(relatedEntity, Metadata.TargetEntityType);
if (relatedEntry != null)
{
SetFkPropertiesModified(relatedEntry, modified);
}
}

private void SetFkPropertiesModified(InternalEntityEntry internalEntityEntry, bool modified)
{
var anyNonPk = Metadata.ForeignKey.Properties.Any(p => !p.IsPrimaryKey());
foreach (var property in Metadata.ForeignKey.Properties)
{
if (anyNonPk
&& !property.IsPrimaryKey())
{
internalEntityEntry.SetPropertyModified(property, isModified: modified, acceptChanges: false);
}
}
}

private bool AnyFkPropertiesModified(InternalEntityEntry internalEntityEntry)
=> Metadata.ForeignKey.Properties.Any(internalEntityEntry.IsModified);

/// <summary>
/// Gets the metadata that describes the facets of this property and how it maps to the database.
/// </summary>
public new virtual INavigation Metadata
=> (INavigation)base.Metadata;
public new virtual INavigationBase Metadata
=> (INavigationBase)base.Metadata;
}
}
Loading