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()));
}
}
用法
使用上述数据层的一种方法是在为您的应用程序设置 XPO 时设置 XpoDefault.DataLayerproperty:
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;
结果
现在您可以在 MiniProfiler 的 UI 中查看整齐分类的 XPO 的数据库查询(一些 - 稍后会详细介绍):
检测重复调用的额外好处如下 :-) :
最后的想法
我已经研究了 9 天了。我用 Telerik 的 JustDecompile 研究了 XPO 的反编译代码,并尝试了太多不同的方法来将 XPO 的分析数据输入 MiniProfiler 以尽可能减少摩擦尽可能。我尝试创建一个 XPO 连接提供程序,继承自 XPO 的 MSSqlConnectionProvider 并覆盖它用于执行查询的方法,但由于该方法不是虚拟的(实际上它是私有的)而放弃了,我必须复制该类的整个源代码,这依赖于 DevExpress 的许多其他源文件。然后我尝试编写一个Xpo.Session 后代来覆盖它的所有数据操作方法,将调用推迟到由MiniProfiler.CustomTiming 调用包围的基本Session 类方法。令我惊讶的是,这些调用都不是虚拟的(从Session 继承的UnitOfWork 类似乎更像是一种黑客攻击而不是一个适当的后代类),所以我最终遇到了与连接提供程序方法相同的问题。然后我尝试连接到框架的其他部分,甚至是它自己的跟踪机制。这是卓有成效的,产生了两个简洁的类:XpoNLogLogger 和 XpoConsoleLogger,但最终不允许我在 MiniProfiler 中显示结果,因为它提供了已经分析和计时的结果 em> 我发现无法将其包含/插入到 MiniProfiler 步骤/自定义时间中。
上面显示的数据层后代解决方案仅解决了部分问题。一方面,它不记录直接 SQL 调用、存储过程调用,也没有 Session 方法,这可能很昂贵(毕竟它甚至不记录从数据库中检索到的对象的水合)。 XPO 实现了两种(可能是三种)不同的跟踪机制。一种使用标准 .NET 跟踪记录 SQL 语句和结果(行数、计时、参数等),另一种使用 DevExpress 的LogManager 类记录会话方法和 SQL 语句(无结果)。 LogManager 是唯一未被视为过时的方法。第三种方法是模仿DataStoreLogger 类,它与我们自己的方法有同样的局限性。
理想情况下,我们应该能够只为任何 XPO Sessionobject 提供一个 ProfiledDbConnection,以获取 MiniProfiler 的所有 SQL 分析功能。
我仍在研究一种封装或继承某些 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");