如何在我的程序中记录从 DbContext.SaveChanges() 生成的 SQL? [复制]

Posted

技术标签:

【中文标题】如何在我的程序中记录从 DbContext.SaveChanges() 生成的 SQL? [复制]【英文标题】:How can I log the generated SQL from DbContext.SaveChanges() in my Program? [duplicate] 【发布时间】:2013-05-28 15:45:12 【问题描述】:

根据this线程,我们可以通过EF记录生成的SQL,但是DbContext.SaveChanges()呢?有没有任何简单的方法可以在没有任何额外框架的情况下完成这项工作?

【问题讨论】:

你想做这样的事情吗? ***.com/questions/11922552/… 不,我想记录 EF 生成的 SQl 语句 查看此链接:jkowalski.com/2010/04/23/… 【参考方案1】:

在实体框架 6.0 中,Database 类有一个属性Action<string> Log。因此设置日志记录非常简单:

context.Database.Log = Console.WriteLine;

如需更多高级需求,您可以设置interceptor。

【讨论】:

或者放到 DbContext ctor 'Database.Log = s => Debug.WriteLine(s);' Public Sub New() Database.Log = Sub(s) Debug.WriteLine(s) End Sub End Sub 检查了所有的答案,没有看到;我们如何在 EF Core 中做到这一点? 对于Serilog 用户来说同样简单,例如:context.Database.Log = Log.Logger.Verbose;(替换您的Serilog.ILogger 实例)。 @ScottFraley 请查看有关 EF Core 的单独问题:***.com/questions/56310854/…【参考方案2】:

见http://www.codeproject.com/Articles/499902/Profiling-Entity-Framework-5-in-code。我使用 Code First、POCO DbContext、Entity Framework 5 在 asp.net mvc 应用程序中实现了 Cook 先生的想法。

应用程序的上下文类派生自 DbContext:

public class MyDbContext : DbContext

上下文的构造函数连接了 SavingChanges 事件(我只想为调试构建做昂贵的反射):

public MyDbContext(): base("MyDbContext")

#if DEBUG
    ((IObjectContextAdapter)this).ObjectContext.SavingChanges += new EventHandler(objContext_SavingChanges);
#endif

保存更改事件将生成的 sql 写入输出窗口。我从库克先生那里复制的代码将 DbParameter 转换为 SqlParamter,我将其保留原样,因为我正在访问 Sql Server,但我假设如果您正在访问其他类型的数据库,转换将失败。

public void objContext_SavingChanges(object sender, EventArgs e)
    
        var commandText = new StringBuilder();

        var conn = sender.GetType()
             .GetProperties(BindingFlags.Public | BindingFlags.Instance)
             .Where(p => p.Name == "Connection")
             .Select(p => p.GetValue(sender, null))
             .SingleOrDefault();
        var entityConn = (EntityConnection)conn;

        var objStateManager = (ObjectStateManager)sender.GetType()
              .GetProperty("ObjectStateManager", BindingFlags.Instance | BindingFlags.Public)
              .GetValue(sender, null);

        var workspace = entityConn.GetMetadataWorkspace();

        var translatorT =
            sender.GetType().Assembly.GetType("System.Data.Mapping.Update.Internal.UpdateTranslator");

        var translator = Activator.CreateInstance(translatorT, BindingFlags.Instance |
            BindingFlags.NonPublic, null, new object[] objStateManager,workspace,
            entityConn,entityConn.ConnectionTimeout , CultureInfo.InvariantCulture);

        var produceCommands = translator.GetType().GetMethod(
            "ProduceCommands", BindingFlags.NonPublic | BindingFlags.Instance);

        var commands = (IEnumerable<object>)produceCommands.Invoke(translator, null);

        foreach (var cmd in commands)
        
            var identifierValues = new Dictionary<int, object>();
            var dcmd =
                (DbCommand)cmd.GetType()
                   .GetMethod("CreateCommand", BindingFlags.Instance | BindingFlags.NonPublic)
                   .Invoke(cmd, new[]  translator, identifierValues );

            foreach (DbParameter param in dcmd.Parameters)
            
                var sqlParam = (SqlParameter)param;

                commandText.AppendLine(String.Format("declare 0 1 2",
                                                        sqlParam.ParameterName,
                                                        sqlParam.SqlDbType.ToString().ToLower(),
                                                        sqlParam.Size > 0 ? "(" + sqlParam.Size + ")" : ""));

                commandText.AppendLine(String.Format("set 0 = '1'", sqlParam.ParameterName, sqlParam.SqlValue));
            

            commandText.AppendLine();
            commandText.AppendLine(dcmd.CommandText);
            commandText.AppendLine("go");
            commandText.AppendLine();
        

        System.Diagnostics.Debug.Write(commandText.ToString());
    

【讨论】:

这段代码有一个错误,sqlParam.SqlValue 没有被转义,所以任何带有单引号的值都会生成无效的 SQL.. 并且还会让你对 SQL 注入开放。跨度> 我不同意你使用“bug”这个词@LachlanB,但是你是对的,它不会生成完美的 sql,必须编辑 sql。或者您可以添加更多逻辑并获得完美的 sql。 怎么不是bug?它在某些条件下生成无效的 SQL,并试图生成有效的 SQL,因此无论从哪个词的定义来看,它都是一个错误。 它不是试图生成有效的 SQL,而是试图向软件工程师展示 Entity Framework 5 生成的 sql,以便该工程师可以调试问题。请注意#DEBUG 预处理器指令,并将文本写入IDE 输出窗口。文本必须手动复制并粘贴到 SSMS 或类似的东西中,并且在某些情况下,开发人员需要在执行之前编辑 sq。这不是错误,这只是代码的设计目的。 好吧,这无可争辩,我将它用于不同的目的。顺便说一句,还有另一个问题(不是错误),对于 nvarchar(max) 数据类型,sqlParam.Size 为 -1,生成的 SQL 会将您的文本截断为一个字符。【参考方案3】:

对于短期日志,我只是放入 DbContext 构造函数:

Database.Log = x => Debug.WriteLine(x);

添加/删除 SQL 日志记录非常快。对于长期使用,可以用支票包裹

#IFDEF DEBUG // or something similar

【讨论】:

【参考方案4】:

如果您想使用拦截器捕获使用 EF6 生成的实际 SQL(可能稍后回放),您可以执行以下操作。

创建你的拦截器

public class InsertUpdateInterceptor : IDbCommandInterceptor

    public virtual void NonQueryExecuting(
        DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
    
        logCommand(command);
    

    public virtual void ReaderExecuting(
        DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    
        // this will capture all SELECT queries if you care about them..
        // however it also captures INSERT statements as well 
        logCommand(command);
    

    public virtual void ScalarExecuting(
     DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
    
        logCommand(command);
    


    private void logCommand(DbCommand dbCommand)
    
        StringBuilder commandText = new StringBuilder();

        commandText.AppendLine("-- New statement generated: " + System.DateTime.Now.ToString());
        commandText.AppendLine();

        // as the command has a bunch of parameters, we need to declare
        // those parameters here so the SQL will execute properly

        foreach (DbParameter param in dbCommand.Parameters)
        
            var sqlParam = (SqlParameter)param;

            commandText.AppendLine(String.Format("DECLARE 0 1 2",
                                                    sqlParam.ParameterName,
                                                    sqlParam.SqlDbType.ToString().ToLower(),
                                                    getSqlDataTypeSize(sqlParam));

            var escapedValue = sqlParam.SqlValue.replace("'", "''");
            commandText.AppendLine(String.Format("SET 0 = '1'", sqlParam.ParameterName, escapedValue ));
            commandText.AppendLine();
        

        commandText.AppendLine(dbCommand.CommandText);
        commandText.AppendLine("GO");
        commandText.AppendLine();
        commandText.AppendLine();

        System.IO.File.AppendAllText("outputfile.sql", commandText.ToString());
    

    private string getSqlDataTypeSize(SqlParameter param)
    
        if (param.Size == 0)
        
            return "";
        

        if (param.Size == -1)
        
            return "(MAX)";
        

        return "(" + param.Size + ")";
    


    // To implement the IDbCommandInterceptor interface you need to also implement these methods like so

    public void NonQueryExecuted(
        DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
    
    

    public void ReaderExecuted(
        DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    
    

    public void ScalarExecuted(
        DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
    
    

你还需要注册你的拦截器。如果您在 ASP.NET 应用程序中执行此操作,请确保您只执行一次,否则您最终会多次拦截同一个请求。

DAO 示例

public class MyDataDAO

    private static bool isDbInterceptionInitialised = false;

    public MyDataDAO()
    
        if (!isDbInterceptionInitialised)
        
            DbInterception.Add(new InsertUpdateInterceptor());
            isDbInterceptionInitialised = true;
        
    

    public void Insert(string dataToInsert)
    
        using (myentities context = new myentities())
        
            MyData myData = new MyData();
            myData.data = dataToInsert;

            // this will trigger the interceptor
            context.SaveChanges();
        
    

【讨论】:

NLogCommandInterceptor for EF6 blog.oneunicorn.com/2013/05/14/… ,任何 SerilogCommandInterceptor 使用 Serilog? 我觉得你的例子很棒,谢谢!但是它似乎不起作用,因为 Object SqlParameter 的属性 SqlValue 是 Object 类型,所以它不知道“替换”方法。 sqlParam.SqlValue.replace("'", "''");【参考方案5】:

这做同样的事情,但每次使用上下文时,它都会在输出窗口中写入 sql 查询。不同的是它不会在 release 中编译。

public MyEntitities()
    : base()

    Database.Log = s => System.Diagnostics.Trace.WriteLine(s);

This *** Explains the Difference between Trace and Debug.

【讨论】:

【参考方案6】:

您可以使用 SQL Server Profiler 并针对您要连接的数据库服务器运行它。

【讨论】:

虽然这是一种记录 SQL 的方法,但它是最后的手段。它需要服务器上的特殊权限,不与应用程序日志集成,记录的内容超出您的搜索范围,并且无法将您的 EF 语句与生成的 SQL 关联。【参考方案7】:

为 EF6 更新了 Tom Regan 的代码。

    public void objContext_SavingChanges(object sender, EventArgs e)
    
        var commandText = new StringBuilder();

        var conn = sender.GetType()
             .GetProperties(BindingFlags.Public | BindingFlags.Instance)
             .Where(p => p.Name == "Connection")
             .Select(p => p.GetValue(sender, null))
             .SingleOrDefault();
        var entityConn = (EntityConnection)conn;

        var objStateManager = (System.Data.Entity.Core.Objects.ObjectStateManager)sender.GetType()
              .GetProperty("ObjectStateManager", BindingFlags.Instance | BindingFlags.Public)
              .GetValue(sender, null);

        var workspace = entityConn.GetMetadataWorkspace();

        var translatorT =
            sender.GetType().Assembly.GetType("System.Data.Entity.Core.Mapping.Update.Internal.UpdateTranslator");

        var entityAdapterT =
            sender.GetType().Assembly.GetType("System.Data.Entity.Core.EntityClient.Internal.EntityAdapter");
        var entityAdapter = Activator.CreateInstance(entityAdapterT, BindingFlags.Instance |
            BindingFlags.NonPublic | BindingFlags.Public, null, new object[]  sender , System.Globalization.CultureInfo.InvariantCulture);

        entityAdapterT.GetProperty("Connection").SetValue(entityAdapter, entityConn);

        var translator = Activator.CreateInstance(translatorT, BindingFlags.Instance |
            BindingFlags.NonPublic | BindingFlags.Public, null, new object[]  entityAdapter , System.Globalization.CultureInfo.InvariantCulture);

        var produceCommands = translator.GetType().GetMethod(
            "ProduceCommands", BindingFlags.NonPublic | BindingFlags.Instance);

        var commands = (IEnumerable<object>)produceCommands.Invoke(translator, null);

        foreach (var cmd in commands)
        
            var identifierValues = new Dictionary<int, object>();
            var dcmd =
                (System.Data.Common.DbCommand)cmd.GetType()
                   .GetMethod("CreateCommand", BindingFlags.Instance | BindingFlags.NonPublic)
                   .Invoke(cmd, new[]  identifierValues );

            foreach (System.Data.Common.DbParameter param in dcmd.Parameters)
            
                var sqlParam = (SqlParameter)param;

                commandText.AppendLine(String.Format("declare 0 1 2",
                                                        sqlParam.ParameterName,
                                                        sqlParam.SqlDbType.ToString().ToLower(),
                                                        sqlParam.Size > 0 ? "(" + sqlParam.Size + ")" : ""));

                commandText.AppendLine(String.Format("set 0 = '1'", sqlParam.ParameterName, sqlParam.SqlValue));
            

            commandText.AppendLine();
            commandText.AppendLine(dcmd.CommandText);
            commandText.AppendLine("go");
            commandText.AppendLine();
        

        System.Diagnostics.Debug.Write(commandText.ToString());
    

【讨论】:

【参考方案8】:

这应该会有所帮助,EFTracingProvider

http://code.msdn.microsoft.com/EFProviderWrappers

【讨论】:

还有什么解决方案可以和DBContext一起工作吗? efwrappers.codeplex.com/discussions/262707

以上是关于如何在我的程序中记录从 DbContext.SaveChanges() 生成的 SQL? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

如何记录在Android中打开相机的历史记录

Syslog:在我的 PHP 应用程序中使用 syslog 记录错误的配置是啥

多线程在我的 c# 程序中执行比顺序执行需要更多时间

如何使用 Swift 在 iOS 中显示从 HealthKit 中检索到的 HKDocumentSample?

如何从图库中隐藏照片并将其存储在我的应用程序中

从iOS的电话记录中拨打voip电话