有没有办法使用Dapper跟踪/记录SQL?

60

有没有一种方法可以将生成的SQL转储到Debug日志或其他地方?我正在使用它在WinForms解决方案中,所以Mini-Profiler想法对我不适用。

6个回答

36

我遇到了同样的问题,在做一些搜索后实现了一些代码,但没有现成的东西可用。有一个Nuget包MiniProfiler.Integrations,我想分享一下。

更新V2: 它支持与其他数据库服务器一起使用,对于MySQL,需要安装MiniProfiler.Integrations.MySql

以下是与SQL Server一起使用的步骤:

1. 实例化连接

var factory = new SqlServerDbConnectionFactory(_connectionString);
using (var connection = ProfiledDbConnectionFactory.New(factory, CustomDbProfiler.Current))
{
 // your code
}

2.所有工作完成后,如果需要,将所有命令写入文件

File.WriteAllText("SqlScripts.txt", CustomDbProfiler.Current.ProfilerContext.BuildCommands());

太棒了。您可能希望将项目站点更改为NuGet中的GitHub,目前似乎指向自身(或先前版本)。 - Mladen Mihajlovic
@hazjack - 抱歉!我刚意识到打错字了。我的意思是“太糟糕了,MiniProfiler不支持MySql”。因为你的解决方案只适用于Sql Server。 - arviman
1
这个例子中是 System.Data.Entity.Infrastructure.SqlConnectionFactory 吗?我找不到 "New" 扩展方法或另一个 SqlConnectionFactory 的实现。这个例子是特定于 MiniProfiler.Integrations 的某个版本吗? - SturmUndDrang
4
MySQL使用起来非常顺畅,我在10分钟内解决了我的问题。改用CustomDbProfiler.Current.ProfilerContext.GetCommands()。谢谢! - Michael K
3
这个回答适用于Net Core吗?我找不到CustomDbProfiler.Current.ProfilerContext - dani herrera
显示剩余6条评论

18

Dapper目前在这里没有仪表点。这可能是因为,正如您所指出的那样,我们(作为作者)使用mini-profiler来处理此问题。但是,如果有帮助的话,mini-profiler的核心部分实际上被设计为架构中立的,我知道其他人也在winforms、wpf、wcf等上使用它——这将使您可以访问性能剖析/跟踪连接包装器。

理论上,完全可以添加一些通用的捕获点,但我担心以下两点:

  • (主要是)安全:由于Dapper没有上下文的概念,恶意代码很容易悄悄地附加以截取通过Dapper发送的所有SQL流量;我真的不喜欢听起来的那个(这不是“装饰器”方法的问题,因为调用者拥有连接,因此具有日志记录上下文)
  • (次要的)性能:但…事实上,很难说一个简单的委托检查(在大多数情况下可能会为空)会产生多大的影响

当然,您还可以做的另一件事是:从mini-profiler中窃取连接包装器代码,并用Debug.WriteLine等替换性能剖析上下文相关的代码。


6
马克,你能举个你担心的攻击类型的例子吗? - MatteoSp

9

您应该考虑使用位于SQL Management Studio菜单中的SQL Profiler → Extras → SQL Server Profiler(不需要Dapper扩展 - 在其他关系型数据库管理系统也可以使用)。

然后,开始一个新会话。

例如,您将看到如下内容(您将看到所有参数和完整的SQL语句):

exec sp_executesql N'SELECT * FROM Updates WHERE CAST(Product_ID as VARCHAR(50)) = @appId AND (Blocked IS NULL OR Blocked = 0) 
                    AND (Beta IS NULL OR Beta = 0 OR @includeBeta = 1) AND (LangCode IS NULL OR LangCode IN (SELECT * FROM STRING_SPLIT(@langCode, '','')))',N'@appId nvarchar(4000),@includeBeta bit,@langCode nvarchar(4000)',@appId=N'fea5b0a7-1da6-4394-b8c8-05e7cb979161',@includeBeta=0,@langCode=N'de'

这种方法存在一些问题,1)如果数据库是常见的(例如在测试环境中有许多进程和用户),可能很难过滤出所需的查询。2)Azure Sql(以及可能的其他DBMS)不允许使用分析器工具连接。 - zameb
这种方法存在一些问题,1)如果数据库是一个常见的数据库(例如,在测试环境中有许多进程和用户),可能很难过滤出所需的查询。2)Azure Sql(以及可能的其他数据库管理系统)不允许使用分析工具进行连接。 - undefined
1
@zameb:您可以按客户端ID(应用程序名称)和其他属性进行过滤,没有问题。如果您已启用了TCP连接到SQL Server并在防火墙中启用了访问权限,则可以连接到Azure上的虚拟SQL Server实例! - Martin.Martinsson

7

试试Dapper.Logging

你可以从NuGet获取它。它的工作方式是将创建实际数据库连接的代码传递给一个工厂,该工厂创建包装连接。每当打开或关闭一个包装连接或对其运行查询时,它都将被记录在日志中。您可以配置日志消息模板和其他设置,例如是否保存SQL参数。经过时间也会被保存。

在我看来,唯一的缺点是文档稀少,但我认为这只是因为它是一个新项目(截至本篇写作)。我不得不在存储库中挖掘一段时间才能理解它,并将其配置为我所喜欢的样式,但现在它运行得很好。

文档描述如下:

The tool consists of simple decorators for the DbConnection and DbCommand which track the execution time and write messages to the ILogger<T>. The ILogger<T> can be handled by any logging framework (e.g. Serilog). The result is similar to the default EF Core logging behavior.

The lib declares a helper method for registering the IDbConnectionFactory in the IoC container. The connection factory is SQL Provider agnostic. That's why you have to specify the real factory method:

services.AddDbConnectionFactory(prv => new SqlConnection(conStr));

After registration, the IDbConnectionFactory can be injected into classes that need a SQL connection.

private readonly IDbConnectionFactory _connectionFactory;
public GetProductsHandler(IDbConnectionFactory connectionFactory)
{
    _connectionFactory = connectionFactory;
}

The IDbConnectionFactory.CreateConnection will return a decorated version that logs the activity.

using (DbConnection db = _connectionFactory.CreateConnection())
{
    //...
}

1
这个问题的“陷阱”在于,当你试图理解一个遗留代码库时,其中有成千上万个地方创建了SQL连接,并且每个连接都略有不同,因此没有可行的方法来进行更改...只有完全透明于现有代码(除了启动时的单个钩子初始化)的东西才是可行的。是的,我确实理解安全方面的顾虑... - David V. Corbin

6

这并不是详尽无遗的,而且本质上是一种小技巧,但如果您有SQL,并且想要初始化参数,那么它对于基本调试非常有用。设置这个扩展方法,然后在任何地方按需要调用它。

public static class DapperExtensions
{
    public static string ArgsAsSql(this DynamicParameters args)
    {
        if (args is null) throw new ArgumentNullException(nameof(args));
        var sb = new StringBuilder();
        foreach (var name in args.ParameterNames)
        {
            var pValue = args.Get<dynamic>(name);

            var type = pValue.GetType();

            if (type == typeof(DateTime))
            sb.AppendFormat("DECLARE @{0} DATETIME ='{1}'\n", name, pValue.ToString("yyyy-MM-dd HH:mm:ss.fff"));
            else if (type == typeof(bool))
                sb.AppendFormat("DECLARE @{0} BIT = {1}\n", name, (bool)pValue ? 1 : 0);
            else if (type == typeof(int))
                sb.AppendFormat("DECLARE @{0} INT = {1}\n", name, pValue);
            else if (type == typeof(List<int>))
                sb.AppendFormat("-- REPLACE @{0} IN SQL: ({1})\n", name, string.Join(",", (List<int>)pValue));
            else
                sb.AppendFormat("DECLARE @{0} NVARCHAR(MAX) = '{1}'\n", name, pValue.ToString());
        }
        return sb.ToString();
    }
}

你可以直接在即时窗口或监视窗口中使用此功能来获取SQL。

3

在这里补充一下,因为我看到这个问题仍然有很多点击量 - 现在我使用 Glimpse(似乎已经不更新了)或Stackify Prefix,它们都具有 SQL 命令跟踪功能。

虽然不完全符合我最初提出的问题,但可以解决相同的问题。


1
Glimpse还没有死吗? - Kiquenet
@Kiquenet 嗯,看起来是这样的 - 我已经很久没有使用它了,所以我从来没有注意到。这真是太遗憾了 - 它曾经非常好。 - Mladen Mihajlovic

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接