91 votes

Comment puis-je enregistrer le SQL généré par DbContext.SaveChanges() dans mon programme ?

Selon ce nous pouvons enregistrer les données générées par SQL via EF mais qu'en est-il de DbContext.SaveChanges() ? Existe-t-il un moyen simple de faire ce travail sans frameworks supplémentaires ?

141voto

Lorentz Vedeler Points 1068

Dans le cadre de l'entity framework 6.0, la classe Database possède une propriété Action<string> Log . de sorte que la configuration de la journalisation est aussi simple que :

context.Database.Log = Console.WriteLine;

Pour des besoins plus avancés, vous pouvez mettre en place un intercepteur .

15voto

Tom Regan Points 422

Voir http://www.codeproject.com/Articles/499902/Profiling-Entity-Framework-5-in-code . J'ai implémenté l'idée de M. Cook dans une application asp.net mvc en utilisant un Code First, POCO DbContext, Entity Framework 5.

La classe de contexte de l'application dérive de DbContext :

public class MyDbContext : DbContext

Le constructeur du contexte accroche l'événement SavingChanges (je ne veux faire la réflexion coûteuse que pour les constructions de débogage) :

public MyDbContext(): base("MyDbContext")
{
#if DEBUG
    ((IObjectContextAdapter)this).ObjectContext.SavingChanges += new EventHandler(objContext_SavingChanges);
#endif
}

L'événement de sauvegarde des modifications écrit le sql généré dans la fenêtre de sortie. Le code que j'ai copié de M. Cook convertit le DbParameter en SqlParamter, que je laisse tel quel parce que je frappe un Sql Server, mais je suppose que cette conversion échouerait si vous frappez un autre type de base de données.

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());
    }

12voto

TPAKTOPA Points 190

Pour la journalisation à court terme, j'ai juste mis dans le constructeur de DbContext :

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

Assez rapide pour ajouter/supprimer la journalisation de SQL. Pour une utilisation à long terme, peut être enveloppé dans des vérifications avec

#IFDEF DEBUG // or something similar

8voto

LachlanB Points 1703

Si vous voulez capturer le SQL réel qui a été généré en utilisant EF6 (peut-être pour le lire plus tard) en utilisant un intercepteur, vous pouvez faire ce qui suit.

Créez votre intercepteur

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

Et vous devez aussi enregistrer votre intercepteur. Si vous faites cela dans une application ASP.NET, assurez-vous de ne le faire qu'une seule fois, sinon vous finirez par intercepter la même requête plusieurs fois.

Exemple de 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();
        }
    }
}

4voto

Travis Peterson Points 132

Cela fait la même chose, mais pour chaque fois que vous utilisez votre contexte, il écrira la requête sql dans la fenêtre de sortie. La différence est qu'il ne compile pas dans la version.

public MyEntitities()
    : base()
{
    Database.Log = s => System.Diagnostics.Trace.WriteLine(s);
}

Ce StackOverflow explique la différence entre Trace et Debug.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X