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 ?
Réponses
Trop de publicités?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 .
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());
}
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
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();
}
}
}
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.
- Réponses précédentes
- Plus de réponses