diff --git a/src/EFCore.Abstractions/Infrastructure/ILazyLoader.cs b/src/EFCore.Abstractions/Infrastructure/ILazyLoader.cs
index c6d14bca713..ad0ec5aa5ca 100644
--- a/src/EFCore.Abstractions/Infrastructure/ILazyLoader.cs
+++ b/src/EFCore.Abstractions/Infrastructure/ILazyLoader.cs
@@ -34,6 +34,17 @@ void SetLoaded(
[CallerMemberName] string navigationName = "",
bool loaded = true);
+ ///
+ /// Gets whether or not the given navigation as known to be completely loaded or known to be
+ /// no longer completely loaded.
+ ///
+ /// The entity on which the navigation property is located.
+ /// The navigation property name.
+ /// if the navigation is known to be loaded.
+ bool IsLoaded(
+ object entity,
+ [CallerMemberName] string navigationName = "");
+
///
/// Loads a navigation property if it has not already been loaded.
///
diff --git a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs
index 7e235b36e0d..ad84019646b 100644
--- a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs
+++ b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs
@@ -474,22 +474,23 @@ private void SetServiceProperties(EntityState oldState, EntityState newState)
{
foreach (var serviceProperty in EntityType.GetServiceProperties())
{
- this[serviceProperty]
- = serviceProperty
+ this[serviceProperty] = (this[serviceProperty] is IInjectableService injectableService
+ ? injectableService.Attaching(Context, Entity, injectableService)
+ : null)
+ ?? serviceProperty
.ParameterBinding
- .ServiceDelegate(
- new MaterializationContext(
- ValueBuffer.Empty,
- Context),
- EntityType,
- Entity);
+ .ServiceDelegate(new MaterializationContext(ValueBuffer.Empty, Context), EntityType, Entity);
}
}
else if (newState == EntityState.Detached)
{
foreach (var serviceProperty in EntityType.GetServiceProperties())
{
- this[serviceProperty] = null;
+ if (!(this[serviceProperty] is IInjectableService detachable)
+ || detachable.Detaching(Context, Entity))
+ {
+ this[serviceProperty] = null;
+ }
}
}
}
@@ -846,7 +847,8 @@ private T ReadShadowValue(int shadowIndex)
private static readonly MethodInfo ReadOriginalValueMethod
= typeof(InternalEntityEntry).GetTypeInfo().GetDeclaredMethod(nameof(ReadOriginalValue))!;
- [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2060",
+ [UnconditionalSuppressMessage(
+ "ReflectionAnalysis", "IL2060",
Justification = "MakeGenericMethod wrapper, see https://github.com/dotnet/linker/issues/2482")]
internal static MethodInfo MakeReadOriginalValueMethod(Type type)
=> ReadOriginalValueMethod.MakeGenericMethod(type);
@@ -858,7 +860,8 @@ private T ReadOriginalValue(IProperty property, int originalValueIndex)
private static readonly MethodInfo ReadRelationshipSnapshotValueMethod
= typeof(InternalEntityEntry).GetTypeInfo().GetDeclaredMethod(nameof(ReadRelationshipSnapshotValue))!;
- [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2060",
+ [UnconditionalSuppressMessage(
+ "ReflectionAnalysis", "IL2060",
Justification = "MakeGenericMethod wrapper, see https://github.com/dotnet/linker/issues/2482")]
internal static MethodInfo MakeReadRelationshipSnapshotValueMethod(Type type)
=> ReadRelationshipSnapshotValueMethod.MakeGenericMethod(type);
@@ -867,7 +870,8 @@ internal static MethodInfo MakeReadRelationshipSnapshotValueMethod(Type type)
private T ReadRelationshipSnapshotValue(IPropertyBase propertyBase, int relationshipSnapshotIndex)
=> _relationshipsSnapshot.GetValue(this, propertyBase, relationshipSnapshotIndex);
- [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2060",
+ [UnconditionalSuppressMessage(
+ "ReflectionAnalysis", "IL2060",
Justification = "MakeGenericMethod wrapper, see https://github.com/dotnet/linker/issues/2482")]
internal static MethodInfo MakeReadStoreGeneratedValueMethod(Type type)
=> ReadStoreGeneratedValueMethod.MakeGenericMethod(type);
@@ -882,7 +886,8 @@ private T ReadStoreGeneratedValue(int storeGeneratedIndex)
private static readonly MethodInfo ReadTemporaryValueMethod
= typeof(InternalEntityEntry).GetTypeInfo().GetDeclaredMethod(nameof(ReadTemporaryValue))!;
- [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2060",
+ [UnconditionalSuppressMessage(
+ "ReflectionAnalysis", "IL2060",
Justification = "MakeGenericMethod wrapper, see https://github.com/dotnet/linker/issues/2482")]
internal static MethodInfo MakeReadTemporaryValueMethod(Type type)
=> ReadTemporaryValueMethod.MakeGenericMethod(type);
@@ -895,7 +900,8 @@ private static readonly MethodInfo GetCurrentValueMethod
= typeof(InternalEntityEntry).GetTypeInfo().GetDeclaredMethods(nameof(GetCurrentValue)).Single(
m => m.IsGenericMethod);
- [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2060",
+ [UnconditionalSuppressMessage(
+ "ReflectionAnalysis", "IL2060",
Justification = "MakeGenericMethod wrapper, see https://github.com/dotnet/linker/issues/2482")]
internal static MethodInfo MakeGetCurrentValueMethod(Type type)
=> GetCurrentValueMethod.MakeGenericMethod(type);
@@ -2001,11 +2007,14 @@ public void SetIsLoaded(INavigationBase navigation, bool loaded = true)
CoreStrings.ReferenceMustBeLoaded(navigation.Name, navigation.DeclaringEntityType.DisplayName()));
}
- _stateData.FlagProperty(navigation.GetIndex(), PropertyFlag.IsLoaded, isFlagged: loaded);
-
- foreach (var lazyLoaderProperty in EntityType.GetServiceProperties().Where(p => p.ClrType == typeof(ILazyLoader)))
+ var lazyLoader = GetLazyLoader();
+ if (lazyLoader != null)
{
- ((ILazyLoader?)this[lazyLoaderProperty])?.SetLoaded(Entity, navigation.Name, loaded);
+ lazyLoader.SetLoaded(Entity, navigation.Name, loaded);
+ }
+ else
+ {
+ _stateData.FlagProperty(navigation.GetIndex(), PropertyFlag.IsLoaded, isFlagged: loaded);
}
}
@@ -2016,7 +2025,14 @@ public void SetIsLoaded(INavigationBase navigation, bool loaded = true)
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public bool IsLoaded(INavigationBase navigation)
- => _stateData.IsPropertyFlagged(navigation.GetIndex(), PropertyFlag.IsLoaded);
+ => GetLazyLoader()?.IsLoaded(Entity, navigation.Name)
+ ?? _stateData.IsPropertyFlagged(navigation.GetIndex(), PropertyFlag.IsLoaded);
+
+ private ILazyLoader? GetLazyLoader()
+ {
+ var lazyLoaderProperty = EntityType.GetServiceProperties().FirstOrDefault(p => p.ClrType == typeof(ILazyLoader));
+ return lazyLoaderProperty != null ? (ILazyLoader?)this[lazyLoaderProperty] : null;
+ }
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
diff --git a/src/EFCore/Diagnostics/CoreLoggerExtensions.cs b/src/EFCore/Diagnostics/CoreLoggerExtensions.cs
index e41f0fb569c..04a1f0eba81 100644
--- a/src/EFCore/Diagnostics/CoreLoggerExtensions.cs
+++ b/src/EFCore/Diagnostics/CoreLoggerExtensions.cs
@@ -1108,7 +1108,7 @@ private static string ExecutionStrategyRetrying(EventDefinitionBase definition,
/// The name of the navigation property.
public static void LazyLoadOnDisposedContextWarning(
this IDiagnosticsLogger diagnostics,
- DbContext context,
+ DbContext? context,
object entityType,
string navigationName)
{
@@ -1188,7 +1188,7 @@ private static string NavigationLazyLoading(EventDefinitionBase definition, Even
/// The name of the navigation property.
public static void DetachedLazyLoadingWarning(
this IDiagnosticsLogger diagnostics,
- DbContext context,
+ DbContext? context,
object entityType,
string navigationName)
{
diff --git a/src/EFCore/Diagnostics/LazyLoadingEventData.cs b/src/EFCore/Diagnostics/LazyLoadingEventData.cs
index 3135451a0cd..4610d29e2a7 100644
--- a/src/EFCore/Diagnostics/LazyLoadingEventData.cs
+++ b/src/EFCore/Diagnostics/LazyLoadingEventData.cs
@@ -16,13 +16,13 @@ public class LazyLoadingEventData : DbContextEventData
///
/// The event definition.
/// A delegate that generates a log message for this event.
- /// The current .
+ /// The current , or if it is no longer available.
/// The entity instance on which lazy loading was initiated.
/// The navigation property name of the relationship to be loaded.
public LazyLoadingEventData(
EventDefinitionBase eventDefinition,
Func messageGenerator,
- DbContext context,
+ DbContext? context,
object entity,
string navigationPropertyName)
: base(eventDefinition, messageGenerator, context)
diff --git a/src/EFCore/Infrastructure/Internal/LazyLoader.cs b/src/EFCore/Infrastructure/Internal/LazyLoader.cs
index f8bfc0b3302..b75c3bb19f4 100644
--- a/src/EFCore/Infrastructure/Internal/LazyLoader.cs
+++ b/src/EFCore/Infrastructure/Internal/LazyLoader.cs
@@ -3,6 +3,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
+using Microsoft.EntityFrameworkCore.Internal;
namespace Microsoft.EntityFrameworkCore.Infrastructure.Internal;
@@ -12,10 +13,12 @@ namespace Microsoft.EntityFrameworkCore.Infrastructure.Internal;
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
-public class LazyLoader : ILazyLoader
+public class LazyLoader : ILazyLoader, IInjectableService
{
private bool _disposed;
+ private bool _detached;
private IDictionary? _loadedStates;
+ private List<(object Entity, string NavigationName)>? _isLoading;
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -47,6 +50,17 @@ public virtual void SetLoaded(
_loadedStates[navigationName] = loaded;
}
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual bool IsLoaded(object entity, string navigationName = "")
+ => _loadedStates != null
+ && _loadedStates.TryGetValue(navigationName, out var loaded)
+ && loaded;
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -61,7 +75,7 @@ public virtual void SetLoaded(
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- protected virtual DbContext Context { get; }
+ protected virtual DbContext? Context { get; set; }
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -75,16 +89,28 @@ public virtual void Load(object entity, [CallerMemberName] string navigationName
Check.NotNull(entity, nameof(entity));
Check.NotEmpty(navigationName, nameof(navigationName));
- if (ShouldLoad(entity, navigationName, out var entry))
+ var navEntry = (entity, navigationName);
+ if (!(_isLoading ??= new List<(object Entity, string NavigationName)>()).Contains(navEntry))
{
try
{
- entry.Load();
+ _isLoading.Add(navEntry);
+ if (ShouldLoad(entity, navigationName, out var entry))
+ {
+ try
+ {
+ entry.Load();
+ }
+ catch
+ {
+ entry.IsLoaded = false;
+ throw;
+ }
+ }
}
- catch
+ finally
{
- SetLoaded(entity, navigationName, false);
- throw;
+ _isLoading.Remove(navEntry);
}
}
}
@@ -103,53 +129,49 @@ public virtual async Task LoadAsync(
Check.NotNull(entity, nameof(entity));
Check.NotEmpty(navigationName, nameof(navigationName));
- if (ShouldLoad(entity, navigationName, out var entry))
+ var navEntry = (entity, navigationName);
+ if (!(_isLoading ??= new List<(object Entity, string NavigationName)>()).Contains(navEntry))
{
try
{
- await entry.LoadAsync(cancellationToken).ConfigureAwait(false);
+ _isLoading.Add(navEntry);
+ if (ShouldLoad(entity, navigationName, out var entry))
+ {
+ try
+ {
+ await entry.LoadAsync(cancellationToken).ConfigureAwait(false);
+ }
+ catch
+ {
+ entry.IsLoaded = false;
+ throw;
+ }
+ }
}
- catch
+ finally
{
- SetLoaded(entity, navigationName, false);
- throw;
+ _isLoading.Remove(navEntry);
}
}
}
private bool ShouldLoad(object entity, string navigationName, [NotNullWhen(true)] out NavigationEntry? navigationEntry)
{
- if (_loadedStates != null
- && _loadedStates.TryGetValue(navigationName, out var loaded)
- && loaded)
+ if (!_detached && !IsLoaded(entity, navigationName))
{
- navigationEntry = null;
- return false;
- }
-
- if (_disposed)
- {
- Logger.LazyLoadOnDisposedContextWarning(Context, entity, navigationName);
- }
- else if (Context.ChangeTracker.LazyLoadingEnabled)
- {
- // Set early to avoid recursive loading overflow
- SetLoaded(entity, navigationName, loaded: true);
-
- var entityEntry = Context.Entry(entity); // Will use local-DetectChanges, if enabled.
- var tempNavigationEntry = entityEntry.Navigation(navigationName);
-
- if (entityEntry.State == EntityState.Detached)
+ if (_disposed)
{
- Logger.DetachedLazyLoadingWarning(Context, entity, navigationName);
+ Logger.LazyLoadOnDisposedContextWarning(Context, entity, navigationName);
}
- else if (!tempNavigationEntry.IsLoaded)
+ else if (Context!.ChangeTracker.LazyLoadingEnabled) // Check again because the nav may be loaded without the loader knowing
{
- Logger.NavigationLazyLoading(Context, entity, navigationName);
-
- navigationEntry = tempNavigationEntry;
+ navigationEntry = Context.Entry(entity).Navigation(navigationName); // Will use local-DetectChanges, if enabled.
+ if (!navigationEntry.IsLoaded)
+ {
+ Logger.NavigationLazyLoading(Context, entity, navigationName);
- return true;
+ return true;
+ }
}
}
@@ -164,5 +186,35 @@ private bool ShouldLoad(object entity, string navigationName, [NotNullWhen(true)
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public virtual void Dispose()
- => _disposed = true;
+ {
+ Context = null;
+ _disposed = true;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual bool Detaching(DbContext context, object entity)
+ {
+ _detached = true;
+ Dispose();
+ return false;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual IInjectableService? Attaching(DbContext context, object entity, IInjectableService? existingService)
+ {
+ _disposed = false;
+ _detached = false;
+ Context = context;
+ return this;
+ }
}
diff --git a/src/EFCore/Internal/EntityFinder.cs b/src/EFCore/Internal/EntityFinder.cs
index c54c7d9751d..e74ab47e506 100644
--- a/src/EFCore/Internal/EntityFinder.cs
+++ b/src/EFCore/Internal/EntityFinder.cs
@@ -419,16 +419,37 @@ private bool TryGetByForeignKey(
///
public virtual void Load(INavigation navigation, InternalEntityEntry entry)
{
- if (entry.EntityState == EntityState.Detached)
- {
- throw new InvalidOperationException(CoreStrings.CannotLoadDetached(navigation.Name, entry.EntityType.DisplayName()));
- }
-
var keyValues = GetLoadValues(navigation, entry);
// Short-circuit for any null key values for perf and because of #6129
if (keyValues != null)
{
- Query(navigation, keyValues).Load();
+ var queryable = Query(navigation, keyValues, entry);
+ if (entry.EntityState == EntityState.Detached)
+ {
+ var inverse = navigation.Inverse;
+ if (navigation.IsCollection)
+ {
+ foreach (var loaded in queryable)
+ {
+ Fixup(entry, navigation, inverse, loaded);
+ }
+ }
+ else
+ {
+ Fixup(entry, navigation, inverse, queryable.FirstOrDefault());
+ }
+ }
+ else
+ {
+ if (navigation.IsCollection)
+ {
+ queryable.Load();
+ }
+ else
+ {
+ _ = queryable.FirstOrDefault();
+ }
+ }
}
entry.SetIsLoaded(navigation);
@@ -445,22 +466,69 @@ public virtual async Task LoadAsync(
InternalEntityEntry entry,
CancellationToken cancellationToken = default)
{
- if (entry.EntityState == EntityState.Detached)
- {
- throw new InvalidOperationException(CoreStrings.CannotLoadDetached(navigation.Name, entry.EntityType.DisplayName()));
- }
-
- // Short-circuit for any null key values for perf and because of #6129
var keyValues = GetLoadValues(navigation, entry);
+ // Short-circuit for any null key values for perf and because of #6129
if (keyValues != null)
{
- await Query(navigation, keyValues).LoadAsync(cancellationToken)
- .ConfigureAwait(false);
+ var queryable = Query(navigation, keyValues, entry);
+ if (entry.EntityState == EntityState.Detached)
+ {
+ var inverse = navigation.Inverse;
+ if (navigation.IsCollection)
+ {
+ await foreach (var loaded in queryable.AsAsyncEnumerable().WithCancellation(cancellationToken).ConfigureAwait(false))
+ {
+ Fixup(entry, navigation, inverse, loaded);
+ }
+ }
+ else
+ {
+ Fixup(
+ entry, navigation, inverse,
+ await queryable.FirstOrDefaultAsync(cancellationToken: cancellationToken).ConfigureAwait(false));
+ }
+ }
+ else
+ {
+ if (navigation.IsCollection)
+ {
+ await Query(navigation, keyValues, entry).LoadAsync(cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ _ = await queryable.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
+ }
+ }
}
entry.SetIsLoaded(navigation);
}
+ private void Fixup(InternalEntityEntry parentEntry, INavigation beingLoaded, INavigation? inverse, object? loaded)
+ {
+ SetValue(parentEntry, beingLoaded, loaded);
+
+ if (inverse != null && loaded != null)
+ {
+ SetValue(_stateManager.GetOrCreateEntry(loaded), inverse, parentEntry.Entity);
+ }
+
+ void SetValue(InternalEntityEntry entry, INavigation navigation, object? value)
+ {
+ if (navigation.IsCollection)
+ {
+ if (value != null)
+ {
+ entry.AddToCollection(navigation, value, forMaterialization: true);
+ }
+ }
+ else
+ {
+ entry.SetProperty(navigation, value, isMaterialization: true, setModified: false);
+ }
+ }
+ }
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -469,11 +537,6 @@ await Query(navigation, keyValues).LoadAsync(cancellationToken)
///
public virtual IQueryable Query(INavigation navigation, InternalEntityEntry entry)
{
- if (entry.EntityState == EntityState.Detached)
- {
- throw new InvalidOperationException(CoreStrings.CannotLoadDetached(navigation.Name, entry.EntityType.DisplayName()));
- }
-
var keyValues = GetLoadValues(navigation, entry);
// Short-circuit for any null key values for perf and because of #6129
if (keyValues == null)
@@ -483,7 +546,7 @@ public virtual IQueryable Query(INavigation navigation, InternalEntityE
return _queryRoot.Where(e => false);
}
- return Query(navigation, keyValues);
+ return Query(navigation, keyValues, entry);
}
///
@@ -528,8 +591,11 @@ public virtual IQueryable Query(INavigation navigation, InternalEntityE
.Select(BuildProjection(entityType));
}
- private IQueryable Query(INavigation navigation, object[] keyValues)
- => _queryRoot.Where(BuildLambda(GetLoadProperties(navigation), new ValueBuffer(keyValues))).AsTracking();
+ private IQueryable Query(INavigation navigation, object[] keyValues, InternalEntityEntry entry)
+ {
+ var queryable = _queryRoot.Where(BuildLambda(GetLoadProperties(navigation), new ValueBuffer(keyValues)));
+ return entry.EntityState == EntityState.Detached ? queryable.AsNoTrackingWithIdentityResolution() : queryable.AsTracking();
+ }
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -547,10 +613,18 @@ IQueryable IEntityFinder.Query(INavigation navigation, InternalEntityEntry entry
: navigation.ForeignKey.PrincipalKey.Properties;
var values = new object[properties.Count];
+ var detached = entry.EntityState == EntityState.Detached;
for (var i = 0; i < values.Length; i++)
{
- var value = entry[properties[i]];
+ var property = properties[i];
+ if (property.IsShadowProperty() && (detached || entry.IsUnknown(property)))
+ {
+ throw new InvalidOperationException(
+ CoreStrings.CannotLoadDetachedShadow(navigation.Name, entry.EntityType.DisplayName()));
+ }
+
+ var value = entry[property];
if (value == null)
{
return null;
diff --git a/src/EFCore/Internal/IInjectableService.cs b/src/EFCore/Internal/IInjectableService.cs
new file mode 100644
index 00000000000..a3c3e86d890
--- /dev/null
+++ b/src/EFCore/Internal/IInjectableService.cs
@@ -0,0 +1,54 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.EntityFrameworkCore.Internal;
+
+///
+///
+/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+/// the same compatibility standards as public APIs. It may be changed or removed without notice in
+/// any release. You should only use it directly in your code with extreme caution and knowing that
+/// doing so can result in application failures when updating to a new Entity Framework Core release.
+///
+///
+/// Implemented by service property types to notify services instances of lifecycle changes.
+///
+///
+public interface IInjectableService
+{
+ ///
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ ///
+ /// Called when the entity holding this service is being detached from a DbContext.
+ ///
+ ///
+ /// The instance.
+ /// The entity instance that is being detached.
+ /// if the service property should be set to .
+ bool Detaching(DbContext context, object entity);
+
+ ///
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ ///
+ /// Called when an entity that needs this is being attached to a DbContext.
+ ///
+ ///
+ /// The instance.
+ /// The entity instance that is being attached.
+ ///
+ /// The existing instance of this service being held by the entity instance, or if there
+ /// is no existing instance.
+ ///
+ /// The service instance to use, or if a new instance should be created.
+ IInjectableService? Attaching(DbContext context, object entity, IInjectableService? existingService);
+}
diff --git a/src/EFCore/Internal/ManyToManyLoader.cs b/src/EFCore/Internal/ManyToManyLoader.cs
index 55f819eea51..dfd9a1caa01 100644
--- a/src/EFCore/Internal/ManyToManyLoader.cs
+++ b/src/EFCore/Internal/ManyToManyLoader.cs
@@ -17,6 +17,7 @@ public class ManyToManyLoader : ICollectionLoader
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -27,6 +28,7 @@ public class ManyToManyLoader : ICollectionLoader
@@ -42,7 +44,20 @@ public virtual void Load(InternalEntityEntry entry)
// Short-circuit for any null key values for perf and because of #6129
if (keyValues != null)
{
- Query(entry.Context, keyValues).Load();
+ var queryable = Query(entry.Context, keyValues, entry);
+
+ if (entry.EntityState == EntityState.Detached)
+ {
+ var inverse = _skipNavigation.Inverse;
+ foreach (var loaded in queryable)
+ {
+ Fixup(entry, loaded);
+ }
+ }
+ else
+ {
+ queryable.Load();
+ }
}
entry.SetIsLoaded(_skipNavigation);
@@ -61,12 +76,43 @@ public virtual async Task LoadAsync(InternalEntityEntry entry, CancellationToken
// Short-circuit for any null key values for perf and because of #6129
if (keyValues != null)
{
- await Query(entry.Context, keyValues).LoadAsync(cancellationToken).ConfigureAwait(false);
+ var queryable = Query(entry.Context, keyValues, entry);
+
+ if (entry.EntityState == EntityState.Detached)
+ {
+ var inverse = _skipNavigation.Inverse;
+ foreach (var loaded in queryable)
+ {
+ Fixup(entry, loaded);
+ }
+ }
+ else
+ {
+ await queryable.LoadAsync(cancellationToken).ConfigureAwait(false);
+ }
}
entry.SetIsLoaded(_skipNavigation);
}
+ private void Fixup(InternalEntityEntry parentEntry, object? loaded)
+ {
+ SetValue(parentEntry, _skipNavigation, loaded);
+
+ if (_inverseSkipNavigation != null && loaded != null)
+ {
+ SetValue(parentEntry.StateManager.GetOrCreateEntry(loaded), _inverseSkipNavigation, parentEntry.Entity);
+ }
+
+ void SetValue(InternalEntityEntry entry, ISkipNavigation navigation, object? value)
+ {
+ if (value != null)
+ {
+ entry.AddToCollection(navigation, value, forMaterialization: true);
+ }
+ }
+ }
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -89,20 +135,24 @@ public virtual IQueryable Query(InternalEntityEntry entry)
return queryRoot.Where(e => false);
}
- return Query(context, keyValues);
+ return Query(context, keyValues, entry);
}
private object[]? PrepareForLoad(InternalEntityEntry entry)
{
- if (entry.EntityState == EntityState.Detached)
- {
- throw new InvalidOperationException(CoreStrings.CannotLoadDetached(_skipNavigation.Name, entry.EntityType.DisplayName()));
- }
-
var properties = _skipNavigation.ForeignKey.PrincipalKey.Properties;
var values = new object[properties.Count];
+ var detached = entry.EntityState == EntityState.Detached;
+
for (var i = 0; i < values.Length; i++)
{
+ var property = properties[i];
+ if (property.IsShadowProperty() && (detached || entry.IsUnknown(property)))
+ {
+ throw new InvalidOperationException(
+ CoreStrings.CannotLoadDetachedShadow(_skipNavigation.Name, entry.EntityType.DisplayName()));
+ }
+
var value = entry[properties[i]];
if (value == null)
{
@@ -124,9 +174,7 @@ public virtual IQueryable Query(InternalEntityEntry entry)
IQueryable ICollectionLoader.Query(InternalEntityEntry entry)
=> Query(entry);
- private IQueryable Query(
- DbContext context,
- object[] keyValues)
+ private IQueryable Query(DbContext context, object[] keyValues, InternalEntityEntry entry)
{
var loadProperties = _skipNavigation.ForeignKey.PrincipalKey.Properties;
@@ -143,12 +191,12 @@ private IQueryable Query(
? context.Set(_skipNavigation.DeclaringEntityType.Name)
: context.Set();
- return queryRoot
- .AsTracking()
+ var queryable = queryRoot
.Where(BuildWhereLambda(loadProperties, new ValueBuffer(keyValues)))
.SelectMany(BuildSelectManyLambda(_skipNavigation))
- .NotQuiteInclude(BuildIncludeLambda(_skipNavigation.Inverse, loadProperties, new ValueBuffer(keyValues)))
- .AsQueryable();
+ .NotQuiteInclude(BuildIncludeLambda(_skipNavigation.Inverse, loadProperties, new ValueBuffer(keyValues)));
+
+ return entry.EntityState == EntityState.Detached ? queryable.AsNoTrackingWithIdentityResolution() : queryable.AsTracking();
}
private static Expression>> BuildIncludeLambda(
diff --git a/src/EFCore/Metadata/Conventions/ServicePropertyDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/ServicePropertyDiscoveryConvention.cs
index 32851190b7a..8ad9ab723bb 100644
--- a/src/EFCore/Metadata/Conventions/ServicePropertyDiscoveryConvention.cs
+++ b/src/EFCore/Metadata/Conventions/ServicePropertyDiscoveryConvention.cs
@@ -62,21 +62,33 @@ private void Process(IConventionEntityTypeBuilder entityTypeBuilder)
{
var entityType = entityTypeBuilder.Metadata;
var model = entityType.Model;
- foreach (var propertyInfo in entityType.GetRuntimeProperties().Values)
+ DiscoverServiceProperties(entityType.GetRuntimeProperties().Values);
+ DiscoverServiceProperties(entityType.GetRuntimeFields().Values);
+
+ void DiscoverServiceProperties(IEnumerable members)
{
- if (!entityTypeBuilder.CanHaveServiceProperty(propertyInfo))
+ foreach (var memberInfo in members)
{
- continue;
- }
+ if (!entityTypeBuilder.CanHaveServiceProperty(memberInfo))
+ {
+ continue;
+ }
- var factory = Dependencies.MemberClassifier.FindServicePropertyCandidateBindingFactory(propertyInfo, model);
- if (factory == null)
- {
- continue;
- }
+ var factory = Dependencies.MemberClassifier.FindServicePropertyCandidateBindingFactory(memberInfo, model);
+ if (factory == null)
+ {
+ continue;
+ }
+
+ var memberType = memberInfo.GetMemberType();
+ if (entityType.GetServiceProperties().Any(p => p.ClrType == memberType))
+ {
+ continue;
+ }
- entityTypeBuilder.ServiceProperty(propertyInfo)?.HasParameterBinding(
- (ServiceParameterBinding)factory.Bind(entityType, propertyInfo.PropertyType, propertyInfo.GetSimpleMemberName()));
+ entityTypeBuilder.ServiceProperty(memberInfo)?.HasParameterBinding(
+ (ServiceParameterBinding)factory.Bind(entityType, memberType, memberInfo.GetSimpleMemberName()));
+ }
}
}
}
diff --git a/src/EFCore/Metadata/Internal/IMemberClassifier.cs b/src/EFCore/Metadata/Internal/IMemberClassifier.cs
index 56038a75e06..6256195145f 100644
--- a/src/EFCore/Metadata/Internal/IMemberClassifier.cs
+++ b/src/EFCore/Metadata/Internal/IMemberClassifier.cs
@@ -56,5 +56,5 @@ public interface IMemberClassifier
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- IParameterBindingFactory? FindServicePropertyCandidateBindingFactory(PropertyInfo propertyInfo, IConventionModel model);
+ IParameterBindingFactory? FindServicePropertyCandidateBindingFactory(MemberInfo memberInfo, IConventionModel model);
}
diff --git a/src/EFCore/Metadata/Internal/MemberClassifier.cs b/src/EFCore/Metadata/Internal/MemberClassifier.cs
index f2d23b5bfdd..16e44d695f9 100644
--- a/src/EFCore/Metadata/Internal/MemberClassifier.cs
+++ b/src/EFCore/Metadata/Internal/MemberClassifier.cs
@@ -186,15 +186,15 @@ public virtual bool IsCandidatePrimitiveProperty(PropertyInfo propertyInfo, ICon
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public virtual IParameterBindingFactory? FindServicePropertyCandidateBindingFactory(
- PropertyInfo propertyInfo,
+ MemberInfo memberInfo,
IConventionModel model)
{
- if (!propertyInfo.IsCandidateProperty(publicOnly: false))
+ if (!memberInfo.IsCandidateProperty(publicOnly: false))
{
return null;
}
- var type = propertyInfo.PropertyType;
+ var type = memberInfo.GetMemberType();
var configurationType = ((Model)model).Configuration?.GetConfigurationType(type);
if (configurationType != TypeConfigurationType.ServiceProperty)
{
@@ -203,13 +203,13 @@ public virtual bool IsCandidatePrimitiveProperty(PropertyInfo propertyInfo, ICon
return null;
}
- if (propertyInfo.IsCandidateProperty()
- && _typeMappingSource.FindMapping(propertyInfo.GetMemberType(), (IModel)model) != null)
+ if (memberInfo.IsCandidateProperty()
+ && _typeMappingSource.FindMapping(memberInfo.GetMemberType(), (IModel)model) != null)
{
return null;
}
}
- return _parameterBindingFactories.FindFactory(type, propertyInfo.GetSimpleMemberName());
+ return _parameterBindingFactories.FindFactory(type, memberInfo.GetSimpleMemberName());
}
}
diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs
index bbedd200447..4f603db2b2e 100644
--- a/src/EFCore/Properties/CoreStrings.Designer.cs
+++ b/src/EFCore/Properties/CoreStrings.Designer.cs
@@ -257,11 +257,11 @@ public static string CannotCreateValueGenerator(object? generatorType, object? m
generatorType, method);
///
- /// The navigation '{1_entityType}.{0_navigation}' cannot be loaded because the entity is not being tracked. Navigations can only be loaded for tracked entities.
+ /// The navigation '{1_entityType}.{0_navigation}' cannot be loaded because one or more of the key or foreign key properties are shadow properties and the entity is not being tracked. Relationships using shadow values can only be loaded for tracked entities.
///
- public static string CannotLoadDetached(object? navigation, object? entityType)
+ public static string CannotLoadDetachedShadow(object? navigation, object? entityType)
=> string.Format(
- GetString("CannotLoadDetached", "0_navigation", "1_entityType"),
+ GetString("CannotLoadDetachedShadow", "0_navigation", "1_entityType"),
navigation, entityType);
///
@@ -3600,7 +3600,7 @@ public static EventDefinition LogInvalidIncludePath(IDiagnostics
}
///
- /// An attempt was made to lazy-load navigation '{entityType}.{navigation}' after the associated DbContext was disposed.
+ /// An attempt was made to lazy-load navigation '{entityType}.{navigation}' after the associated DbContext was disposed or returned to the pool, or the entity was explicitly detached from the context.
///
public static EventDefinition LogLazyLoadOnDisposedContext(IDiagnosticsLogger logger)
{
diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx
index 1c041ce9dd9..92cacbca108 100644
--- a/src/EFCore/Properties/CoreStrings.resx
+++ b/src/EFCore/Properties/CoreStrings.resx
@@ -204,8 +204,8 @@
Cannot create an instance of value generator type '{generatorType}'. Ensure that the type can be instantiated and has a parameterless constructor, or use the overload of '{method}' that accepts a delegate.
-
- The navigation '{1_entityType}.{0_navigation}' cannot be loaded because the entity is not being tracked. Navigations can only be loaded for tracked entities.
+
+ The navigation '{1_entityType}.{0_navigation}' cannot be loaded because one or more of the key or foreign key properties are shadow properties and the entity is not being tracked. Relationships using shadow values can only be loaded for tracked entities.
The type '{type}' cannot be marked as a non-shared type since a shared type entity type with this CLR type exists in the model.
@@ -810,7 +810,7 @@
Error CoreEventId.InvalidIncludePathError object object
- An attempt was made to lazy-load navigation '{entityType}.{navigation}' after the associated DbContext was disposed.
+ An attempt was made to lazy-load navigation '{entityType}.{navigation}' after the associated DbContext was disposed or returned to the pool, or the entity was explicitly detached from the context.
Warning CoreEventId.LazyLoadOnDisposedContextWarning string string
diff --git a/src/Shared/PropertyInfoExtensions.cs b/src/Shared/PropertyInfoExtensions.cs
index e1202f20ef4..32dc9037308 100644
--- a/src/Shared/PropertyInfoExtensions.cs
+++ b/src/Shared/PropertyInfoExtensions.cs
@@ -15,13 +15,16 @@ internal static class PropertyInfoExtensions
public static bool IsStatic(this PropertyInfo property)
=> (property.GetMethod ?? property.SetMethod)!.IsStatic;
- public static bool IsCandidateProperty(this PropertyInfo propertyInfo, bool needsWrite = true, bool publicOnly = true)
- => !propertyInfo.IsStatic()
+ public static bool IsCandidateProperty(this MemberInfo memberInfo, bool needsWrite = true, bool publicOnly = true)
+ => memberInfo is PropertyInfo propertyInfo
+ ? !propertyInfo.IsStatic()
&& propertyInfo.CanRead
&& (!needsWrite || propertyInfo.FindSetterProperty() != null)
&& propertyInfo.GetMethod != null
&& (!publicOnly || propertyInfo.GetMethod.IsPublic)
- && propertyInfo.GetIndexParameters().Length == 0;
+ && propertyInfo.GetIndexParameters().Length == 0
+ : memberInfo is FieldInfo { IsStatic: false } fieldInfo
+ && (!publicOnly || fieldInfo.IsPublic);
public static bool IsIndexerProperty(this PropertyInfo propertyInfo)
{
diff --git a/test/EFCore.InMemory.FunctionalTests/Query/WarningsTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/WarningsTest.cs
index 4acaadd7cca..9b67bc6cd95 100644
--- a/test/EFCore.InMemory.FunctionalTests/Query/WarningsTest.cs
+++ b/test/EFCore.InMemory.FunctionalTests/Query/WarningsTest.cs
@@ -207,6 +207,8 @@ public void Lazy_loading_is_logged_only_when_actually_loading()
.GenerateMessage("WarningAsErrorEntity", "Nav"),
loggerFactory.Log.Select(l => l.Message));
+ var entityEntry = context.Entry(entity);
+ var loaded = entityEntry.Navigation("Nav").IsLoaded;
loggerFactory.Clear();
Assert.NotNull(entity.Nav);
Assert.DoesNotContain(
diff --git a/test/EFCore.Specification.Tests/F1FixtureBase.cs b/test/EFCore.Specification.Tests/F1FixtureBase.cs
index 8532c43715b..95fa9edb64a 100644
--- a/test/EFCore.Specification.Tests/F1FixtureBase.cs
+++ b/test/EFCore.Specification.Tests/F1FixtureBase.cs
@@ -234,10 +234,7 @@ private static void ConfigureConstructorBinding(
if (loaderField != null)
{
- var loaderProperty = typeof(TLoaderEntity) == typeof(TEntity)
- ? entityType.AddServiceProperty(loaderField!, ConfigurationSource.Explicit)
- : entityType.FindServiceProperty(loaderField.Name)!;
-
+ var loaderProperty = entityType.FindServiceProperty(loaderField.Name)!;
parameterBindings.Add(new DependencyInjectionParameterBinding(typeof(ILazyLoader), typeof(ILazyLoader), loaderProperty));
}
diff --git a/test/EFCore.Specification.Tests/FieldsOnlyLoadTestBase.cs b/test/EFCore.Specification.Tests/FieldsOnlyLoadTestBase.cs
index de7fbf3fac1..8d717ab3216 100644
--- a/test/EFCore.Specification.Tests/FieldsOnlyLoadTestBase.cs
+++ b/test/EFCore.Specification.Tests/FieldsOnlyLoadTestBase.cs
@@ -3517,8 +3517,8 @@ public virtual async Task Load_one_to_one_reference_to_dependent_using_Query_sha
public virtual async Task Load_many_to_one_reference_to_principal_null_FK_shadow_fk(EntityState state, bool async)
{
using var context = CreateContext();
- var child = context.Attach(
- new ChildShadowFk { Id = 767 }).Entity;
+ var child = context.Attach(new ChildShadowFk { Id = 767 }).Entity;
+ context.Entry(child).Property("ParentId").CurrentValue = null;
ClearLog();
@@ -3555,8 +3555,8 @@ public virtual async Task Load_many_to_one_reference_to_principal_null_FK_shadow
public virtual async Task Load_one_to_one_reference_to_principal_null_FK_shadow_fk(EntityState state, bool async)
{
using var context = CreateContext();
- var single = context.Attach(
- new SingleShadowFk { Id = 767 }).Entity;
+ var single = context.Attach(new SingleShadowFk { Id = 767 }).Entity;
+ context.Entry(single).Property("ParentId").CurrentValue = null;
ClearLog();
@@ -3594,8 +3594,8 @@ public virtual async Task Load_one_to_one_reference_to_principal_null_FK_shadow_
public virtual async Task Load_many_to_one_reference_to_principal_using_Query_null_FK_shadow_fk(EntityState state, bool async)
{
using var context = CreateContext();
- var child = context.Attach(
- new ChildShadowFk { Id = 767 }).Entity;
+ var child = context.Attach(new ChildShadowFk { Id = 767 }).Entity;
+ context.Entry(child).Property("ParentId").CurrentValue = null;
ClearLog();
@@ -3629,8 +3629,8 @@ public virtual async Task Load_many_to_one_reference_to_principal_using_Query_nu
public virtual async Task Load_one_to_one_reference_to_principal_using_Query_null_FK_shadow_fk(EntityState state, bool async)
{
using var context = CreateContext();
- var single = context.Attach(
- new SingleShadowFk { Id = 767 }).Entity;
+ var single = context.Attach(new SingleShadowFk { Id = 767 }).Entity;
+ context.Entry(single).Property("ParentId").CurrentValue = null;
ClearLog();
@@ -4224,20 +4224,14 @@ public virtual async Task Load_collection_for_detached_throws(bool async, bool n
context.Entry(parent).State = EntityState.Detached;
}
- Assert.Equal(
- CoreStrings.CannotLoadDetached(nameof(Parent.Children), nameof(Parent)),
- (await Assert.ThrowsAsync(
- async () =>
- {
- if (async)
- {
- await collectionEntry.LoadAsync();
- }
- else
- {
- collectionEntry.Load();
- }
- })).Message);
+ if (async)
+ {
+ await collectionEntry.LoadAsync();
+ }
+ else
+ {
+ collectionEntry.Load();
+ }
}
[ConditionalTheory]
@@ -4257,20 +4251,14 @@ public virtual async Task Load_collection_using_string_for_detached_throws(bool
context.Entry(parent).State = EntityState.Detached;
}
- Assert.Equal(
- CoreStrings.CannotLoadDetached(nameof(Parent.Children), nameof(Parent)),
- (await Assert.ThrowsAsync(
- async () =>
- {
- if (async)
- {
- await collectionEntry.LoadAsync();
- }
- else
- {
- collectionEntry.Load();
- }
- })).Message);
+ if (async)
+ {
+ await collectionEntry.LoadAsync();
+ }
+ else
+ {
+ collectionEntry.Load();
+ }
}
[ConditionalTheory]
@@ -4290,20 +4278,14 @@ public virtual async Task Load_collection_with_navigation_for_detached_throws(bo
context.Entry(parent).State = EntityState.Detached;
}
- Assert.Equal(
- CoreStrings.CannotLoadDetached(nameof(Parent.Children), nameof(Parent)),
- (await Assert.ThrowsAsync(
- async () =>
- {
- if (async)
- {
- await collectionEntry.LoadAsync();
- }
- else
- {
- collectionEntry.Load();
- }
- })).Message);
+ if (async)
+ {
+ await collectionEntry.LoadAsync();
+ }
+ else
+ {
+ collectionEntry.Load();
+ }
}
[ConditionalTheory]
@@ -4323,20 +4305,14 @@ public virtual async Task Load_reference_to_principal_for_detached_throws(bool a
context.Entry(child).State = EntityState.Detached;
}
- Assert.Equal(
- CoreStrings.CannotLoadDetached(nameof(Child.Parent), nameof(Child)),
- (await Assert.ThrowsAsync(
- async () =>
- {
- if (async)
- {
- await referenceEntry.LoadAsync();
- }
- else
- {
- referenceEntry.Load();
- }
- })).Message);
+ if (async)
+ {
+ await referenceEntry.LoadAsync();
+ }
+ else
+ {
+ referenceEntry.Load();
+ }
}
[ConditionalTheory]
@@ -4356,20 +4332,14 @@ public virtual async Task Load_reference_with_navigation_to_principal_for_detach
context.Entry(child).State = EntityState.Detached;
}
- Assert.Equal(
- CoreStrings.CannotLoadDetached(nameof(Child.Parent), nameof(Child)),
- (await Assert.ThrowsAsync(
- async () =>
- {
- if (async)
- {
- await referenceEntry.LoadAsync();
- }
- else
- {
- referenceEntry.Load();
- }
- })).Message);
+ if (async)
+ {
+ await referenceEntry.LoadAsync();
+ }
+ else
+ {
+ referenceEntry.Load();
+ }
}
[ConditionalTheory]
@@ -4389,20 +4359,14 @@ public virtual async Task Load_reference_using_string_to_principal_for_detached_
context.Entry(child).State = EntityState.Detached;
}
- Assert.Equal(
- CoreStrings.CannotLoadDetached(nameof(Child.Parent), nameof(Child)),
- (await Assert.ThrowsAsync(
- async () =>
- {
- if (async)
- {
- await referenceEntry.LoadAsync();
- }
- else
- {
- referenceEntry.Load();
- }
- })).Message);
+ if (async)
+ {
+ await referenceEntry.LoadAsync();
+ }
+ else
+ {
+ referenceEntry.Load();
+ }
}
[ConditionalTheory]
@@ -4422,20 +4386,14 @@ public virtual async Task Load_reference_to_dependent_for_detached_throws(bool a
context.Entry(parent).State = EntityState.Detached;
}
- Assert.Equal(
- CoreStrings.CannotLoadDetached(nameof(Parent.Single), nameof(Parent)),
- (await Assert.ThrowsAsync(
- async () =>
- {
- if (async)
- {
- await referenceEntry.LoadAsync();
- }
- else
- {
- referenceEntry.Load();
- }
- })).Message);
+ if (async)
+ {
+ await referenceEntry.LoadAsync();
+ }
+ else
+ {
+ referenceEntry.Load();
+ }
}
[ConditionalTheory]
@@ -4455,20 +4413,14 @@ public virtual async Task Load_reference_to_dependent_with_navigation_for_detach
context.Entry(parent).State = EntityState.Detached;
}
- Assert.Equal(
- CoreStrings.CannotLoadDetached(nameof(Parent.Single), nameof(Parent)),
- (await Assert.ThrowsAsync(
- async () =>
- {
- if (async)
- {
- await referenceEntry.LoadAsync();
- }
- else
- {
- referenceEntry.Load();
- }
- })).Message);
+ if (async)
+ {
+ await referenceEntry.LoadAsync();
+ }
+ else
+ {
+ referenceEntry.Load();
+ }
}
[ConditionalTheory]
@@ -4488,20 +4440,14 @@ public virtual async Task Load_reference_to_dependent_using_string_for_detached_
context.Entry(parent).State = EntityState.Detached;
}
- Assert.Equal(
- CoreStrings.CannotLoadDetached(nameof(Parent.Single), nameof(Parent)),
- (await Assert.ThrowsAsync(
- async () =>
- {
- if (async)
- {
- await referenceEntry.LoadAsync();
- }
- else
- {
- referenceEntry.Load();
- }
- })).Message);
+ if (async)
+ {
+ await referenceEntry.LoadAsync();
+ }
+ else
+ {
+ referenceEntry.Load();
+ }
}
[ConditionalTheory]
@@ -4519,9 +4465,7 @@ public virtual void Query_collection_for_detached_throws(bool noTracking)
context.Entry(parent).State = EntityState.Detached;
}
- Assert.Equal(
- CoreStrings.CannotLoadDetached(nameof(Parent.Children), nameof(Parent)),
- Assert.Throws(() => collectionEntry.Query()).Message);
+ var query = collectionEntry.Query();
}
[ConditionalTheory]
@@ -4539,9 +4483,7 @@ public virtual void Query_collection_using_string_for_detached_throws(bool noTra
context.Entry(parent).State = EntityState.Detached;
}
- Assert.Equal(
- CoreStrings.CannotLoadDetached(nameof(Parent.Children), nameof(Parent)),
- Assert.Throws(() => collectionEntry.Query()).Message);
+ var query = collectionEntry.Query();
}
[ConditionalTheory]
@@ -4559,9 +4501,7 @@ public virtual void Query_collection_with_navigation_for_detached_throws(bool no
context.Entry(parent).State = EntityState.Detached;
}
- Assert.Equal(
- CoreStrings.CannotLoadDetached(nameof(Parent.Children), nameof(Parent)),
- Assert.Throws(() => collectionEntry.Query()).Message);
+ var query = collectionEntry.Query();
}
[ConditionalTheory]
@@ -4579,9 +4519,7 @@ public virtual void Query_reference_to_principal_for_detached_throws(bool noTrac
context.Entry(child).State = EntityState.Detached;
}
- Assert.Equal(
- CoreStrings.CannotLoadDetached(nameof(Child.Parent), nameof(Child)),
- Assert.Throws(() => referenceEntry.Query()).Message);
+ var query = referenceEntry.Query();
}
[ConditionalTheory]
@@ -4599,9 +4537,7 @@ public virtual void Query_reference_with_navigation_to_principal_for_detached_th
context.Entry(child).State = EntityState.Detached;
}
- Assert.Equal(
- CoreStrings.CannotLoadDetached(nameof(Child.Parent), nameof(Child)),
- Assert.Throws(() => referenceEntry.Query()).Message);
+ var query = referenceEntry.Query();
}
[ConditionalTheory]
@@ -4619,9 +4555,7 @@ public virtual void Query_reference_using_string_to_principal_for_detached_throw
context.Entry(child).State = EntityState.Detached;
}
- Assert.Equal(
- CoreStrings.CannotLoadDetached(nameof(Child.Parent), nameof(Child)),
- Assert.Throws(() => referenceEntry.Query()).Message);
+ var query = referenceEntry.Query();
}
[ConditionalTheory]
@@ -4639,9 +4573,7 @@ public virtual void Query_reference_to_dependent_for_detached_throws(bool noTrac
context.Entry(parent).State = EntityState.Detached;
}
- Assert.Equal(
- CoreStrings.CannotLoadDetached(nameof(Parent.Single), nameof(Parent)),
- Assert.Throws(() => referenceEntry.Query()).Message);
+ var query = referenceEntry.Query();
}
[ConditionalTheory]
@@ -4659,9 +4591,7 @@ public virtual void Query_reference_to_dependent_with_navigation_for_detached_th
context.Entry(parent).State = EntityState.Detached;
}
- Assert.Equal(
- CoreStrings.CannotLoadDetached(nameof(Parent.Single), nameof(Parent)),
- Assert.Throws(() => referenceEntry.Query()).Message);
+ var query = referenceEntry.Query();
}
[ConditionalTheory]
@@ -4679,9 +4609,7 @@ public virtual void Query_reference_to_dependent_using_string_for_detached_throw
context.Entry(parent).State = EntityState.Detached;
}
- Assert.Equal(
- CoreStrings.CannotLoadDetached(nameof(Parent.Single), nameof(Parent)),
- Assert.Throws(() => referenceEntry.Query()).Message);
+ var query = referenceEntry.Query();
}
protected class Parent
diff --git a/test/EFCore.Specification.Tests/LazyLoadProxyTestBase.cs b/test/EFCore.Specification.Tests/LazyLoadProxyTestBase.cs
index b24ac9e6d42..76e81817fc5 100644
--- a/test/EFCore.Specification.Tests/LazyLoadProxyTestBase.cs
+++ b/test/EFCore.Specification.Tests/LazyLoadProxyTestBase.cs
@@ -52,19 +52,24 @@ public virtual void Detected_dependent_reference_navigation_changes_are_detected
[ConditionalTheory] // Issue #13138
[InlineData(EntityState.Unchanged)]
+ [InlineData(EntityState.Added)]
[InlineData(EntityState.Modified)]
[InlineData(EntityState.Deleted)]
+ [InlineData(EntityState.Detached)]
public virtual void Lazy_load_one_to_one_reference_with_recursive_property(EntityState state)
{
- using (var context = CreateContext(lazyLoadingEnabled: true))
+ using (var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached))
{
var child = context.Set().Single();
var referenceEntry = context.Entry(child).Reference(e => e.Parent);
- context.Entry(child).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(child).State = state;
+ }
- Assert.True(referenceEntry.IsLoaded);
+ Assert.Equal(state != EntityState.Detached, referenceEntry.IsLoaded);
Assert.NotNull(child.Parent);
@@ -72,14 +77,17 @@ public virtual void Lazy_load_one_to_one_reference_with_recursive_property(Entit
context.ChangeTracker.LazyLoadingEnabled = false;
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
+ Assert.Equal(state == EntityState.Detached ? 0 : 2, context.ChangeTracker.Entries().Count());
- var parent = context.ChangeTracker.Entries().Single().Entity;
+ if (state != EntityState.Detached)
+ {
+ var parent = context.ChangeTracker.Entries().Single().Entity;
- Assert.Equal(parent.Id, child.IdLoadedFromParent);
+ Assert.Equal(parent.Id, child.IdLoadedFromParent);
- Assert.Same(parent, child.Parent);
- Assert.Same(child, parent.WithRecursiveProperty);
+ Assert.Same(parent, child.Parent);
+ Assert.Same(child, parent.WithRecursiveProperty);
+ }
}
}
@@ -262,21 +270,27 @@ public virtual void Attached_collections_are_not_marked_as_loaded(EntityState st
[ConditionalTheory]
[InlineData(EntityState.Unchanged, false, false)]
+ [InlineData(EntityState.Added, false, false)]
[InlineData(EntityState.Modified, false, false)]
[InlineData(EntityState.Deleted, false, false)]
+ [InlineData(EntityState.Detached, false, false)]
[InlineData(EntityState.Unchanged, true, false)]
+ [InlineData(EntityState.Added, true, false)]
[InlineData(EntityState.Modified, true, false)]
[InlineData(EntityState.Deleted, true, false)]
+ [InlineData(EntityState.Detached, true, false)]
[InlineData(EntityState.Unchanged, true, true)]
+ [InlineData(EntityState.Added, true, true)]
[InlineData(EntityState.Modified, true, true)]
[InlineData(EntityState.Deleted, true, true)]
+ [InlineData(EntityState.Detached, true, true)]
public virtual void Lazy_load_collection(EntityState state, bool useAttach, bool useDetach)
{
Parent parent = null;
if (useAttach)
{
- using (var context = CreateContext(lazyLoadingEnabled: true))
+ using (var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached))
{
parent = context.Set().Single();
@@ -286,24 +300,17 @@ public virtual void Lazy_load_collection(EntityState state, bool useAttach, bool
}
}
- if (useDetach)
+ if (state != EntityState.Detached && useDetach)
{
Assert.Null(parent.Children);
}
else
{
- Assert.Equal(
- CoreStrings.WarningAsErrorTemplate(
- CoreEventId.LazyLoadOnDisposedContextWarning.ToString(),
- CoreResources.LogLazyLoadOnDisposedContext(new TestLogger())
- .GenerateMessage("MotherProxy", "Children"),
- "CoreEventId.LazyLoadOnDisposedContextWarning"),
- Assert.Throws(
- () => parent.Children).Message);
+ AssertDisposed(() => parent.Children, "MotherProxy", "Children");
}
}
- using (var context = CreateContext(lazyLoadingEnabled: true))
+ using (var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached))
{
var changeDetector = (ChangeDetectorProxy)context.GetService();
@@ -313,45 +320,61 @@ public virtual void Lazy_load_collection(EntityState state, bool useAttach, bool
var collectionEntry = context.Entry(parent).Collection(e => e.Children);
- context.Entry(parent).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(parent).State = state;
+ }
Assert.False(collectionEntry.IsLoaded);
changeDetector.DetectChangesCalled = false;
- Assert.NotNull(parent.Children);
+ if (state == EntityState.Detached && useAttach)
+ {
+ AssertDisposed(() => parent.Children, "MotherProxy", "Children");
+ }
+ else
+ {
+ Assert.NotNull(parent.Children);
- Assert.False(changeDetector.DetectChangesCalled);
+ Assert.False(changeDetector.DetectChangesCalled);
- Assert.True(collectionEntry.IsLoaded);
+ //Assert.True(collectionEntry.IsLoaded);
- Assert.All(parent.Children.Select(e => e.Parent), c => Assert.Same(parent, c));
+ Assert.All(parent.Children.Select(e => e.Parent), c => Assert.Same(parent, c));
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
+ RecordLog();
+ context.ChangeTracker.LazyLoadingEnabled = false;
- Assert.Equal(2, parent.Children.Count());
- Assert.Equal(3, context.ChangeTracker.Entries().Count());
+ Assert.Equal(2, parent.Children.Count());
+ Assert.Equal(state == EntityState.Detached ? 0 : 3, context.ChangeTracker.Entries().Count());
+ }
}
}
[ConditionalTheory]
[InlineData(EntityState.Unchanged, false, false)]
+ [InlineData(EntityState.Added, false, false)]
[InlineData(EntityState.Modified, false, false)]
[InlineData(EntityState.Deleted, false, false)]
+ [InlineData(EntityState.Detached, false, false)]
[InlineData(EntityState.Unchanged, true, false)]
+ [InlineData(EntityState.Added, true, false)]
[InlineData(EntityState.Modified, true, false)]
[InlineData(EntityState.Deleted, true, false)]
+ [InlineData(EntityState.Detached, true, false)]
[InlineData(EntityState.Unchanged, true, true)]
+ [InlineData(EntityState.Added, true, true)]
[InlineData(EntityState.Modified, true, true)]
[InlineData(EntityState.Deleted, true, true)]
+ [InlineData(EntityState.Detached, true, true)]
public virtual void Lazy_load_many_to_one_reference_to_principal(EntityState state, bool useAttach, bool useDetach)
{
Child child = null;
if (useAttach)
{
- using (var context = CreateContext(lazyLoadingEnabled: true))
+ using (var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached))
{
child = context.Set().Single(e => e.Id == 12);
@@ -361,24 +384,17 @@ public virtual void Lazy_load_many_to_one_reference_to_principal(EntityState sta
}
}
- if (useDetach)
+ if (state != EntityState.Detached && useDetach)
{
Assert.Null(child.Parent);
}
else
{
- Assert.Equal(
- CoreStrings.WarningAsErrorTemplate(
- CoreEventId.LazyLoadOnDisposedContextWarning.ToString(),
- CoreResources.LogLazyLoadOnDisposedContext(new TestLogger())
- .GenerateMessage("ChildProxy", "Parent"),
- "CoreEventId.LazyLoadOnDisposedContextWarning"),
- Assert.Throws(
- () => child.Parent).Message);
+ AssertDisposed(() => child.Parent, "ChildProxy", "Parent");
}
}
- using (var context = CreateContext(lazyLoadingEnabled: true))
+ using (var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached))
{
var changeDetector = (ChangeDetectorProxy)context.GetService();
@@ -388,62 +404,86 @@ public virtual void Lazy_load_many_to_one_reference_to_principal(EntityState sta
var referenceEntry = context.Entry(child).Reference(e => e.Parent);
- context.Entry(child).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(child).State = state;
+ }
Assert.False(referenceEntry.IsLoaded);
changeDetector.DetectChangesCalled = false;
- if (state == EntityState.Deleted)
+ if (state == EntityState.Detached && useAttach)
{
- Assert.Null(child.Parent);
+ AssertDisposed(() => child.Parent, "ChildProxy", "Parent");
}
else
{
- Assert.NotNull(child.Parent);
- }
+ if (state == EntityState.Deleted)
+ {
+ Assert.Null(child.Parent);
+ }
+ else
+ {
+ Assert.NotNull(child.Parent);
+ }
- Assert.False(changeDetector.DetectChangesCalled);
+ Assert.False(changeDetector.DetectChangesCalled);
- Assert.True(referenceEntry.IsLoaded);
+ Assert.True(referenceEntry.IsLoaded);
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
+ RecordLog();
+ context.ChangeTracker.LazyLoadingEnabled = false;
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
+ Assert.Equal(state == EntityState.Detached ? 0 : 2, context.ChangeTracker.Entries().Count());
- var parent = context.ChangeTracker.Entries().Single().Entity;
+ if (state != EntityState.Deleted)
+ {
+ Assert.Contains(child, child.Parent!.Children);
+ }
- if (state == EntityState.Deleted)
- {
- Assert.Null(child.Parent);
- Assert.Null(parent.Children);
- }
- else
- {
- Assert.Same(parent, child.Parent);
- Assert.Same(child, parent.Children.Single());
+ if (state != EntityState.Detached)
+ {
+ var parent = context.ChangeTracker.Entries().Single().Entity;
+
+ if (state == EntityState.Deleted)
+ {
+ Assert.Null(child.Parent);
+ Assert.Null(parent.Children);
+ }
+ else
+ {
+ Assert.Same(parent, child.Parent);
+ Assert.Same(child, parent.Children.Single());
+ }
+ }
}
}
}
[ConditionalTheory]
[InlineData(EntityState.Unchanged, false, false)]
+ [InlineData(EntityState.Added, false, false)]
[InlineData(EntityState.Modified, false, false)]
[InlineData(EntityState.Deleted, false, false)]
+ [InlineData(EntityState.Detached, false, false)]
[InlineData(EntityState.Unchanged, true, false)]
+ [InlineData(EntityState.Added, true, false)]
[InlineData(EntityState.Modified, true, false)]
[InlineData(EntityState.Deleted, true, false)]
+ [InlineData(EntityState.Detached, true, false)]
[InlineData(EntityState.Unchanged, true, true)]
+ [InlineData(EntityState.Added, true, true)]
[InlineData(EntityState.Modified, true, true)]
[InlineData(EntityState.Deleted, true, true)]
+ [InlineData(EntityState.Detached, true, true)]
public virtual void Lazy_load_one_to_one_reference_to_principal(EntityState state, bool useAttach, bool useDetach)
{
Single single = null;
if (useAttach)
{
- using (var context = CreateContext(lazyLoadingEnabled: true))
+ using (var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached))
{
single = context.Set().Single();
@@ -453,24 +493,17 @@ public virtual void Lazy_load_one_to_one_reference_to_principal(EntityState stat
}
}
- if (useDetach)
+ if (state != EntityState.Detached && useDetach)
{
Assert.Null(single.Parent);
}
else
{
- Assert.Equal(
- CoreStrings.WarningAsErrorTemplate(
- CoreEventId.LazyLoadOnDisposedContextWarning.ToString(),
- CoreResources.LogLazyLoadOnDisposedContext(new TestLogger())
- .GenerateMessage("SingleProxy", "Parent"),
- "CoreEventId.LazyLoadOnDisposedContextWarning"),
- Assert.Throws(
- () => single.Parent).Message);
+ AssertDisposed(() => single.Parent, "SingleProxy", "Parent");
}
}
- using (var context = CreateContext(lazyLoadingEnabled: true))
+ using (var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached))
{
var changeDetector = (ChangeDetectorProxy)context.GetService();
@@ -480,62 +513,86 @@ public virtual void Lazy_load_one_to_one_reference_to_principal(EntityState stat
var referenceEntry = context.Entry(single).Reference(e => e.Parent);
- context.Entry(single).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(single).State = state;
+ }
Assert.False(referenceEntry.IsLoaded);
changeDetector.DetectChangesCalled = false;
- if (state == EntityState.Deleted)
+ if (state == EntityState.Detached && useAttach)
{
- Assert.Null(single.Parent);
+ AssertDisposed(() => single.Parent, "SingleProxy", "Parent");
}
else
{
- Assert.NotNull(single.Parent);
- }
+ if (state == EntityState.Deleted)
+ {
+ Assert.Null(single.Parent);
+ }
+ else
+ {
+ Assert.NotNull(single.Parent);
+ }
- Assert.True(referenceEntry.IsLoaded);
+ Assert.True(referenceEntry.IsLoaded);
- Assert.False(changeDetector.DetectChangesCalled);
+ Assert.False(changeDetector.DetectChangesCalled);
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
+ RecordLog();
+ context.ChangeTracker.LazyLoadingEnabled = false;
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
+ if (state != EntityState.Deleted)
+ {
+ Assert.Same(single, single.Parent!.Single);
+ }
- var parent = context.ChangeTracker.Entries().Single().Entity;
+ Assert.Equal(state == EntityState.Detached ? 0 : 2, context.ChangeTracker.Entries().Count());
- if (state == EntityState.Deleted)
- {
- Assert.Null(single.Parent);
- Assert.Null(parent.Single);
- }
- else
- {
- Assert.Same(parent, single.Parent);
- Assert.Same(single, parent.Single);
+ if (state != EntityState.Detached)
+ {
+ var parent = context.ChangeTracker.Entries().Single().Entity;
+
+ if (state == EntityState.Deleted)
+ {
+ Assert.Null(single.Parent);
+ Assert.Null(parent.Single);
+ }
+ else
+ {
+ Assert.Same(parent, single.Parent);
+ Assert.Same(single, parent.Single);
+ }
+ }
}
}
}
[ConditionalTheory]
[InlineData(EntityState.Unchanged, false, false)]
+ [InlineData(EntityState.Added, false, false)]
[InlineData(EntityState.Modified, false, false)]
[InlineData(EntityState.Deleted, false, false)]
+ [InlineData(EntityState.Detached, false, false)]
[InlineData(EntityState.Unchanged, true, false)]
+ [InlineData(EntityState.Added, true, false)]
[InlineData(EntityState.Modified, true, false)]
[InlineData(EntityState.Deleted, true, false)]
+ [InlineData(EntityState.Detached, true, false)]
[InlineData(EntityState.Unchanged, true, true)]
+ [InlineData(EntityState.Added, true, true)]
[InlineData(EntityState.Modified, true, true)]
[InlineData(EntityState.Deleted, true, true)]
+ [InlineData(EntityState.Detached, true, true)]
public virtual void Lazy_load_one_to_one_reference_to_dependent(EntityState state, bool useAttach, bool useDetach)
{
Parent parent = null;
if (useAttach)
{
- using (var context = CreateContext(lazyLoadingEnabled: true))
+ using (var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached))
{
parent = context.Set().Single();
@@ -545,24 +602,17 @@ public virtual void Lazy_load_one_to_one_reference_to_dependent(EntityState stat
}
}
- if (useDetach)
+ if (state != EntityState.Detached && useDetach)
{
Assert.Null(parent.Single);
}
else
{
- Assert.Equal(
- CoreStrings.WarningAsErrorTemplate(
- CoreEventId.LazyLoadOnDisposedContextWarning.ToString(),
- CoreResources.LogLazyLoadOnDisposedContext(new TestLogger())
- .GenerateMessage("MotherProxy", "Single"),
- "CoreEventId.LazyLoadOnDisposedContextWarning"),
- Assert.Throws(
- () => parent.Single).Message);
+ AssertDisposed(() => parent.Single, "MotherProxy", "Single");
}
}
- using (var context = CreateContext(lazyLoadingEnabled: true))
+ using (var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached))
{
var changeDetector = (ChangeDetectorProxy)context.GetService();
@@ -572,37 +622,53 @@ public virtual void Lazy_load_one_to_one_reference_to_dependent(EntityState stat
var referenceEntry = context.Entry(parent).Reference(e => e.Single);
- context.Entry(parent).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(parent).State = state;
+ }
Assert.False(referenceEntry.IsLoaded);
changeDetector.DetectChangesCalled = false;
- Assert.NotNull(parent.Single);
+ if (state == EntityState.Detached && useAttach)
+ {
+ AssertDisposed(() => parent.Single, "MotherProxy", "Single");
+ }
+ else
+ {
+ Assert.NotNull(parent.Single);
+ Assert.Same(parent, parent.Single.Parent);
- Assert.False(changeDetector.DetectChangesCalled);
+ Assert.False(changeDetector.DetectChangesCalled);
- Assert.True(referenceEntry.IsLoaded);
+ Assert.True(referenceEntry.IsLoaded);
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
+ RecordLog();
+ context.ChangeTracker.LazyLoadingEnabled = false;
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
+ Assert.Equal(state == EntityState.Detached ? 0 : 2, context.ChangeTracker.Entries().Count());
- var single = context.ChangeTracker.Entries().Single().Entity;
+ if (state != EntityState.Detached)
+ {
+ var single = context.ChangeTracker.Entries().Single().Entity;
- Assert.Same(single, parent.Single);
- Assert.Same(parent, single.Parent);
+ Assert.Same(single, parent.Single);
+ Assert.Same(parent, single.Parent);
+ }
+ }
}
}
[ConditionalTheory]
[InlineData(EntityState.Unchanged)]
+ [InlineData(EntityState.Added)]
[InlineData(EntityState.Modified)]
[InlineData(EntityState.Deleted)]
+ [InlineData(EntityState.Detached)]
public virtual void Lazy_load_one_to_one_PK_to_PK_reference_to_principal(EntityState state)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
var changeDetector = (ChangeDetectorProxy)context.GetService();
var single = context.Set().Single();
@@ -611,7 +677,10 @@ public virtual void Lazy_load_one_to_one_PK_to_PK_reference_to_principal(EntityS
var referenceEntry = context.Entry(single).Reference(e => e.Parent);
- context.Entry(single).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(single).State = state;
+ }
Assert.False(referenceEntry.IsLoaded);
@@ -633,29 +702,39 @@ public virtual void Lazy_load_one_to_one_PK_to_PK_reference_to_principal(EntityS
RecordLog();
context.ChangeTracker.LazyLoadingEnabled = false;
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
+ Assert.Equal(state == EntityState.Detached ? 0 : 2, context.ChangeTracker.Entries().Count());
- var parent = context.ChangeTracker.Entries().Single().Entity;
-
- if (state == EntityState.Deleted)
+ if (state != EntityState.Deleted)
{
- Assert.Null(single.Parent);
- Assert.Null(parent.SinglePkToPk);
+ Assert.Same(single, single.Parent!.SinglePkToPk);
}
- else
+
+ if (state != EntityState.Detached)
{
- Assert.Same(parent, single.Parent);
- Assert.Same(single, parent.SinglePkToPk);
+ var parent = context.ChangeTracker.Entries().Single().Entity;
+
+ if (state == EntityState.Deleted)
+ {
+ Assert.Null(single.Parent);
+ Assert.Null(parent.SinglePkToPk);
+ }
+ else
+ {
+ Assert.Same(parent, single.Parent);
+ Assert.Same(single, parent.SinglePkToPk);
+ }
}
}
[ConditionalTheory]
[InlineData(EntityState.Unchanged)]
+ [InlineData(EntityState.Added)]
[InlineData(EntityState.Modified)]
[InlineData(EntityState.Deleted)]
+ [InlineData(EntityState.Detached)]
public virtual void Lazy_load_one_to_one_PK_to_PK_reference_to_dependent(EntityState state)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
var changeDetector = (ChangeDetectorProxy)context.GetService();
var parent = context.Set().Single();
@@ -664,7 +743,10 @@ public virtual void Lazy_load_one_to_one_PK_to_PK_reference_to_dependent(EntityS
var referenceEntry = context.Entry(parent).Reference(e => e.SinglePkToPk);
- context.Entry(parent).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(parent).State = state;
+ }
Assert.False(referenceEntry.IsLoaded);
@@ -679,12 +761,20 @@ public virtual void Lazy_load_one_to_one_PK_to_PK_reference_to_dependent(EntityS
RecordLog();
context.ChangeTracker.LazyLoadingEnabled = false;
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
+ Assert.Equal(state == EntityState.Detached ? 0 : 2, context.ChangeTracker.Entries().Count());
- var single = context.ChangeTracker.Entries().Single().Entity;
+ if (state != EntityState.Deleted)
+ {
+ Assert.Same(parent, parent.SinglePkToPk.Parent);
+ }
- Assert.Same(single, parent.SinglePkToPk);
- Assert.Same(parent, single.Parent);
+ if (state != EntityState.Detached)
+ {
+ var single = context.ChangeTracker.Entries().Single().Entity;
+
+ Assert.Same(single, parent.SinglePkToPk);
+ Assert.Same(parent, single.Parent);
+ }
}
[ConditionalFact]
@@ -803,11 +893,13 @@ public virtual void Setting_reference_to_owned_type_to_null_is_allowed_on_virtua
[ConditionalTheory]
[InlineData(EntityState.Unchanged)]
+ [InlineData(EntityState.Added)]
[InlineData(EntityState.Modified)]
[InlineData(EntityState.Deleted)]
+ [InlineData(EntityState.Detached)]
public virtual void Lazy_load_many_to_one_reference_to_principal_null_FK(EntityState state)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
var changeDetector = (ChangeDetectorProxy)context.GetService();
var child = context.CreateProxy();
@@ -819,7 +911,10 @@ public virtual void Lazy_load_many_to_one_reference_to_principal_null_FK(EntityS
var referenceEntry = context.Entry(child).Reference(e => e.Parent);
- context.Entry(child).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(child).State = state;
+ }
Assert.False(referenceEntry.IsLoaded);
@@ -840,11 +935,13 @@ public virtual void Lazy_load_many_to_one_reference_to_principal_null_FK(EntityS
[ConditionalTheory]
[InlineData(EntityState.Unchanged)]
+ [InlineData(EntityState.Added)]
[InlineData(EntityState.Modified)]
[InlineData(EntityState.Deleted)]
+ [InlineData(EntityState.Detached)]
public virtual void Lazy_load_one_to_one_reference_to_principal_null_FK(EntityState state)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
var changeDetector = (ChangeDetectorProxy)context.GetService();
var single = context.CreateProxy();
@@ -856,7 +953,10 @@ public virtual void Lazy_load_one_to_one_reference_to_principal_null_FK(EntitySt
var referenceEntry = context.Entry(single).Reference(e => e.Parent);
- context.Entry(single).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(single).State = state;
+ }
Assert.False(referenceEntry.IsLoaded);
@@ -878,24 +978,32 @@ public virtual void Lazy_load_one_to_one_reference_to_principal_null_FK(EntitySt
[ConditionalTheory]
[InlineData(EntityState.Unchanged)]
+ [InlineData(EntityState.Added)]
[InlineData(EntityState.Modified)]
[InlineData(EntityState.Deleted)]
+ [InlineData(EntityState.Detached)]
public virtual void Lazy_load_many_to_one_reference_to_principal_changed_non_found_FK(EntityState state)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
var changeDetector = (ChangeDetectorProxy)context.GetService();
var child = context.CreateProxy();
child.Id = 767;
child.ParentId = 797;
- context.Attach(child);
+ if (state != EntityState.Detached)
+ {
+ context.Attach(child);
+ }
ClearLog();
var referenceEntry = context.Entry(child).Reference(e => e.Parent);
- context.Entry(child).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(child).State = state;
+ }
referenceEntry.IsLoaded = true;
@@ -903,7 +1011,14 @@ public virtual void Lazy_load_many_to_one_reference_to_principal_changed_non_fou
child.ParentId = 707;
- context.ChangeTracker.DetectChanges();
+ if (state != EntityState.Detached)
+ {
+ context.ChangeTracker.DetectChanges();
+ }
+ else
+ {
+ referenceEntry.IsLoaded = false;
+ }
if (state == EntityState.Deleted)
{
@@ -914,36 +1029,49 @@ public virtual void Lazy_load_many_to_one_reference_to_principal_changed_non_fou
Assert.NotNull(child.Parent);
}
- Assert.True(changeDetector.DetectChangesCalled);
+ if (state != EntityState.Detached)
+ {
+ Assert.True(changeDetector.DetectChangesCalled);
+ }
Assert.True(referenceEntry.IsLoaded);
RecordLog();
context.ChangeTracker.LazyLoadingEnabled = false;
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
+ Assert.Equal(state == EntityState.Detached ? 0 : 2, context.ChangeTracker.Entries().Count());
- var parent = context.ChangeTracker.Entries().Single().Entity;
-
- if (state == EntityState.Deleted)
+ if (state != EntityState.Deleted)
{
- Assert.Null(parent.Children);
- Assert.Null(child.Parent);
+ Assert.Contains(child, child.Parent!.Children);
}
- else
+
+ if (state != EntityState.Detached)
{
- Assert.Same(child, parent.Children.Single());
- Assert.Same(parent, child.Parent);
+ var parent = context.ChangeTracker.Entries().Single().Entity;
+
+ if (state == EntityState.Deleted)
+ {
+ Assert.Null(parent.Children);
+ Assert.Null(child.Parent);
+ }
+ else
+ {
+ Assert.Same(child, parent.Children.Single());
+ Assert.Same(parent, child.Parent);
+ }
}
}
[ConditionalTheory]
[InlineData(EntityState.Unchanged)]
+ [InlineData(EntityState.Added)]
[InlineData(EntityState.Modified)]
[InlineData(EntityState.Deleted)]
+ [InlineData(EntityState.Detached)]
public virtual void Lazy_load_many_to_one_reference_to_principal_changed_found_FK(EntityState state)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
var changeDetector = (ChangeDetectorProxy)context.GetService();
var parent = context.CreateProxy();
@@ -957,14 +1085,20 @@ public virtual void Lazy_load_many_to_one_reference_to_principal_changed_found_F
child.Parent = parent;
parent.Children = new List { child };
- context.Attach(child);
- context.Attach(parent);
+ if (state != EntityState.Detached)
+ {
+ context.Attach(child);
+ context.Attach(parent);
+ }
ClearLog();
var referenceEntry = context.Entry(child).Reference(e => e.Parent);
- context.Entry(child).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(child).State = state;
+ }
referenceEntry.IsLoaded = true;
@@ -990,29 +1124,39 @@ public virtual void Lazy_load_many_to_one_reference_to_principal_changed_found_F
RecordLog();
context.ChangeTracker.LazyLoadingEnabled = false;
- Assert.Equal(3, context.ChangeTracker.Entries().Count());
-
- var newParent = context.ChangeTracker.Entries().Single(e => e.Entity.Id != parent.Id).Entity;
+ Assert.Equal(state == EntityState.Detached ? 0 : 3, context.ChangeTracker.Entries().Count());
- if (state == EntityState.Deleted)
+ if (state != EntityState.Deleted)
{
- Assert.Empty(parent.Children);
- Assert.Null(child.Parent);
+ Assert.Contains(child, child.Parent!.Children);
}
- else
+
+ if (state != EntityState.Detached)
{
- Assert.Same(child, newParent.Children.Single());
- Assert.Same(newParent, child.Parent);
+ var newParent = context.ChangeTracker.Entries().Single(e => e.Entity.Id != parent.Id).Entity;
+
+ if (state == EntityState.Deleted)
+ {
+ Assert.Empty(parent.Children);
+ Assert.Null(child.Parent);
+ }
+ else
+ {
+ Assert.Same(child, newParent.Children.Single());
+ Assert.Same(newParent, child.Parent);
+ }
}
}
[ConditionalTheory]
[InlineData(EntityState.Unchanged)]
+ [InlineData(EntityState.Added)]
[InlineData(EntityState.Modified)]
[InlineData(EntityState.Deleted)]
+ [InlineData(EntityState.Detached)]
public virtual void Lazy_load_collection_not_found(EntityState state)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
var changeDetector = (ChangeDetectorProxy)context.GetService();
var parent = context.CreateProxy();
@@ -1025,7 +1169,10 @@ public virtual void Lazy_load_collection_not_found(EntityState state)
var collectionEntry = context.Entry(parent).Collection(e => e.Children);
- context.Entry(parent).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(parent).State = state;
+ }
Assert.False(collectionEntry.IsLoaded);
@@ -1046,11 +1193,13 @@ public virtual void Lazy_load_collection_not_found(EntityState state)
[ConditionalTheory]
[InlineData(EntityState.Unchanged)]
+ [InlineData(EntityState.Added)]
[InlineData(EntityState.Modified)]
[InlineData(EntityState.Deleted)]
+ [InlineData(EntityState.Detached)]
public virtual void Lazy_load_many_to_one_reference_to_principal_not_found(EntityState state)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
var changeDetector = (ChangeDetectorProxy)context.GetService();
var child = context.CreateProxy();
@@ -1063,7 +1212,10 @@ public virtual void Lazy_load_many_to_one_reference_to_principal_not_found(Entit
var referenceEntry = context.Entry(child).Reference(e => e.Parent);
- context.Entry(child).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(child).State = state;
+ }
Assert.False(referenceEntry.IsLoaded);
@@ -1084,11 +1236,13 @@ public virtual void Lazy_load_many_to_one_reference_to_principal_not_found(Entit
[ConditionalTheory]
[InlineData(EntityState.Unchanged)]
+ [InlineData(EntityState.Added)]
[InlineData(EntityState.Modified)]
[InlineData(EntityState.Deleted)]
+ [InlineData(EntityState.Detached)]
public virtual void Lazy_load_one_to_one_reference_to_principal_not_found(EntityState state)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
var changeDetector = (ChangeDetectorProxy)context.GetService();
var single = context.CreateProxy();
@@ -1101,7 +1255,10 @@ public virtual void Lazy_load_one_to_one_reference_to_principal_not_found(Entity
var referenceEntry = context.Entry(single).Reference(e => e.Parent);
- context.Entry(single).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(single).State = state;
+ }
Assert.False(referenceEntry.IsLoaded);
@@ -1123,11 +1280,13 @@ public virtual void Lazy_load_one_to_one_reference_to_principal_not_found(Entity
[ConditionalTheory]
[InlineData(EntityState.Unchanged)]
+ [InlineData(EntityState.Added)]
[InlineData(EntityState.Modified)]
[InlineData(EntityState.Deleted)]
+ [InlineData(EntityState.Detached)]
public virtual void Lazy_load_one_to_one_reference_to_dependent_not_found(EntityState state)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
var changeDetector = (ChangeDetectorProxy)context.GetService();
var parent = context.CreateProxy();
@@ -1140,7 +1299,10 @@ public virtual void Lazy_load_one_to_one_reference_to_dependent_not_found(Entity
var referenceEntry = context.Entry(parent).Reference(e => e.Single);
- context.Entry(parent).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(parent).State = state;
+ }
Assert.False(referenceEntry.IsLoaded);
@@ -1162,17 +1324,23 @@ public virtual void Lazy_load_one_to_one_reference_to_dependent_not_found(Entity
[ConditionalTheory]
[InlineData(EntityState.Unchanged, CascadeTiming.OnSaveChanges)]
+ [InlineData(EntityState.Added, CascadeTiming.OnSaveChanges)]
[InlineData(EntityState.Modified, CascadeTiming.OnSaveChanges)]
[InlineData(EntityState.Deleted, CascadeTiming.OnSaveChanges)]
+ [InlineData(EntityState.Detached, CascadeTiming.OnSaveChanges)]
[InlineData(EntityState.Unchanged, CascadeTiming.Immediate)]
+ [InlineData(EntityState.Added, CascadeTiming.Immediate)]
[InlineData(EntityState.Modified, CascadeTiming.Immediate)]
[InlineData(EntityState.Deleted, CascadeTiming.Immediate)]
+ [InlineData(EntityState.Detached, CascadeTiming.Immediate)]
[InlineData(EntityState.Unchanged, CascadeTiming.Never)]
+ [InlineData(EntityState.Added, CascadeTiming.Never)]
[InlineData(EntityState.Modified, CascadeTiming.Never)]
[InlineData(EntityState.Deleted, CascadeTiming.Never)]
+ [InlineData(EntityState.Detached, CascadeTiming.Never)]
public virtual void Lazy_load_collection_already_loaded(EntityState state, CascadeTiming cascadeDeleteTiming)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming;
var changeDetector = (ChangeDetectorProxy)context.GetService();
@@ -1183,7 +1351,10 @@ public virtual void Lazy_load_collection_already_loaded(EntityState state, Casca
var collectionEntry = context.Entry(parent).Collection(e => e.Children);
- context.Entry(parent).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(parent).State = state;
+ }
Assert.True(collectionEntry.IsLoaded);
@@ -1210,24 +1381,27 @@ public virtual void Lazy_load_collection_already_loaded(EntityState state, Casca
Assert.All(parent.Children.Select(e => e.Parent), c => Assert.Same(parent, c));
}
- Assert.Equal(3, context.ChangeTracker.Entries().Count());
+ Assert.Equal(state == EntityState.Detached ? 0 : 3, context.ChangeTracker.Entries().Count());
}
[ConditionalTheory]
[InlineData(EntityState.Unchanged, CascadeTiming.OnSaveChanges)]
+ [InlineData(EntityState.Added, CascadeTiming.OnSaveChanges)]
[InlineData(EntityState.Modified, CascadeTiming.OnSaveChanges)]
[InlineData(EntityState.Deleted, CascadeTiming.OnSaveChanges)]
[InlineData(EntityState.Unchanged, CascadeTiming.Immediate)]
+ [InlineData(EntityState.Added, CascadeTiming.Immediate)]
[InlineData(EntityState.Modified, CascadeTiming.Immediate)]
[InlineData(EntityState.Deleted, CascadeTiming.Immediate)]
[InlineData(EntityState.Unchanged, CascadeTiming.Never)]
+ [InlineData(EntityState.Added, CascadeTiming.Never)]
[InlineData(EntityState.Modified, CascadeTiming.Never)]
[InlineData(EntityState.Deleted, CascadeTiming.Never)]
public virtual void Lazy_load_many_to_one_reference_to_principal_already_loaded(
EntityState state,
CascadeTiming cascadeDeleteTiming)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming;
var changeDetector = (ChangeDetectorProxy)context.GetService();
@@ -1238,7 +1412,10 @@ public virtual void Lazy_load_many_to_one_reference_to_principal_already_loaded(
var referenceEntry = context.Entry(child).Reference(e => e.Parent);
- context.Entry(child).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(child).State = state;
+ }
Assert.True(referenceEntry.IsLoaded);
@@ -1253,21 +1430,31 @@ public virtual void Lazy_load_many_to_one_reference_to_principal_already_loaded(
RecordLog();
context.ChangeTracker.LazyLoadingEnabled = false;
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
+ Assert.Equal(state == EntityState.Detached ? 0 : 2, context.ChangeTracker.Entries().Count());
- var parent = context.ChangeTracker.Entries().Single().Entity;
+ if (state != EntityState.Deleted)
+ {
+ Assert.Contains(child, child.Parent!.Children);
+ }
+
+ if (state != EntityState.Detached)
+ {
+ var parent = context.ChangeTracker.Entries().Single().Entity;
- Assert.Same(parent, child.Parent);
- Assert.Same(child, parent.Children.Single());
+ Assert.Same(parent, child.Parent);
+ Assert.Same(child, parent.Children.Single());
+ }
}
[ConditionalTheory]
[InlineData(EntityState.Unchanged)]
+ [InlineData(EntityState.Added)]
[InlineData(EntityState.Modified)]
[InlineData(EntityState.Deleted)]
+ [InlineData(EntityState.Detached)]
public virtual void Lazy_load_one_to_one_reference_to_principal_already_loaded(EntityState state)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
var changeDetector = (ChangeDetectorProxy)context.GetService();
var single = context.Set().Include(e => e.Parent).Single();
@@ -1276,7 +1463,10 @@ public virtual void Lazy_load_one_to_one_reference_to_principal_already_loaded(E
var referenceEntry = context.Entry(single).Reference(e => e.Parent);
- context.Entry(single).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(single).State = state;
+ }
Assert.True(referenceEntry.IsLoaded);
@@ -1291,29 +1481,40 @@ public virtual void Lazy_load_one_to_one_reference_to_principal_already_loaded(E
RecordLog();
context.ChangeTracker.LazyLoadingEnabled = false;
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
+ Assert.Equal(state == EntityState.Detached ? 0 : 2, context.ChangeTracker.Entries().Count());
- var parent = context.ChangeTracker.Entries().Single().Entity;
+ if (state != EntityState.Deleted)
+ {
+ Assert.Same(single, single.Parent.Single);
+ }
- Assert.Same(parent, single.Parent);
- Assert.Same(single, parent.Single);
+ if (state != EntityState.Detached)
+ {
+ var parent = context.ChangeTracker.Entries().Single().Entity;
+
+ Assert.Same(parent, single.Parent);
+ Assert.Same(single, parent.Single);
+ }
}
[ConditionalTheory]
[InlineData(EntityState.Unchanged, CascadeTiming.OnSaveChanges)]
+ [InlineData(EntityState.Added, CascadeTiming.OnSaveChanges)]
[InlineData(EntityState.Modified, CascadeTiming.OnSaveChanges)]
[InlineData(EntityState.Deleted, CascadeTiming.OnSaveChanges)]
[InlineData(EntityState.Unchanged, CascadeTiming.Immediate)]
+ [InlineData(EntityState.Added, CascadeTiming.Immediate)]
[InlineData(EntityState.Modified, CascadeTiming.Immediate)]
[InlineData(EntityState.Deleted, CascadeTiming.Immediate)]
[InlineData(EntityState.Unchanged, CascadeTiming.Never)]
+ [InlineData(EntityState.Added, CascadeTiming.Never)]
[InlineData(EntityState.Modified, CascadeTiming.Never)]
[InlineData(EntityState.Deleted, CascadeTiming.Never)]
public virtual void Lazy_load_one_to_one_reference_to_dependent_already_loaded(
EntityState state,
CascadeTiming cascadeDeleteTiming)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming;
var changeDetector = (ChangeDetectorProxy)context.GetService();
@@ -1324,7 +1525,10 @@ public virtual void Lazy_load_one_to_one_reference_to_dependent_already_loaded(
var referenceEntry = context.Entry(parent).Reference(e => e.Single);
- context.Entry(parent).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(parent).State = state;
+ }
Assert.True(referenceEntry.IsLoaded);
@@ -1339,31 +1543,41 @@ public virtual void Lazy_load_one_to_one_reference_to_dependent_already_loaded(
RecordLog();
context.ChangeTracker.LazyLoadingEnabled = false;
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
-
- var single = context.ChangeTracker.Entries().Single().Entity;
-
- Assert.Same(single, parent.Single);
+ Assert.Equal(state == EntityState.Detached ? 0 : 2, context.ChangeTracker.Entries().Count());
- if (cascadeDeleteTiming == CascadeTiming.Immediate
- && state == EntityState.Deleted)
+ if (state != EntityState.Deleted)
{
- // No fixup to Deleted entity.
- Assert.Null(single.Parent);
+ Assert.Same(parent, parent.Single.Parent);
}
- else
+
+ if (state != EntityState.Detached)
{
- Assert.Same(parent, single.Parent);
+ var single = context.ChangeTracker.Entries().Single().Entity;
+
+ Assert.Same(single, parent.Single);
+
+ if (cascadeDeleteTiming == CascadeTiming.Immediate
+ && state == EntityState.Deleted)
+ {
+ // No fixup to Deleted entity.
+ Assert.Null(single.Parent);
+ }
+ else
+ {
+ Assert.Same(parent, single.Parent);
+ }
}
}
[ConditionalTheory]
[InlineData(EntityState.Unchanged)]
+ [InlineData(EntityState.Added)]
[InlineData(EntityState.Modified)]
[InlineData(EntityState.Deleted)]
+ [InlineData(EntityState.Detached)]
public virtual void Lazy_load_one_to_one_PK_to_PK_reference_to_principal_already_loaded(EntityState state)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
var changeDetector = (ChangeDetectorProxy)context.GetService();
var single = context.Set().Include(e => e.Parent).Single();
@@ -1372,7 +1586,10 @@ public virtual void Lazy_load_one_to_one_PK_to_PK_reference_to_principal_already
var referenceEntry = context.Entry(single).Reference(e => e.Parent);
- context.Entry(single).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(single).State = state;
+ }
Assert.True(referenceEntry.IsLoaded);
@@ -1387,21 +1604,31 @@ public virtual void Lazy_load_one_to_one_PK_to_PK_reference_to_principal_already
RecordLog();
context.ChangeTracker.LazyLoadingEnabled = false;
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
+ Assert.Equal(state == EntityState.Detached ? 0 : 2, context.ChangeTracker.Entries().Count());
+
+ if (state != EntityState.Deleted)
+ {
+ Assert.Same(single, single.Parent.SinglePkToPk);
+ }
- var parent = context.ChangeTracker.Entries().Single().Entity;
+ if (state != EntityState.Detached)
+ {
+ var parent = context.ChangeTracker.Entries().Single().Entity;
- Assert.Same(parent, single.Parent);
- Assert.Same(single, parent.SinglePkToPk);
+ Assert.Same(parent, single.Parent);
+ Assert.Same(single, parent.SinglePkToPk);
+ }
}
[ConditionalTheory]
[InlineData(EntityState.Unchanged)]
+ [InlineData(EntityState.Added)]
[InlineData(EntityState.Modified)]
[InlineData(EntityState.Deleted)]
+ [InlineData(EntityState.Detached)]
public virtual void Lazy_load_one_to_one_PK_to_PK_reference_to_dependent_already_loaded(EntityState state)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
var changeDetector = (ChangeDetectorProxy)context.GetService();
var parent = context.Set().Include(e => e.SinglePkToPk).Single();
@@ -1410,7 +1637,10 @@ public virtual void Lazy_load_one_to_one_PK_to_PK_reference_to_dependent_already
var referenceEntry = context.Entry(parent).Reference(e => e.SinglePkToPk);
- context.Entry(parent).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(parent).State = state;
+ }
Assert.True(referenceEntry.IsLoaded);
@@ -1425,28 +1655,41 @@ public virtual void Lazy_load_one_to_one_PK_to_PK_reference_to_dependent_already
RecordLog();
context.ChangeTracker.LazyLoadingEnabled = false;
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
+ if (state != EntityState.Deleted)
+ {
+ Assert.Same(parent, parent.SinglePkToPk.Parent);
+ }
+
+ if (state != EntityState.Detached)
+ {
+ Assert.Equal(state == EntityState.Detached ? 0 : 2, context.ChangeTracker.Entries().Count());
- var single = context.ChangeTracker.Entries().Single().Entity;
+ var single = context.ChangeTracker.Entries().Single().Entity;
- Assert.Same(single, parent.SinglePkToPk);
- Assert.Same(parent, single.Parent);
+ Assert.Same(single, parent.SinglePkToPk);
+ Assert.Same(parent, single.Parent);
+ }
}
[ConditionalTheory]
[InlineData(EntityState.Unchanged)]
+ [InlineData(EntityState.Added)]
[InlineData(EntityState.Modified)]
[InlineData(EntityState.Deleted)]
+ [InlineData(EntityState.Detached)]
public virtual void Lazy_load_many_to_one_reference_to_principal_alternate_key(EntityState state)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
var child = context.Set().Single(e => e.Id == 32);
ClearLog();
var referenceEntry = context.Entry(child).Reference(e => e.Parent);
- context.Entry(child).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(child).State = state;
+ }
Assert.False(referenceEntry.IsLoaded);
@@ -1464,36 +1707,49 @@ public virtual void Lazy_load_many_to_one_reference_to_principal_alternate_key(E
RecordLog();
context.ChangeTracker.LazyLoadingEnabled = false;
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
-
- var parent = context.ChangeTracker.Entries().Single().Entity;
+ Assert.Equal(state == EntityState.Detached ? 0 : 2, context.ChangeTracker.Entries().Count());
- if (state == EntityState.Deleted)
+ if (state != EntityState.Deleted)
{
- Assert.Null(child.Parent);
- Assert.Null(parent.ChildrenAk);
+ Assert.Same(child, child.Parent!.ChildrenAk.Single());
}
- else
+
+ if (state != EntityState.Detached)
{
- Assert.Same(parent, child.Parent);
- Assert.Same(child, parent.ChildrenAk.Single());
+ var parent = context.ChangeTracker.Entries().Single().Entity;
+
+ if (state == EntityState.Deleted)
+ {
+ Assert.Null(child.Parent);
+ Assert.Null(parent.ChildrenAk);
+ }
+ else
+ {
+ Assert.Same(parent, child.Parent);
+ Assert.Same(child, parent.ChildrenAk.Single());
+ }
}
}
[ConditionalTheory]
[InlineData(EntityState.Unchanged)]
+ [InlineData(EntityState.Added)]
[InlineData(EntityState.Modified)]
[InlineData(EntityState.Deleted)]
+ [InlineData(EntityState.Detached)]
public virtual void Lazy_load_one_to_one_reference_to_principal_alternate_key(EntityState state)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
var single = context.Set().Single();
ClearLog();
var referenceEntry = context.Entry(single).Reference(e => e.Parent);
- context.Entry(single).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(single).State = state;
+ }
Assert.False(referenceEntry.IsLoaded);
@@ -1511,36 +1767,49 @@ public virtual void Lazy_load_one_to_one_reference_to_principal_alternate_key(En
RecordLog();
context.ChangeTracker.LazyLoadingEnabled = false;
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
-
- var parent = context.ChangeTracker.Entries().Single().Entity;
+ Assert.Equal(state == EntityState.Detached ? 0 : 2, context.ChangeTracker.Entries().Count());
- if (state == EntityState.Deleted)
+ if (state != EntityState.Deleted)
{
- Assert.Null(single.Parent);
- Assert.Null(parent.SingleAk);
+ Assert.Same(single, single.Parent!.SingleAk);
}
- else
+
+ if (state != EntityState.Detached)
{
- Assert.Same(parent, single.Parent);
- Assert.Same(single, parent.SingleAk);
+ var parent = context.ChangeTracker.Entries().Single().Entity;
+
+ if (state == EntityState.Deleted)
+ {
+ Assert.Null(single.Parent);
+ Assert.Null(parent.SingleAk);
+ }
+ else
+ {
+ Assert.Same(parent, single.Parent);
+ Assert.Same(single, parent.SingleAk);
+ }
}
}
[ConditionalTheory]
[InlineData(EntityState.Unchanged)]
+ [InlineData(EntityState.Added)]
[InlineData(EntityState.Modified)]
[InlineData(EntityState.Deleted)]
+ [InlineData(EntityState.Detached)]
public virtual void Lazy_load_one_to_one_reference_to_dependent_alternate_key(EntityState state)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
var parent = context.Set().Single();
ClearLog();
var referenceEntry = context.Entry(parent).Reference(e => e.SingleAk);
- context.Entry(parent).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(parent).State = state;
+ }
Assert.False(referenceEntry.IsLoaded);
@@ -1551,21 +1820,31 @@ public virtual void Lazy_load_one_to_one_reference_to_dependent_alternate_key(En
RecordLog();
context.ChangeTracker.LazyLoadingEnabled = false;
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
+ Assert.Equal(state == EntityState.Detached ? 0 : 2, context.ChangeTracker.Entries().Count());
- var single = context.ChangeTracker.Entries().Single().Entity;
+ if (state != EntityState.Deleted)
+ {
+ Assert.Same(parent, parent.SingleAk.Parent);
+ }
- Assert.Same(single, parent.SingleAk);
- Assert.Same(parent, single.Parent);
+ if (state != EntityState.Detached)
+ {
+ var single = context.ChangeTracker.Entries().Single().Entity;
+
+ Assert.Same(single, parent.SingleAk);
+ Assert.Same(parent, single.Parent);
+ }
}
[ConditionalTheory]
[InlineData(EntityState.Unchanged)]
+ [InlineData(EntityState.Added)]
[InlineData(EntityState.Modified)]
[InlineData(EntityState.Deleted)]
+ [InlineData(EntityState.Detached)]
public virtual void Lazy_load_many_to_one_reference_to_principal_null_FK_alternate_key(EntityState state)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
var child = context.CreateProxy();
child.Id = 767;
@@ -1575,7 +1854,10 @@ public virtual void Lazy_load_many_to_one_reference_to_principal_null_FK_alterna
var referenceEntry = context.Entry(child).Reference(e => e.Parent);
- context.Entry(child).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(child).State = state;
+ }
Assert.False(referenceEntry.IsLoaded);
@@ -1592,11 +1874,13 @@ public virtual void Lazy_load_many_to_one_reference_to_principal_null_FK_alterna
[ConditionalTheory]
[InlineData(EntityState.Unchanged)]
+ [InlineData(EntityState.Added)]
[InlineData(EntityState.Modified)]
[InlineData(EntityState.Deleted)]
+ [InlineData(EntityState.Detached)]
public virtual void Lazy_load_one_to_one_reference_to_principal_null_FK_alternate_key(EntityState state)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
var single = context.CreateProxy();
single.Id = 767;
@@ -1606,7 +1890,10 @@ public virtual void Lazy_load_one_to_one_reference_to_principal_null_FK_alternat
var referenceEntry = context.Entry(single).Reference(e => e.Parent);
- context.Entry(single).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(single).State = state;
+ }
Assert.False(referenceEntry.IsLoaded);
@@ -1624,18 +1911,23 @@ public virtual void Lazy_load_one_to_one_reference_to_principal_null_FK_alternat
[ConditionalTheory]
[InlineData(EntityState.Unchanged)]
+ [InlineData(EntityState.Added)]
[InlineData(EntityState.Modified)]
[InlineData(EntityState.Deleted)]
+ [InlineData(EntityState.Detached)]
public virtual void Lazy_load_collection_shadow_fk(EntityState state)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
var parent = context.Set().Single();
ClearLog();
var collectionEntry = context.Entry(parent).Collection(e => e.ChildrenShadowFk);
- context.Entry(parent).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(parent).State = state;
+ }
Assert.False(collectionEntry.IsLoaded);
@@ -1649,117 +1941,154 @@ public virtual void Lazy_load_collection_shadow_fk(EntityState state)
Assert.Equal(2, parent.ChildrenShadowFk.Count());
Assert.All(parent.ChildrenShadowFk.Select(e => e.Parent), c => Assert.Same(parent, c));
- Assert.Equal(3, context.ChangeTracker.Entries().Count());
+ Assert.Equal(state == EntityState.Detached ? 0 : 3, context.ChangeTracker.Entries().Count());
}
[ConditionalTheory]
[InlineData(EntityState.Unchanged)]
+ [InlineData(EntityState.Added)]
[InlineData(EntityState.Modified)]
[InlineData(EntityState.Deleted)]
+ [InlineData(EntityState.Detached)]
public virtual void Lazy_load_many_to_one_reference_to_principal_shadow_fk(EntityState state)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
var child = context.Set().Single(e => e.Id == 52);
ClearLog();
- var referenceEntry = context.Entry(child).Reference(e => e.Parent);
-
- context.Entry(child).State = state;
-
- Assert.False(referenceEntry.IsLoaded);
-
- if (state == EntityState.Deleted)
+ if (state == EntityState.Detached)
{
- Assert.Null(child.Parent);
+ Assert.Equal(
+ CoreStrings.CannotLoadDetachedShadow("Parent", "ChildShadowFk"),
+ Assert.Throws(() => child.Parent).Message);
}
else
{
- Assert.NotNull(child.Parent);
- }
+ var referenceEntry = context.Entry(child).Reference(e => e.Parent);
- Assert.True(referenceEntry.IsLoaded);
+ context.Entry(child).State = state;
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
+ Assert.False(referenceEntry.IsLoaded);
+
+ if (state == EntityState.Deleted)
+ {
+ Assert.Null(child.Parent);
+ }
+ else
+ {
+ Assert.NotNull(child.Parent);
+ }
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
+ Assert.True(referenceEntry.IsLoaded);
- var parent = context.ChangeTracker.Entries().Single().Entity;
+ RecordLog();
+ context.ChangeTracker.LazyLoadingEnabled = false;
- if (state == EntityState.Deleted)
- {
- Assert.Null(child.Parent);
- Assert.Null(parent.ChildrenShadowFk);
- }
- else
- {
- Assert.Same(parent, child.Parent);
- Assert.Same(child, parent.ChildrenShadowFk.Single());
+ Assert.Equal(2, context.ChangeTracker.Entries().Count());
+
+ if (state != EntityState.Deleted)
+ {
+ Assert.Same(child, child.Parent!.ChildrenShadowFk.Single());
+ }
+
+ var parent = context.ChangeTracker.Entries().Single().Entity;
+
+ if (state == EntityState.Deleted)
+ {
+ Assert.Null(child.Parent);
+ Assert.Null(parent.ChildrenShadowFk);
+ }
+ else
+ {
+ Assert.Same(parent, child.Parent);
+ Assert.Same(child, parent.ChildrenShadowFk.Single());
+ }
}
}
[ConditionalTheory]
[InlineData(EntityState.Unchanged)]
+ [InlineData(EntityState.Added)]
[InlineData(EntityState.Modified)]
[InlineData(EntityState.Deleted)]
+ [InlineData(EntityState.Detached)]
public virtual void Lazy_load_one_to_one_reference_to_principal_shadow_fk(EntityState state)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
var single = context.Set().Single();
ClearLog();
- var referenceEntry = context.Entry(single).Reference(e => e.Parent);
-
- context.Entry(single).State = state;
-
- Assert.False(referenceEntry.IsLoaded);
-
- if (state == EntityState.Deleted)
+ if (state == EntityState.Detached)
{
- Assert.Null(single.Parent);
+ Assert.Equal(
+ CoreStrings.CannotLoadDetachedShadow("Parent", "SingleShadowFk"),
+ Assert.Throws(() => single.Parent).Message);
}
else
{
- Assert.NotNull(single.Parent);
- }
+ var referenceEntry = context.Entry(single).Reference(e => e.Parent);
- Assert.True(referenceEntry.IsLoaded);
+ context.Entry(single).State = state;
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
+ Assert.False(referenceEntry.IsLoaded);
+
+ if (state == EntityState.Deleted)
+ {
+ Assert.Null(single.Parent);
+ }
+ else
+ {
+ Assert.NotNull(single.Parent);
+ }
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
+ Assert.True(referenceEntry.IsLoaded);
- var parent = context.ChangeTracker.Entries().Single().Entity;
+ RecordLog();
+ context.ChangeTracker.LazyLoadingEnabled = false;
- if (state == EntityState.Deleted)
- {
- Assert.Null(single.Parent);
- Assert.Null(parent.SingleShadowFk);
- }
- else
- {
- Assert.Same(parent, single.Parent);
- Assert.Same(single, parent.SingleShadowFk);
+ Assert.Equal(2, context.ChangeTracker.Entries().Count());
+
+ if (state != EntityState.Deleted)
+ {
+ Assert.Same(single, single.Parent!.SingleShadowFk);
+ }
+
+ var parent = context.ChangeTracker.Entries().Single().Entity;
+
+ if (state == EntityState.Deleted)
+ {
+ Assert.Null(single.Parent);
+ Assert.Null(parent.SingleShadowFk);
+ }
+ else
+ {
+ Assert.Same(parent, single.Parent);
+ Assert.Same(single, parent.SingleShadowFk);
+ }
}
}
[ConditionalTheory]
[InlineData(EntityState.Unchanged)]
+ [InlineData(EntityState.Added)]
[InlineData(EntityState.Modified)]
[InlineData(EntityState.Deleted)]
+ [InlineData(EntityState.Detached)]
public virtual void Lazy_load_one_to_one_reference_to_dependent_shadow_fk(EntityState state)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
var parent = context.Set().Single();
ClearLog();
var referenceEntry = context.Entry(parent).Reference(e => e.SingleShadowFk);
- context.Entry(parent).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(parent).State = state;
+ }
Assert.False(referenceEntry.IsLoaded);
@@ -1770,31 +2099,45 @@ public virtual void Lazy_load_one_to_one_reference_to_dependent_shadow_fk(Entity
RecordLog();
context.ChangeTracker.LazyLoadingEnabled = false;
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
+ Assert.Equal(state == EntityState.Detached ? 0 : 2, context.ChangeTracker.Entries().Count());
- var single = context.ChangeTracker.Entries().Single().Entity;
+ if (state != EntityState.Deleted)
+ {
+ Assert.Same(parent, parent.SingleShadowFk!.Parent);
+ }
- Assert.Same(single, parent.SingleShadowFk);
- Assert.Same(parent, single.Parent);
+ if (state != EntityState.Detached)
+ {
+ var single = context.ChangeTracker.Entries().Single().Entity;
+
+ Assert.Same(single, parent.SingleShadowFk);
+ Assert.Same(parent, single.Parent);
+ }
}
[ConditionalTheory]
[InlineData(EntityState.Unchanged)]
+ [InlineData(EntityState.Added)]
[InlineData(EntityState.Modified)]
[InlineData(EntityState.Deleted)]
+ [InlineData(EntityState.Detached)]
public virtual void Lazy_load_many_to_one_reference_to_principal_null_FK_shadow_fk(EntityState state)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
var child = context.CreateProxy();
child.Id = 767;
context.Attach(child);
+ context.Entry(child).Property("ParentId").CurrentValue = null;
ClearLog();
var referenceEntry = context.Entry(child).Reference(e => e.Parent);
- context.Entry(child).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(child).State = state;
+ }
Assert.False(referenceEntry.IsLoaded);
@@ -1811,21 +2154,27 @@ public virtual void Lazy_load_many_to_one_reference_to_principal_null_FK_shadow_
[ConditionalTheory]
[InlineData(EntityState.Unchanged)]
+ [InlineData(EntityState.Added)]
[InlineData(EntityState.Modified)]
[InlineData(EntityState.Deleted)]
+ [InlineData(EntityState.Detached)]
public virtual void Lazy_load_one_to_one_reference_to_principal_null_FK_shadow_fk(EntityState state)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
var single = context.CreateProxy();
single.Id = 767;
context.Attach(single);
+ context.Entry(single).Property("ParentId").CurrentValue = null;
ClearLog();
var referenceEntry = context.Entry(single).Reference(e => e.Parent);
- context.Entry(single).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(single).State = state;
+ }
Assert.False(referenceEntry.IsLoaded);
@@ -1843,18 +2192,23 @@ public virtual void Lazy_load_one_to_one_reference_to_principal_null_FK_shadow_f
[ConditionalTheory]
[InlineData(EntityState.Unchanged)]
+ [InlineData(EntityState.Added)]
[InlineData(EntityState.Modified)]
[InlineData(EntityState.Deleted)]
+ [InlineData(EntityState.Detached)]
public virtual void Lazy_load_collection_composite_key(EntityState state)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
var parent = context.Set().Single();
ClearLog();
var collectionEntry = context.Entry(parent).Collection(e => e.ChildrenCompositeKey);
- context.Entry(parent).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(parent).State = state;
+ }
Assert.False(collectionEntry.IsLoaded);
@@ -1868,23 +2222,28 @@ public virtual void Lazy_load_collection_composite_key(EntityState state)
Assert.Equal(2, parent.ChildrenCompositeKey.Count());
Assert.All(parent.ChildrenCompositeKey.Select(e => e.Parent), c => Assert.Same(parent, c));
- Assert.Equal(3, context.ChangeTracker.Entries().Count());
+ Assert.Equal(state == EntityState.Detached ? 0 : 3, context.ChangeTracker.Entries().Count());
}
[ConditionalTheory]
[InlineData(EntityState.Unchanged)]
+ [InlineData(EntityState.Added)]
[InlineData(EntityState.Modified)]
[InlineData(EntityState.Deleted)]
+ [InlineData(EntityState.Detached)]
public virtual void Lazy_load_many_to_one_reference_to_principal_composite_key(EntityState state)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
var child = context.Set().Single(e => e.Id == 52);
ClearLog();
var referenceEntry = context.Entry(child).Reference(e => e.Parent);
- context.Entry(child).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(child).State = state;
+ }
Assert.False(referenceEntry.IsLoaded);
@@ -1902,36 +2261,49 @@ public virtual void Lazy_load_many_to_one_reference_to_principal_composite_key(E
RecordLog();
context.ChangeTracker.LazyLoadingEnabled = false;
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
-
- var parent = context.ChangeTracker.Entries().Single().Entity;
+ Assert.Equal(state == EntityState.Detached ? 0 : 2, context.ChangeTracker.Entries().Count());
- if (state == EntityState.Deleted)
+ if (state != EntityState.Deleted)
{
- Assert.Null(child.Parent);
- Assert.Null(parent.ChildrenCompositeKey);
+ Assert.Same(child, child.Parent!.ChildrenCompositeKey.Single());
}
- else
+
+ if (state != EntityState.Detached)
{
- Assert.Same(parent, child.Parent);
- Assert.Same(child, parent.ChildrenCompositeKey.Single());
+ var parent = context.ChangeTracker.Entries().Single().Entity;
+
+ if (state == EntityState.Deleted)
+ {
+ Assert.Null(child.Parent);
+ Assert.Null(parent.ChildrenCompositeKey);
+ }
+ else
+ {
+ Assert.Same(parent, child.Parent);
+ Assert.Same(child, parent.ChildrenCompositeKey.Single());
+ }
}
}
[ConditionalTheory]
[InlineData(EntityState.Unchanged)]
+ [InlineData(EntityState.Added)]
[InlineData(EntityState.Modified)]
[InlineData(EntityState.Deleted)]
+ [InlineData(EntityState.Detached)]
public virtual void Lazy_load_one_to_one_reference_to_principal_composite_key(EntityState state)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
var single = context.Set().Single();
ClearLog();
var referenceEntry = context.Entry(single).Reference(e => e.Parent);
- context.Entry(single).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(single).State = state;
+ }
Assert.False(referenceEntry.IsLoaded);
@@ -1949,36 +2321,49 @@ public virtual void Lazy_load_one_to_one_reference_to_principal_composite_key(En
RecordLog();
context.ChangeTracker.LazyLoadingEnabled = false;
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
-
- var parent = context.ChangeTracker.Entries().Single().Entity;
+ Assert.Equal(state == EntityState.Detached ? 0 : 2, context.ChangeTracker.Entries().Count());
- if (state == EntityState.Deleted)
+ if (state != EntityState.Deleted)
{
- Assert.Null(single.Parent);
- Assert.Null(parent.SingleCompositeKey);
+ Assert.Same(single, single.Parent!.SingleCompositeKey);
}
- else
+
+ if (state != EntityState.Detached)
{
- Assert.Same(parent, single.Parent);
- Assert.Same(single, parent.SingleCompositeKey);
+ var parent = context.ChangeTracker.Entries().Single().Entity;
+
+ if (state == EntityState.Deleted)
+ {
+ Assert.Null(single.Parent);
+ Assert.Null(parent.SingleCompositeKey);
+ }
+ else
+ {
+ Assert.Same(parent, single.Parent);
+ Assert.Same(single, parent.SingleCompositeKey);
+ }
}
}
[ConditionalTheory]
[InlineData(EntityState.Unchanged)]
+ [InlineData(EntityState.Added)]
[InlineData(EntityState.Modified)]
[InlineData(EntityState.Deleted)]
+ [InlineData(EntityState.Detached)]
public virtual void Lazy_load_one_to_one_reference_to_dependent_composite_key(EntityState state)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
var parent = context.Set().Single();
ClearLog();
var referenceEntry = context.Entry(parent).Reference(e => e.SingleCompositeKey);
- context.Entry(parent).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(parent).State = state;
+ }
Assert.False(referenceEntry.IsLoaded);
@@ -1989,21 +2374,31 @@ public virtual void Lazy_load_one_to_one_reference_to_dependent_composite_key(En
RecordLog();
context.ChangeTracker.LazyLoadingEnabled = false;
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
+ Assert.Equal(state == EntityState.Detached ? 0 : 2, context.ChangeTracker.Entries().Count());
+
+ if (state != EntityState.Deleted)
+ {
+ Assert.Same(parent, parent.SingleCompositeKey.Parent);
+ }
- var single = context.ChangeTracker.Entries().Single().Entity;
+ if (state != EntityState.Detached)
+ {
+ var single = context.ChangeTracker.Entries().Single().Entity;
- Assert.Same(single, parent.SingleCompositeKey);
- Assert.Same(parent, single.Parent);
+ Assert.Same(single, parent.SingleCompositeKey);
+ Assert.Same(parent, single.Parent);
+ }
}
[ConditionalTheory]
[InlineData(EntityState.Unchanged)]
+ [InlineData(EntityState.Added)]
[InlineData(EntityState.Modified)]
[InlineData(EntityState.Deleted)]
+ [InlineData(EntityState.Detached)]
public virtual void Lazy_load_many_to_one_reference_to_principal_null_FK_composite_key(EntityState state)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
var child = context.CreateProxy();
child.Id = 767;
child.ParentId = 567;
@@ -2014,7 +2409,10 @@ public virtual void Lazy_load_many_to_one_reference_to_principal_null_FK_composi
var referenceEntry = context.Entry(child).Reference(e => e.Parent);
- context.Entry(child).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(child).State = state;
+ }
Assert.False(referenceEntry.IsLoaded);
@@ -2031,11 +2429,13 @@ public virtual void Lazy_load_many_to_one_reference_to_principal_null_FK_composi
[ConditionalTheory]
[InlineData(EntityState.Unchanged)]
+ [InlineData(EntityState.Added)]
[InlineData(EntityState.Modified)]
[InlineData(EntityState.Deleted)]
+ [InlineData(EntityState.Detached)]
public virtual void Lazy_load_one_to_one_reference_to_principal_null_FK_composite_key(EntityState state)
{
- using var context = CreateContext(lazyLoadingEnabled: true);
+ using var context = CreateContext(lazyLoadingEnabled: true, tracking: state != EntityState.Detached);
var single = context.CreateProxy();
single.Id = 767;
single.ParentAlternateId = "Boot";
@@ -2046,7 +2446,10 @@ public virtual void Lazy_load_one_to_one_reference_to_principal_null_FK_composit
var referenceEntry = context.Entry(single).Reference(e => e.Parent);
- context.Entry(single).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(single).State = state;
+ }
Assert.False(referenceEntry.IsLoaded);
@@ -2095,54 +2498,6 @@ public virtual void Lazy_load_reference_to_dependent_for_detached_is_no_op()
Assert.Null(parent.Single);
}
- [ConditionalFact]
- public virtual void Lazy_load_collection_for_no_tracking_throws()
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var parent = context.Set().AsNoTracking().Single();
-
- Assert.Equal(
- CoreStrings.WarningAsErrorTemplate(
- CoreEventId.DetachedLazyLoadingWarning.ToString(),
- CoreResources.LogDetachedLazyLoading(new TestLogger())
- .GenerateMessage(nameof(Parent.Children), "MotherProxy"),
- "CoreEventId.DetachedLazyLoadingWarning"),
- Assert.Throws(
- () => parent.Children).Message);
- }
-
- [ConditionalFact]
- public virtual void Lazy_load_reference_to_principal_for_no_tracking_throws()
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var child = context.Set().AsNoTracking().Single(e => e.Id == 12);
-
- Assert.Equal(
- CoreStrings.WarningAsErrorTemplate(
- CoreEventId.DetachedLazyLoadingWarning.ToString(),
- CoreResources.LogDetachedLazyLoading(new TestLogger())
- .GenerateMessage(nameof(Child.Parent), "ChildProxy"),
- "CoreEventId.DetachedLazyLoadingWarning"),
- Assert.Throws(
- () => child.Parent).Message);
- }
-
- [ConditionalFact]
- public virtual void Lazy_load_reference_to_dependent_for_no_tracking_throws()
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var parent = context.Set().AsNoTracking().Single();
-
- Assert.Equal(
- CoreStrings.WarningAsErrorTemplate(
- CoreEventId.DetachedLazyLoadingWarning.ToString(),
- CoreResources.LogDetachedLazyLoading(new TestLogger())
- .GenerateMessage(nameof(Parent.Single), "MotherProxy"),
- "CoreEventId.DetachedLazyLoadingWarning"),
- Assert.Throws(
- () => parent.Single).Message);
- }
-
[ConditionalFact]
public virtual void Lazy_load_collection_for_no_tracking_does_not_throw_if_populated()
{
@@ -2189,16 +2544,21 @@ public virtual void Lazy_load_reference_to_dependent_for_no_tracking_does_not_th
[InlineData(EntityState.Modified, false)]
[InlineData(EntityState.Deleted, true)]
[InlineData(EntityState.Deleted, false)]
+ [InlineData(EntityState.Detached, true)]
+ [InlineData(EntityState.Detached, false)]
public virtual async Task Load_collection(EntityState state, bool async)
{
- using var context = CreateContext();
+ using var context = CreateContext(tracking: state != EntityState.Detached);
var parent = context.Set().Single();
ClearLog();
var collectionEntry = context.Entry(parent).Collection(e => e.Children);
- context.Entry(parent).State = state;
+ if (state != EntityState.Detached)
+ {
+ context.Entry(parent).State = state;
+ }
Assert.False(collectionEntry.IsLoaded);
@@ -2219,7 +2579,7 @@ public virtual async Task Load_collection(EntityState state, bool async)
Assert.Equal(2, parent.Children.Count());
Assert.All(parent.Children.Select(e => e.Parent), c => Assert.Same(parent, c));
- Assert.Equal(3, context.ChangeTracker.Entries().Count());
+ Assert.Equal(state == EntityState.Detached ? 0 : 3, context.ChangeTracker.Entries().Count());
}
[ConditionalFact]
@@ -2929,10 +3289,23 @@ public class OwnedAddress
public int CountryCode { get; set; }
}
- protected DbContext CreateContext(bool lazyLoadingEnabled = false)
+ private static void AssertDisposed(Func testCode, string entityTypeName, string navigationName)
+ => Assert.Equal(
+ CoreStrings.WarningAsErrorTemplate(
+ CoreEventId.LazyLoadOnDisposedContextWarning.ToString(),
+ CoreResources.LogLazyLoadOnDisposedContext(new TestLogger())
+ .GenerateMessage(entityTypeName, navigationName),
+ "CoreEventId.LazyLoadOnDisposedContextWarning"),
+ Assert.Throws(testCode).Message);
+
+ protected DbContext CreateContext(bool lazyLoadingEnabled = false, bool tracking = true)
+ => CreateContext(lazyLoadingEnabled, tracking ? QueryTrackingBehavior.TrackAll : QueryTrackingBehavior.NoTracking);
+
+ protected DbContext CreateContext(bool lazyLoadingEnabled, QueryTrackingBehavior trackingBehavior)
{
var context = Fixture.CreateContext();
context.ChangeTracker.LazyLoadingEnabled = lazyLoadingEnabled;
+ context.ChangeTracker.QueryTrackingBehavior = trackingBehavior;
return context;
}
diff --git a/test/EFCore.Specification.Tests/LoadTestBase.cs b/test/EFCore.Specification.Tests/LoadTestBase.cs
index b4bd6b9df51..0361144ba50 100644
--- a/test/EFCore.Specification.Tests/LoadTestBase.cs
+++ b/test/EFCore.Specification.Tests/LoadTestBase.cs
@@ -3,12 +3,11 @@
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
-using Microsoft.EntityFrameworkCore.Diagnostics.Internal;
// ReSharper disable InconsistentNaming
namespace Microsoft.EntityFrameworkCore;
-public abstract class LoadTestBase : IClassFixture
+public abstract partial class LoadTestBase : IClassFixture
where TFixture : LoadTestBase.LoadFixtureBase
{
protected LoadTestBase(TFixture fixture)
@@ -155,1471 +154,49 @@ public virtual void Attached_collections_are_not_marked_as_loaded(EntityState st
Assert.False(context.Entry(parent).Collection(e => e.ChildrenCompositeKey).IsLoaded);
}
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_collection(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var changeDetector = (ChangeDetectorProxy)context.GetService();
-
- var parent = context.Set().Single();
-
- ClearLog();
-
- var collectionEntry = context.Entry(parent).Collection(e => e.Children);
-
- context.Entry(parent).State = state;
-
- Assert.False(collectionEntry.IsLoaded);
-
- changeDetector.DetectChangesCalled = false;
-
- Assert.NotNull(parent.Children);
-
- Assert.False(changeDetector.DetectChangesCalled);
-
- Assert.True(collectionEntry.IsLoaded);
-
- Assert.All(parent.Children.Select(e => e.Parent), c => Assert.Same(parent, c));
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Equal(2, parent.Children.Count());
- Assert.Equal(3, context.ChangeTracker.Entries().Count());
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_many_to_one_reference_to_principal(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var changeDetector = (ChangeDetectorProxy)context.GetService();
-
- var child = context.Set().Single(e => e.Id == 12);
-
- ClearLog();
-
- var referenceEntry = context.Entry(child).Reference(e => e.Parent);
-
- context.Entry(child).State = state;
-
- Assert.False(referenceEntry.IsLoaded);
-
- changeDetector.DetectChangesCalled = false;
-
- if (state == EntityState.Deleted)
- {
- Assert.Null(child.Parent);
- }
- else
- {
- Assert.NotNull(child.Parent);
- }
-
- Assert.False(changeDetector.DetectChangesCalled);
-
- Assert.True(referenceEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
-
- var parent = context.ChangeTracker.Entries().Single().Entity;
-
- if (state == EntityState.Deleted)
- {
- Assert.Null(child.Parent);
- Assert.Null(parent.Children);
- }
- else
- {
- Assert.Same(parent, child.Parent);
- Assert.Same(child, parent.Children.Single());
- }
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_one_to_one_reference_to_principal(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var changeDetector = (ChangeDetectorProxy)context.GetService();
-
- var single = context.Set().Single();
-
- ClearLog();
-
- var referenceEntry = context.Entry(single).Reference(e => e.Parent);
-
- context.Entry(single).State = state;
-
- Assert.False(referenceEntry.IsLoaded);
-
- changeDetector.DetectChangesCalled = false;
-
- if (state == EntityState.Deleted)
- {
- Assert.Null(single.Parent);
- }
- else
- {
- Assert.NotNull(single.Parent);
- }
-
- Assert.False(changeDetector.DetectChangesCalled);
-
- Assert.True(referenceEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
-
- var parent = context.ChangeTracker.Entries().Single().Entity;
-
- if (state == EntityState.Deleted)
- {
- Assert.Null(single.Parent);
- Assert.Null(parent.Single);
- }
- else
- {
- Assert.Same(parent, single.Parent);
- Assert.Same(single, parent.Single);
- }
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_one_to_one_reference_to_dependent(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var changeDetector = (ChangeDetectorProxy)context.GetService();
-
- var parent = context.Set().Single();
-
- ClearLog();
-
- var referenceEntry = context.Entry(parent).Reference(e => e.Single);
-
- context.Entry(parent).State = state;
-
- Assert.False(referenceEntry.IsLoaded);
-
- changeDetector.DetectChangesCalled = false;
-
- Assert.NotNull(parent.Single);
-
- Assert.False(changeDetector.DetectChangesCalled);
-
- Assert.True(referenceEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
-
- var single = context.ChangeTracker.Entries().Single().Entity;
-
- Assert.Same(single, parent.Single);
- Assert.Same(parent, single.Parent);
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_one_to_one_PK_to_PK_reference_to_principal(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var changeDetector = (ChangeDetectorProxy)context.GetService();
-
- var single = context.Set().Single();
-
- ClearLog();
-
- var referenceEntry = context.Entry(single).Reference(e => e.Parent);
-
- context.Entry(single).State = state;
-
- Assert.False(referenceEntry.IsLoaded);
-
- changeDetector.DetectChangesCalled = false;
-
- if (state == EntityState.Deleted)
- {
- Assert.Null(single.Parent);
- }
- else
- {
- Assert.NotNull(single.Parent);
- }
-
- Assert.False(changeDetector.DetectChangesCalled);
-
- Assert.True(referenceEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
-
- var parent = context.ChangeTracker.Entries().Single().Entity;
-
- if (state == EntityState.Deleted)
- {
- Assert.Null(single.Parent);
- Assert.Null(parent.SinglePkToPk);
- }
- else
- {
- Assert.Same(parent, single.Parent);
- Assert.Same(single, parent.SinglePkToPk);
- }
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_one_to_one_PK_to_PK_reference_to_dependent(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var changeDetector = (ChangeDetectorProxy)context.GetService();
-
- var parent = context.Set().Single();
-
- ClearLog();
-
- var referenceEntry = context.Entry(parent).Reference(e => e.SinglePkToPk);
-
- context.Entry(parent).State = state;
-
- Assert.False(referenceEntry.IsLoaded);
-
- changeDetector.DetectChangesCalled = false;
-
- Assert.NotNull(parent.SinglePkToPk);
-
- Assert.False(changeDetector.DetectChangesCalled);
-
- Assert.True(referenceEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
-
- var single = context.ChangeTracker.Entries().Single().Entity;
-
- Assert.Same(single, parent.SinglePkToPk);
- Assert.Same(parent, single.Parent);
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_many_to_one_reference_to_principal_null_FK(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var changeDetector = (ChangeDetectorProxy)context.GetService();
-
- var child = context.Attach(
- new Child(context.GetService().Load) { Id = 767, ParentId = null }).Entity;
-
- ClearLog();
-
- var referenceEntry = context.Entry(child).Reference(e => e.Parent);
-
- context.Entry(child).State = state;
-
- Assert.False(referenceEntry.IsLoaded);
-
- changeDetector.DetectChangesCalled = false;
-
- Assert.Null(child.Parent);
-
- Assert.False(changeDetector.DetectChangesCalled);
-
- Assert.True(referenceEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Single(context.ChangeTracker.Entries());
- Assert.Null(child.Parent);
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_one_to_one_reference_to_principal_null_FK(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var changeDetector = (ChangeDetectorProxy)context.GetService();
-
- var single = context.Attach(
- new Single(context.GetService().Load) { Id = 767, ParentId = null }).Entity;
-
- ClearLog();
-
- var referenceEntry = context.Entry(single).Reference(e => e.Parent);
-
- context.Entry(single).State = state;
-
- Assert.False(referenceEntry.IsLoaded);
-
- changeDetector.DetectChangesCalled = false;
-
- Assert.Null(single.Parent);
-
- Assert.False(changeDetector.DetectChangesCalled);
-
- Assert.True(referenceEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Single(context.ChangeTracker.Entries());
-
- Assert.Null(single.Parent);
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_collection_not_found(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var changeDetector = (ChangeDetectorProxy)context.GetService();
-
- var parent = context.Attach(
- new Parent(context.GetService().Load) { Id = 767, AlternateId = "NewRoot" }).Entity;
-
- ClearLog();
-
- var collectionEntry = context.Entry(parent).Collection(e => e.Children);
-
- context.Entry(parent).State = state;
-
- Assert.False(collectionEntry.IsLoaded);
-
- changeDetector.DetectChangesCalled = false;
-
- Assert.Empty(parent.Children);
-
- Assert.False(changeDetector.DetectChangesCalled);
-
- Assert.True(collectionEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Empty(parent.Children);
- Assert.Single(context.ChangeTracker.Entries());
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_many_to_one_reference_to_principal_not_found(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var changeDetector = (ChangeDetectorProxy)context.GetService();
-
- var child = context.Attach(
- new Child(context.GetService().Load) { Id = 767, ParentId = 787 }).Entity;
-
- ClearLog();
-
- var referenceEntry = context.Entry(child).Reference(e => e.Parent);
-
- context.Entry(child).State = state;
-
- Assert.False(referenceEntry.IsLoaded);
-
- changeDetector.DetectChangesCalled = false;
-
- Assert.Null(child.Parent);
-
- Assert.False(changeDetector.DetectChangesCalled);
-
- Assert.True(referenceEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Single(context.ChangeTracker.Entries());
- Assert.Null(child.Parent);
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_one_to_one_reference_to_principal_not_found(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var changeDetector = (ChangeDetectorProxy)context.GetService();
-
- var single = context.Attach(
- new Single(context.GetService().Load) { Id = 767, ParentId = 787 }).Entity;
-
- ClearLog();
-
- var referenceEntry = context.Entry(single).Reference(e => e.Parent);
-
- context.Entry(single).State = state;
-
- Assert.False(referenceEntry.IsLoaded);
-
- changeDetector.DetectChangesCalled = false;
-
- Assert.Null(single.Parent);
-
- Assert.False(changeDetector.DetectChangesCalled);
-
- Assert.True(referenceEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Single(context.ChangeTracker.Entries());
-
- Assert.Null(single.Parent);
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_one_to_one_reference_to_dependent_not_found(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var changeDetector = (ChangeDetectorProxy)context.GetService();
-
- var parent = context.Attach(
- new Parent(context.GetService().Load) { Id = 767, AlternateId = "NewRoot" }).Entity;
-
- ClearLog();
-
- var referenceEntry = context.Entry(parent).Reference(e => e.Single);
-
- context.Entry(parent).State = state;
-
- Assert.False(referenceEntry.IsLoaded);
-
- changeDetector.DetectChangesCalled = false;
-
- Assert.Null(parent.Single);
-
- Assert.False(changeDetector.DetectChangesCalled);
-
- Assert.True(referenceEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Single(context.ChangeTracker.Entries());
-
- Assert.Null(parent.Single);
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged, CascadeTiming.Immediate)]
- [InlineData(EntityState.Modified, CascadeTiming.Immediate)]
- [InlineData(EntityState.Deleted, CascadeTiming.Immediate)]
- [InlineData(EntityState.Unchanged, CascadeTiming.OnSaveChanges)]
- [InlineData(EntityState.Modified, CascadeTiming.OnSaveChanges)]
- [InlineData(EntityState.Deleted, CascadeTiming.OnSaveChanges)]
- public virtual void Lazy_load_collection_already_loaded(EntityState state, CascadeTiming deleteOrphansTiming)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming;
-
- var changeDetector = (ChangeDetectorProxy)context.GetService();
-
- var parent = context.Set().Include(e => e.Children).Single();
-
- ClearLog();
-
- var collectionEntry = context.Entry(parent).Collection(e => e.Children);
-
- context.Entry(parent).State = state;
-
- Assert.True(collectionEntry.IsLoaded);
-
- changeDetector.DetectChangesCalled = false;
-
- Assert.NotNull(parent.Children);
-
- Assert.False(changeDetector.DetectChangesCalled);
-
- Assert.True(collectionEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Equal(2, parent.Children.Count());
-
- if (state == EntityState.Deleted
- && deleteOrphansTiming != CascadeTiming.Never)
- {
- Assert.All(parent.Children.Select(e => e.Parent), c => Assert.Null(c));
- }
- else
- {
- Assert.All(parent.Children.Select(e => e.Parent), c => Assert.Same(parent, c));
- }
-
- Assert.Equal(3, context.ChangeTracker.Entries().Count());
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_many_to_one_reference_to_principal_already_loaded(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var changeDetector = (ChangeDetectorProxy)context.GetService();
-
- var child = context.Set().Include(e => e.Parent).Single(e => e.Id == 12);
-
- ClearLog();
-
- var referenceEntry = context.Entry(child).Reference(e => e.Parent);
-
- context.Entry(child).State = state;
-
- Assert.True(referenceEntry.IsLoaded);
-
- changeDetector.DetectChangesCalled = false;
-
- Assert.NotNull(child.Parent);
-
- Assert.False(changeDetector.DetectChangesCalled);
-
- Assert.True(referenceEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
-
- var parent = context.ChangeTracker.Entries().Single().Entity;
-
- Assert.Same(parent, child.Parent);
- Assert.Same(child, parent.Children.Single());
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_one_to_one_reference_to_principal_already_loaded(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var changeDetector = (ChangeDetectorProxy)context.GetService();
-
- var single = context.Set().Include(e => e.Parent).Single();
-
- ClearLog();
-
- var referenceEntry = context.Entry(single).Reference(e => e.Parent);
-
- context.Entry(single).State = state;
-
- Assert.True(referenceEntry.IsLoaded);
-
- changeDetector.DetectChangesCalled = false;
-
- Assert.NotNull(single.Parent);
-
- Assert.False(changeDetector.DetectChangesCalled);
-
- Assert.True(referenceEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
-
- var parent = context.ChangeTracker.Entries().Single().Entity;
-
- Assert.Same(parent, single.Parent);
- Assert.Same(single, parent.Single);
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged, CascadeTiming.Immediate)]
- [InlineData(EntityState.Modified, CascadeTiming.Immediate)]
- [InlineData(EntityState.Deleted, CascadeTiming.Immediate)]
- [InlineData(EntityState.Unchanged, CascadeTiming.OnSaveChanges)]
- [InlineData(EntityState.Modified, CascadeTiming.OnSaveChanges)]
- [InlineData(EntityState.Deleted, CascadeTiming.OnSaveChanges)]
- public virtual void Lazy_load_one_to_one_reference_to_dependent_already_loaded(EntityState state, CascadeTiming deleteOrphansTiming)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming;
-
- var changeDetector = (ChangeDetectorProxy)context.GetService();
-
- var parent = context.Set().Include(e => e.Single).Single();
-
- ClearLog();
-
- var referenceEntry = context.Entry(parent).Reference(e => e.Single);
-
- context.Entry(parent).State = state;
-
- Assert.True(referenceEntry.IsLoaded);
-
- changeDetector.DetectChangesCalled = false;
-
- Assert.NotNull(parent.Single);
-
- Assert.False(changeDetector.DetectChangesCalled);
-
- Assert.True(referenceEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
-
- var single = context.ChangeTracker.Entries().Single().Entity;
-
- Assert.Same(single, parent.Single);
-
- if (state == EntityState.Deleted
- && deleteOrphansTiming != CascadeTiming.Never)
- {
- Assert.Null(single.Parent);
- }
- else
- {
- Assert.Same(parent, single.Parent);
- }
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_one_to_one_PK_to_PK_reference_to_principal_already_loaded(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var changeDetector = (ChangeDetectorProxy)context.GetService();
-
- var single = context.Set().Include(e => e.Parent).Single();
-
- ClearLog();
-
- var referenceEntry = context.Entry(single).Reference(e => e.Parent);
-
- context.Entry(single).State = state;
-
- Assert.True(referenceEntry.IsLoaded);
-
- changeDetector.DetectChangesCalled = false;
-
- Assert.NotNull(single.Parent);
-
- Assert.False(changeDetector.DetectChangesCalled);
-
- Assert.True(referenceEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
-
- var parent = context.ChangeTracker.Entries().Single().Entity;
-
- Assert.Same(parent, single.Parent);
- Assert.Same(single, parent.SinglePkToPk);
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_one_to_one_PK_to_PK_reference_to_dependent_already_loaded(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var changeDetector = (ChangeDetectorProxy)context.GetService();
-
- var parent = context.Set().Include(e => e.SinglePkToPk).Single();
-
- ClearLog();
-
- var referenceEntry = context.Entry(parent).Reference(e => e.SinglePkToPk);
-
- context.Entry(parent).State = state;
-
- Assert.True(referenceEntry.IsLoaded);
-
- changeDetector.DetectChangesCalled = false;
-
- Assert.NotNull(parent.SinglePkToPk);
-
- Assert.False(changeDetector.DetectChangesCalled);
-
- Assert.True(referenceEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
-
- var single = context.ChangeTracker.Entries().Single().Entity;
-
- Assert.Same(single, parent.SinglePkToPk);
- Assert.Same(parent, single.Parent);
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_many_to_one_reference_to_principal_alternate_key(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var child = context.Set().Single(e => e.Id == 32);
-
- ClearLog();
-
- var referenceEntry = context.Entry(child).Reference(e => e.Parent);
-
- context.Entry(child).State = state;
-
- Assert.False(referenceEntry.IsLoaded);
-
- if (state == EntityState.Deleted)
- {
- Assert.Null(child.Parent);
- }
- else
- {
- Assert.NotNull(child.Parent);
- }
-
- Assert.True(referenceEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
-
- var parent = context.ChangeTracker.Entries().Single().Entity;
-
- if (state == EntityState.Deleted)
- {
- Assert.Null(child.Parent);
- Assert.Null(parent.ChildrenAk);
- }
- else
- {
- Assert.Same(parent, child.Parent);
- Assert.Same(child, parent.ChildrenAk.Single());
- }
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_one_to_one_reference_to_principal_alternate_key(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var single = context.Set().Single();
-
- ClearLog();
-
- var referenceEntry = context.Entry(single).Reference(e => e.Parent);
-
- context.Entry(single).State = state;
-
- Assert.False(referenceEntry.IsLoaded);
-
- if (state == EntityState.Deleted)
- {
- Assert.Null(single.Parent);
- }
- else
- {
- Assert.NotNull(single.Parent);
- }
-
- Assert.True(referenceEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
-
- var parent = context.ChangeTracker.Entries().Single().Entity;
-
- if (state == EntityState.Deleted)
- {
- Assert.Null(single.Parent);
- Assert.Null(parent.SingleAk);
- }
- else
- {
- Assert.Same(parent, single.Parent);
- Assert.Same(single, parent.SingleAk);
- }
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_one_to_one_reference_to_dependent_alternate_key(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var parent = context.Set().Single();
-
- ClearLog();
-
- var referenceEntry = context.Entry(parent).Reference(e => e.SingleAk);
-
- context.Entry(parent).State = state;
-
- Assert.False(referenceEntry.IsLoaded);
-
- Assert.NotNull(parent.SingleAk);
-
- Assert.True(referenceEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
-
- var single = context.ChangeTracker.Entries().Single().Entity;
-
- Assert.Same(single, parent.SingleAk);
- Assert.Same(parent, single.Parent);
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_many_to_one_reference_to_principal_null_FK_alternate_key(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var child = context.Attach(
- new ChildAk(context.GetService().Load) { Id = 767, ParentId = null }).Entity;
-
- ClearLog();
-
- var referenceEntry = context.Entry(child).Reference(e => e.Parent);
-
- context.Entry(child).State = state;
-
- Assert.False(referenceEntry.IsLoaded);
-
- Assert.Null(child.Parent);
-
- Assert.True(referenceEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Single(context.ChangeTracker.Entries());
- Assert.Null(child.Parent);
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_one_to_one_reference_to_principal_null_FK_alternate_key(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var single = context.Attach(
- new SingleAk(context.GetService().Load) { Id = 767, ParentId = null }).Entity;
-
- ClearLog();
-
- var referenceEntry = context.Entry(single).Reference(e => e.Parent);
-
- context.Entry(single).State = state;
-
- Assert.False(referenceEntry.IsLoaded);
-
- Assert.Null(single.Parent);
-
- Assert.True(referenceEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Single(context.ChangeTracker.Entries());
-
- Assert.Null(single.Parent);
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_collection_shadow_fk(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var parent = context.Set().Single();
-
- ClearLog();
-
- var collectionEntry = context.Entry(parent).Collection(e => e.ChildrenShadowFk);
-
- context.Entry(parent).State = state;
-
- Assert.False(collectionEntry.IsLoaded);
-
- Assert.NotNull(parent.ChildrenShadowFk);
-
- Assert.True(collectionEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Equal(2, parent.ChildrenShadowFk.Count());
- Assert.All(parent.ChildrenShadowFk.Select(e => e.Parent), c => Assert.Same(parent, c));
-
- Assert.Equal(3, context.ChangeTracker.Entries().Count());
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_many_to_one_reference_to_principal_shadow_fk(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var child = context.Set().Single(e => e.Id == 52);
-
- ClearLog();
-
- var referenceEntry = context.Entry(child).Reference(e => e.Parent);
-
- context.Entry(child).State = state;
-
- Assert.False(referenceEntry.IsLoaded);
-
- if (state == EntityState.Deleted)
- {
- Assert.Null(child.Parent);
- }
- else
- {
- Assert.NotNull(child.Parent);
- }
-
- Assert.True(referenceEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
-
- var parent = context.ChangeTracker.Entries().Single().Entity;
-
- if (state == EntityState.Deleted)
- {
- Assert.Null(child.Parent);
- Assert.Null(parent.ChildrenShadowFk);
- }
- else
- {
- Assert.Same(parent, child.Parent);
- Assert.Same(child, parent.ChildrenShadowFk.Single());
- }
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_one_to_one_reference_to_principal_shadow_fk(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var single = context.Set().Single();
-
- ClearLog();
-
- var referenceEntry = context.Entry(single).Reference(e => e.Parent);
-
- context.Entry(single).State = state;
-
- Assert.False(referenceEntry.IsLoaded);
-
- if (state == EntityState.Deleted)
- {
- Assert.Null(single.Parent);
- }
- else
- {
- Assert.NotNull(single.Parent);
- }
-
- Assert.True(referenceEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
-
- var parent = context.ChangeTracker.Entries().Single().Entity;
-
- if (state == EntityState.Deleted)
- {
- Assert.Null(single.Parent);
- Assert.Null(parent.SingleShadowFk);
- }
- else
- {
- Assert.Same(parent, single.Parent);
- Assert.Same(single, parent.SingleShadowFk);
- }
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_one_to_one_reference_to_dependent_shadow_fk(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var parent = context.Set().Single();
-
- ClearLog();
-
- var referenceEntry = context.Entry(parent).Reference(e => e.SingleShadowFk);
-
- context.Entry(parent).State = state;
-
- Assert.False(referenceEntry.IsLoaded);
-
- Assert.NotNull(parent.SingleShadowFk);
-
- Assert.True(referenceEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
-
- var single = context.ChangeTracker.Entries().Single().Entity;
-
- Assert.Same(single, parent.SingleShadowFk);
- Assert.Same(parent, single.Parent);
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_many_to_one_reference_to_principal_null_FK_shadow_fk(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var child = context.Attach(
- new ChildShadowFk(context.GetService().Load) { Id = 767 }).Entity;
-
- ClearLog();
-
- var referenceEntry = context.Entry(child).Reference(e => e.Parent);
-
- context.Entry(child).State = state;
-
- Assert.False(referenceEntry.IsLoaded);
-
- Assert.Null(child.Parent);
-
- Assert.True(referenceEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Single(context.ChangeTracker.Entries());
- Assert.Null(child.Parent);
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_one_to_one_reference_to_principal_null_FK_shadow_fk(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var single = context.Attach(
- new SingleShadowFk(context.GetService().Load) { Id = 767 }).Entity;
-
- ClearLog();
-
- var referenceEntry = context.Entry(single).Reference(e => e.Parent);
-
- context.Entry(single).State = state;
-
- Assert.False(referenceEntry.IsLoaded);
-
- Assert.Null(single.Parent);
-
- Assert.True(referenceEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Single(context.ChangeTracker.Entries());
-
- Assert.Null(single.Parent);
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_collection_composite_key(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var parent = context.Set().Single();
-
- ClearLog();
-
- var collectionEntry = context.Entry(parent).Collection(e => e.ChildrenCompositeKey);
-
- context.Entry(parent).State = state;
-
- Assert.False(collectionEntry.IsLoaded);
-
- Assert.NotNull(parent.ChildrenCompositeKey);
-
- Assert.True(collectionEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Equal(2, parent.ChildrenCompositeKey.Count());
- Assert.All(parent.ChildrenCompositeKey.Select(e => e.Parent), c => Assert.Same(parent, c));
-
- Assert.Equal(3, context.ChangeTracker.Entries().Count());
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_many_to_one_reference_to_principal_composite_key(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var child = context.Set().Single(e => e.Id == 52);
-
- ClearLog();
-
- var referenceEntry = context.Entry(child).Reference(e => e.Parent);
-
- context.Entry(child).State = state;
-
- Assert.False(referenceEntry.IsLoaded);
-
- if (state == EntityState.Deleted)
- {
- Assert.Null(child.Parent);
- }
- else
- {
- Assert.NotNull(child.Parent);
- }
-
- Assert.True(referenceEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
-
- var parent = context.ChangeTracker.Entries().Single().Entity;
-
- if (state == EntityState.Deleted)
- {
- Assert.Null(child.Parent);
- Assert.Null(parent.ChildrenCompositeKey);
- }
- else
- {
- Assert.Same(parent, child.Parent);
- Assert.Same(child, parent.ChildrenCompositeKey.Single());
- }
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_one_to_one_reference_to_principal_composite_key(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var single = context.Set().Single();
-
- ClearLog();
-
- var referenceEntry = context.Entry(single).Reference(e => e.Parent);
-
- context.Entry(single).State = state;
-
- Assert.False(referenceEntry.IsLoaded);
-
- if (state == EntityState.Deleted)
- {
- Assert.Null(single.Parent);
- }
- else
- {
- Assert.NotNull(single.Parent);
- }
-
- Assert.True(referenceEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
-
- var parent = context.ChangeTracker.Entries().Single().Entity;
-
- if (state == EntityState.Deleted)
- {
- Assert.Null(single.Parent);
- Assert.Null(parent.SingleCompositeKey);
- }
- else
- {
- Assert.Same(parent, single.Parent);
- Assert.Same(single, parent.SingleCompositeKey);
- }
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_one_to_one_reference_to_dependent_composite_key(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var parent = context.Set().Single();
-
- ClearLog();
-
- var referenceEntry = context.Entry(parent).Reference(e => e.SingleCompositeKey);
-
- context.Entry(parent).State = state;
-
- Assert.False(referenceEntry.IsLoaded);
-
- Assert.NotNull(parent.SingleCompositeKey);
-
- Assert.True(referenceEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
-
- var single = context.ChangeTracker.Entries().Single().Entity;
-
- Assert.Same(single, parent.SingleCompositeKey);
- Assert.Same(parent, single.Parent);
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_many_to_one_reference_to_principal_null_FK_composite_key(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var child = context.Attach(
- new ChildCompositeKey(context.GetService().Load) { Id = 767, ParentId = 567 }).Entity;
-
- ClearLog();
-
- var referenceEntry = context.Entry(child).Reference(e => e.Parent);
-
- context.Entry(child).State = state;
-
- Assert.False(referenceEntry.IsLoaded);
-
- Assert.Null(child.Parent);
-
- Assert.True(referenceEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Single(context.ChangeTracker.Entries());
- Assert.Null(child.Parent);
- }
-
- [ConditionalTheory]
- [InlineData(EntityState.Unchanged)]
- [InlineData(EntityState.Modified)]
- [InlineData(EntityState.Deleted)]
- public virtual void Lazy_load_one_to_one_reference_to_principal_null_FK_composite_key(EntityState state)
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var single = context.Attach(
- new SingleCompositeKey(context.GetService().Load) { Id = 767, ParentAlternateId = "Boot" }).Entity;
-
- ClearLog();
-
- var referenceEntry = context.Entry(single).Reference(e => e.Parent);
-
- context.Entry(single).State = state;
-
- Assert.False(referenceEntry.IsLoaded);
-
- Assert.Null(single.Parent);
-
- Assert.True(referenceEntry.IsLoaded);
-
- RecordLog();
- context.ChangeTracker.LazyLoadingEnabled = false;
-
- Assert.Single(context.ChangeTracker.Entries());
-
- Assert.Null(single.Parent);
- }
-
- [ConditionalTheory]
- [InlineData(true)]
- [InlineData(false)]
- public virtual void Lazy_load_collection_for_detached_throws(bool noTracking)
- {
- using var context = CreateContext(lazyLoadingEnabled: true, noTracking: noTracking);
- var parent = context.Set().Single();
-
- if (!noTracking)
- {
- context.Entry(parent).State = EntityState.Detached;
- }
-
- Assert.Equal(
- CoreStrings.WarningAsErrorTemplate(
- CoreEventId.DetachedLazyLoadingWarning.ToString(),
- CoreResources.LogDetachedLazyLoading(new TestLogger())
- .GenerateMessage(nameof(Parent.Children), "Parent"),
- "CoreEventId.DetachedLazyLoadingWarning"),
- Assert.Throws(
- () => parent.Children).Message);
- }
-
- [ConditionalTheory]
- [InlineData(true)]
- [InlineData(false)]
- public virtual void Lazy_load_reference_to_principal_for_detached_throws(bool noTracking)
- {
- using var context = CreateContext(lazyLoadingEnabled: true, noTracking: noTracking);
- var child = context.Set().Single(e => e.Id == 12);
-
- if (!noTracking)
- {
- context.Entry(child).State = EntityState.Detached;
- }
-
- Assert.Equal(
- CoreStrings.WarningAsErrorTemplate(
- CoreEventId.DetachedLazyLoadingWarning.ToString(),
- CoreResources.LogDetachedLazyLoading(new TestLogger())
- .GenerateMessage(nameof(Child.Parent), "Child"),
- "CoreEventId.DetachedLazyLoadingWarning"),
- Assert.Throws(
- () => child.Parent).Message);
- }
-
- [ConditionalTheory]
- [InlineData(true)]
- [InlineData(false)]
- public virtual void Lazy_load_reference_to_dependent_for_detached_throws(bool noTracking)
- {
- using var context = CreateContext(lazyLoadingEnabled: true, noTracking: noTracking);
- var parent = context.Set().Single();
-
- if (!noTracking)
- {
- context.Entry(parent).State = EntityState.Detached;
- }
-
- Assert.Equal(
- CoreStrings.WarningAsErrorTemplate(
- CoreEventId.DetachedLazyLoadingWarning.ToString(),
- CoreResources.LogDetachedLazyLoading(new TestLogger())
- .GenerateMessage(nameof(Parent.Single), "Parent"),
- "CoreEventId.DetachedLazyLoadingWarning"),
- Assert.Throws(
- () => parent.Single).Message);
- }
-
- [ConditionalFact]
- public virtual void Lazy_loading_uses_field_access_when_abstract_base_class_navigation()
- {
- using var context = CreateContext(lazyLoadingEnabled: true);
- var product = context.Set().Single();
- var deposit = product.Deposit;
-
- Assert.NotNull(deposit);
- Assert.Same(deposit, product.Deposit);
- }
-
[ConditionalTheory]
[InlineData(EntityState.Unchanged, QueryTrackingBehavior.TrackAll, true)]
[InlineData(EntityState.Unchanged, QueryTrackingBehavior.TrackAll, false)]
+ [InlineData(EntityState.Added, QueryTrackingBehavior.TrackAll, true)]
+ [InlineData(EntityState.Added, QueryTrackingBehavior.TrackAll, false)]
[InlineData(EntityState.Modified, QueryTrackingBehavior.TrackAll, true)]
[InlineData(EntityState.Modified, QueryTrackingBehavior.TrackAll, false)]
[InlineData(EntityState.Deleted, QueryTrackingBehavior.TrackAll, true)]
[InlineData(EntityState.Deleted, QueryTrackingBehavior.TrackAll, false)]
+ [InlineData(EntityState.Detached, QueryTrackingBehavior.TrackAll, true)]
+ [InlineData(EntityState.Detached, QueryTrackingBehavior.TrackAll, false)]
[InlineData(EntityState.Unchanged, QueryTrackingBehavior.NoTracking, true)]
[InlineData(EntityState.Unchanged, QueryTrackingBehavior.NoTracking, false)]
+ [InlineData(EntityState.Added, QueryTrackingBehavior.NoTracking, true)]
+ [InlineData(EntityState.Added, QueryTrackingBehavior.NoTracking, false)]
[InlineData(EntityState.Modified, QueryTrackingBehavior.NoTracking, true)]
[InlineData(EntityState.Modified, QueryTrackingBehavior.NoTracking, false)]
[InlineData(EntityState.Deleted, QueryTrackingBehavior.NoTracking, true)]
[InlineData(EntityState.Deleted, QueryTrackingBehavior.NoTracking, false)]
+ [InlineData(EntityState.Detached, QueryTrackingBehavior.NoTracking, true)]
+ [InlineData(EntityState.Detached, QueryTrackingBehavior.NoTracking, false)]
[InlineData(EntityState.Unchanged, QueryTrackingBehavior.NoTrackingWithIdentityResolution, true)]
[InlineData(EntityState.Unchanged, QueryTrackingBehavior.NoTrackingWithIdentityResolution, false)]
+ [InlineData(EntityState.Added, QueryTrackingBehavior.NoTrackingWithIdentityResolution, true)]
+ [InlineData(EntityState.Added, QueryTrackingBehavior.NoTrackingWithIdentityResolution, false)]
[InlineData(EntityState.Modified, QueryTrackingBehavior.NoTrackingWithIdentityResolution, true)]
[InlineData(EntityState.Modified, QueryTrackingBehavior.NoTrackingWithIdentityResolution, false)]
[InlineData(EntityState.Deleted, QueryTrackingBehavior.NoTrackingWithIdentityResolution, true)]
[InlineData(EntityState.Deleted, QueryTrackingBehavior.NoTrackingWithIdentityResolution, false)]
+ [InlineData(EntityState.Detached, QueryTrackingBehavior.NoTrackingWithIdentityResolution, true)]
+ [InlineData(EntityState.Detached, QueryTrackingBehavior.NoTrackingWithIdentityResolution, false)]
public virtual async Task Load_collection(EntityState state, QueryTrackingBehavior queryTrackingBehavior, bool async)
{
using var context = CreateContext();
-
- context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
+ context.ChangeTracker.QueryTrackingBehavior = queryTrackingBehavior;
var parent = context.Set().Single();
ClearLog();
- var collectionEntry = context.Entry(parent).Collection(e => e.Children);
+ SetState(context, parent, state, queryTrackingBehavior);
- context.Entry(parent).State = state;
+ var collectionEntry = context.Entry(parent).Collection(e => e.Children);
Assert.False(collectionEntry.IsLoaded);
@@ -1640,16 +217,20 @@ public virtual async Task Load_collection(EntityState state, QueryTrackingBehavi
Assert.Equal(2, parent.Children.Count());
Assert.All(parent.Children.Select(e => e.Parent), c => Assert.Same(parent, c));
- Assert.Equal(3, context.ChangeTracker.Entries().Count());
+ Assert.Equal(state == EntityState.Detached ? 0 : 3, context.ChangeTracker.Entries().Count());
}
[ConditionalTheory]
[InlineData(EntityState.Unchanged, true)]
[InlineData(EntityState.Unchanged, false)]
+ [InlineData(EntityState.Added, true)]
+ [InlineData(EntityState.Added, false)]
[InlineData(EntityState.Modified, true)]
[InlineData(EntityState.Modified, false)]
[InlineData(EntityState.Deleted, true)]
[InlineData(EntityState.Deleted, false)]
+ [InlineData(EntityState.Detached, true)]
+ [InlineData(EntityState.Detached, false)]
public virtual async Task Load_many_to_one_reference_to_principal(EntityState state, bool async)
{
using var context = CreateContext();
@@ -1676,29 +257,41 @@ public virtual async Task Load_many_to_one_reference_to_principal(EntityState st
RecordLog();
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
-
- var parent = context.ChangeTracker.Entries().Single().Entity;
+ Assert.Equal(state == EntityState.Detached ? 0 : 2, context.ChangeTracker.Entries().Count());
- if (state == EntityState.Deleted)
+ if (state != EntityState.Deleted)
{
- Assert.Null(child.Parent);
- Assert.Null(parent.Children);
+ Assert.Same(child, child.Parent.Children.Single());
}
- else
+
+ if (state != EntityState.Detached)
{
- Assert.Same(parent, child.Parent);
- Assert.Same(child, parent.Children.Single());
+ var parent = context.ChangeTracker.Entries().Single().Entity;
+
+ if (state == EntityState.Deleted)
+ {
+ Assert.Null(child.Parent);
+ Assert.Null(parent.Children);
+ }
+ else
+ {
+ Assert.Same(parent, child.Parent);
+ Assert.Same(child, parent.Children.Single());
+ }
}
}
[ConditionalTheory]
[InlineData(EntityState.Unchanged, true)]
[InlineData(EntityState.Unchanged, false)]
+ [InlineData(EntityState.Added, true)]
+ [InlineData(EntityState.Added, false)]
[InlineData(EntityState.Modified, true)]
[InlineData(EntityState.Modified, false)]
[InlineData(EntityState.Deleted, true)]
[InlineData(EntityState.Deleted, false)]
+ [InlineData(EntityState.Detached, true)]
+ [InlineData(EntityState.Detached, false)]
public virtual async Task Load_one_to_one_reference_to_principal(EntityState state, bool async)
{
using var context = CreateContext();
@@ -1725,29 +318,41 @@ public virtual async Task Load_one_to_one_reference_to_principal(EntityState sta
RecordLog();
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
-
- var parent = context.ChangeTracker.Entries().Single().Entity;
+ Assert.Equal(state == EntityState.Detached ? 0 : 2, context.ChangeTracker.Entries().Count());
- if (state == EntityState.Deleted)
+ if (state != EntityState.Deleted)
{
- Assert.Null(single.Parent);
- Assert.Null(parent.Single);
+ Assert.Same(single, single.Parent.Single);
}
- else
+
+ if (state != EntityState.Detached)
{
- Assert.Same(parent, single.Parent);
- Assert.Same(single, parent.Single);
+ var parent = context.ChangeTracker.Entries().Single().Entity;
+
+ if (state == EntityState.Deleted)
+ {
+ Assert.Null(single.Parent);
+ Assert.Null(parent.Single);
+ }
+ else
+ {
+ Assert.Same(parent, single.Parent);
+ Assert.Same(single, parent.Single);
+ }
}
}
[ConditionalTheory]
[InlineData(EntityState.Unchanged, true)]
[InlineData(EntityState.Unchanged, false)]
+ [InlineData(EntityState.Added, true)]
+ [InlineData(EntityState.Added, false)]
[InlineData(EntityState.Modified, true)]
[InlineData(EntityState.Modified, false)]
[InlineData(EntityState.Deleted, true)]
[InlineData(EntityState.Deleted, false)]
+ [InlineData(EntityState.Detached, true)]
+ [InlineData(EntityState.Detached, false)]
public virtual async Task Load_one_to_one_reference_to_principal_when_NoTracking_behavior(EntityState state, bool async)
{
using var context = CreateContext();
@@ -1776,29 +381,41 @@ public virtual async Task Load_one_to_one_reference_to_principal_when_NoTracking
RecordLog();
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
-
- var parent = context.ChangeTracker.Entries().Single().Entity;
+ Assert.Equal(state == EntityState.Detached ? 0 : 2, context.ChangeTracker.Entries().Count());
- if (state == EntityState.Deleted)
+ if (state != EntityState.Deleted)
{
- Assert.Null(single.Parent);
- Assert.Null(parent.Single);
+ Assert.Same(single, single.Parent.Single);
}
- else
+
+ if (state != EntityState.Detached)
{
- Assert.Same(parent, single.Parent);
- Assert.Same(single, parent.Single);
+ var parent = context.ChangeTracker.Entries().Single().Entity;
+
+ if (state == EntityState.Deleted)
+ {
+ Assert.Null(single.Parent);
+ Assert.Null(parent.Single);
+ }
+ else
+ {
+ Assert.Same(parent, single.Parent);
+ Assert.Same(single, parent.Single);
+ }
}
}
[ConditionalTheory]
[InlineData(EntityState.Unchanged, true)]
[InlineData(EntityState.Unchanged, false)]
+ [InlineData(EntityState.Added, true)]
+ [InlineData(EntityState.Added, false)]
[InlineData(EntityState.Modified, true)]
[InlineData(EntityState.Modified, false)]
[InlineData(EntityState.Deleted, true)]
[InlineData(EntityState.Deleted, false)]
+ [InlineData(EntityState.Detached, true)]
+ [InlineData(EntityState.Detached, false)]
public virtual async Task Load_one_to_one_reference_to_dependent(EntityState state, bool async)
{
using var context = CreateContext();
@@ -1825,21 +442,30 @@ public virtual async Task Load_one_to_one_reference_to_dependent(EntityState sta
RecordLog();
- Assert.Equal(2, context.ChangeTracker.Entries().Count());
+ Assert.Equal(state == EntityState.Detached ? 0 : 2, context.ChangeTracker.Entries().Count());
- var single = context.ChangeTracker.Entries