diff --git a/global.json b/global.json index 20f482a..f15a959 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,6 @@ { "sdk": { - "version": "9.0.100" + "version": "9.0.100", + "rollForward": "latestMinor" } } \ No newline at end of file diff --git a/src/EntityFrameworkCore.Projectables/Infrastructure/Internal/CustomConventionSetPlugin.cs b/src/EntityFrameworkCore.Projectables/Infrastructure/Internal/CustomConventionSetPlugin.cs new file mode 100644 index 0000000..4bb5def --- /dev/null +++ b/src/EntityFrameworkCore.Projectables/Infrastructure/Internal/CustomConventionSetPlugin.cs @@ -0,0 +1,14 @@ +using Microsoft.EntityFrameworkCore.Metadata.Conventions; +using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; + +namespace EntityFrameworkCore.Projectables.Infrastructure.Internal; + +public class CustomConventionSetPlugin : IConventionSetPlugin +{ + public ConventionSet ModifyConventions(ConventionSet conventionSet) + { + conventionSet.ModelFinalizingConventions.Add(new ProjectablesExpandQueryFiltersConvention()); + + return conventionSet; + } +} \ No newline at end of file diff --git a/src/EntityFrameworkCore.Projectables/Infrastructure/Internal/ProjectablesExpandQueryFiltersConvention.cs b/src/EntityFrameworkCore.Projectables/Infrastructure/Internal/ProjectablesExpandQueryFiltersConvention.cs new file mode 100644 index 0000000..0b788e3 --- /dev/null +++ b/src/EntityFrameworkCore.Projectables/Infrastructure/Internal/ProjectablesExpandQueryFiltersConvention.cs @@ -0,0 +1,26 @@ +using System.Linq.Expressions; +using EntityFrameworkCore.Projectables.Extensions; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.EntityFrameworkCore.Metadata.Conventions; + +namespace EntityFrameworkCore.Projectables.Infrastructure.Internal; + +public class ProjectablesExpandQueryFiltersConvention : IModelFinalizingConvention +{ + + /// + public void ProcessModelFinalizing( + IConventionModelBuilder modelBuilder, + IConventionContext context) + { + foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) + { + var queryFilter = entityType.GetQueryFilter(); + if (queryFilter != null) + { + // Expands query filters + entityType.SetQueryFilter(queryFilter.ExpandProjectables() as LambdaExpression); + } + } + } +} \ No newline at end of file diff --git a/src/EntityFrameworkCore.Projectables/Infrastructure/Internal/ProjectionOptionsExtension.cs b/src/EntityFrameworkCore.Projectables/Infrastructure/Internal/ProjectionOptionsExtension.cs index ac593a9..fbfa4be 100644 --- a/src/EntityFrameworkCore.Projectables/Infrastructure/Internal/ProjectionOptionsExtension.cs +++ b/src/EntityFrameworkCore.Projectables/Infrastructure/Internal/ProjectionOptionsExtension.cs @@ -54,6 +54,9 @@ static object CreateTargetInstance(IServiceProvider services, ServiceDescriptor return ActivatorUtilities.GetServiceOrCreateInstance(services, descriptor.ImplementationType!); } + // Custom convention to handle global query filters, etc + services.AddScoped(); + if (_compatibilityMode is CompatibilityMode.Full) { var targetDescriptor = services.FirstOrDefault(x => x.ServiceType == typeof(IQueryCompiler)); diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexModelTests.ProjectQueryFilters.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexModelTests.ProjectQueryFilters.verified.txt new file mode 100644 index 0000000..1739868 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexModelTests.ProjectQueryFilters.verified.txt @@ -0,0 +1,15 @@ +SELECT [t0].[RecordDate] +FROM [User] AS [u] +INNER JOIN ( + SELECT [t].[RecordDate], [t].[UserId] + FROM ( + SELECT [o0].[RecordDate], [o0].[UserId], ROW_NUMBER() OVER(PARTITION BY [o0].[UserId] ORDER BY [o0].[RecordDate] DESC) AS [row] + FROM [Order] AS [o0] + ) AS [t] + WHERE [t].[row] <= 2 +) AS [t0] ON [u].[Id] = [t0].[UserId] +WHERE ( + SELECT TOP(1) [o].[Id] + FROM [Order] AS [o] + WHERE [u].[Id] = [o].[UserId] + ORDER BY [o].[RecordDate] DESC) > 100 \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexModelTests.cs b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexModelTests.cs index 6ce61b3..a87da15 100644 --- a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexModelTests.cs +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexModelTests.cs @@ -86,5 +86,17 @@ public Task ProjectOverMethodTakingDbContext() return Verifier.Verify(query.ToQueryString()); } + + [Fact] + public Task ProjectQueryFilters() + { + using var dbContext = new SampleUserWithGlobalQueryFilterDbContext(); + + var query = dbContext.Set() + .SelectMany(x => x.Last2Orders) + .Select(x => x.RecordDate); + + return Verifier.Verify(query.ToQueryString()); + } } } diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/Helpers/SampleUserWithGlobalQueryFilterDbContext.cs b/tests/EntityFrameworkCore.Projectables.FunctionalTests/Helpers/SampleUserWithGlobalQueryFilterDbContext.cs new file mode 100644 index 0000000..b8d284a --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/Helpers/SampleUserWithGlobalQueryFilterDbContext.cs @@ -0,0 +1,21 @@ +using EntityFrameworkCore.Projectables.Infrastructure; +using Microsoft.EntityFrameworkCore; + +namespace EntityFrameworkCore.Projectables.FunctionalTests.Helpers +{ + public class SampleUserWithGlobalQueryFilterDbContext : SampleDbContext + { + public SampleUserWithGlobalQueryFilterDbContext(CompatibilityMode compatibilityMode = CompatibilityMode.Full) : base(compatibilityMode) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity(b => { + b.HasQueryFilter(u => u.LastOrder.Id > 100); + }); + } + } +}