From 898909660cc535c8ab063533fee3e6bd74ec4e19 Mon Sep 17 00:00:00 2001 From: Koen Bekkenutte <2912652+kbekkenutte@users.noreply.github.com> Date: Thu, 3 Jun 2021 03:07:05 +0800 Subject: [PATCH 1/5] Added a basic benchmark --- EntityFrameworkCore.Projectables.sln | 11 +++- ...ameworkCore.Projectables.Benchmarks.csproj | 18 ++++++ .../PlainOverhead.cs | 61 ++++++++++++++++++ .../Program.cs | 5 ++ .../ProjectableProperties.cs | 64 +++++++++++++++++++ 5 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 benchmarks/EntityFrameworkCore.Projectables.Benchmarks/EntityFrameworkCore.Projectables.Benchmarks.csproj create mode 100644 benchmarks/EntityFrameworkCore.Projectables.Benchmarks/PlainOverhead.cs create mode 100644 benchmarks/EntityFrameworkCore.Projectables.Benchmarks/Program.cs create mode 100644 benchmarks/EntityFrameworkCore.Projectables.Benchmarks/ProjectableProperties.cs diff --git a/EntityFrameworkCore.Projectables.sln b/EntityFrameworkCore.Projectables.sln index 2dd71fa..8972b5a 100644 --- a/EntityFrameworkCore.Projectables.sln +++ b/EntityFrameworkCore.Projectables.sln @@ -29,7 +29,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EntityFrameworkCore.Project EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EntityFrameworkCore.Projectables.FunctionalTests", "tests\EntityFrameworkCore.Projectables.FunctionalTests\EntityFrameworkCore.Projectables.FunctionalTests.csproj", "{A36F5471-0C16-4453-811B-818326931313}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReadmeSample", "samples\ReadmeSample\ReadmeSample.csproj", "{6F63E04C-9267-4211-8AC7-290C60331D60}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReadmeSample", "samples\ReadmeSample\ReadmeSample.csproj", "{6F63E04C-9267-4211-8AC7-290C60331D60}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{D1DB50EE-D639-46B0-8966-D0AA5F569620}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EntityFrameworkCore.Projectables.Benchmarks", "benchmarks\EntityFrameworkCore.Projectables.Benchmarks\EntityFrameworkCore.Projectables.Benchmarks.csproj", "{F2F01B61-5FB8-4F96-A6DE-824C9756B365}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -69,6 +73,10 @@ Global {6F63E04C-9267-4211-8AC7-290C60331D60}.Debug|Any CPU.Build.0 = Debug|Any CPU {6F63E04C-9267-4211-8AC7-290C60331D60}.Release|Any CPU.ActiveCfg = Release|Any CPU {6F63E04C-9267-4211-8AC7-290C60331D60}.Release|Any CPU.Build.0 = Release|Any CPU + {F2F01B61-5FB8-4F96-A6DE-824C9756B365}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F2F01B61-5FB8-4F96-A6DE-824C9756B365}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F2F01B61-5FB8-4F96-A6DE-824C9756B365}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F2F01B61-5FB8-4F96-A6DE-824C9756B365}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -82,6 +90,7 @@ Global {2F0DD7D7-867F-4478-9E22-45C114B61C46} = {F5E4436F-87F2-46AB-A9EB-59B4BF21BF7A} {A36F5471-0C16-4453-811B-818326931313} = {F5E4436F-87F2-46AB-A9EB-59B4BF21BF7A} {6F63E04C-9267-4211-8AC7-290C60331D60} = {07584D01-2D30-404B-B0D1-32080C0CC18A} + {F2F01B61-5FB8-4F96-A6DE-824C9756B365} = {D1DB50EE-D639-46B0-8966-D0AA5F569620} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D17BD356-592C-4628-9D81-A04E24FF02F3} diff --git a/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/EntityFrameworkCore.Projectables.Benchmarks.csproj b/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/EntityFrameworkCore.Projectables.Benchmarks.csproj new file mode 100644 index 0000000..9512f40 --- /dev/null +++ b/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/EntityFrameworkCore.Projectables.Benchmarks.csproj @@ -0,0 +1,18 @@ + + + + Exe + net5.0 + + + + + + + + + + + + + diff --git a/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/PlainOverhead.cs b/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/PlainOverhead.cs new file mode 100644 index 0000000..9df4e11 --- /dev/null +++ b/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/PlainOverhead.cs @@ -0,0 +1,61 @@ +using System; +using System.Linq; +using BenchmarkDotNet.Attributes; +using EntityFrameworkCore.Projectables.Extensions; +using Microsoft.EntityFrameworkCore; + +namespace EntityFrameworkCore.Projectables.Benchmarks +{ + public class PlainOverhead + { + class TestEntity + { + public int Id { get; set; } + } + + class TestDbContext : DbContext + { + readonly bool _useProjectables; + + public TestDbContext(bool useProjectables) + { + _useProjectables = useProjectables; + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseSqlServer("Server=(localdb)\\MSSQLLocalDB;Database=ReadmeSample;Trusted_Connection=True"); + + if (_useProjectables) + { + optionsBuilder.UseProjectables(); + } + } + + public DbSet Entities => Set(); + } + + [Benchmark(Baseline = true)] + public void WithoutProjectables() + { + using var dbContext = new TestDbContext(false); + + for (int i = 0; i < 10000; i++) + { + dbContext.Entities.Select(x => x.Id).ToQueryString(); + } + } + + [Benchmark] + public void WithProjectables() + { + using var dbContext = new TestDbContext(true); + + for (int i = 0; i < 10000; i++) + { + dbContext.Entities.Select(x => x.Id).ToQueryString(); + } + } + + } +} diff --git a/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/Program.cs b/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/Program.cs new file mode 100644 index 0000000..699d8c5 --- /dev/null +++ b/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/Program.cs @@ -0,0 +1,5 @@ +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Running; +using EntityFrameworkCore.Projectables.Benchmarks; + +BenchmarkSwitcher.FromAssembly(typeof(PlainOverhead).Assembly).Run(args, DefaultConfig.Instance.WithOption(ConfigOptions.DisableOptimizationsValidator, true)); diff --git a/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/ProjectableProperties.cs b/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/ProjectableProperties.cs new file mode 100644 index 0000000..28218cf --- /dev/null +++ b/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/ProjectableProperties.cs @@ -0,0 +1,64 @@ +using System; +using System.Linq; +using BenchmarkDotNet.Attributes; +using EntityFrameworkCore.Projectables.Extensions; +using Microsoft.EntityFrameworkCore; + +namespace EntityFrameworkCore.Projectables.Benchmarks +{ + public class ProjectableProperties + { + public class TestEntity + { + public int Id { get; set; } + + [Projectable] + public int IdPlus1 => Id + 1; + } + + class TestDbContext : DbContext + { + readonly bool _useProjectables; + + public TestDbContext(bool useProjectables) + { + _useProjectables = useProjectables; + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseSqlServer("Server=(localdb)\\MSSQLLocalDB;Database=ReadmeSample;Trusted_Connection=True"); + + if (_useProjectables) + { + optionsBuilder.UseProjectables(); + } + } + + public DbSet Entities => Set(); + } + + [Benchmark(Baseline = true)] + public void WithoutProjectables() + { + using var dbContext = new TestDbContext(false); + + for (int i = 0; i < 10000; i++) + { + dbContext.Entities.Select(x => x.Id + 1).ToQueryString(); + } + } + + [Benchmark] + public void WithProjectables() + { + using var dbContext = new TestDbContext(true); + + for (int i = 0; i < 10000; i++) + { + dbContext.Entities.Select(x => x.IdPlus1).ToQueryString(); + } + } + + } +} From 11582d467ca62157f9160eb97d70eb98c8ca1639 Mon Sep 17 00:00:00 2001 From: Koen Bekkenutte <2912652+kbekkenutte@users.noreply.github.com> Date: Thu, 3 Jun 2021 04:46:25 +0800 Subject: [PATCH 2/5] Added compatiblity mode --- .../Helpers/TestDbContext.cs | 37 +++++++++ .../Helpers/TestEntity.cs | 16 ++++ .../PlainOverhead.cs | 44 ++++------ .../ProjectableProperties.cs | 44 +++------- .../Extensions/DbContextOptionsExtensions.cs | 11 ++- .../Infrastructure/CompatibilityMode.cs | 14 ++++ ...stomQueryTranslationPreprocessorFactory.cs | 40 ++++++++++ .../Internal/ProjectionOptionsExtension.cs | 80 ++++++++++++++++--- .../ProjectableOptionsBuilder.cs | 38 +++++++++ 9 files changed, 249 insertions(+), 75 deletions(-) create mode 100644 benchmarks/EntityFrameworkCore.Projectables.Benchmarks/Helpers/TestDbContext.cs create mode 100644 benchmarks/EntityFrameworkCore.Projectables.Benchmarks/Helpers/TestEntity.cs create mode 100644 src/EntityFrameworkCore.Projectables/Infrastructure/CompatibilityMode.cs create mode 100644 src/EntityFrameworkCore.Projectables/Infrastructure/Internal/CustomQueryTranslationPreprocessorFactory.cs create mode 100644 src/EntityFrameworkCore.Projectables/Infrastructure/ProjectableOptionsBuilder.cs diff --git a/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/Helpers/TestDbContext.cs b/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/Helpers/TestDbContext.cs new file mode 100644 index 0000000..512ad3c --- /dev/null +++ b/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/Helpers/TestDbContext.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using EntityFrameworkCore.Projectables.Extensions; +using EntityFrameworkCore.Projectables.Infrastructure; + +namespace EntityFrameworkCore.Projectables.Benchmarks.Helpers +{ + class TestDbContext : DbContext + { + readonly bool _useProjectables; + readonly bool _useFullCompatibiltyMode; + + public TestDbContext(bool useProjectables, bool useFullCompatibiltyMode = true) + { + _useProjectables = useProjectables; + _useFullCompatibiltyMode = useFullCompatibiltyMode; + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseSqlServer("Server=(localdb)\\MSSQLLocalDB;Database=ReadmeSample;Trusted_Connection=True"); + + if (_useProjectables) + { + optionsBuilder.UseProjectables(projectableOptions => { + projectableOptions.CompatibilityMode(_useFullCompatibiltyMode ? CompatibilityMode.Full : CompatibilityMode.Limited); + }); + } + } + + public DbSet Entities => Set(); + } +} diff --git a/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/Helpers/TestEntity.cs b/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/Helpers/TestEntity.cs new file mode 100644 index 0000000..55cd47c --- /dev/null +++ b/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/Helpers/TestEntity.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EntityFrameworkCore.Projectables.Benchmarks.Helpers +{ + public class TestEntity + { + public int Id { get; set; } + + [Projectable] + public int IdPlus1 => Id + 1; + } +} diff --git a/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/PlainOverhead.cs b/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/PlainOverhead.cs index 9df4e11..d064b7d 100644 --- a/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/PlainOverhead.cs +++ b/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/PlainOverhead.cs @@ -1,40 +1,16 @@ using System; +using System.IO.Compression; using System.Linq; using BenchmarkDotNet.Attributes; +using EntityFrameworkCore.Projectables.Benchmarks.Helpers; using EntityFrameworkCore.Projectables.Extensions; +using EntityFrameworkCore.Projectables.Infrastructure; using Microsoft.EntityFrameworkCore; namespace EntityFrameworkCore.Projectables.Benchmarks { public class PlainOverhead { - class TestEntity - { - public int Id { get; set; } - } - - class TestDbContext : DbContext - { - readonly bool _useProjectables; - - public TestDbContext(bool useProjectables) - { - _useProjectables = useProjectables; - } - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - optionsBuilder.UseSqlServer("Server=(localdb)\\MSSQLLocalDB;Database=ReadmeSample;Trusted_Connection=True"); - - if (_useProjectables) - { - optionsBuilder.UseProjectables(); - } - } - - public DbSet Entities => Set(); - } - [Benchmark(Baseline = true)] public void WithoutProjectables() { @@ -47,7 +23,7 @@ namespace EntityFrameworkCore.Projectables.Benchmarks } [Benchmark] - public void WithProjectables() + public void WithProjectablesWithFullCompatibility() { using var dbContext = new TestDbContext(true); @@ -57,5 +33,17 @@ namespace EntityFrameworkCore.Projectables.Benchmarks } } + + [Benchmark] + public void WithProjectablesWithLimitedCompatibility() + { + using var dbContext = new TestDbContext(true, false); + + for (int i = 0; i < 10000; i++) + { + dbContext.Entities.Select(x => x.Id).ToQueryString(); + } + } + } } diff --git a/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/ProjectableProperties.cs b/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/ProjectableProperties.cs index 28218cf..00a20f0 100644 --- a/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/ProjectableProperties.cs +++ b/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/ProjectableProperties.cs @@ -1,43 +1,15 @@ using System; using System.Linq; using BenchmarkDotNet.Attributes; +using EntityFrameworkCore.Projectables.Benchmarks.Helpers; using EntityFrameworkCore.Projectables.Extensions; +using EntityFrameworkCore.Projectables.Infrastructure; using Microsoft.EntityFrameworkCore; namespace EntityFrameworkCore.Projectables.Benchmarks { public class ProjectableProperties { - public class TestEntity - { - public int Id { get; set; } - - [Projectable] - public int IdPlus1 => Id + 1; - } - - class TestDbContext : DbContext - { - readonly bool _useProjectables; - - public TestDbContext(bool useProjectables) - { - _useProjectables = useProjectables; - } - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - optionsBuilder.UseSqlServer("Server=(localdb)\\MSSQLLocalDB;Database=ReadmeSample;Trusted_Connection=True"); - - if (_useProjectables) - { - optionsBuilder.UseProjectables(); - } - } - - public DbSet Entities => Set(); - } - [Benchmark(Baseline = true)] public void WithoutProjectables() { @@ -50,7 +22,7 @@ namespace EntityFrameworkCore.Projectables.Benchmarks } [Benchmark] - public void WithProjectables() + public void WithProjectablesWithFullCompatibility() { using var dbContext = new TestDbContext(true); @@ -60,5 +32,15 @@ namespace EntityFrameworkCore.Projectables.Benchmarks } } + [Benchmark] + public void WithProjectablesWithLimitedCompatibility() + { + using var dbContext = new TestDbContext(true, false); + + for (int i = 0; i < 10000; i++) + { + dbContext.Entities.Select(x => x.IdPlus1).ToQueryString(); + } + } } } diff --git a/src/EntityFrameworkCore.Projectables/Extensions/DbContextOptionsExtensions.cs b/src/EntityFrameworkCore.Projectables/Extensions/DbContextOptionsExtensions.cs index 147a351..04d46eb 100644 --- a/src/EntityFrameworkCore.Projectables/Extensions/DbContextOptionsExtensions.cs +++ b/src/EntityFrameworkCore.Projectables/Extensions/DbContextOptionsExtensions.cs @@ -1,4 +1,5 @@ -using EntityFrameworkCore.Projectables.Infrastructure.Internal; +using EntityFrameworkCore.Projectables.Infrastructure; +using EntityFrameworkCore.Projectables.Infrastructure.Internal; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using System; @@ -12,15 +13,17 @@ namespace EntityFrameworkCore.Projectables.Extensions { public static class DbContextOptionsExtensions { - public static DbContextOptionsBuilder UseProjectables(this DbContextOptionsBuilder optionsBuilder) + public static DbContextOptionsBuilder UseProjectables(this DbContextOptionsBuilder optionsBuilder, Action? configure = null) where TContext : DbContext - => (DbContextOptionsBuilder)UseProjectables((DbContextOptionsBuilder)optionsBuilder); + => (DbContextOptionsBuilder)UseProjectables((DbContextOptionsBuilder)optionsBuilder, configure); - public static DbContextOptionsBuilder UseProjectables(this DbContextOptionsBuilder optionsBuilder) + public static DbContextOptionsBuilder UseProjectables(this DbContextOptionsBuilder optionsBuilder, Action? configure = null) { var extension = optionsBuilder.Options.FindExtension() ?? new ProjectionOptionsExtension(); ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); + configure?.Invoke(new ProjectableOptionsBuilder(optionsBuilder)); + return optionsBuilder; } } diff --git a/src/EntityFrameworkCore.Projectables/Infrastructure/CompatibilityMode.cs b/src/EntityFrameworkCore.Projectables/Infrastructure/CompatibilityMode.cs new file mode 100644 index 0000000..54217a0 --- /dev/null +++ b/src/EntityFrameworkCore.Projectables/Infrastructure/CompatibilityMode.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EntityFrameworkCore.Projectables.Infrastructure +{ + public enum CompatibilityMode + { + Full, + Limited + } +} diff --git a/src/EntityFrameworkCore.Projectables/Infrastructure/Internal/CustomQueryTranslationPreprocessorFactory.cs b/src/EntityFrameworkCore.Projectables/Infrastructure/Internal/CustomQueryTranslationPreprocessorFactory.cs new file mode 100644 index 0000000..652cd2e --- /dev/null +++ b/src/EntityFrameworkCore.Projectables/Infrastructure/Internal/CustomQueryTranslationPreprocessorFactory.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; +using EntityFrameworkCore.Projectables.Services; +using Microsoft.EntityFrameworkCore.Query; + +namespace EntityFrameworkCore.Projectables.Infrastructure.Internal +{ + public class CustomQueryTranslationPreprocessorFactory : IQueryTranslationPreprocessorFactory + { + readonly IQueryTranslationPreprocessorFactory _decoratedFactory; + readonly QueryTranslationPreprocessorDependencies _queryTranslationPreprocessorDependencies; + readonly ProjectableExpressionReplacer _projectableExpressionReplacer = new(new ProjectionExpressionResolver()); + + public CustomQueryTranslationPreprocessorFactory(IQueryTranslationPreprocessorFactory decoratedFactory, QueryTranslationPreprocessorDependencies queryTranslationPreprocessorDependencies) + { + _decoratedFactory = decoratedFactory; + _queryTranslationPreprocessorDependencies = queryTranslationPreprocessorDependencies; + } + + public QueryTranslationPreprocessor Create(QueryCompilationContext queryCompilationContext) + => new CustomQueryTranslationPreprocessor(_queryTranslationPreprocessorDependencies, queryCompilationContext, _projectableExpressionReplacer); + } + + public class CustomQueryTranslationPreprocessor : QueryTranslationPreprocessor + { + readonly ProjectableExpressionReplacer _projectableExpressionReplacer; + + public CustomQueryTranslationPreprocessor(QueryTranslationPreprocessorDependencies dependencies, QueryCompilationContext queryCompilationContext, ProjectableExpressionReplacer projectableExpressionReplacer) : base(dependencies, queryCompilationContext) + { + _projectableExpressionReplacer = projectableExpressionReplacer; + } + + public override Expression Process(Expression query) + => base.Process(_projectableExpressionReplacer.Visit(query)); + } +} \ 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 15d055b..0f7040a 100644 --- a/src/EntityFrameworkCore.Projectables/Infrastructure/Internal/ProjectionOptionsExtension.cs +++ b/src/EntityFrameworkCore.Projectables/Infrastructure/Internal/ProjectionOptionsExtension.cs @@ -14,11 +14,21 @@ namespace EntityFrameworkCore.Projectables.Infrastructure.Internal { public class ProjectionOptionsExtension : IDbContextOptionsExtension { + CompatibilityMode _compatibilityMode = CompatibilityMode.Full; + public ProjectionOptionsExtension() { Info = new ExtensionInfo(this); } + public ProjectionOptionsExtension(ProjectionOptionsExtension copyFrom) + : this() + { + _compatibilityMode = copyFrom._compatibilityMode; + } + + protected ProjectionOptionsExtension Clone() => new(this); + public DbContextOptionsExtensionInfo Info { get; } [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "EF1001:Internal EF Core API usage.", Justification = "Needed")] @@ -37,19 +47,47 @@ namespace EntityFrameworkCore.Projectables.Infrastructure.Internal return ActivatorUtilities.GetServiceOrCreateInstance(services, descriptor.ImplementationType!); } - var targetDescriptor = services.FirstOrDefault(x => x.ServiceType == typeof(IQueryCompiler)); - if (targetDescriptor is null) + if (_compatibilityMode is CompatibilityMode.Full) { - throw new InvalidOperationException("No QueryProvider is configured yet. Please make sure to configure a database provider first"); ; - } - - var decoratorObjectFactory = ActivatorUtilities.CreateFactory(typeof(CustomQueryProvider), new [] { targetDescriptor.ServiceType }); + var targetDescriptor = services.FirstOrDefault(x => x.ServiceType == typeof(IQueryCompiler)); + if (targetDescriptor is null) + { + throw new InvalidOperationException("No QueryProvider is configured yet. Please make sure to configure a database provider first"); ; + } - services.Replace(ServiceDescriptor.Describe( - targetDescriptor.ServiceType, - serviceProvider => decoratorObjectFactory(serviceProvider, new[] { CreateTargetInstance(serviceProvider, targetDescriptor) }), - targetDescriptor.Lifetime - )); + var decoratorObjectFactory = ActivatorUtilities.CreateFactory(typeof(CustomQueryProvider), new[] { targetDescriptor.ServiceType }); + + services.Replace(ServiceDescriptor.Describe( + targetDescriptor.ServiceType, + serviceProvider => decoratorObjectFactory(serviceProvider, new[] { CreateTargetInstance(serviceProvider, targetDescriptor) }), + targetDescriptor.Lifetime + )); + } + else + { + var targetDescriptor = services.FirstOrDefault(x => x.ServiceType == typeof(IQueryTranslationPreprocessorFactory)); + if (targetDescriptor is null) + { + throw new InvalidOperationException("No QueryTranslationPreprocessorFactory is configured yet. Please make sure to configure a database provider first"); ; + } + + var decoratorObjectFactory = ActivatorUtilities.CreateFactory(typeof(CustomQueryTranslationPreprocessorFactory), new[] { targetDescriptor.ServiceType }); + + services.Replace(ServiceDescriptor.Describe( + targetDescriptor.ServiceType, + serviceProvider => decoratorObjectFactory(serviceProvider, new[] { CreateTargetInstance(serviceProvider, targetDescriptor) }), + targetDescriptor.Lifetime + )); + } + } + + public ProjectionOptionsExtension WithCompatibilityMode(CompatibilityMode compatibilityMode) + { + var clone = Clone(); + + clone._compatibilityMode = compatibilityMode; + + return clone; } public void Validate(IDbContextOptions options) @@ -58,13 +96,29 @@ namespace EntityFrameworkCore.Projectables.Infrastructure.Internal sealed class ExtensionInfo : DbContextOptionsExtensionInfo { + private int? _serviceProviderHash; + public ExtensionInfo(IDbContextOptionsExtension extension) : base(extension) { } public override bool IsDatabaseProvider => false; public override string LogFragment => string.Empty; - public override long GetServiceProviderHashCode() => 0; + public override long GetServiceProviderHashCode() + { + if (_serviceProviderHash == null) + { + var hashCode = nameof(ProjectionOptionsExtension).GetHashCode(); + + var extension = (ProjectionOptionsExtension)Extension; + + hashCode ^= extension._compatibilityMode.GetHashCode(); + + _serviceProviderHash = hashCode; + } + + return _serviceProviderHash.Value; + } public override void PopulateDebugInfo(IDictionary debugInfo) { @@ -72,6 +126,8 @@ namespace EntityFrameworkCore.Projectables.Infrastructure.Internal { throw new ArgumentNullException(nameof(debugInfo)); } + + debugInfo["Projectables:CompatibilityMode"] = ((ProjectionOptionsExtension)Extension)._compatibilityMode.ToString(); } } } diff --git a/src/EntityFrameworkCore.Projectables/Infrastructure/ProjectableOptionsBuilder.cs b/src/EntityFrameworkCore.Projectables/Infrastructure/ProjectableOptionsBuilder.cs new file mode 100644 index 0000000..d7a3668 --- /dev/null +++ b/src/EntityFrameworkCore.Projectables/Infrastructure/ProjectableOptionsBuilder.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using EntityFrameworkCore.Projectables.Infrastructure.Internal; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace EntityFrameworkCore.Projectables.Infrastructure +{ + public class ProjectableOptionsBuilder + { + readonly DbContextOptionsBuilder _optionsBuilder; + + public ProjectableOptionsBuilder(DbContextOptionsBuilder optionsBuilder) + { + _optionsBuilder = optionsBuilder ?? throw new ArgumentNullException(nameof(optionsBuilder)); + } + + public ProjectableOptionsBuilder CompatibilityMode(CompatibilityMode mode) + => WithOption(x => x.WithCompatibilityMode(mode)); + + /// + /// Sets an option by cloning the extension used to store the settings. This ensures the builder + /// does not modify options that are already in use elsewhere. + /// + /// An action to set the option. + /// The same builder instance so that multiple calls can be chained. + protected virtual ProjectableOptionsBuilder WithOption(Func setAction) + { + ((IDbContextOptionsBuilderInfrastructure)_optionsBuilder).AddOrUpdateExtension( + setAction(_optionsBuilder.Options.FindExtension() ?? new ProjectionOptionsExtension())); + + return this; + } + } +} From 45618966f81e0553dde684b2584acdc4983169a1 Mon Sep 17 00:00:00 2001 From: Koen Bekkenutte <2912652+kbekkenutte@users.noreply.github.com> Date: Thu, 3 Jun 2021 19:45:02 +0800 Subject: [PATCH 3/5] Refactored benchmark a bit --- .../Helpers/TestEntity.cs | 3 ++ .../Helpers/TestEntityExtensions.cs | 13 +++++ .../ProjectableExtensionMethods.cs | 49 +++++++++++++++++++ .../ProjectableMethods.cs | 48 ++++++++++++++++++ .../ProjectableProperties.cs | 11 +++-- 5 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 benchmarks/EntityFrameworkCore.Projectables.Benchmarks/Helpers/TestEntityExtensions.cs create mode 100644 benchmarks/EntityFrameworkCore.Projectables.Benchmarks/ProjectableExtensionMethods.cs create mode 100644 benchmarks/EntityFrameworkCore.Projectables.Benchmarks/ProjectableMethods.cs diff --git a/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/Helpers/TestEntity.cs b/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/Helpers/TestEntity.cs index 55cd47c..4bc741c 100644 --- a/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/Helpers/TestEntity.cs +++ b/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/Helpers/TestEntity.cs @@ -12,5 +12,8 @@ namespace EntityFrameworkCore.Projectables.Benchmarks.Helpers [Projectable] public int IdPlus1 => Id + 1; + + [Projectable] + public int IdPlus1Method() => Id + 1; } } diff --git a/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/Helpers/TestEntityExtensions.cs b/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/Helpers/TestEntityExtensions.cs new file mode 100644 index 0000000..d3ebee3 --- /dev/null +++ b/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/Helpers/TestEntityExtensions.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EntityFrameworkCore.Projectables.Benchmarks.Helpers +{ + public static class TestEntityExtensions + { + public static int IdPlus1ExtensionMethod(this TestEntity entity) => entity.Id + 1; + } +} diff --git a/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/ProjectableExtensionMethods.cs b/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/ProjectableExtensionMethods.cs new file mode 100644 index 0000000..fdb0f9f --- /dev/null +++ b/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/ProjectableExtensionMethods.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using EntityFrameworkCore.Projectables.Benchmarks.Helpers; +using Microsoft.EntityFrameworkCore; + +namespace EntityFrameworkCore.Projectables.Benchmarks +{ + public class ProjectableExtensionMethods + { + const int innerLoop = 10000; + + [Benchmark(Baseline = true)] + public void WithoutProjectables() + { + using var dbContext = new TestDbContext(false); + + for (int i = 0; i < innerLoop; i++) + { + dbContext.Entities.Select(x => x.Id + 1).ToQueryString(); + } + } + + [Benchmark] + public void WithProjectablesWithFullCompatibility() + { + using var dbContext = new TestDbContext(true); + + for (int i = 0; i < innerLoop; i++) + { + dbContext.Entities.Select(x => x.IdPlus1Method()).ToQueryString(); + } + } + + [Benchmark] + public void WithProjectablesWithLimitedCompatibility() + { + using var dbContext = new TestDbContext(true, false); + + for (int i = 0; i < innerLoop; i++) + { + dbContext.Entities.Select(x => x.IdPlus1Method()).ToQueryString(); + } + } + } +} diff --git a/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/ProjectableMethods.cs b/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/ProjectableMethods.cs new file mode 100644 index 0000000..785b52c --- /dev/null +++ b/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/ProjectableMethods.cs @@ -0,0 +1,48 @@ +using System; +using System.Linq; +using BenchmarkDotNet.Attributes; +using EntityFrameworkCore.Projectables.Benchmarks.Helpers; +using EntityFrameworkCore.Projectables.Extensions; +using EntityFrameworkCore.Projectables.Infrastructure; +using Microsoft.EntityFrameworkCore; + +namespace EntityFrameworkCore.Projectables.Benchmarks +{ + public class ProjectableMethods + { + const int innerLoop = 10000; + + [Benchmark(Baseline = true)] + public void WithoutProjectables() + { + using var dbContext = new TestDbContext(false); + + for (int i = 0; i < innerLoop; i++) + { + dbContext.Entities.Select(x => x.Id + 1).ToQueryString(); + } + } + + [Benchmark] + public void WithProjectablesWithFullCompatibility() + { + using var dbContext = new TestDbContext(true); + + for (int i = 0; i < innerLoop; i++) + { + dbContext.Entities.Select(x => x.IdPlus1Method()).ToQueryString(); + } + } + + [Benchmark] + public void WithProjectablesWithLimitedCompatibility() + { + using var dbContext = new TestDbContext(true, false); + + for (int i = 0; i < innerLoop; i++) + { + dbContext.Entities.Select(x => x.IdPlus1Method()).ToQueryString(); + } + } + } +} diff --git a/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/ProjectableProperties.cs b/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/ProjectableProperties.cs index 00a20f0..6f2fa57 100644 --- a/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/ProjectableProperties.cs +++ b/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/ProjectableProperties.cs @@ -10,14 +10,17 @@ namespace EntityFrameworkCore.Projectables.Benchmarks { public class ProjectableProperties { + const int innerLoop = 10000; + [Benchmark(Baseline = true)] public void WithoutProjectables() { using var dbContext = new TestDbContext(false); - for (int i = 0; i < 10000; i++) + for (int i = 0; i < innerLoop; i++) { - dbContext.Entities.Select(x => x.Id + 1).ToQueryString(); + if (1 == i) + throw new Exception(dbContext.Entities.Select(x => x.Id + 1).ToQueryString()); } } @@ -26,7 +29,7 @@ namespace EntityFrameworkCore.Projectables.Benchmarks { using var dbContext = new TestDbContext(true); - for (int i = 0; i < 10000; i++) + for (int i = 0; i < innerLoop; i++) { dbContext.Entities.Select(x => x.IdPlus1).ToQueryString(); } @@ -37,7 +40,7 @@ namespace EntityFrameworkCore.Projectables.Benchmarks { using var dbContext = new TestDbContext(true, false); - for (int i = 0; i < 10000; i++) + for (int i = 0; i < innerLoop; i++) { dbContext.Entities.Select(x => x.IdPlus1).ToQueryString(); } From cba010618ccca9859c437dc49d68fdc92b9b39c5 Mon Sep 17 00:00:00 2001 From: Koen Bekkenutte <2912652+kbekkenutte@users.noreply.github.com> Date: Fri, 4 Jun 2021 00:38:27 +0800 Subject: [PATCH 4/5] Use nuget package in our basic example --- samples/BasicSample/BasicSample.csproj | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/samples/BasicSample/BasicSample.csproj b/samples/BasicSample/BasicSample.csproj index de68914..ce9c4f6 100644 --- a/samples/BasicSample/BasicSample.csproj +++ b/samples/BasicSample/BasicSample.csproj @@ -13,11 +13,7 @@ - - - - - + From faf2cac1c3ced99126cc1b590b2b9ac1c63bedb6 Mon Sep 17 00:00:00 2001 From: Koen Bekkenutte <2912652+kbekkenutte@users.noreply.github.com> Date: Fri, 4 Jun 2021 00:39:11 +0800 Subject: [PATCH 5/5] Ensure the benchmark project does not get packed --- .../EntityFrameworkCore.Projectables.Benchmarks.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/EntityFrameworkCore.Projectables.Benchmarks.csproj b/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/EntityFrameworkCore.Projectables.Benchmarks.csproj index 9512f40..534651a 100644 --- a/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/EntityFrameworkCore.Projectables.Benchmarks.csproj +++ b/benchmarks/EntityFrameworkCore.Projectables.Benchmarks/EntityFrameworkCore.Projectables.Benchmarks.csproj @@ -2,7 +2,8 @@ Exe - net5.0 + net5.0, + false