From bd5ebf727add956564e99210e0b225b14217a6fe Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Thu, 21 Oct 2021 16:27:31 +0200 Subject: [PATCH] Document compiled queries Closes #502 --- .../advanced-performance-topics.md | 25 +++ .../core/performance/efficient-querying.md | 3 +- samples/core/Benchmarks/AverageBlogRanking.cs | 167 +++++++------- samples/core/Benchmarks/Benchmarks.csproj | 2 +- samples/core/Benchmarks/CompiledQueries.cs | 80 +++++++ samples/core/Benchmarks/ContextPooling.cs | 91 ++++---- .../DynamicallyConstructedQueries.cs | 145 ++++++------- samples/core/Benchmarks/Inheritance.cs | 203 +++++++++--------- samples/core/Benchmarks/Program.cs | 7 +- .../CompiledQueries/CompiledQueries.csproj | 10 - .../Model/AdventureWorksContext.cs | 45 ---- .../core/CompiledQueries/Model/Customer.cs | 15 -- samples/core/CompiledQueries/Program.cs | 92 -------- samples/core/CompiledQueries/README.md | 5 - samples/core/Performance/Program.cs | 19 +- samples/core/Samples.sln | 6 - 16 files changed, 425 insertions(+), 490 deletions(-) create mode 100644 samples/core/Benchmarks/CompiledQueries.cs delete mode 100644 samples/core/CompiledQueries/CompiledQueries.csproj delete mode 100644 samples/core/CompiledQueries/Model/AdventureWorksContext.cs delete mode 100644 samples/core/CompiledQueries/Model/Customer.cs delete mode 100644 samples/core/CompiledQueries/Program.cs delete mode 100644 samples/core/CompiledQueries/README.md diff --git a/entity-framework/core/performance/advanced-performance-topics.md b/entity-framework/core/performance/advanced-performance-topics.md index 2e25bf94e6..deb3f105b5 100644 --- a/entity-framework/core/performance/advanced-performance-topics.md +++ b/entity-framework/core/performance/advanced-performance-topics.md @@ -58,6 +58,31 @@ Context pooling works by reusing the same context instance across requests. This Context pooling is intended for scenarios where the context configuration, which includes services resolved, is fixed between requests. For cases where [Scoped](/aspnet/core/fundamentals/dependency-injection#service-lifetimes) services are required, or configuration needs to be changed, don't use pooling. +## Compiled queries + +When EF receives a LINQ query tree for execution, it must first "compile" that tree into a SQL query. Because this is a heavy process, EF caches queries by the query tree shape: queries with the same structure reuse internally-cached compilation outputs, and can skip repeated compilation. This ensures that executing the same LINQ query multiple times is very fast, even if parameter values differ. + +However, EF must still perform certain tasks before it can make use of the internal query cache. For example, your query's expression tree must be (recursively) compared with the expression trees of cached queries, to find the correct cached query. The overhead for this initial processing is negligible in the majority of EF applications, especially when compared to other costs associated with query execution (network I/O, actual query processing and disk I/O at the database...). However, in certain high-performance scenarios it may be desirable to eliminate it. + +EF supports *compiled queries*, which allow the explicit, up-front compilation of a LINQ query into a .NET delegate. Once this is done, the delegate can be invoked directly to execute the query, without providing the LINQ expression tree; this bypasses the cache lookup, and provides the most optimized way to execute a query in EF Core. Following are some benchmark results comparing compiled and non-compiled query performance; benchmark on your platform before making any decisions. [The source code is available here](https://github.com/dotnet/EntityFramework.Docs/tree/main/samples/core/Benchmarks/ContextPooling.cs), feel free to use it as a basis for your own measurements. + +| Method | NumBlogs | Mean | Error | StdDev | Gen 0 | Allocated | +|--------------------- |--------- |---------:|---------:|---------:|-------:|----------:| +| WithCompiledQuery | 1 | 564.2 us | 6.75 us | 5.99 us | 1.9531 | 9 KB | +| WithoutCompiledQuery | 1 | 671.6 us | 12.72 us | 16.54 us | 2.9297 | 13 KB | +| WithCompiledQuery | 10 | 645.3 us | 10.00 us | 9.35 us | 2.9297 | 13 KB | +| WithoutCompiledQuery | 10 | 709.8 us | 25.20 us | 73.10 us | 3.9063 | 18 KB | + +To used compiled queries, first compile a query with as follows (use for synchronous queries): + +[!code-csharp[Main](../../../samples/core/Performance/Program.cs#CompiledQueryCompile)] + +In this code sample, we provide EF with a lambda accepting a `DbContext` instance, and an arbitrary parameter to be passed to the query. You can now invoke that delegate whenever you wish to execute the query: + +[!code-csharp[Main](../../../samples/core/Performance/Program.cs#CompiledQueryExecute)] + +Note that the delegate is thread-safe, and can be invoked concurrently on different context instances. + ## Query caching and parameterization When EF receives a LINQ query tree for execution, it must first "compile" that tree into a SQL query. Because this is a heavy process, EF caches queries by the query tree *shape*: queries with the same structure reuse internally-cached compilation outputs, and can skip repeated compilation. The different queries may still reference different *values*, but as long as these values are properly parameterized, the structure is the same and caching will function properly. diff --git a/entity-framework/core/performance/efficient-querying.md b/entity-framework/core/performance/efficient-querying.md index 6679ca43d2..9e31586c49 100644 --- a/entity-framework/core/performance/efficient-querying.md +++ b/entity-framework/core/performance/efficient-querying.md @@ -205,4 +205,5 @@ For more information, see the page on [async programming](xref:core/miscellaneou ## Additional resources -See the [performance section](xref:core/querying/null-comparisons#writing-performant-queries) of the null comparison documentation page for some best practices when comparing nullable values. +* See the [advanced performance topics page](xref:core/performance/advanced-performance-topics) for additional topics related to efficient querying. +* See the [performance section](xref:core/querying/null-comparisons#writing-performant-queries) of the null comparison documentation page for some best practices when comparing nullable values. diff --git a/samples/core/Benchmarks/AverageBlogRanking.cs b/samples/core/Benchmarks/AverageBlogRanking.cs index ac57517496..670eaf614f 100644 --- a/samples/core/Benchmarks/AverageBlogRanking.cs +++ b/samples/core/Benchmarks/AverageBlogRanking.cs @@ -3,109 +3,106 @@ using BenchmarkDotNet.Attributes; using Microsoft.EntityFrameworkCore; -namespace Benchmarks +[MemoryDiagnoser] +public class AverageBlogRanking { - [MemoryDiagnoser] - public class AverageBlogRanking + [Params(1000)] + public int NumBlogs; // number of records to write [once], and read [each pass] + + [GlobalSetup] + public void Setup() { - [Params(1000)] - public int NumBlogs; // number of records to write [once], and read [each pass] + using var context = new BloggingContext(); + context.Database.EnsureDeleted(); + context.Database.EnsureCreated(); + context.SeedData(NumBlogs); + } - [GlobalSetup] - public void Setup() + #region LoadEntities + [Benchmark] + public double LoadEntities() + { + var sum = 0; + var count = 0; + using var ctx = new BloggingContext(); + foreach (var blog in ctx.Blogs) { - using var context = new BloggingContext(); - context.Database.EnsureDeleted(); - context.Database.EnsureCreated(); - context.SeedData(NumBlogs); + sum += blog.Rating; + count++; } - #region LoadEntities - [Benchmark] - public double LoadEntities() - { - var sum = 0; - var count = 0; - using var ctx = new BloggingContext(); - foreach (var blog in ctx.Blogs) - { - sum += blog.Rating; - count++; - } - - return (double)sum / count; - } - #endregion + return (double)sum / count; + } + #endregion - #region LoadEntitiesNoTracking - [Benchmark] - public double LoadEntitiesNoTracking() + #region LoadEntitiesNoTracking + [Benchmark] + public double LoadEntitiesNoTracking() + { + var sum = 0; + var count = 0; + using var ctx = new BloggingContext(); + foreach (var blog in ctx.Blogs.AsNoTracking()) { - var sum = 0; - var count = 0; - using var ctx = new BloggingContext(); - foreach (var blog in ctx.Blogs.AsNoTracking()) - { - sum += blog.Rating; - count++; - } - - return (double)sum / count; + sum += blog.Rating; + count++; } - #endregion - #region ProjectOnlyRanking - [Benchmark] - public double ProjectOnlyRanking() - { - var sum = 0; - var count = 0; - using var ctx = new BloggingContext(); - foreach (var rating in ctx.Blogs.Select(b => b.Rating)) - { - sum += rating; - count++; - } - - return (double)sum / count; - } - #endregion + return (double)sum / count; + } + #endregion - #region CalculateInDatabase - [Benchmark(Baseline = true)] - public double CalculateInDatabase() + #region ProjectOnlyRanking + [Benchmark] + public double ProjectOnlyRanking() + { + var sum = 0; + var count = 0; + using var ctx = new BloggingContext(); + foreach (var rating in ctx.Blogs.Select(b => b.Rating)) { - using var ctx = new BloggingContext(); - return ctx.Blogs.Average(b => b.Rating); + sum += rating; + count++; } - #endregion - public class BloggingContext : DbContext - { - public DbSet Blogs { get; set; } + return (double)sum / count; + } + #endregion + + #region CalculateInDatabase + [Benchmark(Baseline = true)] + public double CalculateInDatabase() + { + using var ctx = new BloggingContext(); + return ctx.Blogs.Average(b => b.Rating); + } + #endregion - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - => optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True"); + public class BloggingContext : DbContext + { + public DbSet Blogs { get; set; } - public void SeedData(int numblogs) - { - Blogs.AddRange( - Enumerable.Range(0, numblogs).Select( - i => new Blog - { - Name = $"Blog{i}", Url = $"blog{i}.blogs.net", CreationTime = new DateTime(2020, 1, 1), Rating = i % 5 - })); - SaveChanges(); - } - } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True"); - public class Blog + public void SeedData(int numblogs) { - public int BlogId { get; set; } - public string Name { get; set; } - public string Url { get; set; } - public DateTime CreationTime { get; set; } - public int Rating { get; set; } + Blogs.AddRange( + Enumerable.Range(0, numblogs).Select( + i => new Blog + { + Name = $"Blog{i}", Url = $"blog{i}.blogs.net", CreationTime = new DateTime(2020, 1, 1), Rating = i % 5 + })); + SaveChanges(); } } + + public class Blog + { + public int BlogId { get; set; } + public string Name { get; set; } + public string Url { get; set; } + public DateTime CreationTime { get; set; } + public int Rating { get; set; } + } } diff --git a/samples/core/Benchmarks/Benchmarks.csproj b/samples/core/Benchmarks/Benchmarks.csproj index 5eb3ccf91f..4c85581238 100644 --- a/samples/core/Benchmarks/Benchmarks.csproj +++ b/samples/core/Benchmarks/Benchmarks.csproj @@ -6,7 +6,7 @@ - + diff --git a/samples/core/Benchmarks/CompiledQueries.cs b/samples/core/Benchmarks/CompiledQueries.cs new file mode 100644 index 0000000000..587f1fe98b --- /dev/null +++ b/samples/core/Benchmarks/CompiledQueries.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using Microsoft.EntityFrameworkCore; + +[MemoryDiagnoser] +public class CompiledQueries +{ + private static readonly Func> _compiledQuery + = EF.CompileAsyncQuery((BloggingContext context) => context.Blogs.Where(b => b.Url.StartsWith("http://"))); + + private BloggingContext _context; + + [Params(1, 10)] + public int NumBlogs { get; set; } + + [GlobalSetup] + public async Task Setup() + { + using var context = new BloggingContext(); + await context.Database.EnsureDeletedAsync(); + await context.Database.EnsureCreatedAsync(); + await context.SeedDataAsync(NumBlogs); + + _context = new BloggingContext(); + } + + [Benchmark] + public async ValueTask WithCompiledQuery() + { + var idSum = 0; + + await foreach (var blog in _compiledQuery(_context)) + { + idSum += blog.Id; + } + + return idSum; + } + + [Benchmark] + public async ValueTask WithoutCompiledQuery() + { + var idSum = 0; + + await foreach (var blog in _context.Blogs.Where(b => b.Url.StartsWith("http://")).AsAsyncEnumerable()) + { + idSum += blog.Id; + } + + return idSum; + } + + [GlobalCleanup] + public ValueTask Cleanup() => _context.DisposeAsync(); + + public class BloggingContext : DbContext + { + public DbSet Blogs { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder + .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True") + .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); + + public async Task SeedDataAsync(int numBlogs) + { + Blogs.AddRange(Enumerable.Range(0, numBlogs).Select(i => new Blog { Url = $"http://www.someblog{i}.com"})); + await SaveChangesAsync(); + } + } + + public class Blog + { + public int Id { get; set; } + public string Url { get; set; } + } +} diff --git a/samples/core/Benchmarks/ContextPooling.cs b/samples/core/Benchmarks/ContextPooling.cs index 6161ab8114..fa0899e445 100644 --- a/samples/core/Benchmarks/ContextPooling.cs +++ b/samples/core/Benchmarks/ContextPooling.cs @@ -4,66 +4,63 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; -namespace Benchmarks +[MemoryDiagnoser] +public class ContextPooling { - [MemoryDiagnoser] - public class ContextPooling - { - private DbContextOptions _options; - private PooledDbContextFactory _poolingFactory; - - [Params(1)] - public int NumBlogs { get; set; } + private DbContextOptions _options; + private PooledDbContextFactory _poolingFactory; - [GlobalSetup] - public void Setup() - { - _options = new DbContextOptionsBuilder() - .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True") - .Options; + [Params(1)] + public int NumBlogs { get; set; } - using var context = new BloggingContext(_options); - context.Database.EnsureDeleted(); - context.Database.EnsureCreated(); - context.SeedData(NumBlogs); + [GlobalSetup] + public void Setup() + { + _options = new DbContextOptionsBuilder() + .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True") + .Options; - _poolingFactory = new PooledDbContextFactory(_options); - } + using var context = new BloggingContext(_options); + context.Database.EnsureDeleted(); + context.Database.EnsureCreated(); + context.SeedData(NumBlogs); - [Benchmark] - public List WithoutContextPooling() - { - using var context = new BloggingContext(_options); + _poolingFactory = new PooledDbContextFactory(_options); + } - return context.Blogs.ToList(); - } + [Benchmark] + public List WithoutContextPooling() + { + using var context = new BloggingContext(_options); - [Benchmark] - public List WithContextPooling() - { - using var context = _poolingFactory.CreateDbContext(); + return context.Blogs.ToList(); + } - return context.Blogs.ToList(); - } + [Benchmark] + public List WithContextPooling() + { + using var context = _poolingFactory.CreateDbContext(); - public class BloggingContext : DbContext - { - public DbSet Blogs { get; set; } + return context.Blogs.ToList(); + } - public BloggingContext(DbContextOptions options) : base(options) {} + public class BloggingContext : DbContext + { + public DbSet Blogs { get; set; } - public void SeedData(int numBlogs) - { - Blogs.AddRange(Enumerable.Range(0, numBlogs).Select(i => new Blog { Url = $"http://www.someblog{i}.com"})); - SaveChanges(); - } - } + public BloggingContext(DbContextOptions options) : base(options) {} - public class Blog + public void SeedData(int numBlogs) { - public int BlogId { get; set; } - public string Url { get; set; } - public int Rating { get; set; } + Blogs.AddRange(Enumerable.Range(0, numBlogs).Select(i => new Blog { Url = $"http://www.someblog{i}.com"})); + SaveChanges(); } } + + public class Blog + { + public int BlogId { get; set; } + public string Url { get; set; } + public int Rating { get; set; } + } } diff --git a/samples/core/Benchmarks/DynamicallyConstructedQueries.cs b/samples/core/Benchmarks/DynamicallyConstructedQueries.cs index e789a08295..5743fcec61 100644 --- a/samples/core/Benchmarks/DynamicallyConstructedQueries.cs +++ b/samples/core/Benchmarks/DynamicallyConstructedQueries.cs @@ -6,100 +6,97 @@ using BenchmarkDotNet.Attributes; using Microsoft.EntityFrameworkCore; -namespace Benchmarks +[MemoryDiagnoser] +public class DynamicallyConstructedQueries { - [MemoryDiagnoser] - public class DynamicallyConstructedQueries + private int _blogNumber; + + [GlobalSetup] + public static void GlobalSetup() + { + using var context = new BloggingContext(); + context.Database.EnsureDeleted(); + context.Database.EnsureCreated(); + } + + #region WithConstant + [Benchmark] + public int WithConstant() { - private int _blogNumber; + return GetBlogCount("blog" + Interlocked.Increment(ref _blogNumber)); - [GlobalSetup] - public static void GlobalSetup() + static int GetBlogCount(string url) { using var context = new BloggingContext(); - context.Database.EnsureDeleted(); - context.Database.EnsureCreated(); - } - #region WithConstant - [Benchmark] - public int WithConstant() - { - return GetBlogCount("blog" + Interlocked.Increment(ref _blogNumber)); + IQueryable blogs = context.Blogs; - static int GetBlogCount(string url) + if (url is not null) { - using var context = new BloggingContext(); - - IQueryable blogs = context.Blogs; - - if (url is not null) - { - var blogParam = Expression.Parameter(typeof(Blog), "b"); - var whereLambda = Expression.Lambda>( - Expression.Equal( - Expression.MakeMemberAccess( - blogParam, - typeof(Blog).GetMember(nameof(Blog.Url)).Single() - ), - Expression.Constant(url)), - blogParam); - - blogs = blogs.Where(whereLambda); - } - - return blogs.Count(); + var blogParam = Expression.Parameter(typeof(Blog), "b"); + var whereLambda = Expression.Lambda>( + Expression.Equal( + Expression.MakeMemberAccess( + blogParam, + typeof(Blog).GetMember(nameof(Blog.Url)).Single() + ), + Expression.Constant(url)), + blogParam); + + blogs = blogs.Where(whereLambda); } - } - #endregion - #region WithParameter - [Benchmark] - public int WithParameter() - { - return GetBlogCount("blog" + Interlocked.Increment(ref _blogNumber)); + return blogs.Count(); + } + } + #endregion - int GetBlogCount(string url) - { - using var context = new BloggingContext(); + #region WithParameter + [Benchmark] + public int WithParameter() + { + return GetBlogCount("blog" + Interlocked.Increment(ref _blogNumber)); - IQueryable blogs = context.Blogs; + int GetBlogCount(string url) + { + using var context = new BloggingContext(); - if (url is not null) - { - blogs = blogs.Where(b => b.Url == url); - } + IQueryable blogs = context.Blogs; - return blogs.Count(); + if (url is not null) + { + blogs = blogs.Where(b => b.Url == url); } + + return blogs.Count(); } - #endregion + } + #endregion - public class BloggingContext : DbContext - { - public DbSet Blogs { get; set; } - public DbSet Posts { get; set; } + public class BloggingContext : DbContext + { + public DbSet Blogs { get; set; } + public DbSet Posts { get; set; } - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - => optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True"); - } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True"); + } - public class Blog - { - public int BlogId { get; set; } - public string Url { get; set; } - public int Rating { get; set; } - public List Posts { get; set; } - } + public class Blog + { + public int BlogId { get; set; } + public string Url { get; set; } + public int Rating { get; set; } + public List Posts { get; set; } + } - public class Post - { - public int PostId { get; set; } - public string Title { get; set; } - public string Content { get; set; } + public class Post + { + public int PostId { get; set; } + public string Title { get; set; } + public string Content { get; set; } - public int BlogId { get; set; } - public Blog Blog { get; set; } - } + public int BlogId { get; set; } + public Blog Blog { get; set; } } } diff --git a/samples/core/Benchmarks/Inheritance.cs b/samples/core/Benchmarks/Inheritance.cs index 820bdac85c..5409c39167 100644 --- a/samples/core/Benchmarks/Inheritance.cs +++ b/samples/core/Benchmarks/Inheritance.cs @@ -4,132 +4,129 @@ using BenchmarkDotNet.Attributes; using Microsoft.EntityFrameworkCore; -namespace Benchmarks +[MemoryDiagnoser] +public class Inheritance { - [MemoryDiagnoser] - public class Inheritance + [Params(5000)] + public int RowsPerEntityType { get; set; } + + [GlobalSetup(Target = nameof(TPH))] + public void SetupTPH() { - [Params(5000)] - public int RowsPerEntityType { get; set; } + Console.WriteLine("Setting up database..."); + using var context = new TPHContext(); + context.Database.EnsureDeleted(); + context.Database.EnsureCreated(); + context.SeedData(RowsPerEntityType); + Console.WriteLine("Setup complete."); + } - [GlobalSetup(Target = nameof(TPH))] - public void SetupTPH() - { - Console.WriteLine("Setting up database..."); - using var context = new TPHContext(); - context.Database.EnsureDeleted(); - context.Database.EnsureCreated(); - context.SeedData(RowsPerEntityType); - Console.WriteLine("Setup complete."); - } + [GlobalSetup(Target = nameof(TPT))] + public void SetupTPT() + { + Console.WriteLine("Setting up database..."); + using var context = new TPTContext(); + context.Database.EnsureDeleted(); + context.Database.EnsureCreated(); + context.SeedData(RowsPerEntityType); + Console.WriteLine("Setup complete."); + } - [GlobalSetup(Target = nameof(TPT))] - public void SetupTPT() - { - Console.WriteLine("Setting up database..."); - using var context = new TPTContext(); - context.Database.EnsureDeleted(); - context.Database.EnsureCreated(); - context.SeedData(RowsPerEntityType); - Console.WriteLine("Setup complete."); - } + [Benchmark] + public List TPH() + { + using var context = new TPHContext(); - [Benchmark] - public List TPH() - { - using var context = new TPHContext(); + return context.Roots.ToList(); + } - return context.Roots.ToList(); - } + [Benchmark] + public List TPT() + { + using var context = new TPTContext(); - [Benchmark] - public List TPT() - { - using var context = new TPTContext(); + return context.Roots.ToList(); + } - return context.Roots.ToList(); - } + public abstract class InheritanceContext : DbContext + { + public DbSet Roots { get; set; } - public abstract class InheritanceContext : DbContext - { - public DbSet Roots { get; set; } - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - => optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True"); - - public void SeedData(int rowsPerEntityType) - { - Set().AddRange(Enumerable.Range(0, rowsPerEntityType).Select(i => new Root { RootProperty = i })); - Set().AddRange(Enumerable.Range(0, rowsPerEntityType).Select(i => new Child1 { Child1Property = i })); - Set().AddRange(Enumerable.Range(0, rowsPerEntityType).Select(i => new Child1A { Child1AProperty = i })); - Set().AddRange(Enumerable.Range(0, rowsPerEntityType).Select(i => new Child1B { Child1BProperty = i })); - Set().AddRange(Enumerable.Range(0, rowsPerEntityType).Select(i => new Child2 { Child2Property = i })); - Set().AddRange(Enumerable.Range(0, rowsPerEntityType).Select(i => new Child2A { Child2AProperty = i })); - Set().AddRange(Enumerable.Range(0, rowsPerEntityType).Select(i => new Child2B { Child2BProperty = i })); - SaveChanges(); - } - } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True"); - public class TPHContext : InheritanceContext + public void SeedData(int rowsPerEntityType) { - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.Entity(); - modelBuilder.Entity(); - modelBuilder.Entity(); - modelBuilder.Entity(); - modelBuilder.Entity(); - modelBuilder.Entity(); - } + Set().AddRange(Enumerable.Range(0, rowsPerEntityType).Select(i => new Root { RootProperty = i })); + Set().AddRange(Enumerable.Range(0, rowsPerEntityType).Select(i => new Child1 { Child1Property = i })); + Set().AddRange(Enumerable.Range(0, rowsPerEntityType).Select(i => new Child1A { Child1AProperty = i })); + Set().AddRange(Enumerable.Range(0, rowsPerEntityType).Select(i => new Child1B { Child1BProperty = i })); + Set().AddRange(Enumerable.Range(0, rowsPerEntityType).Select(i => new Child2 { Child2Property = i })); + Set().AddRange(Enumerable.Range(0, rowsPerEntityType).Select(i => new Child2A { Child2AProperty = i })); + Set().AddRange(Enumerable.Range(0, rowsPerEntityType).Select(i => new Child2B { Child2BProperty = i })); + SaveChanges(); } + } - public class TPTContext : InheritanceContext + public class TPHContext : InheritanceContext + { + protected override void OnModelCreating(ModelBuilder modelBuilder) { - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.Entity().ToTable("Child1"); - modelBuilder.Entity().ToTable("Child1A"); - modelBuilder.Entity().ToTable("Child1B"); - modelBuilder.Entity().ToTable("Child2"); - modelBuilder.Entity().ToTable("Child2A"); - modelBuilder.Entity().ToTable("Child2B"); - } + modelBuilder.Entity(); + modelBuilder.Entity(); + modelBuilder.Entity(); + modelBuilder.Entity(); + modelBuilder.Entity(); + modelBuilder.Entity(); } + } - public class Root + public class TPTContext : InheritanceContext + { + protected override void OnModelCreating(ModelBuilder modelBuilder) { - public int Id { get; set; } - public int RootProperty { get; set; } + modelBuilder.Entity().ToTable("Child1"); + modelBuilder.Entity().ToTable("Child1A"); + modelBuilder.Entity().ToTable("Child1B"); + modelBuilder.Entity().ToTable("Child2"); + modelBuilder.Entity().ToTable("Child2A"); + modelBuilder.Entity().ToTable("Child2B"); } + } - public class Child1 : Root - { - public int Child1Property { get; set; } - } + public class Root + { + public int Id { get; set; } + public int RootProperty { get; set; } + } - public class Child1A : Root - { - public int Child1AProperty { get; set; } - } + public class Child1 : Root + { + public int Child1Property { get; set; } + } - public class Child1B : Root - { - public int Child1BProperty { get; set; } - } + public class Child1A : Root + { + public int Child1AProperty { get; set; } + } - public class Child2 : Root - { - public int Child2Property { get; set; } - } + public class Child1B : Root + { + public int Child1BProperty { get; set; } + } - public class Child2A : Root - { - public int Child2AProperty { get; set; } - } + public class Child2 : Root + { + public int Child2Property { get; set; } + } - public class Child2B : Root - { - public int Child2BProperty { get; set; } - } + public class Child2A : Root + { + public int Child2AProperty { get; set; } + } + + public class Child2B : Root + { + public int Child2BProperty { get; set; } } } diff --git a/samples/core/Benchmarks/Program.cs b/samples/core/Benchmarks/Program.cs index a000fbd7eb..26ee04d39e 100644 --- a/samples/core/Benchmarks/Program.cs +++ b/samples/core/Benchmarks/Program.cs @@ -1,9 +1,6 @@ using BenchmarkDotNet.Running; -namespace Benchmarks +public class Program { - public class Program - { - public static void Main(string[] args) => BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); - } + public static void Main(string[] args) => BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); } diff --git a/samples/core/CompiledQueries/CompiledQueries.csproj b/samples/core/CompiledQueries/CompiledQueries.csproj deleted file mode 100644 index 64f9809c8d..0000000000 --- a/samples/core/CompiledQueries/CompiledQueries.csproj +++ /dev/null @@ -1,10 +0,0 @@ - - - Exe - net5.0 - Samples - - - - - diff --git a/samples/core/CompiledQueries/Model/AdventureWorksContext.cs b/samples/core/CompiledQueries/Model/AdventureWorksContext.cs deleted file mode 100644 index 4836f5b392..0000000000 --- a/samples/core/CompiledQueries/Model/AdventureWorksContext.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Microsoft.EntityFrameworkCore; - -namespace Samples.Model -{ - public class AdventureWorksContext : DbContext - { - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - optionsBuilder.UseSqlServer( - "data source=(localdb)\\mssqllocaldb;initial catalog=AdventureWorks2014;Trusted_Connection=True;MultipleActiveResultSets=True"); - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.Entity( - entity => - { - entity.ToTable("Customer", "Sales"); - - entity.HasIndex(e => e.AccountNumber) - .HasDatabaseName("AK_Customer_AccountNumber") - .IsUnique(); - - entity.HasIndex(e => e.TerritoryID) - .HasDatabaseName("IX_Customer_TerritoryID"); - - entity.HasIndex(e => e.rowguid) - .HasDatabaseName("AK_Customer_rowguid") - .IsUnique(); - - entity.Property(e => e.AccountNumber) - .IsRequired() - .HasColumnType("varchar(10)"); - - entity.Property(e => e.ModifiedDate) - .HasColumnType("datetime") - .HasDefaultValueSql("getdate()"); - - entity.Property(e => e.rowguid).HasDefaultValueSql("newid()"); - }); - } - - public virtual DbSet Customers { get; set; } - } -} diff --git a/samples/core/CompiledQueries/Model/Customer.cs b/samples/core/CompiledQueries/Model/Customer.cs deleted file mode 100644 index 3f79e12905..0000000000 --- a/samples/core/CompiledQueries/Model/Customer.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Samples.Model -{ - public class Customer - { - public int CustomerID { get; set; } - public int? PersonID { get; set; } - public int? StoreID { get; set; } - public int? TerritoryID { get; set; } - public string AccountNumber { get; set; } - public Guid rowguid { get; set; } - public DateTime ModifiedDate { get; set; } - } -} diff --git a/samples/core/CompiledQueries/Program.cs b/samples/core/CompiledQueries/Program.cs deleted file mode 100644 index 3c0a5ab4b5..0000000000 --- a/samples/core/CompiledQueries/Program.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using System.Diagnostics; -using System.Linq; -using Microsoft.EntityFrameworkCore; -using Samples.Model; - -namespace Samples -{ - public class Program - { - private static void Main() - { - using (var db = new AdventureWorksContext()) - { - db.Database.EnsureDeleted(); - db.Database.EnsureCreated(); - - foreach (var accountNumber in GetAccountNumbers(2000)) - { - db.Add(new Customer { AccountNumber = accountNumber, }); - } - - db.SaveChanges(); - } - - // Warmup - using (var db = new AdventureWorksContext()) - { - var customer = db.Customers.First(); - } - - RunTest( - accountNumbers => - { - using (var db = new AdventureWorksContext()) - { - foreach (var id in accountNumbers) - { - // Use a regular auto-compiled query - var customer = db.Customers.Single(c => c.AccountNumber == id); - } - } - }, - name: "Regular"); - - RunTest( - accountNumbers => - { - // Create an explicit compiled query - var query = EF.CompileQuery( - (AdventureWorksContext db, string id) - => db.Customers.Single(c => c.AccountNumber == id)); - - using (var db = new AdventureWorksContext()) - { - foreach (var id in accountNumbers) - { - // Invoke the compiled query - query(db, id); - } - } - }, - name: "Compiled"); - } - - private static void RunTest(Action test, string name) - { - var accountNumbers = GetAccountNumbers(500); - var stopwatch = new Stopwatch(); - - stopwatch.Start(); - - test(accountNumbers); - - stopwatch.Stop(); - - Console.WriteLine($"{name}: {stopwatch.ElapsedMilliseconds.ToString().PadLeft(4)}ms"); - } - - private static string[] GetAccountNumbers(int count) - { - var accountNumbers = new string[count]; - - for (var i = 0; i < count; i++) - { - accountNumbers[i] = "AW" + (i + 1).ToString().PadLeft(8, '0'); - } - - return accountNumbers; - } - } -} diff --git a/samples/core/CompiledQueries/README.md b/samples/core/CompiledQueries/README.md deleted file mode 100644 index 846f120378..0000000000 --- a/samples/core/CompiledQueries/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Explicitly Compiled Queries - -This sample demonstrates Explicitly Compiled Queries. The program runs a benchmark test that measures throughput (queries/second) of regular auto-compiled queries compared to an explicitly compiled query. - -This sample depends on the Adventure Works 2014 sample database, which may be downloaded [here](https://msftdbprodsamples.codeplex.com/releases/view/125550). diff --git a/samples/core/Performance/Program.cs b/samples/core/Performance/Program.cs index cc525583ea..da33e0c4d3 100644 --- a/samples/core/Performance/Program.cs +++ b/samples/core/Performance/Program.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Performance.LazyLoading; @@ -9,7 +10,13 @@ namespace Performance { internal class Program { - private static void Main(string[] args) + #region CompiledQueryCompile + private static readonly Func> _compiledQuery + = EF.CompileAsyncQuery( + (BloggingContext context, int length) => context.Blogs.Where(b => b.Url.StartsWith("http://") && b.Url.Length == length)); + #endregion + + private static async Task Main(string[] args) { using (var context = new BloggingContext()) { @@ -217,6 +224,16 @@ private static void Main(string[] args) var allPosts = context.Posts.ToList(); } #endregion + + using (var context = new BloggingContext()) + { + #region CompiledQueryExecute + await foreach (var blog in _compiledQuery(context, 8)) + { + // Do something with the results + } + #endregion + } } } } diff --git a/samples/core/Samples.sln b/samples/core/Samples.sln index 67498519f6..4e3b6e0d00 100644 --- a/samples/core/Samples.sln +++ b/samples/core/Samples.sln @@ -85,8 +85,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RelatedData", "Querying\Rel EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleLogging", "Miscellaneous\Logging\SimpleLogging\SimpleLogging.csproj", "{6A67BD7F-86AA-4F18-ACAF-0420482189AE}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompiledQueries", "CompiledQueries\CompiledQueries.csproj", "{077C2862-467F-4DE0-8412-0F0670C36593}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Intro", "Intro\Intro.csproj", "{937D4D99-A6FE-459A-A5B8-A96D5D952D42}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NullableReferenceTypes", "Miscellaneous\NullableReferenceTypes\NullableReferenceTypes.csproj", "{0F8779BE-F32A-42A9-B2D7-1974A1D9DD09}" @@ -307,10 +305,6 @@ Global {6A67BD7F-86AA-4F18-ACAF-0420482189AE}.Debug|Any CPU.Build.0 = Debug|Any CPU {6A67BD7F-86AA-4F18-ACAF-0420482189AE}.Release|Any CPU.ActiveCfg = Release|Any CPU {6A67BD7F-86AA-4F18-ACAF-0420482189AE}.Release|Any CPU.Build.0 = Release|Any CPU - {077C2862-467F-4DE0-8412-0F0670C36593}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {077C2862-467F-4DE0-8412-0F0670C36593}.Debug|Any CPU.Build.0 = Debug|Any CPU - {077C2862-467F-4DE0-8412-0F0670C36593}.Release|Any CPU.ActiveCfg = Release|Any CPU - {077C2862-467F-4DE0-8412-0F0670C36593}.Release|Any CPU.Build.0 = Release|Any CPU {937D4D99-A6FE-459A-A5B8-A96D5D952D42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {937D4D99-A6FE-459A-A5B8-A96D5D952D42}.Debug|Any CPU.Build.0 = Debug|Any CPU {937D4D99-A6FE-459A-A5B8-A96D5D952D42}.Release|Any CPU.ActiveCfg = Release|Any CPU