В соответствии с этим потоком мы можем записать сгенерированный SQL
через EF
, но как насчет DbContext.SaveChanges()
? Есть ли простой способ выполнить эту работу без каких-либо дополнительных фреймворков?
Как я могу зарегистрировать сгенерированный SQL из DbContext.SaveChanges() в моей программе?
Ответ 1
В платформе сущностей 6.0 класс Database имеет свойство Action<string> Log
. так что настроить регистрацию так же просто, как:
context.Database.Log = Console.WriteLine;
Для более сложных задач вы можете настроить перехватчик.
Ответ 2
См. http://www.codeproject.com/Articles/499902/Profiling-Entity-Framework-5-in-code. Я реализовал идею г-на Кука в приложении mpc asp.net с использованием кода First, POCO DbContext, Entity Framework 5.
Класс контекста для приложения происходит из DbContext:
public class MyDbContext : DbContext
Конструктор контекста подключает событие SavingChanges (я хочу только сделать дорогостоящее отражение для отладочных сборников):
public MyDbContext(): base("MyDbContext")
{
#if DEBUG
((IObjectContextAdapter)this).ObjectContext.SavingChanges += new EventHandler(objContext_SavingChanges);
#endif
}
Событие смены сохранения записывает сгенерированный sql в окно вывода. Код, который я скопировал из Mr. Cook, преобразует 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());
}
Ответ 3
Для кратковременного ведения журнала я просто вставляю в конструктор DbContext:
Database.Log = x => Debug.WriteLine(x);
Довольно быстро добавлять/удалять ведение журнала SQL. Для долгосрочного использования можно обернуть в чеки с помощью
#IFDEF DEBUG // or something similar
Ответ 4
Если вы хотите захватить фактический SQL, который был сгенерирован с использованием EF6 (возможно, для воспроизведения позже) с использованием перехватчика, вы можете сделать следующее.
Создайте свой перехватчик
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();
}
}
}
Ответ 5
Это делает то же самое, но каждый раз, когда вы используете свой контекст, он будет писать sql-запрос в окне вывода. Разница в том, что он не компилируется в релизе.
public MyEntitities()
: base()
{
Database.Log = s => System.Diagnostics.Trace.WriteLine(s);
}
Ответ 6
Код Tom Regan обновлен для EF6.
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());
}
Ответ 7
Вы можете использовать Профайлер SQL Server и запускать его с сервером базы данных, к которому вы подключаетесь.
Ответ 8
Это должно помочь, EFTracingProvider