我正在尝试设置我的项目,以便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
属性设置为此连接。
一切编译都很好,但是在运行时会抛出以下异常:
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的支持中心找到此问题:https://www.devexpress.com/Support/Center/Question/Details/Q495411/hooks-to-time-and-log-xpo-sql
但是答案是敷衍了事,它只是告诉他们的客户编写一个实现IDataStore
接口的类,并参考DataStoreLogger
源代码作为示例……由于我没有消息来源,因为我的订阅中没有包含该消息,我对如何实现此消息一无所知。
答案 0 :(得分:0)
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.DataLayer
属性:
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的用户界面中查看整齐归类的XPO数据库查询(其中的一些内容-稍后会进行进一步的介绍)
具有如下检测重复呼叫的附加好处:-):
最终想法
我已经研究了9天了。我已经用 Telerik的JustDecompile 研究了XPO的反编译代码,并尝试了太多不同的方法以最小的摩擦将分析数据从 XPO 馈送到 MiniProfiler 尽可能。我试图创建一个从XPO的MSSqlConnectionProvider
继承来的 XPO连接提供程序,并覆盖了它用于执行查询的方法,但是由于该方法不是虚拟的(实际上是私有的)而放弃了而且我将不得不复制该类的整个源代码,该类源于DevExpress中的许多其他源文件。然后,我尝试编写一个Xpo.Session
后代以覆盖其所有数据处理方法,将调用推迟到由Session
调用包围的基本MiniProfiler.CustomTiming
类方法。令我惊讶的是,这些调用中没有一个是虚拟的(继承自UnitOfWork
的{{1}}类似乎比适当的后代类更像是一种hack),因此我最终遇到了与连接提供者方法。然后,我尝试连接到框架的其他部分,即使它是自己的跟踪机制。这是富有成果的,产生了两个整洁的类:Session
和XpoNLogLogger
,但是最终不允许我在 MiniProfiler 中显示结果,因为它已经提供了定时结果,但我发现没有办法将其包含/插入MiniProfiler步骤/自定义定时中。
上面显示的数据层后代解决方案仅解决了部分问题。对于它来说,它不会记录直接SQL调用,存储过程调用以及没有Session方法,这可能会很昂贵(毕竟它甚至不会记录从数据库中检索到的对象的混合状态)。 XPO 实现了两种(也许是三种)不同的跟踪机制。一种使用标准.NET跟踪记录SQL语句和结果(行计数,计时,参数等),另一种使用DevExpress的XpoConsoleLogger
类记录日志会话方法和SQL语句(无结果)。 LogManager是唯一不会被淘汰的方法。模仿LogManager
类的第三种方法也遭受了我们自己方法的限制。
理想情况下,我们应该只需要向任何XPO DataStoreLogger
对象提供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