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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2826,6 +2826,23 @@ private void CreateSkipNavigation(

SetNavigationBaseProperties(navigation, memberAccessReplacements, parameters);

if (parameters.ForNativeAot)
{
AddNamespace(navigation.TargetEntityType.ClrType, parameters.Namespaces);
AddNamespace(navigation.DeclaringEntityType.ClrType, parameters.Namespaces);
mainBuilder
.Append(navigationVariable)
.AppendLine(".SetManyToManyLoaderFactory(")
.IncrementIndent()
.Append("static (factory, navigation) => factory.Create<")
.Append(_code.Reference(navigation.TargetEntityType.ClrType))
.Append(", ")
.Append(_code.Reference(navigation.DeclaringEntityType.ClrType))
.AppendLine(">(navigation));")
.DecrementIndent()
.AppendLine();
}

CreateAnnotations(navigation, _annotationCodeGenerator.Generate, parameters);

mainBuilder
Expand Down
3 changes: 2 additions & 1 deletion src/EFCore/ChangeTracking/CollectionEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,8 @@ private void EnsureInitialized()
[field: AllowNull, MaybeNull]
private ICollectionLoader TargetLoader
=> field ??= Metadata is IRuntimeSkipNavigation skipNavigation
? skipNavigation.GetManyToManyLoader()
? skipNavigation.GetManyToManyLoader(
InternalEntry.Context.GetDependencies().ManyToManyLoaderFactory)
: new EntityFinderCollectionLoaderAdapter(
InternalEntry.StateManager.CreateEntityFinder(Metadata.TargetEntityType),
(INavigation)Metadata);
Expand Down
10 changes: 10 additions & 0 deletions src/EFCore/DbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,16 @@ IDbSetSource IDbContextDependencies.SetSource
IEntityFinderFactory IDbContextDependencies.EntityFinderFactory
=> DbContextDependencies.EntityFinderFactory;

/// <summary>
/// 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.
/// </summary>
[EntityFrameworkInternal]
IManyToManyLoaderFactory IDbContextDependencies.ManyToManyLoaderFactory
=> DbContextDependencies.ManyToManyLoaderFactory;

/// <summary>
/// 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
Expand Down
2 changes: 2 additions & 0 deletions src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public static readonly IDictionary<Type, ServiceCharacteristics> CoreServices
{ typeof(IDbSetInitializer), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(IDbSetSource), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(IEntityFinderSource), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(IManyToManyLoaderFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(IStructuralTypeMaterializerSource), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(ITypeMappingSource), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(IModelCustomizer), new ServiceCharacteristics(ServiceLifetime.Singleton) },
Expand Down Expand Up @@ -239,6 +240,7 @@ public virtual EntityFrameworkServicesBuilder TryAddCoreServices()
TryAdd<IDbSetInitializer, DbSetInitializer>();
TryAdd<IDbSetSource, DbSetSource>();
TryAdd<IEntityFinderSource, EntityFinderSource>();
TryAdd<IManyToManyLoaderFactory, ManyToManyLoaderFactory>();
TryAdd<IStructuralTypeMaterializerSource, StructuralTypeMaterializerSource>();
TryAdd<IProviderConventionSetBuilder, ProviderConventionSetBuilder>();
TryAdd<IConventionSetBuilder, RuntimeConventionSetBuilder>();
Expand Down
10 changes: 10 additions & 0 deletions src/EFCore/Internal/DbContextDependencies.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public DbContextDependencies(
IChangeDetector changeDetector,
IDbSetSource setSource,
IEntityFinderSource entityFinderSource,
IManyToManyLoaderFactory manyToManyLoaderFactory,
IEntityGraphAttacher entityGraphAttacher,
IAsyncQueryProvider queryProvider,
IStateManager stateManager,
Expand All @@ -51,6 +52,7 @@ public DbContextDependencies(
UpdateLogger = updateLogger;
InfrastructureLogger = infrastructureLogger;
EntityFinderFactory = new EntityFinderFactory(entityFinderSource, stateManager, setSource, currentContext.Context);
ManyToManyLoaderFactory = manyToManyLoaderFactory;
}

/// <summary>
Expand All @@ -69,6 +71,14 @@ public DbContextDependencies(
/// </summary>
public IEntityFinderFactory EntityFinderFactory { get; }

/// <summary>
/// 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.
/// </summary>
public IManyToManyLoaderFactory ManyToManyLoaderFactory { get; }

/// <summary>
/// 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
Expand Down
8 changes: 8 additions & 0 deletions src/EFCore/Internal/IDbContextDependencies.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ public interface IDbContextDependencies
/// </summary>
IEntityFinderFactory EntityFinderFactory { get; }

/// <summary>
/// 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.
/// </summary>
IManyToManyLoaderFactory ManyToManyLoaderFactory { get; }

/// <summary>
/// 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
Expand Down
37 changes: 37 additions & 0 deletions src/EFCore/Internal/IManyToManyLoaderFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// 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;

/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// The service lifetime is <see cref="ServiceLifetime.Singleton" />. This means a single instance
/// is used by many <see cref="DbContext" /> instances and the loaders it creates may be cached on the
/// (singleton) model, so implementations and the loaders they return must be thread-safe and must not
/// capture any scoped service or <see cref="DbContext" />.
Comment thread
AndriySvyryd marked this conversation as resolved.
/// </remarks>
public interface IManyToManyLoaderFactory
{
/// <summary>
/// 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.
/// </summary>
ICollectionLoader Create(ISkipNavigation skipNavigation);

/// <summary>
/// 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.
/// </summary>
ICollectionLoader Create<TEntity, TSourceEntity>(ISkipNavigation skipNavigation)
where TEntity : class
where TSourceEntity : class;
}
39 changes: 16 additions & 23 deletions src/EFCore/Internal/ManyToManyLoaderFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,38 +11,31 @@ namespace Microsoft.EntityFrameworkCore.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.
/// </summary>
public class ManyToManyLoaderFactory
public class ManyToManyLoaderFactory : IManyToManyLoaderFactory
{
private static readonly MethodInfo GenericCreate
= typeof(ManyToManyLoaderFactory).GetTypeInfo().GetDeclaredMethod(nameof(CreateManyToMany))!;

private ManyToManyLoaderFactory()
{
}

/// <summary>
/// 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.
/// </summary>
public static readonly ManyToManyLoaderFactory Instance = new();

/// <summary>
/// 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.
/// </summary>
/// <inheritdoc />
public virtual ICollectionLoader Create(ISkipNavigation skipNavigation)
=> (ICollectionLoader)GenericCreate.MakeGenericMethod(
skipNavigation.TargetEntityType.ClrType,
skipNavigation.DeclaringEntityType.ClrType)
.Invoke(null, [skipNavigation])!;
.Invoke(this, [skipNavigation])!;

/// <inheritdoc />
public virtual ICollectionLoader Create<TEntity, TSourceEntity>(ISkipNavigation skipNavigation)
where TEntity : class
where TSourceEntity : class
=> new ManyToManyLoader<TEntity, TSourceEntity>(skipNavigation);

// Invoked via reflection by the non-generic Create above (GenericCreate). It deliberately
// forwards to the virtual Create<,> so that a provider overriding Create<,> is still honored
// on the reflection path. Do not inline this into MakeGenericMethod against Create<,> directly:
// Create is overloaded, so GetDeclaredMethod(nameof(Create)) would be ambiguous.
[UsedImplicitly]
private static ICollectionLoader CreateManyToMany<TEntity, TTargetEntity>(ISkipNavigation skipNavigation)
private ICollectionLoader CreateManyToMany<TEntity, TSourceEntity>(ISkipNavigation skipNavigation)
where TEntity : class
where TTargetEntity : class
=> new ManyToManyLoader<TEntity, TTargetEntity>(skipNavigation);
where TSourceEntity : class
=> Create<TEntity, TSourceEntity>(skipNavigation);
}
2 changes: 1 addition & 1 deletion src/EFCore/Metadata/Internal/IRuntimeSkipNavigation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ public interface IRuntimeSkipNavigation : ISkipNavigation, IRuntimeNavigationBas
/// 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.
/// </summary>
ICollectionLoader GetManyToManyLoader();
ICollectionLoader GetManyToManyLoader(IManyToManyLoaderFactory factory);
}
32 changes: 9 additions & 23 deletions src/EFCore/Metadata/Internal/SkipNavigation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public class SkipNavigation : PropertyBase, IMutableSkipNavigation, IConventionS

// Warning: Never access these fields directly as access needs to be thread-safe
private bool _collectionAccessorInitialized;
private ICollectionLoader? _manyToManyLoader;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down Expand Up @@ -367,21 +368,6 @@ public virtual IClrCollectionAccessor? CollectionAccessor
return ClrCollectionAccessorFactory.Instance.Create(navigation);
});

/// <summary>
/// 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.
/// </summary>
[field: AllowNull, MaybeNull]
public virtual ICollectionLoader ManyToManyLoader
=> NonCapturingLazyInitializer.EnsureInitialized(
ref field, this, static navigation =>
{
navigation.EnsureReadOnly();
return ManyToManyLoaderFactory.Instance.Create(navigation);
});

/// <summary>
/// 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
Expand Down Expand Up @@ -480,12 +466,12 @@ IReadOnlySkipNavigation IReadOnlySkipNavigation.Inverse
IClrCollectionAccessor? IPropertyBase.GetCollectionAccessor()
=> CollectionAccessor;

/// <summary>
/// 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.
/// </summary>
ICollectionLoader IRuntimeSkipNavigation.GetManyToManyLoader()
=> ManyToManyLoader;
/// <inheritdoc />
ICollectionLoader IRuntimeSkipNavigation.GetManyToManyLoader(IManyToManyLoaderFactory factory)
=> NonCapturingLazyInitializer.EnsureInitialized(
ref _manyToManyLoader, this, factory, static (navigation, factory) =>
{
navigation.EnsureReadOnly();
return factory.Create(navigation);
});
}
29 changes: 24 additions & 5 deletions src/EFCore/Metadata/RuntimeSkipNavigation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ public class RuntimeSkipNavigation : RuntimePropertyBase, IRuntimeSkipNavigation
private IClrCollectionAccessor? _collectionAccessor;
private bool _collectionAccessorInitialized;
private ICollectionLoader? _manyToManyLoader;
// An optional compiled-model delegate that creates the loader, letting a compiled model carry the
// concrete generic types for native AOT. Null unless set during model build.
private Func<IManyToManyLoaderFactory, ISkipNavigation, ICollectionLoader>? _manyToManyLoaderFactory;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename to _manyToManyLoaderDelegatedFactory


/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down Expand Up @@ -137,6 +140,17 @@ public virtual void SetCollectionAccessor<TEntity, TCollection, TElement>(
_collectionAccessorInitialized = true;
}

/// <summary>
/// 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.
/// </summary>
[EntityFrameworkInternal]
public virtual void SetManyToManyLoaderFactory(
Func<IManyToManyLoaderFactory, ISkipNavigation, ICollectionLoader> factory)
=> _manyToManyLoaderFactory = factory;

/// <summary>
/// Returns a string that represents the current object.
/// </summary>
Expand Down Expand Up @@ -204,10 +218,15 @@ bool IReadOnlySkipNavigation.IsOnDependent
: null);

/// <inheritdoc />
ICollectionLoader IRuntimeSkipNavigation.GetManyToManyLoader()
ICollectionLoader IRuntimeSkipNavigation.GetManyToManyLoader(IManyToManyLoaderFactory factory)
=> NonCapturingLazyInitializer.EnsureInitialized(
ref _manyToManyLoader, this, static navigation =>
RuntimeFeature.IsDynamicCodeSupported
? ManyToManyLoaderFactory.Instance.Create(navigation)
: throw new InvalidOperationException(CoreStrings.NativeAotNoCompiledModel));
ref _manyToManyLoader, this, factory, static (navigation, factory) =>
{
var generated = navigation._manyToManyLoaderFactory;
return generated != null
? generated(factory, navigation)
: RuntimeFeature.IsDynamicCodeSupported
? factory.Create(navigation)
: throw new InvalidOperationException(CoreStrings.NativeAotNoCompiledModel);
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,9 @@ public static RuntimeSkipNavigation CreateSkipNavigation1(RuntimeEntityType decl
(CompiledModelTestBase.PrincipalBase entity, ICollection<CompiledModelTestBase.PrincipalBase> collection) => PrincipalBaseUnsafeAccessors.Deriveds(entity) = ((ICollection<CompiledModelTestBase.PrincipalBase>)collection),
ICollection<CompiledModelTestBase.PrincipalBase> (CompiledModelTestBase.PrincipalBase entity, Action<CompiledModelTestBase.PrincipalBase, ICollection<CompiledModelTestBase.PrincipalBase>> setter) => ClrCollectionAccessorFactory.CreateAndSetHashSet<CompiledModelTestBase.PrincipalBase, ICollection<CompiledModelTestBase.PrincipalBase>, CompiledModelTestBase.PrincipalBase>(entity, setter),
ICollection<CompiledModelTestBase.PrincipalBase> () => ((ICollection<CompiledModelTestBase.PrincipalBase>)(((ICollection<CompiledModelTestBase.PrincipalBase>)(new HashSet<CompiledModelTestBase.PrincipalBase>(ReferenceEqualityComparer.Instance))))));
skipNavigation.SetManyToManyLoaderFactory(
static (factory, navigation) => factory.Create<CompiledModelTestBase.PrincipalDerived<CompiledModelTestBase.DependentBase<byte?>>, CompiledModelTestBase.PrincipalBase>(navigation));

return skipNavigation;
}

Expand Down
Loading
Loading