diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.ClientMethods.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.ClientMethods.cs index ca6f4397cab..33b01fd885a 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.ClientMethods.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.ClientMethods.cs @@ -327,7 +327,7 @@ private static void InitializeSplitIncludeCollection TParent entity, Func parentIdentifier, INavigationBase navigation, - IClrCollectionAccessor clrCollectionAccessor, + IClrCollectionAccessor? clrCollectionAccessor, bool trackingQuery, bool setLoaded) where TParent : class @@ -348,7 +348,7 @@ private static void InitializeSplitIncludeCollection } } - collection = clrCollectionAccessor.GetOrCreate(entity, forMaterialization: true); + collection = clrCollectionAccessor?.GetOrCreate(entity, forMaterialization: true); } var parentKey = parentIdentifier(queryContext, parentDataReader); diff --git a/src/EFCore/Metadata/Internal/ClrCollectionAccessor.cs b/src/EFCore/Metadata/Internal/ClrCollectionAccessor.cs index 52b807c6c92..79d8acbb5cd 100644 --- a/src/EFCore/Metadata/Internal/ClrCollectionAccessor.cs +++ b/src/EFCore/Metadata/Internal/ClrCollectionAccessor.cs @@ -17,6 +17,7 @@ public class ClrICollectionAccessor : IClrCollec where TElement : class { private readonly string _propertyName; + private readonly bool _shadow; private readonly Func? _getCollection; private readonly Action? _setCollection; private readonly Action? _setCollectionForMaterialization; @@ -40,6 +41,7 @@ public virtual Type CollectionType /// public ClrICollectionAccessor( string propertyName, + bool shadow, Func? getCollection, Action? setCollection, Action? setCollectionForMaterialization, @@ -47,6 +49,7 @@ public ClrICollectionAccessor( Func? createCollection) { _propertyName = propertyName; + _shadow = shadow; _getCollection = getCollection; _setCollection = setCollection; _setCollectionForMaterialization = setCollectionForMaterialization; @@ -137,6 +140,11 @@ private ICollection GetOrCreateCollection(object instance, bool forMat private ICollection? GetCollection(object instance) { + if (_shadow) + { + return (ICollection?)_createCollection?.Invoke(); + } + var enumerable = _getCollection!((TEntity)instance); var collection = enumerable as ICollection; diff --git a/src/EFCore/Metadata/Internal/ClrCollectionAccessorFactory.cs b/src/EFCore/Metadata/Internal/ClrCollectionAccessorFactory.cs index bc4c47d4501..aac580012e7 100644 --- a/src/EFCore/Metadata/Internal/ClrCollectionAccessorFactory.cs +++ b/src/EFCore/Metadata/Internal/ClrCollectionAccessorFactory.cs @@ -205,6 +205,7 @@ private static IClrCollectionAccessor CreateGeneric( navigation.Name, + navigation.IsShadowProperty(), getterDelegate, setterDelegate, setterDelegateForMaterialization, diff --git a/test/EFCore.Specification.Tests/EFCore.Specification.Tests.csproj b/test/EFCore.Specification.Tests/EFCore.Specification.Tests.csproj index 91b644ecdda..82ce956b506 100644 --- a/test/EFCore.Specification.Tests/EFCore.Specification.Tests.csproj +++ b/test/EFCore.Specification.Tests/EFCore.Specification.Tests.csproj @@ -48,6 +48,7 @@ + diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/ManyToManyNoTrackingSplitQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/ManyToManyNoTrackingSplitQuerySqliteTest.cs new file mode 100644 index 00000000000..4693be00e4c --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/ManyToManyNoTrackingSplitQuerySqliteTest.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Sqlite.Internal; + +namespace Microsoft.EntityFrameworkCore.Query; + +public class ManyToManyNoTrackingSplitQuerySqliteTest + : ManyToManyNoTrackingQueryRelationalTestBase +{ + public ManyToManyNoTrackingSplitQuerySqliteTest(ManyToManySplitQuerySqliteFixture fixture) + : base(fixture) + { + } + + // Sqlite does not support Apply operations + + public override async Task Skip_navigation_order_by_single_or_default(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Skip_navigation_order_by_single_or_default(async))).Message); +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/ManyToManySplitQuerySqliteFixture.cs b/test/EFCore.Sqlite.FunctionalTests/Query/ManyToManySplitQuerySqliteFixture.cs new file mode 100644 index 00000000000..4279f4421c9 --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/ManyToManySplitQuerySqliteFixture.cs @@ -0,0 +1,13 @@ +// 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.Query; + +public class ManyToManySplitQuerySqliteFixture : ManyToManyQuerySqliteFixture +{ + protected override string StoreName + => "ManyToManySplitQuery"; + + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + => base.AddOptions(builder.UseSqlite(b => b.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery))); +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/ManyToManySplitQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/ManyToManySplitQuerySqliteTest.cs new file mode 100644 index 00000000000..e1b20113f09 --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/ManyToManySplitQuerySqliteTest.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Sqlite.Internal; + +namespace Microsoft.EntityFrameworkCore.Query; + +public class ManyToManySplitQuerySqliteTest : ManyToManyQueryRelationalTestBase +{ + public ManyToManySplitQuerySqliteTest(ManyToManySplitQuerySqliteFixture fixture) + : base(fixture) + { + } + + public override async Task Skip_navigation_order_by_single_or_default(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Skip_navigation_order_by_single_or_default(async))).Message); +}