EF6拦截SQL并登录到数据库OutOfMemoryException

时间:2018-07-03 21:00:39

标签: c# entity-framework logging

我正在尝试记录已记录到表的SQL,但是我收到OutOfMemoryException。我知道为什么得到例外,但我不知道如何避免。

ApplicationDBContext:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser> {   
    public ApplicationDbContext() :
            base("IdentityDBContext", false) {
        DbInterception.Add(new InsertUpdateInterceptor());
    }
}

WebDataEntities:

   public WebDataEntities()
            : base("name=WebDataEntities")
        {
        }

日志类:

 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.ToString().Replace("'", "''");
                commandText.AppendLine(String.Format("SET {0} = '{1}'", sqlParam.ParameterName, escapedValue));
                commandText.AppendLine();
            }

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

            using(var context = new WebDataEntities()) {
                context.tbl_FSV_AspNetIdentity_SQL_Log.Add(new tbl_FSV_AspNetIdentity_SQL_Log { Sql = commandText.ToString() });
                context.SaveChanges();
            }

            //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) {
        }
    }

我收到异常,是因为当我尝试记录SQL时,它陷入了试图记录sql的循环中,而该日志应该将sql记录到DB中。但是我使用两个不同的上下文,所以为什么要尝试两次记录SQL?

2 个答案:

答案 0 :(得分:2)

关于EF拦截上下文的两个重要事实:

  • 这是一个全局静态单例
  • 它不是专用于一种上下文类型。

因此,一行...

DbInterception.Add(new InsertUpdateInterceptor());

每次创建新的InsertUpdateInterceptor 时,只需在拦截上下文中添加一个新的ApplicationDbContext实例,就会为您的应用程序中的每种上下文类型这样做。

这就是WebDataEntities中的任何命令都会导致无限循环的原因:它们自己记录。

解决方案是使用简单的ADO.Net代码(SqlCommand等)在命令拦截器或其他类型的ORM(例如LINQ-to-SQL,现在仍然如此)中将任何内容记录到数据库中尽管已过时,但仍受支持)。

DbInterception.Add(new InsertUpdateInterceptor())行应在应用程序启动时被调用一次。在ApplicationDbContext中执行它并不意味着它仅适用于该实例。这是一个静态调用,因此它不知道其环境。

答案 1 :(得分:0)

盖特钉牢了。我只是在第二次通读中注意到了这一点。要将拦截器添加到单个上下文中:

public ApplicationDbContext() :
            base("IdentityDBContext", false) 
{
    this.AddInterceptor(new InsertUpdateInterceptor());
}