Débogage de la méthode de mise à jour de la base de données de la console du gestionnaire de packages

106

Je voulais déboguer la Seed()méthode dans ma classe de configuration de base de données Entity Framework lorsque j'exécute à Update-Databasepartir de la console du gestionnaire de package, mais je ne savais pas comment le faire. Je voulais partager la solution avec d'autres au cas où ils auraient le même problème.

Sachin Kainth
la source

Réponses:

158

Voici une question similaire avec une solution qui fonctionne vraiment bien.
Cela ne nécessite PAS Thread.Sleep.
Lance juste le débogueur en utilisant ce code.

Extrait de la réponse

if (!System.Diagnostics.Debugger.IsAttached) 
    System.Diagnostics.Debugger.Launch();
EthR
la source
@tchelidze, vous pouvez appeler migrate.exedepuis la console pour attacher le studio visuel en cours d'exécution. MOre info dans cette réponse: stackoverflow.com/a/52700520/350384
Mariusz Pawelski
20

La façon dont j'ai résolu ce problème était d'ouvrir une nouvelle instance de Visual Studio, puis d'ouvrir la même solution dans cette nouvelle instance de Visual Studio. J'ai ensuite attaché le débogueur de cette nouvelle instance à l'ancienne instance (devenv.exe) tout en exécutant la commande update-database. Cela m'a permis de déboguer la méthode Seed.

Juste pour m'assurer de ne pas manquer le point d'arrêt en ne me fixant pas à temps, j'ai ajouté un Thread.Sleep avant le point d'arrêt.

J'espère que ça aidera quelqu'un.

Sachin Kainth
la source
12

Si vous avez besoin d'obtenir la valeur d'une variable spécifique, un hack rapide consiste à lancer une exception:

throw new Exception(variable);
cederlof
la source
3
Rapide et sale :)
DanKodi
5

Une solution plus propre (je suppose que cela nécessite EF 6) serait à mon humble avis d'appeler update-database à partir du code:

var configuration = new DbMigrationsConfiguration<TContext>();
var databaseMigrator = new DbMigrator(configuration);
databaseMigrator.Update();

Cela vous permet de déboguer la méthode Seed.

Vous pouvez aller plus loin et construire un test unitaire (ou, plus précisément, un test d'intégration) qui crée une base de données de test vide, applique toutes les migrations EF, exécute la méthode Seed et supprime à nouveau la base de données de test:

var configuration = new DbMigrationsConfiguration<TContext>();
Database.Delete("TestDatabaseNameOrConnectionString");

var databaseMigrator = new DbMigrator(configuration);
databaseMigrator.Update();

Database.Delete("TestDatabaseNameOrConnectionString");

Mais veillez à ne pas l'exécuter sur votre base de données de développement!

Jesper Mygind
la source
1
Dans EF Core, car il n'y a pas de classe DbMigrationsConfiguration, utilisez plutôt myDbContext.Database.GetPendingMigrations ().
stevie_c
3

Je sais que c'est une vieille question, mais si tout ce que vous voulez, ce sont des messages, et que vous ne vous souciez pas d'inclure des références à WinForms dans votre projet, j'ai créé une fenêtre de débogage simple où je peux envoyer des événements Trace.

Pour un débogage plus sérieux et étape par étape, j'ouvrirai une autre instance de Visual Studio, mais ce n'est pas nécessaire pour des choses simples.

Voici tout le code:

SeedApplicationContext.cs

using System;
using System.Data.Entity;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;

namespace Data.Persistence.Migrations.SeedDebug
{
  public class SeedApplicationContext<T> : ApplicationContext
    where T : DbContext
  {
    private class SeedTraceListener : TraceListener
    {
      private readonly SeedApplicationContext<T> _appContext;

      public SeedTraceListener(SeedApplicationContext<T> appContext)
      {
        _appContext = appContext;
      }

      public override void Write(string message)
      {
        _appContext.WriteDebugText(message);
      }

      public override void WriteLine(string message)
      {
        _appContext.WriteDebugLine(message);
      }
    }

    private Form _debugForm;
    private TextBox _debugTextBox;
    private TraceListener _traceListener;

    private readonly Action<T> _seedAction;
    private readonly T _dbcontext;

    public Exception Exception { get; private set; }
    public bool WaitBeforeExit { get; private set; }

    public SeedApplicationContext(Action<T> seedAction, T dbcontext, bool waitBeforeExit = false)
    {
      _dbcontext = dbcontext;
      _seedAction = seedAction;
      WaitBeforeExit = waitBeforeExit;
      _traceListener = new SeedTraceListener(this);
      CreateDebugForm();
      MainForm = _debugForm;
      Trace.Listeners.Add(_traceListener);
    }

    private void CreateDebugForm()
    {
      var textbox = new TextBox {Multiline = true, Dock = DockStyle.Fill, ScrollBars = ScrollBars.Both, WordWrap = false};
      var form = new Form {Font = new Font(@"Lucida Console", 8), Text = "Seed Trace"};
      form.Controls.Add(tb);
      form.Shown += OnFormShown;
      _debugForm = form;
      _debugTextBox = textbox;
    }

    private void OnFormShown(object sender, EventArgs eventArgs)
    {
      WriteDebugLine("Initializing seed...");
      try
      {
        _seedAction(_dbcontext);
        if(!WaitBeforeExit)
          _debugForm.Close();
        else
          WriteDebugLine("Finished seed. Close this window to continue");
      }
      catch (Exception e)
      {
        Exception = e;
        var einner = e;
        while (einner != null)
        {
          WriteDebugLine(string.Format("[Exception {0}] {1}", einner.GetType(), einner.Message));
          WriteDebugLine(einner.StackTrace);
          einner = einner.InnerException;
          if (einner != null)
            WriteDebugLine("------- Inner Exception -------");
        }
      }
    }

    protected override void Dispose(bool disposing)
    {
      if (disposing && _traceListener != null)
      {
        Trace.Listeners.Remove(_traceListener);
        _traceListener.Dispose();
        _traceListener = null;
      }
      base.Dispose(disposing);
    }

    private void WriteDebugText(string message)
    {
      _debugTextBox.Text += message;
      Application.DoEvents();
    }

    private void WriteDebugLine(string message)
    {
      WriteDebugText(message + Environment.NewLine);
    }
  }
}

Et sur votre configuration standard.cs

// ...
using System.Windows.Forms;
using Data.Persistence.Migrations.SeedDebug;
// ...

namespace Data.Persistence.Migrations
{
  internal sealed class Configuration : DbMigrationsConfiguration<MyContext>
  {
    public Configuration()
    {
      // Migrations configuration here
    }

    protected override void Seed(MyContext context)
    {
      // Create our application context which will host our debug window and message loop
      var appContext = new SeedApplicationContext<MyContext>(SeedInternal, context, false);
      Application.Run(appContext);
      var e = appContext.Exception;
      Application.Exit();
      // Rethrow the exception to the package manager console
      if (e != null)
        throw e;
    }

    // Our original Seed method, now with Trace support!
    private void SeedInternal(MyContext context)
    {
      // ...
      Trace.WriteLine("I'm seeding!")
      // ...
    }
  }
}
Jcl
la source
1
Bien sûr, la fenêtre de débogage peut être aussi compliquée que vous le souhaitez (vous pouvez même utiliser le concepteur pour créer un formulaire complet et le faire circuler afin que la SeedInternalméthode puisse l'utiliser)
Jcl
1

Le débogage est une chose mais n'oubliez pas d'appeler: context.Update ()

N'enveloppez pas non plus try catch sans que de bonnes exceptions internes ne se répandent sur la console.
https://coderwall.com/p/fbcyaw/debug-into-entity-framework-code-first avec catch (DbEntityValidationException ex)

Steven Packham
la source
Veuillez vérifier cette URL, il sera utile d'améliorer la qualité de votre contenu
Willie Cheng
0

J'ai 2 solutions de contournement (sans Debugger.Launch()car cela ne fonctionne pas pour moi):

  1. Pour imprimer le message dans la console du gestionnaire de package, utilisez l'exception:
    throw new Exception("Your message");

  2. Une autre façon consiste à imprimer le message dans le fichier en créant un cmdprocessus:


    // Logs to file {solution folder}\seed.log data from Seed method (for DEBUG only)
    private void Log(string msg)
    {
        string echoCmd = $"/C echo {DateTime.Now} - {msg} >> seed.log";
        System.Diagnostics.Process.Start("cmd.exe", echoCmd);
    }
Gendolph
la source