From 0df6e4843818fa36fa60609744793c0712785c0c Mon Sep 17 00:00:00 2001 From: electricvampire Date: Sat, 5 Oct 2024 17:10:06 +0530 Subject: [PATCH] Show query even if queryplan times out --- .../QueryPlanUserControl.xaml.cs | 32 ++++ .../EFCoreQueryableObjectSource.cs | 154 ++++++++++++++---- 2 files changed, 152 insertions(+), 34 deletions(-) diff --git a/src/EFCore.Visualizer/QueryPlanUserControl.xaml.cs b/src/EFCore.Visualizer/QueryPlanUserControl.xaml.cs index 4c1fb5c..ecd2db9 100644 --- a/src/EFCore.Visualizer/QueryPlanUserControl.xaml.cs +++ b/src/EFCore.Visualizer/QueryPlanUserControl.xaml.cs @@ -42,6 +42,7 @@ private void QueryPlanUserControlUnloaded(object sender, RoutedEventArgs e) protected override async void OnInitialized(EventArgs e) #pragma warning restore VSTHRD100 // Avoid async void methods { + var query = string.Empty; try { base.OnInitialized(e); @@ -50,6 +51,8 @@ protected override async void OnInitialized(EventArgs e) var environment = await CoreWebView2Environment.CreateAsync(userDataFolder: Path.Combine(AssemblyLocation, "WVData")); await webView.EnsureCoreWebView2Async(environment); + query = await GetQueryAsync(); + #if !DEBUG webView.CoreWebView2.Settings.AreBrowserAcceleratorKeysEnabled = false; webView.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false; @@ -84,10 +87,39 @@ protected override async void OnInitialized(EventArgs e) } catch (Exception ex) { + if (!string.IsNullOrEmpty(query)) + webView.CoreWebView2.NavigateToString(query); MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error); } } + private async Task GetQueryAsync() + { + var query = string.Empty; + var response = await visualizerTarget.ObjectSource.RequestDataAsync(ConvertStringToReadOnlySequence("GetQuery"), CancellationToken.None); + if (response.HasValue) + { + using var stream = response.Value.AsStream(); + using var binaryReader = new BinaryReader(stream, Encoding.Default); + var isError = binaryReader.ReadBoolean(); + if (!isError) + { + query = binaryReader.ReadString(); + } + } + return query; + } + private static ReadOnlySequence ConvertStringToReadOnlySequence(string input) + { + byte[] byteArray = Encoding.UTF8.GetBytes(input); + + ReadOnlyMemory readOnlyMemory = new ReadOnlyMemory(byteArray); + + ReadOnlySequence readOnlySequence = new ReadOnlySequence(readOnlyMemory); + + return readOnlySequence; + } + private void ButtonReviewClick(object sender, RoutedEventArgs e) { StartProcess("https://marketplace.visualstudio.com/items?itemName=GiorgiDalakishvili.EFCoreVisualizer&ssr=false#review-details"); diff --git a/src/IQueryableObjectSource/EFCoreQueryableObjectSource.cs b/src/IQueryableObjectSource/EFCoreQueryableObjectSource.cs index 7763ba0..d9fff49 100644 --- a/src/IQueryableObjectSource/EFCoreQueryableObjectSource.cs +++ b/src/IQueryableObjectSource/EFCoreQueryableObjectSource.cs @@ -6,6 +6,9 @@ using System.Linq; using System.Text; using System.Text.Encodings.Web; +using System.Threading.Tasks; +using System.Net; +using Microsoft.EntityFrameworkCore.Storage; namespace IQueryableObjectSource { @@ -22,55 +25,138 @@ public override void TransferData(object target, Stream incomingData, Stream out try { - using var command = queryable.CreateDbCommand(); - var provider = GetDatabaseProvider(command); - - if (provider == null) + var dbOperation = ConvertStreamToString(incomingData); + switch (dbOperation) { - return; + case "GetQuery": + HandleGetQuery(queryable, outgoingData); + break; + + default: + HandleGetQueryPlan(queryable, incomingData, outgoingData); + break; } + } + catch (Exception ex) + { + WriteError(outgoingData, ex.Message); + } + } + private void HandleGetQuery(IQueryable queryable, Stream outgoingData) + { + using var queryWriter = new BinaryWriter(outgoingData, Encoding.Default, true); + queryWriter.Write(false); // Indicates no error + queryWriter.Write(GenerateHtml(queryable.ToQueryString())); + } + private void HandleGetQueryPlan(IQueryable queryable, Stream incomingData, Stream outgoingData) + { + using var command = queryable.CreateDbCommand(); + var provider = GetDatabaseProvider(command); - var query = queryable.ToQueryString(); - var rawPlan = provider.ExtractPlan(); + if (provider == null) + { + return; + } - var buffer = new byte[3]; - var isBackgroundDarkColor = false; + var query = queryable.ToQueryString(); + var rawPlan = provider.ExtractPlan(); - var r = 255; - var g = 255; - var b = 255; + var (r, g, b) = ReadBackgroundColor(incomingData); + var isBackgroundDarkColor = r * 0.2126 + g * 0.7152 + b * 0.0722 < 255 / 2.0; - if (incomingData.Read(buffer, 0, buffer.Length) == buffer.Length) - { - r = buffer[0]; - g = buffer[1]; - b = buffer[2]; - } + var planFile = GeneratePlanFile(provider, query, rawPlan, r, g, b, isBackgroundDarkColor); + + using var writer = new BinaryWriter(outgoingData, Encoding.Default, true); + writer.Write(false); // Indicates no error + writer.Write(planFile); + } + private (int r, int g, int b) ReadBackgroundColor(Stream incomingData) + { + var buffer = new byte[3]; + var r = 255; + var g = 255; + var b = 255; + + if (incomingData.Read(buffer, 0, buffer.Length) == buffer.Length) + { + r = buffer[0]; + g = buffer[1]; + b = buffer[2]; + } - isBackgroundDarkColor = r * 0.2126 + g * 0.7152 + b * 0.0722 < 255 / 2.0; + return (r, g, b); + } - var planFile = Path.Combine(provider.GetPlanDirectory(ResourcesLocation), Path.ChangeExtension(Path.GetRandomFileName(), "html")); + private string GeneratePlanFile(DatabaseProvider provider, string query, string rawPlan, int r, int g, int b, bool isBackgroundDarkColor) + { + var planDirectory = provider.GetPlanDirectory(ResourcesLocation); + var planFile = Path.Combine(planDirectory, Path.ChangeExtension(Path.GetRandomFileName(), "html")); - var planPageHtml = File.ReadAllText(Path.Combine(provider.GetPlanDirectory(ResourcesLocation), "template.html")) - .Replace("{backColor}", $"rgb({r} {g} {b})") - .Replace("{textColor}", isBackgroundDarkColor ? "white" : "black") - .Replace("{plan}", JavaScriptEncoder.UnsafeRelaxedJsonEscaping.Encode(rawPlan).Replace("'", "\\'")) - .Replace("{query}", JavaScriptEncoder.UnsafeRelaxedJsonEscaping.Encode(query).Replace("'", "\\'")); + var planPageHtml = File.ReadAllText(Path.Combine(planDirectory, "template.html")) + .Replace("{backColor}", $"rgb({r} {g} {b})") + .Replace("{textColor}", isBackgroundDarkColor ? "white" : "black") + .Replace("{plan}", JavaScriptEncoder.UnsafeRelaxedJsonEscaping.Encode(rawPlan).Replace("'", "\\'")) + .Replace("{query}", JavaScriptEncoder.UnsafeRelaxedJsonEscaping.Encode(query).Replace("'", "\\'")); - File.WriteAllText(planFile, planPageHtml); + File.WriteAllText(planFile, planPageHtml); - using var writer = new BinaryWriter(outgoingData, Encoding.Default, true); - writer.Write(false); - writer.Write(planFile); - } - catch (Exception ex) + return planFile; + } + + private void WriteError(Stream outgoingData, string errorMessage) + { + using var writer = new BinaryWriter(outgoingData, Encoding.Default, true); + writer.Write(true); // Indicates an error occurred + writer.Write(errorMessage); + } + + public static string ConvertStreamToString(Stream stream) + { + using (MemoryStream memoryStream = new MemoryStream()) { - using var writer = new BinaryWriter(outgoingData, Encoding.Default, true); - writer.Write(true); - writer.Write(ex.Message); + stream.CopyTo(memoryStream); + byte[] byteArray = memoryStream.ToArray(); + + //Try to convert byte array to a string using UTF-8 encoding + try + { + string result = Encoding.UTF8.GetString(byteArray); + return result; + } + catch (Exception) + { + return "GetQueryPlan"; + } } } + private static string GenerateHtml(string query) + { + string escapedQuery = WebUtility.HtmlEncode(query); + + // Simple HTML structure to display the query + StringBuilder htmlBuilder = new StringBuilder(); + htmlBuilder.AppendLine(""); + htmlBuilder.AppendLine("Query Plan Visualizer"); + htmlBuilder.AppendLine(""); + htmlBuilder.AppendLine(""); + htmlBuilder.AppendLine(""); + htmlBuilder.AppendLine("

SQL Query

"); + htmlBuilder.AppendLine("
"); + htmlBuilder.AppendLine("
" + escapedQuery + "
"); + htmlBuilder.AppendLine("
"); + htmlBuilder.AppendLine(""); + htmlBuilder.AppendLine(""); + + return htmlBuilder.ToString(); + } + + private static DatabaseProvider GetDatabaseProvider(DbCommand command) { return command.GetType().FullName switch