├── .gitattributes
├── .github
└── FUNDING.yml
├── .gitignore
├── Icon.png
├── IconSmall.png
├── License.md
├── NuGet README.md
├── README.md
├── screenshots
├── Jeremy Likness.png
├── Julie Lerman.png
├── Missing Index.PNG
├── Postgres Query Plan.PNG
├── Query Plan.PNG
├── QueryPlanNew.PNG
└── Scott Hanselman.png
└── src
├── LINQPadQueryPlanVisualizer.nuspec
├── QueryPlanVisualizer.LinqPad5
├── Helpers
│ ├── DatabaseHelper.cs
│ ├── EntityFrameworkDatabaseHelper.cs
│ └── LinqToSqlDatabaseHelper.cs
├── MissingIndexDetails.cs
├── NativeMethods.cs
├── Properties
│ ├── AssemblyInfo.cs
│ ├── DataSources
│ │ └── MissingIndexDetails.datasource
│ ├── Resources.Designer.cs
│ └── Resources.resx
├── QueryPlanProcessor.cs
├── QueryPlanUserControl.Designer.cs
├── QueryPlanUserControl.cs
├── QueryPlanUserControl.resx
├── QueryPlanVisualizer.LinqPad5.csproj
├── QueryPlanVisualizer.cs
├── Resources
│ ├── jquery-1.12.1.min.js
│ ├── qp.css
│ ├── qp.js
│ ├── qp.xslt
│ ├── qp_icons.png
│ ├── showplanxml.xsd
│ └── template.html
├── XmlUtils.cs
└── packages.config
├── QueryPlanVisualizer.LinqPad6
├── DatabaseProvider.cs
├── Helpers
│ ├── HttpClientExtensions.cs
│ └── XmlUtils.cs
├── MissingIndexDetails.cs
├── MyLinkLabel.cs
├── NativeMethods.cs
├── OrmHelper.cs
├── PlanConvertor.cs
├── PostgresResources.Designer.cs
├── PostgresResources.resx
├── QueryPlanUserControl.Designer.cs
├── QueryPlanUserControl.cs
├── QueryPlanUserControl.resx
├── QueryPlanVisualizer.LinqPad6.csproj
├── QueryPlanVisualizer.cs
├── Resources
│ ├── Postgres
│ │ ├── all.css
│ │ ├── app.css
│ │ ├── app.js
│ │ ├── bootstrap.min.css
│ │ ├── chunk-vendors.js
│ │ └── index.html
│ └── SqlServer
│ │ ├── qp.css
│ │ ├── qp.min.js
│ │ ├── qp_icons.png
│ │ └── template.html
├── SqlServerResources.Designer.cs
├── SqlServerResources.resx
└── TemporaryFiles.cs
└── QueryPlanVisualizer.sln
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
65 | src/QueryPlanVisualizer.LinqPad6/Resources/**/*.css linguist-vendored
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: Giorgi
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: giorgi
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.sln.docstates
8 |
9 | # Build results
10 |
11 | [Dd]ebug/
12 | [Rr]elease/
13 | x64/
14 | build/
15 | [Bb]in/
16 | [Oo]bj/
17 |
18 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets
19 | !packages/*/build/
20 |
21 | # MSTest test Results
22 | [Tt]est[Rr]esult*/
23 | [Bb]uild[Ll]og.*
24 |
25 | *_i.c
26 | *_p.c
27 | *.ilk
28 | *.meta
29 | *.obj
30 | *.pch
31 | *.pdb
32 | *.pgc
33 | *.pgd
34 | *.rsp
35 | *.sbr
36 | *.tlb
37 | *.tli
38 | *.tlh
39 | *.tmp
40 | *.tmp_proj
41 | *.log
42 | *.vspscc
43 | *.vssscc
44 | .builds
45 | *.pidb
46 | *.log
47 | *.scc
48 |
49 | # Visual C++ cache files
50 | ipch/
51 | *.aps
52 | *.ncb
53 | *.opensdf
54 | *.sdf
55 | *.cachefile
56 |
57 | # Visual Studio profiler
58 | *.psess
59 | *.vsp
60 | *.vspx
61 |
62 | # Guidance Automation Toolkit
63 | *.gpState
64 |
65 | # ReSharper is a .NET coding add-in
66 | _ReSharper*/
67 | *.[Rr]e[Ss]harper
68 |
69 | # TeamCity is a build add-in
70 | _TeamCity*
71 |
72 | # DotCover is a Code Coverage Tool
73 | *.dotCover
74 |
75 | # NCrunch
76 | *.ncrunch*
77 | .*crunch*.local.xml
78 |
79 | # Installshield output folder
80 | [Ee]xpress/
81 |
82 | # DocProject is a documentation generator add-in
83 | DocProject/buildhelp/
84 | DocProject/Help/*.HxT
85 | DocProject/Help/*.HxC
86 | DocProject/Help/*.hhc
87 | DocProject/Help/*.hhk
88 | DocProject/Help/*.hhp
89 | DocProject/Help/Html2
90 | DocProject/Help/html
91 |
92 | # Click-Once directory
93 | publish/
94 |
95 | # Publish Web Output
96 | *.Publish.xml
97 |
98 | # NuGet Packages Directory
99 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line
100 | #packages/
101 |
102 | # Windows Azure Build Output
103 | csx
104 | *.build.csdef
105 |
106 | # Windows Store app package directory
107 | AppPackages/
108 |
109 | # Others
110 | sql/
111 | *.Cache
112 | ClientBin/
113 | [Ss]tyle[Cc]op.*
114 | ~$*
115 | *~
116 | *.dbmdl
117 | *.publishsettings
118 |
119 | # RIA/Silverlight projects
120 | Generated_Code/
121 |
122 | # Backup & report files from converting an old project file to a newer
123 | # Visual Studio version. Backup files are not needed, because we have git ;-)
124 | _UpgradeReport_Files/
125 | Backup*/
126 | UpgradeLog*.XML
127 | UpgradeLog*.htm
128 |
129 | # SQL Server files
130 | App_Data/*.mdf
131 | App_Data/*.ldf
132 |
133 | # =========================
134 | # Windows detritus
135 | # =========================
136 |
137 | # Windows image file caches
138 | Thumbs.db
139 | ehthumbs.db
140 |
141 | # Folder config file
142 | Desktop.ini
143 |
144 | # Recycle Bin used on file shares
145 | $RECYCLE.BIN/
146 |
147 | # Mac crap
148 | .DS_Store
149 |
150 |
151 | .vs/
152 | packages/
153 | html-query-plan-2.6/
154 |
--------------------------------------------------------------------------------
/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Giorgi/LINQPad.QueryPlanVisualizer/093f853de63260b04d8a724c56bdf5fee82e017e/Icon.png
--------------------------------------------------------------------------------
/IconSmall.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Giorgi/LINQPad.QueryPlanVisualizer/093f853de63260b04d8a724c56bdf5fee82e017e/IconSmall.png
--------------------------------------------------------------------------------
/License.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016 Giorgi Dalakishvili. All rights reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use
4 | these files except in compliance with the License. You may obtain a copy of the
5 | License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software distributed
10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the
12 | specific language governing permissions and limitations under the License.
13 |
--------------------------------------------------------------------------------
/NuGet README.md:
--------------------------------------------------------------------------------
1 | # LINQPad.QueryPlanVisualizer
2 |
3 | ## SQL Server and PostgreSQL query execution plan visualizer for LINQPad
4 |
5 | ## Features
6 |
7 | * View query execution plan inside LINQPad
8 | * View missing indexes for query
9 | * Share plan to [https://www.brentozar.com/pastetheplan/](https://www.brentozar.com/pastetheplan/) or [https://explain.dalibo.com/](https://explain.dalibo.com/)
10 | * Create missing indexes directly from LINQPad
11 | * Open plan in SQL Server Management Studio or another default app
12 | * Save plan to disk
13 |
14 | ## Getting Started
15 |
16 | **If you use LINQPad 6, you must use version 2.0 of this library. For LINQPad 5, you must use version 1.0**
17 |
18 | The library can show query plans for `LINQ to SQL` driver and `Entity Framework Core 5`.
19 |
20 | ### Install from NuGet
21 |
22 | If you have a Developer or higher edition of LINQPad, you can use the `LINQPadQueryPlanVisualizer` package from NuGet
23 | to add the visualizer to your queries.
24 |
25 | ### Install as plugin
26 |
27 | To install the visualizer as a LINQPad plugin, download the [latest release](https://github.com/Giorgi/QueryPlanVisualizer/releases/latest) and drop the visualizer dll directly inside LINQPad's plugins folder (by default found at **My Documents\LINQPad Plugins\NetCore3** for LINQPad 6 and **My Documents\LINQPad Plugins\Framework 4.6** for LINQPad 5). The plugin will be automatically available in all your queries.
28 |
29 | ## Viewing query plan
30 |
31 | To view query plan or missing indexes, call static `QueryPlanVisualizer.DumpPlan(query)` method or call `DumpPlan` extension method on an `IQueryable` instance. You will also need to add `ExecutionPlanVisualizer` to the namespaces list (click F4 to open the dialog). If you want to dump query result as well, pass `true` as a second parameter.
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LINQPad.QueryPlanVisualizer
2 |
3 | Visualize Entity Framework and Linq to SQL queries in LINQPad. For Visual Studio extension see [EFCore.Visualizer](https://github.com/Giorgi/EFCore.Visualizer)
4 |
5 | [](https://www.nuget.org/packages/LINQPadQueryPlanVisualizer/)
6 | [](https://github.com/Giorgi/LINQPad.QueryPlanVisualizer/releases)
7 | [](License.md)
8 | [](https://ko-fi.com/U6U81LHU8)
9 |
10 | ## Entity Framework Community Standup Live Show
11 |
12 | [](https://www.youtube.com/watch?v=Zhy5antRDJk)
13 |
14 | ## SQL Server and PostgreSQL query execution plan visualizer for LINQPad
15 |
16 |
17 |
18 | ## Features
19 |
20 | * View query execution plan inside LINQPad
21 | * View missing indexes for query
22 | * Share plan to [https://www.brentozar.com/pastetheplan/](https://www.brentozar.com/pastetheplan/) or [https://explain.dalibo.com/](https://explain.dalibo.com/)
23 | * Create missing indexes directly from LINQPad
24 | * Open plan in SQL Server Management Studio or another default app
25 | * Save plan to disk
26 |
27 | Supported databases: Sql Server and PostgreSQL.
28 |
29 | Supported ORMs: `Entity Framework Core 5` and `LINQ to SQL`
30 |
31 | ## Getting Started
32 |
33 | **If you use LINQPad 6 or newer, you must use version 2.X of this library. For LINQPad 5, you must use version 1.X**
34 |
35 | Version 2.1 and newer uses **Microsoft Edge WebView2** to display the query plan. This requires either **WebView2 Runtime** to be installed or a recent version of Edge Canary. To download WebView2 Runtime visit [Microsoft Edge WebView2 Download Page](https://developer.microsoft.com/en-us/microsoft-edge/webview2/).
36 |
37 | ### Install from NuGet
38 |
39 | If you have a Developer or higher edition of LINQPad, you can use the `LINQPadQueryPlanVisualizer` package from NuGet
40 | to add the visualizer to your queries.
41 |
42 | ### Install as plugin
43 |
44 | To install the visualizer as a LINQPad plugin, download the [latest release](https://github.com/Giorgi/QueryPlanVisualizer/releases/latest) and drop the visualizer dll directly inside LINQPad's plugins folder (by default found at **My Documents\LINQPad Plugins\NetCore3** for LINQPad 6 and **My Documents\LINQPad Plugins\Framework 4.6** for LINQPad 5). The plugin will be automatically available in all your queries.
45 |
46 | ## Viewing query plan
47 |
48 | To view query plan or missing indexes, call static `QueryPlanVisualizer.DumpPlan(query)` method or call `DumpPlan` extension method on an `IQueryable` instance. You will also need to add `ExecutionPlanVisualizer` to the namespaces list (click F4 to open the dialog). If you want to dump query result as well, pass `true` as a second parameter.
49 |
50 | Query execution plan for Sql Server:
51 |
52 | 
53 |
54 | Query execution plan for PostgreSQL:
55 |
56 | 
57 |
58 | ## Viewing missing indexes
59 |
60 | For SQL Server, the query plan can also return information about missing indexes in `QueryPlan/MissingIndexes/MissingIndexGroup` element. If missing indexes are present in the plan the visualizer will show a second tab with the missing index details and a button to create the index.
61 |
62 | Missing index:
63 |
64 | 
65 |
66 | ## What Others Are Saying
67 |
68 | [](https://twitter.com/shanselman/status/1555036430392389632)
69 |
70 | [](https://twitter.com/julielerman/status/1415367790844907527)
71 |
72 | [](https://twitter.com/jeremylikness/status/1415368187760185346)
73 |
--------------------------------------------------------------------------------
/screenshots/Jeremy Likness.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Giorgi/LINQPad.QueryPlanVisualizer/093f853de63260b04d8a724c56bdf5fee82e017e/screenshots/Jeremy Likness.png
--------------------------------------------------------------------------------
/screenshots/Julie Lerman.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Giorgi/LINQPad.QueryPlanVisualizer/093f853de63260b04d8a724c56bdf5fee82e017e/screenshots/Julie Lerman.png
--------------------------------------------------------------------------------
/screenshots/Missing Index.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Giorgi/LINQPad.QueryPlanVisualizer/093f853de63260b04d8a724c56bdf5fee82e017e/screenshots/Missing Index.PNG
--------------------------------------------------------------------------------
/screenshots/Postgres Query Plan.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Giorgi/LINQPad.QueryPlanVisualizer/093f853de63260b04d8a724c56bdf5fee82e017e/screenshots/Postgres Query Plan.PNG
--------------------------------------------------------------------------------
/screenshots/Query Plan.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Giorgi/LINQPad.QueryPlanVisualizer/093f853de63260b04d8a724c56bdf5fee82e017e/screenshots/Query Plan.PNG
--------------------------------------------------------------------------------
/screenshots/QueryPlanNew.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Giorgi/LINQPad.QueryPlanVisualizer/093f853de63260b04d8a724c56bdf5fee82e017e/screenshots/QueryPlanNew.PNG
--------------------------------------------------------------------------------
/screenshots/Scott Hanselman.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Giorgi/LINQPad.QueryPlanVisualizer/093f853de63260b04d8a724c56bdf5fee82e017e/screenshots/Scott Hanselman.png
--------------------------------------------------------------------------------
/src/LINQPadQueryPlanVisualizer.nuspec:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | LINQPadQueryPlanVisualizer
5 | 1.2.0
6 | LINQPad Query Plan Visualizer
7 | Giorgi
8 | https://github.com/Giorgi/LINQPad.QueryPlanVisualizer/blob/master/License.md
9 | https://github.com/Giorgi/LINQPad.QueryPlanVisualizer
10 | false
11 | SQL Server query execution plan visualizer for LINQPad.
12 |
13 | Features Include:
14 |
15 | View query execution plan inside LINQPad
16 | View missing indexes for query
17 | Create missing indexes directly from LINQPad
18 | Open plan in SQL Server Management Studio or other default app
19 | Save plan to xml file
20 | SQL Server query execution plan visualizer for LINQPad
21 |
22 | Added support for showing plan from xml string
23 |
24 | Updated to version 1.1 of html-query-plan
25 |
26 |
27 | LINQPad SQL SQLServer
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/QueryPlanVisualizer.LinqPad5/Helpers/DatabaseHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Data;
3 | using System.Data.Common;
4 | using System.Data.Entity.Infrastructure;
5 | using System.Data.Linq;
6 | using System.Linq;
7 | using System.Reflection;
8 | using System.Threading.Tasks;
9 | using LINQPad;
10 |
11 | namespace ExecutionPlanVisualizer.Helpers
12 | {
13 | internal abstract class DatabaseHelper
14 | {
15 | private DbConnection _dbConnection;
16 |
17 | public static DatabaseHelper Create(DataContextBase dataContextBase, IQueryable queryable)
18 | {
19 | if (dataContextBase != null)
20 | {
21 | return new LinqToSqlDatabaseHelper(dataContextBase);
22 | }
23 |
24 | var table = queryable as ITable;
25 |
26 | if (table != null)
27 | {
28 | return new LinqToSqlDatabaseHelper(table.Context);
29 | }
30 |
31 | var dataQueryType = typeof(DataContext).Assembly.GetType("System.Data.Linq.DataQuery`1");
32 |
33 | if (queryable.GetType().GetGenericTypeDefinition() == dataQueryType)
34 | {
35 | var contextField = queryable.GetType().GetField("context", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.GetField);
36 | var context = contextField?.GetValue(queryable) as DataContext;
37 |
38 | if (context != null)
39 | {
40 | return new LinqToSqlDatabaseHelper(context);
41 | }
42 | }
43 |
44 | return CreateEntityFrameworkDatabaseHelper(queryable);
45 | }
46 |
47 | private static DatabaseHelper CreateEntityFrameworkDatabaseHelper(IQueryable queryable)
48 | {
49 | var query = queryable as DbQuery;
50 | if (query != null)
51 | {
52 | var bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.GetProperty;
53 |
54 | var internalQuery = query.GetType().GetProperty("InternalQuery", bindingFlags)?.GetValue(query);
55 | var objectQuery =
56 | internalQuery?.GetType().GetProperty("ObjectQuery")?.GetValue(internalQuery) as
57 | System.Data.Objects.ObjectQuery;
58 |
59 | if (objectQuery != null)
60 | //EF5 uses ObjectQuery from System.Data.Objects namespace, EF6 uses System.Data.Entity.Core.Objects so it will be null
61 | {
62 | return new EntityFramework5DatabaseHelper(objectQuery);
63 | }
64 | }
65 |
66 | return new EntityFrameworkDatabaseHelper();
67 | }
68 |
69 | public DbConnection Connection
70 | {
71 | get
72 | {
73 | if (_dbConnection == null)
74 | {
75 | throw new InvalidOperationException("Connection has not been set.");
76 | }
77 | return _dbConnection;
78 | }
79 | set { _dbConnection = value; }
80 | }
81 |
82 | public virtual string GetSqlServerQueryExecutionPlan(IQueryable queryable)
83 | {
84 | using (var command = CreateCommand(queryable))
85 | {
86 | try
87 | {
88 | if (Connection.State != ConnectionState.Open)
89 | {
90 | Connection.Open();
91 | }
92 |
93 | using (var setStatisticsCommand = Connection.CreateCommand())
94 | {
95 | setStatisticsCommand.CommandText = "SET STATISTICS XML ON";
96 | setStatisticsCommand.ExecuteNonQuery();
97 | }
98 |
99 | using (var reader = command.ExecuteReader())
100 | {
101 | while (reader.NextResult())
102 | {
103 | if (reader.GetName(0) == "Microsoft SQL Server 2005 XML Showplan")
104 | {
105 | reader.Read();
106 | return reader.GetString(0);
107 | }
108 | }
109 | }
110 |
111 | return null;
112 | }
113 | finally
114 | {
115 | Connection.Close();
116 | }
117 | }
118 | }
119 |
120 | public virtual async Task CreateIndexAsync(string script)
121 | {
122 | try
123 | {
124 | await Connection.OpenAsync();
125 |
126 | using (var command = Connection.CreateCommand())
127 | {
128 | command.CommandText = script;
129 | var result = await command.ExecuteNonQueryAsync();
130 | }
131 | }
132 | finally
133 | {
134 | Connection.Close();
135 | }
136 | }
137 |
138 | protected abstract DbCommand CreateCommand(IQueryable queryable);
139 | }
140 | }
--------------------------------------------------------------------------------
/src/QueryPlanVisualizer.LinqPad5/Helpers/EntityFrameworkDatabaseHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Data.Common;
3 | using System.Data.Entity.Infrastructure.Interception;
4 | using System.Data.EntityClient;
5 | using System.Data.Objects;
6 | using System.Linq;
7 |
8 | namespace ExecutionPlanVisualizer.Helpers
9 | {
10 | class EntityFramework5DatabaseHelper : DatabaseHelper
11 | {
12 | private ObjectParameterCollection parameters;
13 |
14 | public EntityFramework5DatabaseHelper(ObjectQuery objectQuery)
15 | {
16 | Connection = (objectQuery.Context.Connection as EntityConnection)?.StoreConnection;
17 | parameters = objectQuery.Parameters;
18 | }
19 |
20 | protected override DbCommand CreateCommand(IQueryable queryable)
21 | {
22 | var command = Connection.CreateCommand();
23 | command.CommandText = queryable.ToString();
24 |
25 | var copiedParameters = parameters.Select(parameter =>
26 | {
27 | var parameterCopy = command.CreateParameter();
28 | parameterCopy.ParameterName = parameter.Name;
29 | parameterCopy.Value = parameter.Value;
30 | return parameterCopy;
31 | }).ToArray();
32 |
33 | command.Parameters.AddRange(copiedParameters);
34 |
35 | return command;
36 | }
37 | }
38 |
39 | internal class EntityFrameworkDatabaseHelper : DatabaseHelper
40 | {
41 | protected override DbCommand CreateCommand(IQueryable queryable)
42 | {
43 | var interceptor = new CommandCapturingInterceptor();
44 |
45 | DbInterception.Add(interceptor);
46 |
47 | try
48 | {
49 | var result = queryable.Provider.Execute(queryable.Expression);
50 | }
51 | catch (Exception ex) when (ex is CommandCapturedException || ex.InnerException is CommandCapturedException)
52 | {
53 | }
54 | finally
55 | {
56 | DbInterception.Remove(interceptor);
57 | }
58 |
59 | if (interceptor.Command == null)
60 | {
61 | throw new InvalidOperationException("DbInterception failed to capture DbCommand.");
62 | }
63 |
64 | Connection = interceptor.Command.Connection;
65 |
66 | var command = Connection.CreateCommand();
67 |
68 | command.CommandText = interceptor.Command.CommandText;
69 | var copiedParameters = interceptor.Command.Parameters.OfType()
70 | .Select(parameter =>
71 | {
72 | var parameterCopy = command.CreateParameter();
73 | parameterCopy.ParameterName = parameter.ParameterName;
74 | parameterCopy.DbType = parameter.DbType;
75 | parameterCopy.Value = parameter.Value;
76 | return parameterCopy;
77 | }).ToArray();
78 |
79 | command.Parameters.AddRange(copiedParameters);
80 |
81 | return command;
82 | }
83 |
84 | private class CommandCapturedException : Exception { }
85 |
86 | private sealed class CommandCapturingInterceptor : IDbCommandInterceptor
87 | {
88 | public DbCommand Command { get; private set; }
89 |
90 | public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext interceptionContext)
91 | {
92 | }
93 |
94 | public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext interceptionContext)
95 | {
96 | }
97 |
98 | public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext interceptionContext)
99 | {
100 | Command = command;
101 | interceptionContext.Exception = new CommandCapturedException();
102 | }
103 |
104 | public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext interceptionContext)
105 | {
106 | }
107 |
108 | public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext