Skip to content

Commit 0df6e48

Browse files
Show query even if queryplan times out
1 parent 9047b68 commit 0df6e48

File tree

2 files changed

+152
-34
lines changed

2 files changed

+152
-34
lines changed

src/EFCore.Visualizer/QueryPlanUserControl.xaml.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ private void QueryPlanUserControlUnloaded(object sender, RoutedEventArgs e)
4242
protected override async void OnInitialized(EventArgs e)
4343
#pragma warning restore VSTHRD100 // Avoid async void methods
4444
{
45+
var query = string.Empty;
4546
try
4647
{
4748
base.OnInitialized(e);
@@ -50,6 +51,8 @@ protected override async void OnInitialized(EventArgs e)
5051
var environment = await CoreWebView2Environment.CreateAsync(userDataFolder: Path.Combine(AssemblyLocation, "WVData"));
5152
await webView.EnsureCoreWebView2Async(environment);
5253

54+
query = await GetQueryAsync();
55+
5356
#if !DEBUG
5457
webView.CoreWebView2.Settings.AreBrowserAcceleratorKeysEnabled = false;
5558
webView.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false;
@@ -84,10 +87,39 @@ protected override async void OnInitialized(EventArgs e)
8487
}
8588
catch (Exception ex)
8689
{
90+
if (!string.IsNullOrEmpty(query))
91+
webView.CoreWebView2.NavigateToString(query);
8792
MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
8893
}
8994
}
9095

96+
private async Task<string> GetQueryAsync()
97+
{
98+
var query = string.Empty;
99+
var response = await visualizerTarget.ObjectSource.RequestDataAsync(ConvertStringToReadOnlySequence("GetQuery"), CancellationToken.None);
100+
if (response.HasValue)
101+
{
102+
using var stream = response.Value.AsStream();
103+
using var binaryReader = new BinaryReader(stream, Encoding.Default);
104+
var isError = binaryReader.ReadBoolean();
105+
if (!isError)
106+
{
107+
query = binaryReader.ReadString();
108+
}
109+
}
110+
return query;
111+
}
112+
private static ReadOnlySequence<byte> ConvertStringToReadOnlySequence(string input)
113+
{
114+
byte[] byteArray = Encoding.UTF8.GetBytes(input);
115+
116+
ReadOnlyMemory<byte> readOnlyMemory = new ReadOnlyMemory<byte>(byteArray);
117+
118+
ReadOnlySequence<byte> readOnlySequence = new ReadOnlySequence<byte>(readOnlyMemory);
119+
120+
return readOnlySequence;
121+
}
122+
91123
private void ButtonReviewClick(object sender, RoutedEventArgs e)
92124
{
93125
StartProcess("https://marketplace.visualstudio.com/items?itemName=GiorgiDalakishvili.EFCoreVisualizer&ssr=false#review-details");

src/IQueryableObjectSource/EFCoreQueryableObjectSource.cs

Lines changed: 120 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
using System.Linq;
77
using System.Text;
88
using System.Text.Encodings.Web;
9+
using System.Threading.Tasks;
10+
using System.Net;
11+
using Microsoft.EntityFrameworkCore.Storage;
912

1013
namespace IQueryableObjectSource
1114
{
@@ -22,55 +25,138 @@ public override void TransferData(object target, Stream incomingData, Stream out
2225

2326
try
2427
{
25-
using var command = queryable.CreateDbCommand();
26-
var provider = GetDatabaseProvider(command);
27-
28-
if (provider == null)
28+
var dbOperation = ConvertStreamToString(incomingData);
29+
switch (dbOperation)
2930
{
30-
return;
31+
case "GetQuery":
32+
HandleGetQuery(queryable, outgoingData);
33+
break;
34+
35+
default:
36+
HandleGetQueryPlan(queryable, incomingData, outgoingData);
37+
break;
3138
}
39+
}
40+
catch (Exception ex)
41+
{
42+
WriteError(outgoingData, ex.Message);
43+
}
44+
}
45+
private void HandleGetQuery(IQueryable queryable, Stream outgoingData)
46+
{
47+
using var queryWriter = new BinaryWriter(outgoingData, Encoding.Default, true);
48+
queryWriter.Write(false); // Indicates no error
49+
queryWriter.Write(GenerateHtml(queryable.ToQueryString()));
50+
}
51+
private void HandleGetQueryPlan(IQueryable queryable, Stream incomingData, Stream outgoingData)
52+
{
53+
using var command = queryable.CreateDbCommand();
54+
var provider = GetDatabaseProvider(command);
3255

33-
var query = queryable.ToQueryString();
34-
var rawPlan = provider.ExtractPlan();
56+
if (provider == null)
57+
{
58+
return;
59+
}
3560

36-
var buffer = new byte[3];
37-
var isBackgroundDarkColor = false;
61+
var query = queryable.ToQueryString();
62+
var rawPlan = provider.ExtractPlan();
3863

39-
var r = 255;
40-
var g = 255;
41-
var b = 255;
64+
var (r, g, b) = ReadBackgroundColor(incomingData);
65+
var isBackgroundDarkColor = r * 0.2126 + g * 0.7152 + b * 0.0722 < 255 / 2.0;
4266

43-
if (incomingData.Read(buffer, 0, buffer.Length) == buffer.Length)
44-
{
45-
r = buffer[0];
46-
g = buffer[1];
47-
b = buffer[2];
48-
}
67+
var planFile = GeneratePlanFile(provider, query, rawPlan, r, g, b, isBackgroundDarkColor);
68+
69+
using var writer = new BinaryWriter(outgoingData, Encoding.Default, true);
70+
writer.Write(false); // Indicates no error
71+
writer.Write(planFile);
72+
}
73+
private (int r, int g, int b) ReadBackgroundColor(Stream incomingData)
74+
{
75+
var buffer = new byte[3];
76+
var r = 255;
77+
var g = 255;
78+
var b = 255;
79+
80+
if (incomingData.Read(buffer, 0, buffer.Length) == buffer.Length)
81+
{
82+
r = buffer[0];
83+
g = buffer[1];
84+
b = buffer[2];
85+
}
4986

50-
isBackgroundDarkColor = r * 0.2126 + g * 0.7152 + b * 0.0722 < 255 / 2.0;
87+
return (r, g, b);
88+
}
5189

52-
var planFile = Path.Combine(provider.GetPlanDirectory(ResourcesLocation), Path.ChangeExtension(Path.GetRandomFileName(), "html"));
90+
private string GeneratePlanFile(DatabaseProvider provider, string query, string rawPlan, int r, int g, int b, bool isBackgroundDarkColor)
91+
{
92+
var planDirectory = provider.GetPlanDirectory(ResourcesLocation);
93+
var planFile = Path.Combine(planDirectory, Path.ChangeExtension(Path.GetRandomFileName(), "html"));
5394

54-
var planPageHtml = File.ReadAllText(Path.Combine(provider.GetPlanDirectory(ResourcesLocation), "template.html"))
55-
.Replace("{backColor}", $"rgb({r} {g} {b})")
56-
.Replace("{textColor}", isBackgroundDarkColor ? "white" : "black")
57-
.Replace("{plan}", JavaScriptEncoder.UnsafeRelaxedJsonEscaping.Encode(rawPlan).Replace("'", "\\'"))
58-
.Replace("{query}", JavaScriptEncoder.UnsafeRelaxedJsonEscaping.Encode(query).Replace("'", "\\'"));
95+
var planPageHtml = File.ReadAllText(Path.Combine(planDirectory, "template.html"))
96+
.Replace("{backColor}", $"rgb({r} {g} {b})")
97+
.Replace("{textColor}", isBackgroundDarkColor ? "white" : "black")
98+
.Replace("{plan}", JavaScriptEncoder.UnsafeRelaxedJsonEscaping.Encode(rawPlan).Replace("'", "\\'"))
99+
.Replace("{query}", JavaScriptEncoder.UnsafeRelaxedJsonEscaping.Encode(query).Replace("'", "\\'"));
59100

60-
File.WriteAllText(planFile, planPageHtml);
101+
File.WriteAllText(planFile, planPageHtml);
61102

62-
using var writer = new BinaryWriter(outgoingData, Encoding.Default, true);
63-
writer.Write(false);
64-
writer.Write(planFile);
65-
}
66-
catch (Exception ex)
103+
return planFile;
104+
}
105+
106+
private void WriteError(Stream outgoingData, string errorMessage)
107+
{
108+
using var writer = new BinaryWriter(outgoingData, Encoding.Default, true);
109+
writer.Write(true); // Indicates an error occurred
110+
writer.Write(errorMessage);
111+
}
112+
113+
public static string ConvertStreamToString(Stream stream)
114+
{
115+
using (MemoryStream memoryStream = new MemoryStream())
67116
{
68-
using var writer = new BinaryWriter(outgoingData, Encoding.Default, true);
69-
writer.Write(true);
70-
writer.Write(ex.Message);
117+
stream.CopyTo(memoryStream);
118+
byte[] byteArray = memoryStream.ToArray();
119+
120+
//Try to convert byte array to a string using UTF-8 encoding
121+
try
122+
{
123+
string result = Encoding.UTF8.GetString(byteArray);
124+
return result;
125+
}
126+
catch (Exception)
127+
{
128+
return "GetQueryPlan";
129+
}
71130
}
72131
}
73132

133+
private static string GenerateHtml(string query)
134+
{
135+
string escapedQuery = WebUtility.HtmlEncode(query);
136+
137+
// Simple HTML structure to display the query
138+
StringBuilder htmlBuilder = new StringBuilder();
139+
htmlBuilder.AppendLine("<html>");
140+
htmlBuilder.AppendLine("<head><title>Query Plan Visualizer</title>");
141+
htmlBuilder.AppendLine("<style>");
142+
htmlBuilder.AppendLine("body { font-family: Arial, sans-serif; margin: 20px; }");
143+
htmlBuilder.AppendLine("h2 { color: #333; }");
144+
htmlBuilder.AppendLine(".query-box { background-color: #f4f4f4; padding: 10px; border-radius: 5px; border: 1px solid #ccc; overflow-x: auto; max-width: 100%; }");
145+
htmlBuilder.AppendLine("pre { white-space: pre-wrap; word-wrap: break-word; }");
146+
htmlBuilder.AppendLine("</style>");
147+
htmlBuilder.AppendLine("</head>");
148+
htmlBuilder.AppendLine("<body>");
149+
htmlBuilder.AppendLine("<h2>SQL Query</h2>");
150+
htmlBuilder.AppendLine("<div class='query-box'>");
151+
htmlBuilder.AppendLine("<pre>" + escapedQuery + "</pre>");
152+
htmlBuilder.AppendLine("</div>");
153+
htmlBuilder.AppendLine("</body>");
154+
htmlBuilder.AppendLine("</html>");
155+
156+
return htmlBuilder.ToString();
157+
}
158+
159+
74160
private static DatabaseProvider GetDatabaseProvider(DbCommand command)
75161
{
76162
return command.GetType().FullName switch

0 commit comments

Comments
 (0)