检索带参数的 LINQ to sql 语句 (IQueryable)

问题描述 投票:0回答:7

我试图弄清楚是否有办法检索在数据库服务器上执行的(完整)sql 语句。
我已经找到了一些东西,但它并不完全是我想要的:

IQueryable<SomeType> someQuery = ...
string command = dataContext.GetCommand(query).CommandText;

就我而言,这给了我一个类似以下的命令字符串:

SELECT TOP (50) [t0].[ID], ....
FROM [dbo].[someTable] AS [t0]
WHERE ([t0].[someColumn] IS NOT NULL) AND (([t0].[someColumn]) IN (@p0))

在数据库上执行:

exec sp_executesql N'SELECT TOP (50) [t0].[ID], ...
FROM [dbo].[someTable] AS [t0]
WHERE ([t0].[someColumn] IS NOT NULL) AND (([t0].[someColumn]) IN (@p0, @p1))',N'@p0  int,@p1 int',@p0=401,@p1=201

有没有办法从 C# 代码中检索这个“完整”语句(还有参数值)?

c# linq-to-sql
7个回答
27
投票

如果您有以下实例,您还可以看到生成的 sql 查询

IQueryable<T>
并调用
.ToString()
方法。
例如:

var db = new DbContext();
IQueryable<Blog> query = db.Blog.Where(tt=> tt.Id > 100).OrderByDescending(tt=>tt.Id);
var sqlString = query.ToString();
Console.WriteLine(sqlString);

这将生成以下输出:

SELECT [Extent1].[Id] AS [Id], 
[Extent1].[Title] AS [Title], 
[Extent1].[Author] AS [Author], 
[Extent1].[Text] AS [Text], 
[Extent1].[CreatedAt] AS [CreatedAt], 
[Extent1].[UpdatedAt] AS [UpdatedAt]
FROM [dbo].[Blogs] AS [Extent1]
WHERE [Extent1].[Id] > 100
ORDER BY [Extent1].[Id] DESC

22
投票

在最新版本的 EF Core 5 ToQueryString

query.ToQueryString()

15
投票

获得命令后,您可以打印 CommandText,然后循环遍历参数集合并打印所有单独的参数。

还有 linq-to-sql 调试可视化工具,它在调试模式下执行相同的操作。

查看正在发生的查询的一个非常好的工具是 Linq-to-sql 探查器


12
投票
(SqlCommand)dataContext.GetCommand(query)

您可以访问参数集合。


7
投票

我正在使用

Datacontext.Log
属性来获取生成的 SQL 语句 (它包括语句文本和参数)。

只需设置

YourDataContext.Log = SomeTextWriter

它可以写入文件(

Log = new StreamWriter(@"c:\temp\linq.log")
)或调试窗口,参见这篇文章


1
投票

在 Locals 窗格中查看

IQueryable
时,您可以访问 DebugView > Query,其中将包含 SQL 语句。


0
投票

对于任何尝试将 IQueryable linq 查询转换为其 TSQL 表示形式的人,您可以使用以下内容,这是针对 EntityFramework 6 的。

解决方案由Entity Framework - IQueryable to DataTable提供的以下解决方案组成,以及Entity Framework如何管理将查询结果映射到匿名类型?上面由HydroPowerDeveloper指出。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Core.Objects;
using System.Data.SqlClient;
using System.Reflection;
using System.Text.RegularExpressions;


public class DbQuerySQL
{
    public string TSQL { get; } // Use readonly property
    public List<SqlParameter> SqlParameters { get; } // Use readonly property

    public DbQuerySQL(string tsql, List<SqlParameter> sqlParameters)
    {
        TSQL = tsql;
        SqlParameters = sqlParameters;
    }
}

public static class DbQueryExtensions
{
    /// <summary>
    /// Converts an IQueryable<T> query to a SQL string representation along with its parameters.
    /// </summary>
    /// <typeparam name="T">The type of elements in the query.</typeparam>
    /// <param name="query">The IQueryable<T> query to convert.</param>
    /// <returns>A DbQuerySQL object containing the SQL string representation and its parameters.</returns>
    public static DbQuerySQL ToDbQuerySQL<T>(this IQueryable<T> query)
    {
        // Retrieve the ObjectQuery<T> from the IQueryable<T> query
        var objectQuery = (query as DbQuery<T>).GetObjectQuery();

        // Get the SQL string representation of the query
        var sqlTrace = objectQuery.ToTraceString();

        // Transform the SQL string to include mapped column names and store it in finalSql
        var finalSql = GetFinalSqlFromTrace(sqlTrace, objectQuery);

        // Extract parameters from the ObjectQuery<T> and convert them to SqlParameters
        var sqlParameters = objectQuery.Parameters
            .Select(parameter => new SqlParameter("@" + parameter.Name, parameter.Value))
            .ToList();

        // Return a DbQuerySQL object containing the final SQL string and its parameters
        return new DbQuerySQL(finalSql, sqlParameters);
    }

    /// <summary>
    /// Modifies the SQL trace string to replace column names with their corresponding entity properties.
    /// </summary>
    /// <typeparam name="T">The type of elements in the ObjectQuery.</typeparam>
    /// <param name="sqlTrace">The SQL trace string to modify.</param>
    /// <param name="objectQuery">The ObjectQuery representing the SQL query.</param>
    /// <returns>The modified SQL trace string with replaced column names.</returns>
    private static string GetFinalSqlFromTrace<T>(string sqlTrace, ObjectQuery<T> objectQuery)
    {
        // Map the column names to entity properties
        var columnMappings = GetPropertyPositions(objectQuery);

        // Define a regular expression to capture the entire SELECT clause, including its columns, from a multiline SQL query string.
        var regex = new Regex("^(SELECT(?<columns>.*?))(FROM)", RegexOptions.Singleline);

        // Apply the regular expression to the SQL trace
        Match match = regex.Match(sqlTrace);

        // Check if the regular expression matched successfully
        if (match.Success)
        {
            // Get the captured columns group
            var cols = match.Groups["columns"];
            if (cols.Success)
            {
                // Split the captured columns by comma to extract each column name
                const string As = " AS ";
                var colNames = cols.Value.Split(',');

                // Construct the new SELECT clause with replaced columns
                var newSelectClause = new StringBuilder();
                foreach (var colName in colNames)
                {
                    // Get the corresponding column mapping and column name
                    var index = Array.IndexOf(colNames, colName);
                    if (index < columnMappings.Length)
                    {
                        var columnMapping = columnMappings[index];
                        var colNameAs = colName.Substring(colName.IndexOf(As, StringComparison.InvariantCulture) + As.Length);
                        var newCol = colName.Replace($"{As}{colNameAs}", $"{As}[{columnMapping}]");
                        newSelectClause.Append(newCol);

                        if (index < colNames.Length - 1)
                        {
                            newSelectClause.Append(", ");
                        }
                    }
                }

                // Replace only the columns within the original SELECT clause
                sqlTrace = sqlTrace.Replace(cols.Value, newSelectClause.ToString());
            }
        }
        return sqlTrace;
    }

    /// <summary>
    /// Retrieves the ObjectQuery<T> from the provided DbQuery<T>.
    /// </summary>
    /// <typeparam name="T">The type of elements in the query.</typeparam>
    /// <param name="query">The DbQuery<T> instance from which to retrieve the ObjectQuery<T>.</param>
    /// <returns>The ObjectQuery<T> instance corresponding to the DbQuery<T>.</returns>
    private static ObjectQuery<T> GetObjectQuery<T>(this DbQuery<T> query)
    {
        // Get the _internalQuery field from the DbQuery<T> using reflection
        var internalQueryField = query.GetType().GetField("_internalQuery", BindingFlags.NonPublic | BindingFlags.Instance);
        // Retrieve the value of the _internalQuery field
        var internalQuery = internalQueryField.GetValue(query);

        // Get the _objectQuery field from the internal query object using reflection
        var objectQueryField = internalQuery.GetType().GetField("_objectQuery", BindingFlags.NonPublic | BindingFlags.Instance);
        // Retrieve the value of the _objectQuery field, which should be an ObjectQuery<T>
        return (ObjectQuery<T>)objectQueryField.GetValue(internalQuery);
    }

    /// <summary>
    /// Retrieves the property positions (column names) from the ObjectQuery's column mappings.
    /// </summary>
    /// <param name="query">The ObjectQuery from which to retrieve the property positions.</param>
    /// <returns>An array of strings representing the property positions (column names).</returns>
    private static string[] GetPropertyPositions(this ObjectQuery query)
    {
        // Retrieve the QueryState object from the ObjectQuery using reflection
        object queryState = GetProperty(query, "QueryState");
        //// Ensure that the retrieved object is of the expected type
        //AssertNonNullAndOfType(queryState, "System.Data.Entity.Core.Objects.ELinq.ELinqQueryState");

        // Retrieve the cached query execution plan from the QueryState object
        object plan = GetField(queryState, "_cachedPlan");
        //// Ensure that the retrieved object is of the expected type
        //AssertNonNullAndOfType(plan, "System.Data.Entity.Core.Objects.Internal.ObjectQueryExecutionPlan");

        // Retrieve the command definition from the query execution plan
        object commandDefinition = GetField(plan, "CommandDefinition");
        //// Ensure that the retrieved object is of the expected type
        //AssertNonNullAndOfType(commandDefinition, "System.Data.Entity.Core.EntityClient.Internal.EntityCommandDefinition");

        // Retrieve the column map generators from the command definition
        var columnMapGeneratorArray = GetField(commandDefinition, "_columnMapGenerators") as object[];
        //// Ensure that the retrieved object is of the expected type
        //AssertNonNullAndOfType(columnMapGeneratorArray, "System.Data.Entity.Core.EntityClient.Internal.EntityCommandDefinition+IColumnMapGenerator[]");

        var columnMapGenerator = columnMapGeneratorArray[0];

        // Retrieve the column map from the column map generator
        object columnMap = GetField(columnMapGenerator, "_columnMap");
        //// Ensure that the retrieved object is of the expected type
        //AssertNonNullAndOfType(columnMap, "System.Data.Entity.Core.Query.InternalTrees.SimpleCollectionColumnMap");

        // Retrieve the element from the column map
        object columnMapElement = GetProperty(columnMap, "Element");
        //// Ensure that the retrieved object is of the expected type
        //AssertNonNullAndOfType(columnMapElement, "System.Data.Entity.Core.Query.InternalTrees.RecordColumnMap");

        // Retrieve the properties (column maps) from the element
        Array columnMapProperties = GetProperty(columnMapElement, "Properties") as Array;
        //// Ensure that the retrieved object is of the expected type
        //AssertNonNullAndOfType(columnMapProperties, "System.Data.Entity.Core.Query.InternalTrees.ColumnMap[]");

        // Determine the number of properties (columns)
        int n = columnMapProperties.Length;
        string[] propertyPositions = new string[n];

        // Iterate through the properties (columns) to retrieve their names and positions
        for (int i = 0; i < n; ++i)
        {
            // Retrieve the column map at index i
            object column = columnMapProperties.GetValue(i);
            //// Ensure that the retrieved object is of the expected type
            //AssertNonNullAndOfType(column, "System.Data.Entity.Core.Query.InternalTrees.ScalarColumnMap");

            // Retrieve the name of the column
            string colName = (string)GetProperty(column, "Name");

            // Retrieve the position of the column
            object columnPositionOfAProperty = GetProperty(column, "ColumnPos");
            //// Ensure that the retrieved object is of the expected type
            //AssertNonNullAndOfType(columnPositionOfAProperty, "System.Int32");

            // Store the column name at its corresponding position in the array
            propertyPositions[(int)columnPositionOfAProperty] = colName;
        }

        return propertyPositions;
    }

    /// <summary>
    /// Retrieves the value of a non-public instance property from an object using reflection.
    /// </summary>
    /// <param name="obj">The object from which to retrieve the property value.</param>
    /// <param name="propName">The name of the property to retrieve.</param>
    /// <returns>The value of the specified property.</returns>
    private static object GetProperty(object obj, string propName)
    {
        // Get the PropertyInfo object for the specified property name
        PropertyInfo prop = obj.GetType().GetProperty(propName, BindingFlags.NonPublic | BindingFlags.Instance);
        // If the property does not exist, throw an exception indicating that the Entity Framework internals have changed
        if (prop == null) throw EFChangedException();
        // Retrieve and return the value of the property from the object
        return prop.GetValue(obj, new object[0]);
    }

    /// <summary>
    /// Retrieves the value of a non-public instance field from an object using reflection.
    /// </summary>
    /// <param name="obj">The object from which to retrieve the field value.</param>
    /// <param name="fieldName">The name of the field to retrieve.</param>
    /// <returns>The value of the specified field.</returns>
    private static object GetField(object obj, string fieldName)
    {
        // Get the FieldInfo object for the specified field name
        FieldInfo field = obj.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
        // If the field does not exist, throw an exception indicating that the Entity Framework internals have changed
        if (field == null) throw EFChangedException();
        // Retrieve and return the value of the field from the object
        return field.GetValue(obj);
    }

    /// <summary>
    /// Asserts that the specified object is not null and is of the expected type.
    /// </summary>
    /// <param name="obj">The object to assert.</param>
    /// <param name="fullName">The full name of the expected type.</param>
    private static void AssertNonNullAndOfType(object obj, string fullName)
    {
        // If the object is null, throw an exception indicating that the Entity Framework internals have changed
        if (obj == null) throw EFChangedException();
        // Retrieve the full name of the object's type
        string typeFullName = obj.GetType().FullName;
        // If the type does not match the expected type, throw an exception indicating that the Entity Framework internals have changed
        if (typeFullName != fullName) throw EFChangedException();
    }

    /// <summary>
    /// Constructs and returns an InvalidOperationException indicating that the Entity Framework internals have changed.
    /// </summary>
    /// <returns>An InvalidOperationException indicating that the Entity Framework internals have changed.</returns>
    private static InvalidOperationException EFChangedException()
    {
        // Create and return an InvalidOperationException with an appropriate error message
        return new InvalidOperationException("Entity Framework internals have changed, please review and fix reflection code");
    }
}

方法调用示例:

using (MyDbEntities dataContext = new MyDbEntities())
{
    int someTableAId = 1234;
    var query = dataContext.SomeTableA.AsNoTracking().Where(ps => ps.SomeTableAId == someTableAId)
       .Join(dataContext.SomeTableB.AsNoTracking(), ps => ps.SomeTableBId, sta => sta.SomeTableBId, (someTableA, someTableB) => new { someTableA, someTableB })
       .Select(s => new MySpecialSummary // using linq projection into a class that is not part of the database
       {
           SomeTableBId = s.someTableB.SomeTableBId,
           SomeFlag = s.someTableB.IsSomeFlag,
           SomeTableCode = s.someTableB.IdentificationCode,
           SomeTableAliases = dataContext.Fn_GetAliases(s.someTableB.SomeTableBId).Select(tvFn => tvFn.AliasNames).FirstOrDefault(), // database table function call
           SomeTableCombo = s.someTableA.ComboName,
           SomeTableName = s.someTableB.SomeTableName
       });

    DbQuerySQL dbQuery = query.ToDbQuerySQL(); // Get the TSQL code and sql parameters 

    var locations = query.ToList();
}
© www.soinside.com 2019 - 2024. All rights reserved.