From bbaaabaebafab4d0df79d3ea2b5f8f7855738bb6 Mon Sep 17 00:00:00 2001 From: rudis Date: Tue, 27 Sep 2022 20:50:21 +0200 Subject: [PATCH 01/10] Implement GetAuthors_Optimized Implement GetAuthors_Optimized, add DeepEqual for verifying equality. --- BenchmarkService.cs | 37 ++++++++++++++++++++++++++++++++++--- OptimizeMePlease.csproj | 1 + Program.cs | 18 +++++++++++------- 3 files changed, 46 insertions(+), 10 deletions(-) diff --git a/BenchmarkService.cs b/BenchmarkService.cs index d0a958d..e8802ba 100644 --- a/BenchmarkService.cs +++ b/BenchmarkService.cs @@ -14,7 +14,7 @@ public BenchmarkService() } /// - /// Get top 2 Authors (FirstName, LastName, UserName, Email, Age, Country) + /// Get top 2 Authors (FirstName, LastName, UserName, Email, Age, Country) /// from country Serbia aged 27, with the highest BooksCount /// and all his/her books (Book Name/Title and Publishment Year) published before 1900 /// @@ -88,9 +88,40 @@ public List GetAuthors() [Benchmark] public List GetAuthors_Optimized() { - List authors = new List(); + using var dbContext = new AppDbContext(); + + var authors = dbContext.Authors.AsNoTracking() + .Select(x => new AuthorDTO + { + UserCreated = x.User.Created, + UserEmailConfirmed = x.User.EmailConfirmed, + UserFirstName = x.User.FirstName, + UserLastActivity = x.User.LastActivity, + UserLastName = x.User.LastName, + UserEmail = x.User.Email, + UserName = x.User.UserName, + UserId = x.User.Id, + RoleId = x.User.UserRoles.FirstOrDefault(y => y.UserId == x.UserId).RoleId, + BooksCount = x.BooksCount, + AllBooks = x.Books.Select(y => new BookDto + { + Id = y.Id, + Name = y.Name, + Published = y.Published, + ISBN = y.ISBN, + PublisherName = y.Publisher.Name, + PublishedYear = y.Published.Year + }).Where(b => b.Published.Year < 1900).ToList(), + AuthorAge = x.Age, + AuthorCountry = x.Country, + AuthorNickName = x.NickName, + Id = x.Id + }) + .Where(x => x.AuthorCountry == "Serbia" && x.AuthorAge == 27); + + var orderedAuthors = authors.OrderByDescending(x => x.BooksCount).Take(2).ToList(); - return authors; + return orderedAuthors; } } } diff --git a/OptimizeMePlease.csproj b/OptimizeMePlease.csproj index 7e4fcc9..b5464c2 100644 --- a/OptimizeMePlease.csproj +++ b/OptimizeMePlease.csproj @@ -8,6 +8,7 @@ + diff --git a/Program.cs b/Program.cs index b05ee2d..5d168ef 100644 --- a/Program.cs +++ b/Program.cs @@ -1,4 +1,5 @@ using BenchmarkDotNet.Running; +using DeepEqual.Syntax; using Microsoft.Data.SqlClient; using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.Smo; @@ -9,28 +10,31 @@ namespace OptimizeMePlease { /// - /// Steps: - /// + /// Steps: + /// /// 1. Create a database with name "OptimizeMePlease" /// 2. Run application Debug/Release mode for the first time. IWillPopulateData method will get the script and populate /// created db. - /// 3. Comment or delete IWillPopulateData() call from Main method. + /// 3. Comment or delete IWillPopulateData() call from Main method. /// 4. Go to BenchmarkService.cs class /// 5. Start coding within GetAuthors_Optimized method - /// GOOD LUCK! :D + /// GOOD LUCK! :D /// public class Program { static void Main(string[] args) { - //Debugging + //Debugging BenchmarkService benchmarkService = new BenchmarkService(); - benchmarkService.GetAuthors(); + var authors = benchmarkService.GetAuthors(); + var optimizedAuthors = benchmarkService.GetAuthors_Optimized(); + + authors.ShouldDeepEqual(optimizedAuthors); //Comment me after first execution, please. //IWillPopulateData(); - //BenchmarkRunner.Run(); + BenchmarkRunner.Run(); } public static void IWillPopulateData() From ea2a97b7521e2fa83cd34fe654d42e65f4002704 Mon Sep 17 00:00:00 2001 From: rudis Date: Tue, 27 Sep 2022 23:08:56 +0200 Subject: [PATCH 02/10] Downgrade EF. Use static context provider with change tracking off. --- BenchmarkService.cs | 6 ++---- ContextProvider.cs | 19 +++++++++++++++++++ Migrations/20220920221036_Initial.Designer.cs | 1 - ...5132829_RemovedRoleIdFromUsers.Designer.cs | 1 - Migrations/AppDbContextModelSnapshot.cs | 1 - OptimizeMePlease.csproj | 11 ++--------- Program.cs | 2 +- 7 files changed, 24 insertions(+), 17 deletions(-) create mode 100644 ContextProvider.cs diff --git a/BenchmarkService.cs b/BenchmarkService.cs index e8802ba..878e12c 100644 --- a/BenchmarkService.cs +++ b/BenchmarkService.cs @@ -88,9 +88,7 @@ public List GetAuthors() [Benchmark] public List GetAuthors_Optimized() { - using var dbContext = new AppDbContext(); - - var authors = dbContext.Authors.AsNoTracking() + var authors = ContextProvider.AppDbContext.Authors .Select(x => new AuthorDTO { UserCreated = x.User.Created, @@ -101,7 +99,7 @@ public List GetAuthors_Optimized() UserEmail = x.User.Email, UserName = x.User.UserName, UserId = x.User.Id, - RoleId = x.User.UserRoles.FirstOrDefault(y => y.UserId == x.UserId).RoleId, + RoleId = x.User.UserRoles.FirstOrDefault().RoleId, BooksCount = x.BooksCount, AllBooks = x.Books.Select(y => new BookDto { diff --git a/ContextProvider.cs b/ContextProvider.cs new file mode 100644 index 0000000..7fdb937 --- /dev/null +++ b/ContextProvider.cs @@ -0,0 +1,19 @@ +using OptimizeMePlease.Context; +using System; +using System.Collections.Generic; +using System.Text; + +namespace OptimizeMePlease +{ + public static class ContextProvider + { + static ContextProvider() + { + AppDbContext = new AppDbContext(); + AppDbContext.ChangeTracker.AutoDetectChangesEnabled = false; + AppDbContext.ChangeTracker.QueryTrackingBehavior = Microsoft.EntityFrameworkCore.QueryTrackingBehavior.NoTracking; + } + + public static AppDbContext AppDbContext { get; } + } +} diff --git a/Migrations/20220920221036_Initial.Designer.cs b/Migrations/20220920221036_Initial.Designer.cs index 6516510..13444e4 100644 --- a/Migrations/20220920221036_Initial.Designer.cs +++ b/Migrations/20220920221036_Initial.Designer.cs @@ -4,7 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using OptimizeMePlease.Context; namespace OptimizeMePlease.Migrations diff --git a/Migrations/20220925132829_RemovedRoleIdFromUsers.Designer.cs b/Migrations/20220925132829_RemovedRoleIdFromUsers.Designer.cs index b3b0ac4..9dd7fd7 100644 --- a/Migrations/20220925132829_RemovedRoleIdFromUsers.Designer.cs +++ b/Migrations/20220925132829_RemovedRoleIdFromUsers.Designer.cs @@ -4,7 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using OptimizeMePlease.Context; namespace OptimizeMePlease.Migrations diff --git a/Migrations/AppDbContextModelSnapshot.cs b/Migrations/AppDbContextModelSnapshot.cs index e9d903d..1d44277 100644 --- a/Migrations/AppDbContextModelSnapshot.cs +++ b/Migrations/AppDbContextModelSnapshot.cs @@ -3,7 +3,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using OptimizeMePlease.Context; namespace OptimizeMePlease.Migrations diff --git a/OptimizeMePlease.csproj b/OptimizeMePlease.csproj index b5464c2..fd2f139 100644 --- a/OptimizeMePlease.csproj +++ b/OptimizeMePlease.csproj @@ -1,4 +1,4 @@ - + Exe @@ -7,15 +7,8 @@ - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Program.cs b/Program.cs index 5d168ef..b53f792 100644 --- a/Program.cs +++ b/Program.cs @@ -29,7 +29,7 @@ static void Main(string[] args) var authors = benchmarkService.GetAuthors(); var optimizedAuthors = benchmarkService.GetAuthors_Optimized(); - authors.ShouldDeepEqual(optimizedAuthors); + //authors.ShouldDeepEqual(optimizedAuthors); //Comment me after first execution, please. //IWillPopulateData(); From 7a3d9abc6debb863801424aa428eec8b729acc2b Mon Sep 17 00:00:00 2001 From: rudis Date: Tue, 27 Sep 2022 23:52:58 +0200 Subject: [PATCH 03/10] Upgrade to net7.0 and EF7. --- BenchmarkService.cs | 4 ++-- Context/AppDbContext.cs | 2 +- OptimizeMePlease.csproj | 4 ++-- Program.cs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/BenchmarkService.cs b/BenchmarkService.cs index 878e12c..7b3b1fd 100644 --- a/BenchmarkService.cs +++ b/BenchmarkService.cs @@ -117,9 +117,9 @@ public List GetAuthors_Optimized() }) .Where(x => x.AuthorCountry == "Serbia" && x.AuthorAge == 27); - var orderedAuthors = authors.OrderByDescending(x => x.BooksCount).Take(2).ToList(); + var orderedAuthors = authors.OrderByDescending(x => x.BooksCount).Take(2); - return orderedAuthors; + return orderedAuthors.ToList(); } } } diff --git a/Context/AppDbContext.cs b/Context/AppDbContext.cs index a4c4b8b..b5d6bdd 100644 --- a/Context/AppDbContext.cs +++ b/Context/AppDbContext.cs @@ -7,7 +7,7 @@ public class AppDbContext : DbContext { protected override void OnConfiguring(DbContextOptionsBuilder options) { - options.UseSqlServer("Server=localhost;Database=OptimizeMePlease;Trusted_Connection=True;Integrated Security=true;MultipleActiveResultSets=true"); + options.UseSqlServer("Server=localhost;Database=OptimizeMePlease;Trusted_Connection=True;Integrated Security=true;MultipleActiveResultSets=true;Encrypt=false"); } protected override void OnModelCreating(ModelBuilder modelBuilder) diff --git a/OptimizeMePlease.csproj b/OptimizeMePlease.csproj index fd2f139..2987ec1 100644 --- a/OptimizeMePlease.csproj +++ b/OptimizeMePlease.csproj @@ -2,13 +2,13 @@ Exe - netcoreapp3.1 + net7.0 - + diff --git a/Program.cs b/Program.cs index b53f792..2d5dd03 100644 --- a/Program.cs +++ b/Program.cs @@ -29,7 +29,7 @@ static void Main(string[] args) var authors = benchmarkService.GetAuthors(); var optimizedAuthors = benchmarkService.GetAuthors_Optimized(); - //authors.ShouldDeepEqual(optimizedAuthors); + authors.ShouldDeepEqual(optimizedAuthors); //Comment me after first execution, please. //IWillPopulateData(); @@ -39,7 +39,7 @@ static void Main(string[] args) public static void IWillPopulateData() { - string sqlConnectionString = @"Server=localhost;Database=OptimizeMePlease;Trusted_Connection=True;Integrated Security=true;MultipleActiveResultSets=true"; + string sqlConnectionString = @"Server=localhost;Database=OptimizeMePlease;Trusted_Connection=True;Integrated Security=true;MultipleActiveResultSets=true;Encrypt=false"; string workingDirectory = Environment.CurrentDirectory; string path = Path.Combine(Directory.GetParent(workingDirectory).Parent.Parent.FullName, @"script.sql"); From 1a4b39da609670b0853116caa00de7e30e5e85ca Mon Sep 17 00:00:00 2001 From: rudis Date: Fri, 30 Sep 2022 13:13:30 +0200 Subject: [PATCH 04/10] Add GetAuthors_Optimized_Indexed. --- BenchmarkService.cs | 95 ++++++++++++++++++++++++++----------- Context/IndexedDbContext.cs | 12 +++++ ContextProvider.cs | 8 ++-- IndexedContextProvider.cs | 17 +++++++ Program.cs | 2 + 5 files changed, 101 insertions(+), 33 deletions(-) create mode 100644 Context/IndexedDbContext.cs create mode 100644 IndexedContextProvider.cs diff --git a/BenchmarkService.cs b/BenchmarkService.cs index 7b3b1fd..db0ab5b 100644 --- a/BenchmarkService.cs +++ b/BenchmarkService.cs @@ -88,34 +88,73 @@ public List GetAuthors() [Benchmark] public List GetAuthors_Optimized() { - var authors = ContextProvider.AppDbContext.Authors - .Select(x => new AuthorDTO - { - UserCreated = x.User.Created, - UserEmailConfirmed = x.User.EmailConfirmed, - UserFirstName = x.User.FirstName, - UserLastActivity = x.User.LastActivity, - UserLastName = x.User.LastName, - UserEmail = x.User.Email, - UserName = x.User.UserName, - UserId = x.User.Id, - RoleId = x.User.UserRoles.FirstOrDefault().RoleId, - BooksCount = x.BooksCount, - AllBooks = x.Books.Select(y => new BookDto - { - Id = y.Id, - Name = y.Name, - Published = y.Published, - ISBN = y.ISBN, - PublisherName = y.Publisher.Name, - PublishedYear = y.Published.Year - }).Where(b => b.Published.Year < 1900).ToList(), - AuthorAge = x.Age, - AuthorCountry = x.Country, - AuthorNickName = x.NickName, - Id = x.Id - }) - .Where(x => x.AuthorCountry == "Serbia" && x.AuthorAge == 27); + var authors = + ContextProvider.AppDbContext.Authors + .Select(x => new AuthorDTO + { + UserCreated = x.User.Created, + UserEmailConfirmed = x.User.EmailConfirmed, + UserFirstName = x.User.FirstName, + UserLastActivity = x.User.LastActivity, + UserLastName = x.User.LastName, + UserEmail = x.User.Email, + UserName = x.User.UserName, + UserId = x.User.Id, + RoleId = x.User.UserRoles.FirstOrDefault().RoleId, + BooksCount = x.BooksCount, + AllBooks = x.Books.Select(y => new BookDto + { + Id = y.Id, + Name = y.Name, + Published = y.Published, + ISBN = y.ISBN, + PublisherName = y.Publisher.Name, + PublishedYear = y.Published.Year + }).Where(b => b.Published.Year < 1900).ToList(), + AuthorAge = x.Age, + AuthorCountry = x.Country, + AuthorNickName = x.NickName, + Id = x.Id + }) + .Where(x => x.AuthorCountry == "Serbia" && x.AuthorAge == 27); + + var orderedAuthors = authors.OrderByDescending(x => x.BooksCount).Take(2); + + return orderedAuthors.ToList(); + } + + [Benchmark] + public List GetAuthors_Optimized_Indexed() + { + var authors = + IndexedContextProvider.AppDbContext.Authors + .Select(x => new AuthorDTO + { + UserCreated = x.User.Created, + UserEmailConfirmed = x.User.EmailConfirmed, + UserFirstName = x.User.FirstName, + UserLastActivity = x.User.LastActivity, + UserLastName = x.User.LastName, + UserEmail = x.User.Email, + UserName = x.User.UserName, + UserId = x.User.Id, + RoleId = x.User.UserRoles.FirstOrDefault().RoleId, + BooksCount = x.BooksCount, + AllBooks = x.Books.Select(y => new BookDto + { + Id = y.Id, + Name = y.Name, + Published = y.Published, + ISBN = y.ISBN, + PublisherName = y.Publisher.Name, + PublishedYear = y.Published.Year + }).Where(b => b.Published.Year < 1900).ToList(), + AuthorAge = x.Age, + AuthorCountry = x.Country, + AuthorNickName = x.NickName, + Id = x.Id + }) + .Where(x => x.AuthorCountry == "Serbia" && x.AuthorAge == 27); var orderedAuthors = authors.OrderByDescending(x => x.BooksCount).Take(2); diff --git a/Context/IndexedDbContext.cs b/Context/IndexedDbContext.cs new file mode 100644 index 0000000..15a16f0 --- /dev/null +++ b/Context/IndexedDbContext.cs @@ -0,0 +1,12 @@ +using Microsoft.EntityFrameworkCore; + +namespace OptimizeMePlease.Context +{ + public class IndexedDbContext : AppDbContext + { + protected override void OnConfiguring(DbContextOptionsBuilder options) + { + options.UseSqlServer("Server=localhost;Database=OptimizeMePlease-Indexed;Trusted_Connection=True;Integrated Security=true;MultipleActiveResultSets=true;Encrypt=false"); + } + } +} \ No newline at end of file diff --git a/ContextProvider.cs b/ContextProvider.cs index 7fdb937..0c8d42a 100644 --- a/ContextProvider.cs +++ b/ContextProvider.cs @@ -1,7 +1,5 @@ -using OptimizeMePlease.Context; -using System; -using System.Collections.Generic; -using System.Text; +using Microsoft.EntityFrameworkCore; +using OptimizeMePlease.Context; namespace OptimizeMePlease { @@ -11,7 +9,7 @@ static ContextProvider() { AppDbContext = new AppDbContext(); AppDbContext.ChangeTracker.AutoDetectChangesEnabled = false; - AppDbContext.ChangeTracker.QueryTrackingBehavior = Microsoft.EntityFrameworkCore.QueryTrackingBehavior.NoTracking; + AppDbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; } public static AppDbContext AppDbContext { get; } diff --git a/IndexedContextProvider.cs b/IndexedContextProvider.cs new file mode 100644 index 0000000..66611d9 --- /dev/null +++ b/IndexedContextProvider.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; +using OptimizeMePlease.Context; + +namespace OptimizeMePlease +{ + public static class IndexedContextProvider + { + static IndexedContextProvider() + { + AppDbContext = new IndexedDbContext(); + AppDbContext.ChangeTracker.AutoDetectChangesEnabled = false; + AppDbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + } + + public static AppDbContext AppDbContext { get; } + } +} diff --git a/Program.cs b/Program.cs index 2d5dd03..42e62d1 100644 --- a/Program.cs +++ b/Program.cs @@ -28,8 +28,10 @@ static void Main(string[] args) BenchmarkService benchmarkService = new BenchmarkService(); var authors = benchmarkService.GetAuthors(); var optimizedAuthors = benchmarkService.GetAuthors_Optimized(); + var indexedAuthors = benchmarkService.GetAuthors_Optimized_Indexed(); authors.ShouldDeepEqual(optimizedAuthors); + authors.ShouldDeepEqual(indexedAuthors); //Comment me after first execution, please. //IWillPopulateData(); From 17cec2bb6765d181ae85da67a669944bf9a505bd Mon Sep 17 00:00:00 2001 From: rudis Date: Wed, 5 Oct 2022 12:41:04 +0200 Subject: [PATCH 05/10] Trying out expression version from https://github.com/qjustfeelitp/OptimizeMePlease_Challange --- BenchmarkService.cs | 52 +++++++++++++++++++++++++++++++++++++++++++- OptimizeMePlease.sln | 25 +++++++++++++++++++++ Program.cs | 2 ++ 3 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 OptimizeMePlease.sln diff --git a/BenchmarkService.cs b/BenchmarkService.cs index db0ab5b..bed65dd 100644 --- a/BenchmarkService.cs +++ b/BenchmarkService.cs @@ -1,11 +1,17 @@ using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Order; using Microsoft.EntityFrameworkCore; using OptimizeMePlease.Context; +using OptimizeMePlease.Entities; +using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; namespace OptimizeMePlease { + [InProcess] + [Orderer(SummaryOrderPolicy.FastestToSlowest)] [MemoryDiagnoser] public class BenchmarkService { @@ -19,7 +25,7 @@ public BenchmarkService() /// and all his/her books (Book Name/Title and Publishment Year) published before 1900 /// /// - [Benchmark] + [Benchmark(Baseline = true)] public List GetAuthors() { using var dbContext = new AppDbContext(); @@ -160,5 +166,49 @@ public List GetAuthors_Optimized_Indexed() return orderedAuthors.ToList(); } + + [Benchmark] + public IList GetAuthors_Optimized_Expression() + { + var db = ContextProvider.AppDbContext; + + return Get(db).ToList(); + } + + private const string Serbia = nameof(Serbia); + private const int Age = 27; + private const int Year = 1900; + + private static readonly Expression> AuthorWhereFilterExpression = author => (author.Country == Serbia) && (author.Age == Age); + private static readonly Expression> BookWhereFilterExpression = book => book.Published < EF.Functions.DateFromParts(Year, 1, 1); + + private static readonly Expression> BookSelectorExpression = book => new BookDto + { + Name = book.Name, + PublishedYear = book.Published.Year + }; + + private static readonly Expression> AuthorDtoSelectorExpression = author => new AuthorDTO + { + UserFirstName = author.User.FirstName, + UserLastName = author.User.LastName, + UserEmail = author.User.Email, + UserName = author.User.UserName, + BooksCount = author.BooksCount, + AllBooks = author.Books.AsQueryable() + .Where(BookWhereFilterExpression) + .Select(BookSelectorExpression) + .ToList(), + AuthorAge = author.Age, + AuthorCountry = author.Country + }; + + private static readonly Func> Get = + EF.CompileQuery((DbContext db) => + db.Set() + .Where(AuthorWhereFilterExpression) + .OrderByDescending(x => x.BooksCount) + .Take(2) + .Select(AuthorDtoSelectorExpression)); } } diff --git a/OptimizeMePlease.sln b/OptimizeMePlease.sln new file mode 100644 index 0000000..ff42c99 --- /dev/null +++ b/OptimizeMePlease.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.4.32916.344 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OptimizeMePlease", "OptimizeMePlease.csproj", "{26129639-E19A-4B85-85C0-AB850993B97F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {26129639-E19A-4B85-85C0-AB850993B97F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {26129639-E19A-4B85-85C0-AB850993B97F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {26129639-E19A-4B85-85C0-AB850993B97F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {26129639-E19A-4B85-85C0-AB850993B97F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {0C6563DF-3842-41E2-B16C-53A233FFA1B1} + EndGlobalSection +EndGlobal diff --git a/Program.cs b/Program.cs index 42e62d1..bf920b7 100644 --- a/Program.cs +++ b/Program.cs @@ -29,9 +29,11 @@ static void Main(string[] args) var authors = benchmarkService.GetAuthors(); var optimizedAuthors = benchmarkService.GetAuthors_Optimized(); var indexedAuthors = benchmarkService.GetAuthors_Optimized_Indexed(); + var expressionAuthors = benchmarkService.GetAuthors_Optimized_Expression(); authors.ShouldDeepEqual(optimizedAuthors); authors.ShouldDeepEqual(indexedAuthors); + //authors.ShouldDeepEqual(expressionAuthors); //Comment me after first execution, please. //IWillPopulateData(); From 5c26e4f7b6eebbc07a1ae940628ea7f3766985e7 Mon Sep 17 00:00:00 2001 From: rudis Date: Wed, 5 Oct 2022 12:48:27 +0200 Subject: [PATCH 06/10] Make GetAuthors_Optimized_Expression data equivalent. --- BenchmarkService.cs | 35 +++++++++++++++++++++++------------ Program.cs | 2 +- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/BenchmarkService.cs b/BenchmarkService.cs index bed65dd..5c1a5aa 100644 --- a/BenchmarkService.cs +++ b/BenchmarkService.cs @@ -182,25 +182,36 @@ public IList GetAuthors_Optimized_Expression() private static readonly Expression> AuthorWhereFilterExpression = author => (author.Country == Serbia) && (author.Age == Age); private static readonly Expression> BookWhereFilterExpression = book => book.Published < EF.Functions.DateFromParts(Year, 1, 1); - private static readonly Expression> BookSelectorExpression = book => new BookDto + private static readonly Expression> BookSelectorExpression = y => new BookDto { - Name = book.Name, - PublishedYear = book.Published.Year + Id = y.Id, + Name = y.Name, + Published = y.Published, + ISBN = y.ISBN, + PublisherName = y.Publisher.Name, + PublishedYear = y.Published.Year }; - private static readonly Expression> AuthorDtoSelectorExpression = author => new AuthorDTO + private static readonly Expression> AuthorDtoSelectorExpression = x => new AuthorDTO { - UserFirstName = author.User.FirstName, - UserLastName = author.User.LastName, - UserEmail = author.User.Email, - UserName = author.User.UserName, - BooksCount = author.BooksCount, - AllBooks = author.Books.AsQueryable() + UserCreated = x.User.Created, + UserEmailConfirmed = x.User.EmailConfirmed, + UserFirstName = x.User.FirstName, + UserLastActivity = x.User.LastActivity, + UserLastName = x.User.LastName, + UserEmail = x.User.Email, + UserName = x.User.UserName, + UserId = x.User.Id, + RoleId = x.User.UserRoles.FirstOrDefault().RoleId, + BooksCount = x.BooksCount, + AllBooks = x.Books.AsQueryable() .Where(BookWhereFilterExpression) .Select(BookSelectorExpression) .ToList(), - AuthorAge = author.Age, - AuthorCountry = author.Country + AuthorAge = x.Age, + AuthorCountry = x.Country, + AuthorNickName = x.NickName, + Id = x.Id }; private static readonly Func> Get = diff --git a/Program.cs b/Program.cs index bf920b7..5f9a21c 100644 --- a/Program.cs +++ b/Program.cs @@ -33,7 +33,7 @@ static void Main(string[] args) authors.ShouldDeepEqual(optimizedAuthors); authors.ShouldDeepEqual(indexedAuthors); - //authors.ShouldDeepEqual(expressionAuthors); + authors.ShouldDeepEqual(expressionAuthors); //Comment me after first execution, please. //IWillPopulateData(); From 89ad01395f8f61f352bdbce09d20e1ff90272457 Mon Sep 17 00:00:00 2001 From: rudis Date: Wed, 5 Oct 2022 12:51:57 +0200 Subject: [PATCH 07/10] Add context static hack disclaimer... --- ContextProvider.cs | 1 + IndexedContextProvider.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/ContextProvider.cs b/ContextProvider.cs index 0c8d42a..c6b2202 100644 --- a/ContextProvider.cs +++ b/ContextProvider.cs @@ -3,6 +3,7 @@ namespace OptimizeMePlease { + // I use a static context to gain a few more ms, must not be used in code meant for production. public static class ContextProvider { static ContextProvider() diff --git a/IndexedContextProvider.cs b/IndexedContextProvider.cs index 66611d9..58f7c6f 100644 --- a/IndexedContextProvider.cs +++ b/IndexedContextProvider.cs @@ -3,6 +3,7 @@ namespace OptimizeMePlease { + // I use a static context to gain a few more ms, must not be used in code meant for production. public static class IndexedContextProvider { static IndexedContextProvider() From 8437519033061ec14af61fa5cf3aee3e8ac10015 Mon Sep 17 00:00:00 2001 From: rudis Date: Wed, 5 Oct 2022 12:53:57 +0200 Subject: [PATCH 08/10] Comment on copied code. --- BenchmarkService.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/BenchmarkService.cs b/BenchmarkService.cs index 5c1a5aa..fcd29f0 100644 --- a/BenchmarkService.cs +++ b/BenchmarkService.cs @@ -167,6 +167,10 @@ public List GetAuthors_Optimized_Indexed() return orderedAuthors.ToList(); } + /// + /// Not my original work, copied and adapted from https://github.com/qjustfeelitp/OptimizeMePlease_Challange. + /// + /// [Benchmark] public IList GetAuthors_Optimized_Expression() { From e91506a5b1422df76cb79b79fc34f56ee2e03d8b Mon Sep 17 00:00:00 2001 From: rudis Date: Wed, 5 Oct 2022 15:09:07 +0200 Subject: [PATCH 09/10] Add Dapper versions, indexed expression version. --- BenchmarkService.cs | 93 ++++++++++++++++++++++++++++++++++++++++- OptimizeMePlease.csproj | 1 + Program.cs | 2 + 3 files changed, 94 insertions(+), 2 deletions(-) diff --git a/BenchmarkService.cs b/BenchmarkService.cs index fcd29f0..b51c3d4 100644 --- a/BenchmarkService.cs +++ b/BenchmarkService.cs @@ -1,5 +1,5 @@ using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Order; +using Dapper; using Microsoft.EntityFrameworkCore; using OptimizeMePlease.Context; using OptimizeMePlease.Entities; @@ -11,7 +11,6 @@ namespace OptimizeMePlease { [InProcess] - [Orderer(SummaryOrderPolicy.FastestToSlowest)] [MemoryDiagnoser] public class BenchmarkService { @@ -167,6 +166,89 @@ public List GetAuthors_Optimized_Indexed() return orderedAuthors.ToList(); } + const string dapperSql = """ + SELECT u.Created UserCreated, + u.EmailConfirmed UserEmailConfirmed, + u.FirstName UserFirstName, + u.LastActivity UserLastActivity, + u.LastName UserLastName, + u.Email UserEmail, + u.UserName UserName, + u.Id UserId, + (SELECT TOP 1 RoleId FROM UserRoles ur WHERE ur.UserId = a.UserId) RoleId, + a.BooksCount, + a.Age AuthorAge, a.Country AuthorCountry, a.NickName AuthorNickName, a.Id, + b.Id, b.Name, b.Published, b.ISBN, b.PublisherName, b.PublishedYear + FROM Authors a + JOIN Users u ON u.Id = a.UserId + JOIN ( + SELECT b.Id, b.Name, b.Published, b.ISBN, p.Name PublisherName, DATEPART(YEAR, b.Published) PublishedYear, b.AuthorId + FROM Books b + JOIN Publishers p ON p.Id = b.PublisherId + ) b ON b.AuthorId = a.Id AND PublishedYear < @publishedYear + WHERE a.Country = @country AND a.Age = @age + ORDER BY a.BooksCount, b.Id; + """; + [Benchmark] + public List GetAuthors_Optimized_Dapper() + { + var queriedAuthors = new Dictionary(); + + var authors = ContextProvider.AppDbContext.Database.GetDbConnection().Query( + dapperSql, + (author, book) => + { + if (!queriedAuthors.TryGetValue(author.Id, out var authorEntry)) + { + authorEntry = author; + authorEntry.AllBooks ??= new List(); + queriedAuthors.Add(authorEntry.Id, authorEntry); + } + + if (!authorEntry.AllBooks.Any(b => b.Id == book.Id)) + { + authorEntry.AllBooks.Add(book); + } + + return authorEntry; + }, + new { country = "Serbia", age = 27, publishedYear = 1900 } + ) + .ToList(); + + return queriedAuthors.Values.ToList(); + } + + [Benchmark] + public List GetAuthors_Optimized_DapperIndexed() + { + var queriedAuthors = new Dictionary(); + + var authors = IndexedContextProvider.AppDbContext.Database.GetDbConnection().Query( + dapperSql, + (author, book) => + { + if (!queriedAuthors.TryGetValue(author.Id, out var authorEntry)) + { + authorEntry = author; + authorEntry.AllBooks ??= new List(); + queriedAuthors.Add(authorEntry.Id, authorEntry); + } + + if (!authorEntry.AllBooks.Any(b => b.Id == book.Id)) + { + authorEntry.AllBooks.Add(book); + } + + return authorEntry; + }, + new { country = "Serbia", age = 27, publishedYear = 1900 } + ) + .ToList(); + + return queriedAuthors.Values.ToList(); + } + /// /// Not my original work, copied and adapted from https://github.com/qjustfeelitp/OptimizeMePlease_Challange. /// @@ -178,6 +260,13 @@ public IList GetAuthors_Optimized_Expression() return Get(db).ToList(); } + [Benchmark] + public IList GetAuthors_Optimized_ExpressionIndexed() + { + var db = IndexedContextProvider.AppDbContext; + + return Get(db).ToList(); + } private const string Serbia = nameof(Serbia); private const int Age = 27; diff --git a/OptimizeMePlease.csproj b/OptimizeMePlease.csproj index 2987ec1..18c714f 100644 --- a/OptimizeMePlease.csproj +++ b/OptimizeMePlease.csproj @@ -7,6 +7,7 @@ + diff --git a/Program.cs b/Program.cs index 5f9a21c..d2a1607 100644 --- a/Program.cs +++ b/Program.cs @@ -30,10 +30,12 @@ static void Main(string[] args) var optimizedAuthors = benchmarkService.GetAuthors_Optimized(); var indexedAuthors = benchmarkService.GetAuthors_Optimized_Indexed(); var expressionAuthors = benchmarkService.GetAuthors_Optimized_Expression(); + var dapperAuthors = benchmarkService.GetAuthors_Optimized_Dapper(); authors.ShouldDeepEqual(optimizedAuthors); authors.ShouldDeepEqual(indexedAuthors); authors.ShouldDeepEqual(expressionAuthors); + authors.ShouldDeepEqual(dapperAuthors); //Comment me after first execution, please. //IWillPopulateData(); From b26ea93a93b1282f71218468e3f17e98016f5e65 Mon Sep 17 00:00:00 2001 From: rudis Date: Thu, 6 Oct 2022 10:17:56 +0200 Subject: [PATCH 10/10] DBContext pooling. --- BenchmarkService.cs | 41 +++++++++++++++++++++++++++++-------- Context/AppDbContext.cs | 8 +------- Context/IndexedDbContext.cs | 12 ----------- ContextProvider.cs | 18 ---------------- IndexedContextProvider.cs | 18 ---------------- 5 files changed, 33 insertions(+), 64 deletions(-) delete mode 100644 Context/IndexedDbContext.cs delete mode 100644 ContextProvider.cs delete mode 100644 IndexedContextProvider.cs diff --git a/BenchmarkService.cs b/BenchmarkService.cs index b51c3d4..c37eff4 100644 --- a/BenchmarkService.cs +++ b/BenchmarkService.cs @@ -1,6 +1,7 @@ using BenchmarkDotNet.Attributes; using Dapper; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; using OptimizeMePlease.Context; using OptimizeMePlease.Entities; using System; @@ -14,8 +15,22 @@ namespace OptimizeMePlease [MemoryDiagnoser] public class BenchmarkService { + PooledDbContextFactory _dbContextFactory; + PooledDbContextFactory _indexedDbContextFactory; + public BenchmarkService() { + var options = new DbContextOptionsBuilder() + .UseSqlServer("Server=localhost;Database=OptimizeMePlease;Trusted_Connection=True;Integrated Security=true;MultipleActiveResultSets=true;Encrypt=false") + .Options; + + _dbContextFactory = new PooledDbContextFactory(options); + + var indexedDbOptions = new DbContextOptionsBuilder() + .UseSqlServer("Server=localhost;Database=OptimizeMePlease-Indexed;Trusted_Connection=True;Integrated Security=true;MultipleActiveResultSets=true;Encrypt=false") + .Options; + + _indexedDbContextFactory = new PooledDbContextFactory(indexedDbOptions); } /// @@ -27,7 +42,7 @@ public BenchmarkService() [Benchmark(Baseline = true)] public List GetAuthors() { - using var dbContext = new AppDbContext(); + using var dbContext = _dbContextFactory.CreateDbContext(); var authors = dbContext.Authors .Include(x => x.User) @@ -93,8 +108,10 @@ public List GetAuthors() [Benchmark] public List GetAuthors_Optimized() { + using var dbContext = _dbContextFactory.CreateDbContext(); + var authors = - ContextProvider.AppDbContext.Authors + dbContext.Authors .Select(x => new AuthorDTO { UserCreated = x.User.Created, @@ -131,8 +148,10 @@ public List GetAuthors_Optimized() [Benchmark] public List GetAuthors_Optimized_Indexed() { + using var dbContext = _indexedDbContextFactory.CreateDbContext(); + var authors = - IndexedContextProvider.AppDbContext.Authors + dbContext.Authors .Select(x => new AuthorDTO { UserCreated = x.User.Created, @@ -192,9 +211,11 @@ FROM Books b [Benchmark] public List GetAuthors_Optimized_Dapper() { + using var dbContext = _dbContextFactory.CreateDbContext(); + var queriedAuthors = new Dictionary(); - var authors = ContextProvider.AppDbContext.Database.GetDbConnection().Query( + var authors = dbContext.Database.GetDbConnection().Query( dapperSql, (author, book) => { @@ -222,9 +243,11 @@ public List GetAuthors_Optimized_Dapper() [Benchmark] public List GetAuthors_Optimized_DapperIndexed() { + using var dbContext = _indexedDbContextFactory.CreateDbContext(); + var queriedAuthors = new Dictionary(); - var authors = IndexedContextProvider.AppDbContext.Database.GetDbConnection().Query( + var authors = dbContext.Database.GetDbConnection().Query( dapperSql, (author, book) => { @@ -256,16 +279,16 @@ public List GetAuthors_Optimized_DapperIndexed() [Benchmark] public IList GetAuthors_Optimized_Expression() { - var db = ContextProvider.AppDbContext; + using var dbContext = _dbContextFactory.CreateDbContext(); - return Get(db).ToList(); + return Get(dbContext).ToList(); } [Benchmark] public IList GetAuthors_Optimized_ExpressionIndexed() { - var db = IndexedContextProvider.AppDbContext; + using var dbContext = _indexedDbContextFactory.CreateDbContext(); - return Get(db).ToList(); + return Get(dbContext).ToList(); } private const string Serbia = nameof(Serbia); diff --git a/Context/AppDbContext.cs b/Context/AppDbContext.cs index b5d6bdd..863d527 100644 --- a/Context/AppDbContext.cs +++ b/Context/AppDbContext.cs @@ -5,14 +5,8 @@ namespace OptimizeMePlease.Context { public class AppDbContext : DbContext { - protected override void OnConfiguring(DbContextOptionsBuilder options) + public AppDbContext(DbContextOptions options) : base(options) { - options.UseSqlServer("Server=localhost;Database=OptimizeMePlease;Trusted_Connection=True;Integrated Security=true;MultipleActiveResultSets=true;Encrypt=false"); - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); } public DbSet Users { get; set; } diff --git a/Context/IndexedDbContext.cs b/Context/IndexedDbContext.cs deleted file mode 100644 index 15a16f0..0000000 --- a/Context/IndexedDbContext.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.EntityFrameworkCore; - -namespace OptimizeMePlease.Context -{ - public class IndexedDbContext : AppDbContext - { - protected override void OnConfiguring(DbContextOptionsBuilder options) - { - options.UseSqlServer("Server=localhost;Database=OptimizeMePlease-Indexed;Trusted_Connection=True;Integrated Security=true;MultipleActiveResultSets=true;Encrypt=false"); - } - } -} \ No newline at end of file diff --git a/ContextProvider.cs b/ContextProvider.cs deleted file mode 100644 index c6b2202..0000000 --- a/ContextProvider.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using OptimizeMePlease.Context; - -namespace OptimizeMePlease -{ - // I use a static context to gain a few more ms, must not be used in code meant for production. - public static class ContextProvider - { - static ContextProvider() - { - AppDbContext = new AppDbContext(); - AppDbContext.ChangeTracker.AutoDetectChangesEnabled = false; - AppDbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; - } - - public static AppDbContext AppDbContext { get; } - } -} diff --git a/IndexedContextProvider.cs b/IndexedContextProvider.cs deleted file mode 100644 index 58f7c6f..0000000 --- a/IndexedContextProvider.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using OptimizeMePlease.Context; - -namespace OptimizeMePlease -{ - // I use a static context to gain a few more ms, must not be used in code meant for production. - public static class IndexedContextProvider - { - static IndexedContextProvider() - { - AppDbContext = new IndexedDbContext(); - AppDbContext.ChangeTracker.AutoDetectChangesEnabled = false; - AppDbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; - } - - public static AppDbContext AppDbContext { get; } - } -}