我正在尝试设置我的项目,以便MiniProfiler能够分析XPO的SQL调用。这应该是一个非常简单的尝试,因为MiniProfiler只包含一个普通的连接,但这种简单的方法不起作用。这是应该有效的代码:
protected void Button1_Click(object sender, EventArgs e) {
var s = new UnitOfWork();
IDbConnection conn = new ProfiledDbConnection(new SqlConnection(Global.ConnStr), MiniProfiler.Current);
s.Connection = conn;
for (int i = 0; i < 200; i++) {
var p = new Person(s) {
Name = $"Name of {i}",
Age = i,
};
if (i % 25 == 0)
s.CommitChanges();
}
s.CommitChanges();
}
这段代码只需用SqlConnection
包装ProfiledDbConnection
然后将Session/UnitOfWork.Connection
property设置为此连接。
一切都编译得很好,但在运行时会抛出以下异常:
DevExpress.Xpo.Exceptions.CannotFindAppropriateConnectionProviderException
HResult=0x80131500
Message=Invalid connection string specified: 'ProfiledDbConnection(Data Source=.\SQLEXPRESS;Initial Catalog=sample;Persist Security Info=True;Integrated Security=SSPI;)'.
Source=<Cannot evaluate the exception source>
StackTrace:
em DevExpress.Xpo.XpoDefault.GetConnectionProvider(IDbConnection connection, AutoCreateOption autoCreateOption)
em DevExpress.Xpo.XpoDefault.GetDataLayer(IDbConnection connection, XPDictionary dictionary, AutoCreateOption autoCreateOption, IDisposable[]& objectsToDisposeOnDisconnect)
em DevExpress.Xpo.Session.ConnectOldStyle()
em DevExpress.Xpo.Session.Connect()
em DevExpress.Xpo.Session.get_Dictionary()
em DevExpress.Xpo.Session.GetClassInfo(Type classType)
em DevExpress.Xpo.XPObject..ctor(Session session)
em WebApplication1.Person..ctor(Session s) na C:\Users\USER\source\repos\WebApplication2\WebApplication1\Person.cs:linha 11
em WebApplication1._Default.Button1_Click(Object sender, EventArgs e) na C:\Users\USER\source\repos\WebApplication2\WebApplication1\Default.aspx.cs:linha 28
em System.Web.UI.WebControls.Button.OnClick(EventArgs e)
em System.Web.UI.WebControls.Button.RaisePostBackEvent(String eventArgument)
em System.Web.UI.WebControls.Button.System.Web.UI.IPostBackEventHandler.RaisePostBackEvent(String eventArgument)
em System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument)
em System.Web.UI.Page.RaisePostBackEvent(NameValueCollection postData)
em System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
我在DevExpress支持中心找到了这个问题:qazxsw poi
但答案是敷衍了事,它只是告诉他们的客户写一个实现https://www.devexpress.com/Support/Center/Question/Details/Q495411/hooks-to-time-and-log-xpo-sql接口的类,并参考IDataStore
source代码作为一个例子...因为我没有源,因为我的订阅不包括它我是对如何实现这一点感到茫然。
9天之后,我提出了一个低摩擦,虽然非理想的解决方案,它包含两个继承自DataStoreLogger
and SimpleDataLayer
的新类:
ProfiledThreadSafeDataLayer.cs
ThreadSafeDataLayer
ProfiledDataLayer.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);
}
}
}
和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()
用法
使用上述数据层的方法之一是在为应用程序设置XPO时设置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()));
}
}
property:
XpoDefault.DataLayer
结果
现在你可以查看(稍后会有更多内容)XPO的数据库查询整齐地分类在MiniProfiler的UI中:
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;
检测重复呼叫的附加好处如下:-):
最后的想法
我现在已经挖了9天了。我用Telerik的JustDecompile研究了XPO的反编译代码,尝试了太多不同的方法,将XPO中的分析数据提供给MiniProfiler,并尽可能减少摩擦。我试图创建一个XPO连接提供程序,继承自XPO的并覆盖它用于执行查询的方法,但放弃了,因为该方法不是虚拟的(事实上它是私有的),我将不得不复制整个源代码该类依赖于DevExpress的许多其他源文件。然后我尝试编写一个MSSqlConnectionProvider
后代来覆盖它的所有数据操作方法,将调用推迟到Xpo.Session
调用包围的基本Session
类方法。令我惊讶的是,这些调用都不是虚拟的(MiniProfiler.CustomTiming
类,它继承自UnitOfWork
,似乎更像是一个hack而不是一个适当的后代类)所以我最终遇到了与连接提供程序方法相同的问题。然后我尝试连接到框架的其他部分,甚至它自己的跟踪机制。这是富有成效的,导致两个整齐的类:Session
和XpoNLogLogger
,但最终不允许我在MiniProfiler中显示结果,因为它提供了已经分析和定时的结果,我发现无法包含/插入MiniProfiler步骤/自定义时序。
上面显示的数据层后代解决方案仅解决了部分问题。例如,它不记录直接SQL调用,存储过程调用和没有会话方法,这可能很昂贵(毕竟它甚至不记录从数据库中检索的对象的保湿)。 XPO实现了两种(可能是三种)不同的跟踪机制。使用标准.NET跟踪以及使用DevExpress'XpoConsoleLogger
类的其他日志会话方法和SQL语句(无结果)记录SQL语句和结果(行数,时序,参数等)。 LogManager是唯一不被认为过时的方法。模仿LogManager
类的第三种方法受到我们自己方法的相同限制。
理想情况下,我们应该能够为任何XPO DataStoreLogger
object提供ProfiledDbConnection
,以获得所有MiniProfiler的SQL分析功能。
我还在研究一种方法来包装或继承一些XPO的框架类,以便为基于XPO的项目提供更完整/更好的MiniProfiler性能分析体验。如果我发现任何有用的东西,我会更新这个案例。
XPO测井课程
在研究这个时,我创建了两个非常有用的类:
XpoNLogLogger.cs
Session
XpoConsoleLogger.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);
}
}
}
要使用这些类,只需按如下方式设置XPO的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);
}
}
}
:
LogManager.Transport