我正在尝试设置我的项目,以便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();
}这段代码简单地用ProfiledDbConnection包装了一个SqlConnection,然后将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源代码作为示例……由于我没有源代码,因为我的订阅没有包括它,所以我对如何实现它感到困惑。
发布于 2019-03-24 03:18:58
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的UI中查看(稍后将详细介绍)XPO的数据库查询:

具有如下检测重复呼叫的附加好处:-):

最终想法
我已经研究了9天了。我用Telerik的JustDecompile研究了XPO的反编译代码,尝试了太多不同的方法,以尽可能少的摩擦将性能分析数据从XPO馈送到MiniProfiler中。我曾试图创建一个XPO连接提供程序,继承自XPO的MSSqlConnectionProvider并覆盖它用来执行查询的方法,但后来放弃了,因为该方法不是虚拟的(实际上它是私有的),而且我必须复制该类的全部源代码,而该类依赖于DevExpress中的许多其他源文件。然后,我尝试编写一个Xpo.Session后代来覆盖它的所有数据操作方法,推迟对被MiniProfiler.CustomTiming调用包围的基本Session类方法的调用。令我惊讶的是,这些调用都不是虚拟的(继承自Session的UnitOfWork类似乎更像是一个hack,而不是一个适当的子类),所以我最终遇到了与连接提供程序方法相同的问题。然后,我尝试连接到框架的其他部分,甚至是它自己的跟踪机制。这是卓有成效的,产生了两个整洁的类:XpoNLogLogger和XpoConsoleLogger,但最终不允许我在MiniProfiler中显示结果,因为它提供了已经分析和计时的结果,而我发现无法将这些结果包含/插入到MiniProfiler步骤/自定义计时中。
上面显示的数据层后代解决方案只解决了问题的一部分。首先,它不记录直接SQL调用、存储过程调用和会话方法,这可能很昂贵(毕竟,它甚至不记录从数据库检索到的对象的水合过程)。XPO实现了两种(也许三种)截然不同的跟踪机制。一个记录SQL语句和结果(行数、计时、参数等)使用标准的.NET跟踪和其他日志会话方法,以及使用DevExpress的LogManager类的SQL语句(没有结果)。LogManager是唯一未被认为已过时的方法。第三种方法是模仿DataStoreLogger类,它与我们自己的方法有相同的局限性。
理想情况下,我们应该只需要为任何XPO对象提供一个ProfiledDbConnection,就可以获得MiniProfiler的所有Session分析功能。
我仍然在研究一种方法来包装或继承一些XPO的框架类,以便为基于XPO的项目提供更完整/更好的MiniProfiler分析体验。如果我发现任何有用的东西,我会更新这个案例。
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");https://stackoverflow.com/questions/55173087
复制相似问题