From 5a8bca6a4174b95011bc91a52ad0284da0db60d2 Mon Sep 17 00:00:00 2001 From: Anas Chahid Date: Mon, 26 Sep 2022 18:25:37 +0100 Subject: [PATCH 1/4] Optimize EF Core query --- AuthorDTO.cs | 2 +- AuthorDTO_Simple.cs | 21 +++++++++++ BenchmarkService.cs | 81 ++++++++++++++++++++++++++++++++++++++++- BookDto.cs | 2 +- BookDto_Meaningful.cs | 11 ++++++ Context/AppDbContext.cs | 2 +- OptimizeMePlease.csproj | 10 ++--- OptimizeMePlease.sln | 22 +++++++++++ Program.cs | 11 ++++-- 9 files changed, 149 insertions(+), 13 deletions(-) create mode 100644 AuthorDTO_Simple.cs create mode 100644 BookDto_Meaningful.cs create mode 100644 OptimizeMePlease.sln diff --git a/AuthorDTO.cs b/AuthorDTO.cs index dc6416c..7f5a82d 100644 --- a/AuthorDTO.cs +++ b/AuthorDTO.cs @@ -21,5 +21,5 @@ public class AuthorDTO public int AuthorAge { get; set; } public string AuthorCountry { get; set; } public string AuthorNickName { get; set; } - } + } } diff --git a/AuthorDTO_Simple.cs b/AuthorDTO_Simple.cs new file mode 100644 index 0000000..4813b89 --- /dev/null +++ b/AuthorDTO_Simple.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; + +namespace OptimizeMePlease +{ + public class AuthorDTO_Simple + { + public int Id { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public int Age { get; set; } + public string Country { get; set; } + public string NickName { get; set; } + public string Email { get; set; } + public string UserName { get; set; } + public int UserId { get; set; } + public int RoleId { get; set; } + public int BooksCount { get; set; } + public IEnumerable AllBooks { get; set; } + + } +} diff --git a/BenchmarkService.cs b/BenchmarkService.cs index d0a958d..b5def8a 100644 --- a/BenchmarkService.cs +++ b/BenchmarkService.cs @@ -88,9 +88,86 @@ public List GetAuthors() [Benchmark] public List GetAuthors_Optimized() { - List authors = new List(); + using var dbContext = new AppDbContext(); + + var authors = dbContext.Authors + .AsNoTracking() + .Where(x => x.Country == "Serbia" && x.Age == 27) + .OrderByDescending(b => b.BooksCount) + .Take(2) + .Include(x => x.User) + .ThenInclude(x => x.UserRoles) + .Include(x => x.Books) + .ThenInclude(x => x.Publisher) + .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.SingleOrDefault(y => y.UserId == x.UserId).RoleId, + BooksCount = x.BooksCount, + AllBooks = x.Books.Where(b => b.Published.Year < 1900).Select(y => new BookDto + { + Id = y.Id, + Name = y.Name, + Published = y.Published, + ISBN = y.ISBN, + PublisherName = y.Publisher.Name, + PublishedYear = y.Published.Year + }).ToList(), + AuthorAge = x.Age, + AuthorCountry = x.Country, + AuthorNickName = x.NickName, + Id = x.Id + }); + + return authors.ToList(); + } + + + [Benchmark] + public List GetAuthors_Optimized_Meaningful() + { + using var dbContext = new AppDbContext(); + + var authors = dbContext.Authors + .AsNoTracking() + .Where(x => x.Country == "Serbia" && x.Age == 27) + .OrderByDescending(b => b.BooksCount) + .Take(2) + .Include(x => x.User) + .ThenInclude(x => x.UserRoles) + .Include(x => x.Books) + .ThenInclude(x => x.Publisher) + .Select(x => new AuthorDTO_Simple + { + FirstName = x.User.FirstName, + LastName = x.User.LastName, + Email = x.User.Email, + UserName = x.User.UserName, + UserId = x.User.Id, + RoleId = x.User.UserRoles.SingleOrDefault(y => y.UserId == x.UserId).RoleId, + BooksCount = x.BooksCount, + AllBooks = x.Books.Where(b => b.Published.Year < 1900).Select(y => new BookDto_Meaningful + { + Id = y.Id, + Name = y.Name, + ISBN = y.ISBN, + PublisherName = y.Publisher.Name, + PublishedYear = y.Published.Year + }), + Age = x.Age, + Country = x.Country, + NickName = x.NickName, + Id = x.Id + }); - return authors; + return authors.ToList(); } } } diff --git a/BookDto.cs b/BookDto.cs index 7438367..55f1336 100644 --- a/BookDto.cs +++ b/BookDto.cs @@ -10,5 +10,5 @@ public class BookDto public int PublishedYear { get; set; } public string PublisherName { get; set; } public string ISBN { get; set; } - } + } } diff --git a/BookDto_Meaningful.cs b/BookDto_Meaningful.cs new file mode 100644 index 0000000..aaecda7 --- /dev/null +++ b/BookDto_Meaningful.cs @@ -0,0 +1,11 @@ +namespace OptimizeMePlease +{ + public class BookDto_Meaningful + { + public int Id { get; set; } + public string Name { get; set; } + public int PublishedYear { get; set; } + public string PublisherName { get; set; } + public string ISBN { get; set; } + } +} diff --git a/Context/AppDbContext.cs b/Context/AppDbContext.cs index a4c4b8b..e8f3892 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("Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=master;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False;Database=OptimizeMePlease"); } protected override void OnModelCreating(ModelBuilder modelBuilder) diff --git a/OptimizeMePlease.csproj b/OptimizeMePlease.csproj index 7e4fcc9..c898ab0 100644 --- a/OptimizeMePlease.csproj +++ b/OptimizeMePlease.csproj @@ -2,16 +2,16 @@ Exe - netcoreapp3.1 + net6.0 - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/OptimizeMePlease.sln b/OptimizeMePlease.sln new file mode 100644 index 0000000..583c5ea --- /dev/null +++ b/OptimizeMePlease.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30114.105 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OptimizeMePlease", "OptimizeMePlease.csproj", "{BD7CFAC4-DEF7-4A49-BA9A-39D891961236}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BD7CFAC4-DEF7-4A49-BA9A-39D891961236}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BD7CFAC4-DEF7-4A49-BA9A-39D891961236}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BD7CFAC4-DEF7-4A49-BA9A-39D891961236}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BD7CFAC4-DEF7-4A49-BA9A-39D891961236}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Program.cs b/Program.cs index b05ee2d..2ec46ab 100644 --- a/Program.cs +++ b/Program.cs @@ -25,17 +25,22 @@ static void Main(string[] args) { //Debugging BenchmarkService benchmarkService = new BenchmarkService(); - benchmarkService.GetAuthors(); + // Original query + var nonOpt = benchmarkService.GetAuthors(); + // Optimized query without uchanging the dto + var Opt = benchmarkService.GetAuthors_Optimized(); + // Optimized query with changing DTOs + var Opt_Meaningful = benchmarkService.GetAuthors_Optimized_Meaningful(); //Comment me after first execution, please. //IWillPopulateData(); - //BenchmarkRunner.Run(); + BenchmarkRunner.Run(); } public static void IWillPopulateData() { - string sqlConnectionString = @"Server=localhost;Database=OptimizeMePlease;Trusted_Connection=True;Integrated Security=true;MultipleActiveResultSets=true"; + string sqlConnectionString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=master;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False;Database=OptimizeMePlease"; string workingDirectory = Environment.CurrentDirectory; string path = Path.Combine(Directory.GetParent(workingDirectory).Parent.Parent.FullName, @"script.sql"); From b2774003918190262b9ac83055a3b9f8b6092ea2 Mon Sep 17 00:00:00 2001 From: Anas CHAHID <64654197+skillmaker-dev@users.noreply.github.com> Date: Mon, 26 Sep 2022 19:28:29 +0100 Subject: [PATCH 2/4] Update README.md --- README.md | 54 ++---------------------------------------------------- 1 file changed, 2 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 2bef293..636b9e2 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,3 @@ -# OptimizeMePlease +### Note: In GetAuthors_Optimized_Meaningful I edited the DTOs to a more meaningful one including only the properties that are necessary (In my opinion). -## You are probably here because you saw my post on Linkedin. -## Welcome! - -# Steps - -- Before you run an application, you will need to create a database (I'm using MSSQL) named "OptimizeMePlease" -- Go to Program.cs class -- "IWillPopulateDate()" is a method which will get a script from the project directory and run in on created DB -- Run application in Debug/Release mode -- Comment or delete IWillPopulateData() call from Main method -- Go to BenchmarkService.cs class -- Start coding within GetAuthors_Optimized method - -# How do I submit my solution? - -- Send me a Linkedin message with your github username, I will add you and create a branch for you. - -### OR - -- Clone the project, create a branch and work on that branch. - -### OR - -- If you don't want to bother with github, after you're done with the changes, send me the results on Linkedin. - -# Rules - -- Only Entity Framework (Core) is allowed for using -- The data obtained in the non-optimized version of the code must also be obtained in the optimized version -- If you see potential optimization of something else, you can do it -- Entities and DbContext cannot be changed (you got legacy code no matter what it might be bad :) ) -- The models returned from the method can be changed - -# What should the method return? - -- Given that there is a predefined database of data, the method should in any case return the list of data currently returned by the non-optimized method. - -# How will performance success be measured? - -- Given that each of us works on a computer with different performance and power, the execution time quotient of the non-optimized and optimized method will be checked for each separately. - -Example: - -Non-optimized method Execution time: 1.1s = 1100ms -Optimized method Execution time: 200ms - -### Result: 1100ms/200ms = 5.5x faster. - -In addition, I will check the performance on my computer for individual results. - -# GOOD LUCK! +![image](https://user-images.githubusercontent.com/64654197/192352383-31bb2c6c-7f64-4a5c-9b41-c7d03aadd900.png) From 1f036995a4bea4a27b41b7857f7d2a741d7cd866 Mon Sep 17 00:00:00 2001 From: Anas Chahid Date: Fri, 30 Sep 2022 02:54:30 +0100 Subject: [PATCH 3/4] Add more optimization --- AuthorDTO_Simple.cs | 8 ++++---- BenchmarkService.cs | 32 +++++++++++--------------------- BookDto_Meaningful.cs | 12 +++++++----- Program.cs | 8 ++++---- 4 files changed, 26 insertions(+), 34 deletions(-) diff --git a/AuthorDTO_Simple.cs b/AuthorDTO_Simple.cs index 4813b89..8f09dbf 100644 --- a/AuthorDTO_Simple.cs +++ b/AuthorDTO_Simple.cs @@ -4,16 +4,16 @@ namespace OptimizeMePlease { public class AuthorDTO_Simple { - public int Id { get; set; } + //public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public int Age { get; set; } public string Country { get; set; } - public string NickName { get; set; } + //public string NickName { get; set; } public string Email { get; set; } public string UserName { get; set; } - public int UserId { get; set; } - public int RoleId { get; set; } + //public int UserId { get; set; } + //public int RoleId { get; set; } public int BooksCount { get; set; } public IEnumerable AllBooks { get; set; } diff --git a/BenchmarkService.cs b/BenchmarkService.cs index b5def8a..368ec20 100644 --- a/BenchmarkService.cs +++ b/BenchmarkService.cs @@ -1,6 +1,7 @@ using BenchmarkDotNet.Attributes; using Microsoft.EntityFrameworkCore; using OptimizeMePlease.Context; +using System; using System.Collections.Generic; using System.Linq; @@ -19,7 +20,7 @@ public BenchmarkService() /// and all his/her books (Book Name/Title and Publishment Year) published before 1900 /// /// - [Benchmark] + //[Benchmark] public List GetAuthors() { using var dbContext = new AppDbContext(); @@ -85,7 +86,7 @@ public List GetAuthors() return finalAuthors; } - [Benchmark] + //[Benchmark] public List GetAuthors_Optimized() { using var dbContext = new AppDbContext(); @@ -134,40 +135,29 @@ public List GetAuthors_Optimized() public List GetAuthors_Optimized_Meaningful() { using var dbContext = new AppDbContext(); - - var authors = dbContext.Authors + var date = new DateTime(1900, 1, 1); + return dbContext.Authors + .Include(x => x.Books.Where(b => b.Published < date)) .AsNoTracking() .Where(x => x.Country == "Serbia" && x.Age == 27) - .OrderByDescending(b => b.BooksCount) + .OrderByDescending(x => x.BooksCount) .Take(2) - .Include(x => x.User) - .ThenInclude(x => x.UserRoles) - .Include(x => x.Books) - .ThenInclude(x => x.Publisher) .Select(x => new AuthorDTO_Simple { FirstName = x.User.FirstName, LastName = x.User.LastName, Email = x.User.Email, UserName = x.User.UserName, - UserId = x.User.Id, - RoleId = x.User.UserRoles.SingleOrDefault(y => y.UserId == x.UserId).RoleId, BooksCount = x.BooksCount, - AllBooks = x.Books.Where(b => b.Published.Year < 1900).Select(y => new BookDto_Meaningful + AllBooks = x.Books.Select(y => new BookDto_Meaningful { - Id = y.Id, Name = y.Name, - ISBN = y.ISBN, - PublisherName = y.Publisher.Name, - PublishedYear = y.Published.Year + PublishedYear = y.Published }), Age = x.Age, Country = x.Country, - NickName = x.NickName, - Id = x.Id - }); - - return authors.ToList(); + }) + .ToList(); } } } diff --git a/BookDto_Meaningful.cs b/BookDto_Meaningful.cs index aaecda7..eb97c2b 100644 --- a/BookDto_Meaningful.cs +++ b/BookDto_Meaningful.cs @@ -1,11 +1,13 @@ -namespace OptimizeMePlease +using System; + +namespace OptimizeMePlease { public class BookDto_Meaningful { - public int Id { get; set; } + //public int Id { get; set; } public string Name { get; set; } - public int PublishedYear { get; set; } - public string PublisherName { get; set; } - public string ISBN { get; set; } + public DateTime PublishedYear { get; set; } + //public string PublisherName { get; set; } + //public string ISBN { get; set; } } } diff --git a/Program.cs b/Program.cs index 2ec46ab..91a7fd2 100644 --- a/Program.cs +++ b/Program.cs @@ -24,13 +24,13 @@ public class Program static void Main(string[] args) { //Debugging - BenchmarkService benchmarkService = new BenchmarkService(); + //BenchmarkService benchmarkService = new BenchmarkService(); // Original query - var nonOpt = benchmarkService.GetAuthors(); + //var nonOpt = benchmarkService.GetAuthors(); // Optimized query without uchanging the dto - var Opt = benchmarkService.GetAuthors_Optimized(); + //var Opt = benchmarkService.GetAuthors_Optimized(); // Optimized query with changing DTOs - var Opt_Meaningful = benchmarkService.GetAuthors_Optimized_Meaningful(); + //var Opt_Meaningful = benchmarkService.GetAuthors_Optimized_Meaningful(); //Comment me after first execution, please. //IWillPopulateData(); From 13e73bec6dbed45f4a302c5a141b4698d76e9402 Mon Sep 17 00:00:00 2001 From: Anas CHAHID <64654197+skillmaker-dev@users.noreply.github.com> Date: Fri, 30 Sep 2022 02:55:37 +0100 Subject: [PATCH 4/4] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 636b9e2..62cbe27 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ ### Note: In GetAuthors_Optimized_Meaningful I edited the DTOs to a more meaningful one including only the properties that are necessary (In my opinion). -![image](https://user-images.githubusercontent.com/64654197/192352383-31bb2c6c-7f64-4a5c-9b41-c7d03aadd900.png) +![image](https://user-images.githubusercontent.com/64654197/193173418-097870d8-b93f-41fe-b89a-dee5d9ebbb13.png)