Через 9 дней я придумала низкое трение, хотя и неидеальное решение, которое состоит из двух новых классов, унаследованных от SimpleDataLayer
и ThreadSafeDataLayer
:
ProfiledThreadSafeDataLayer.cs
using DevExpress.Xpo.DB;
using DevExpress.Xpo.Metadata;
using StackExchange.Profiling;
using System.Reflection;
namespace DevExpress.Xpo
{
public class ProfiledThreadSafeDataLayer : ThreadSafeDataLayer
{
public MiniProfiler Profiler { get { return MiniProfiler.Current; } }
public ProfiledThreadSafeDataLayer(XPDictionary dictionary, IDataStore provider, params Assembly[] persistentObjectsAssemblies)
: base(dictionary, provider, persistentObjectsAssemblies) { }
public override ModificationResult ModifyData(params ModificationStatement[] dmlStatements) {
if (Profiler != null) using (Profiler.CustomTiming("xpo", dmlStatements.ToSql(), nameof(ModifyData))) {
return base.ModifyData(dmlStatements);
}
return base.ModifyData(dmlStatements);
}
public override SelectedData SelectData(params SelectStatement[] selects) {
if (Profiler != null) using (Profiler.CustomTiming("xpo", selects.ToSql(), nameof(SelectData))) {
return base.SelectData(selects);
}
return base.SelectData(selects);
}
}
}
ProfiledDataLayer.cs
using DevExpress.Xpo.DB;
using DevExpress.Xpo.Metadata;
using StackExchange.Profiling;
namespace DevExpress.Xpo
{
public class ProfiledSimpleDataLayer : SimpleDataLayer
{
public MiniProfiler Profiler { get { return MiniProfiler.Current; } }
public ProfiledSimpleDataLayer(IDataStore provider) : this(null, provider) { }
public ProfiledSimpleDataLayer(XPDictionary dictionary, IDataStore provider) : base(dictionary, provider) { }
public override ModificationResult ModifyData(params ModificationStatement[] dmlStatements) {
if (Profiler != null) using (Profiler.CustomTiming("xpo", dmlStatements.ToSql(), nameof(ModifyData))) {
return base.ModifyData(dmlStatements);
}
return base.ModifyData(dmlStatements);
}
public override SelectedData SelectData(params SelectStatement[] selects) {
if (Profiler != null) using (Profiler.CustomTiming("xpo", selects.ToSql(), nameof(SelectData))) {
return base.SelectData(selects);
}
return base.SelectData(selects);
}
}
}
И методы расширения .ToSql()
:
using DevExpress.Xpo.DB;
using System.Data;
using System.Linq;
namespace DevExpress.Xpo
{
public static class StatementsExtensions
{
public static string ToSql(this SelectStatement[] selects) => string.Join("\r\n", selects.Select(s => s.ToString()));
public static string ToSql(this ModificationStatement[] dmls) => string.Join("\r\n", dmls.Select(s => s.ToString()));
}
}
1020 * USAGE *
Один из способов использования указанных выше слоев данных - установить свойство XpoDefault.DataLayer
при настройке XPO для вашего приложения:
XpoDefault.Session = null;
XPDictionary dict = new ReflectionDictionary();
IDataStore store = XpoDefault.GetConnectionProvider(connectionString, AutoCreateOption.SchemaAlreadyExists);
dict.GetDataStoreSchema(typeof(Some.Class).Assembly, typeof(Another.Class).Assembly);
// It's here that we setup the profiled data layer
IDataLayer dl = new ProfiledThreadSafeDataLayer(dict, store); // or ProfiledSimpleDataLayer if not an ASP.NET app
XpoDefault.DataLayer = dl;
РЕЗУЛЬТАТЫ
Теперь вы можете просматривать (некоторые из них - подробнее об этом позже) запросы базы данных XPO, аккуратно распределенные по категориям в пользовательском интерфейсе MiniProfiler:
![XPO queries inside MiniProfiler UI](https://i.stack.imgur.com/34ZJX.png)
С дополнительным преимуществом обнаружения повторяющихся вызовов следующим образом :-):
![MiniProfiler detecting duplicate XPO calls](https://i.stack.imgur.com/3BotL.png)
ЗАКЛЮЧИТЕЛЬНЫЕ МЫСЛИ
Я копаюсь вокруг этого уже 9 дней. Я изучил декомпилированный код XPO с помощью Telerik JustDecompile и перепробовал слишком много разных подходов для подачи данных профилирования из XPO в MiniProfiler с минимально возможным трением. Я попытался создать XPO Connection Provider , унаследованный от XPO MSSqlConnectionProvider
, и переопределить метод, который он использует для выполнения запросов, но отказался, поскольку этот метод не является виртуальным (на самом деле он закрытым), и я бы хотел должны скопировать весь исходный код для этого класса, который зависит от многих других исходных файлов из DevExpress. Затем я попытался написать потомок Xpo.Session
, чтобы переопределить все его методы манипулирования данными, отложив вызов метода базового класса Session
, окруженного вызовом MiniProfiler.CustomTiming
. К моему удивлению, ни один из этих вызовов не был виртуальным (класс UnitOfWork
, который наследуется от Session
, кажется более взломанным, чем правильный класс-потомок), поэтому я столкнулся с той же проблемой, с которой столкнулся при подходе с использованием провайдера соединений. Затем я попытался подключиться к другим частям фреймворка, даже к его собственному механизму трассировки. Это было плодотворно, в результате мы получили два аккуратных класса: XpoNLogLogger
и XpoConsoleLogger
, но в конечном итоге не позволили мне показать результаты внутри MiniProfiler , поскольку он предоставил уже профилированных и синхронизированных результатов который я не нашел способа включить / вставить в шаг MiniProfiler / выбор времени.
Показанное выше решение потомков уровня данных решает только часть проблемы. С одной стороны, он не регистрирует прямые вызовы SQL, вызовы хранимых процедур и методы Session, что может быть дорогостоящим (в конце концов, он даже не регистрирует гидратацию объектов, извлеченных из базы данных). XPO реализует два (возможно, три) различных механизма трассировки. Один протоколирует операторы и результаты SQL (количество строк, тайминги, параметры и т. Д.), Используя стандартную трассировку .NET, а другие методы сеанса журнала и операторы SQL (без результатов), используя класс DevExpress 'LogManager
. LogManager - единственный метод, который не считается устаревшим. Третий метод, который должен имитировать класс DataStoreLogger
, страдает теми же ограничениями нашего собственного подхода.
В идеале мы должны иметь возможность просто предоставить ProfiledDbConnection
любому объекту XPO Session
, чтобы получить все возможности профилирования SQL в MiniProfiler.
Я все еще исследую способ обернуть или унаследовать некоторые классы каркаса XPO, чтобы обеспечить более полный / лучший опыт профилирования с MiniProfiler для проектов на основе XPO. Я обновлю этот случай, если найду что-нибудь полезное.
Классы регистрации XPO
Исследуя это, я создал два очень полезных класса:
XpoNLogLogger.cs
using DevExpress.Xpo.Logger;
using NLog;
using System;
namespace Simpax.Xpo.Loggers
{
public class XpoNLogLogger: DevExpress.Xpo.Logger.ILogger
{
static Logger logger = NLog.LogManager.GetLogger("xpo");
public int Count => int.MaxValue;
public int LostMessageCount => 0;
public virtual bool IsServerActive => true;
public virtual bool Enabled { get; set; } = true;
public int Capacity => int.MaxValue;
public void ClearLog() { }
public virtual void Log(LogMessage message) {
logger.Debug(message.ToString());
}
public virtual void Log(LogMessage[] messages) {
if (!logger.IsDebugEnabled) return;
foreach (var m in messages)
Log(m);
}
}
}
XpoConsoleLogger.cs
using DevExpress.Xpo.Logger;
using System;
namespace Simpax.Xpo.Loggers
{
public class XpoConsoleLogger : DevExpress.Xpo.Logger.ILogger
{
public int Count => int.MaxValue;
public int LostMessageCount => 0;
public virtual bool IsServerActive => true;
public virtual bool Enabled { get; set; } = true;
public int Capacity => int.MaxValue;
public void ClearLog() { }
public virtual void Log(LogMessage message) => Console.WriteLine(message.ToString());
public virtual void Log(LogMessage[] messages) {
foreach (var m in messages)
Log(m);
}
}
}
Чтобы использовать эти классы, просто установите XPO LogManager.Transport
следующим образом:
DevExpress.Xpo.Logger.LogManager.SetTransport(new XpoNLogLogger(), "SQL;Session;DataCache");