将MiniProfiler与DevExpress XPO(ORM)配合使用

问题描述 投票:0回答:1

我正在尝试设置我的项目,以便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.Connectionproperty设置为此连接。

一切都编译得很好,但在运行时会抛出以下异常:

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接口的类,并参考IDataStoresource代码作为一个例子...因为我没有源,因为我的订阅不包括它我是对如何实现这一点感到茫然。

c# devexpress mvc-mini-profiler xpo miniprofiler
1个回答
0
投票

9天之后,我提出了一个低摩擦,虽然非理想的解决方案,它包含两个继承自DataStoreLoggerand 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;

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

XPO queries inside MiniProfiler UI


最后的想法

我现在已经挖了9天了。我用Telerik的JustDecompile研究了XPO的反编译代码,尝试了太多不同的方法,将XPO中的分析数据提供给MiniProfiler,并尽可能减少摩擦。我试图创建一个XPO连接提供程序,继承自XPO的MiniProfiler detecting duplicate XPO calls并覆盖它用于执行查询的方法,但放弃了,因为该方法不是虚拟的(事实上它是私有的),我将不得不复制整个源代码该类依赖于DevExpress的许多其他源文件。然后我尝试编写一个MSSqlConnectionProvider后代来覆盖它的所有数据操作方法,将调用推迟到Xpo.Session调用包围的基本Session类方法。令我惊讶的是,这些调用都不是虚拟的(MiniProfiler.CustomTiming类,它继承自UnitOfWork,似乎更像是一个hack而不是一个适当的后代类)所以我最终遇到了与连接提供程序方法相同的问题。然后我尝试连接到框架的其他部分,甚至它自己的跟踪机制。这是富有成效的,导致两个整齐的类:SessionXpoNLogLogger,但最终不允许我在MiniProfiler中显示结果,因为它提供了已经分析和定时的结果,我发现无法包含/插入MiniProfiler步骤/自定义时序。

上面显示的数据层后代解决方案仅解决了部分问题。例如,它不记录直接SQL调用,存储过程调用和没有会话方法,这可能很昂贵(毕竟它甚至不记录从数据库中检索的对象的保湿)。 XPO实现了两种(可能是三种)不同的跟踪机制。使用标准.NET跟踪以及使用DevExpress'XpoConsoleLogger类的其他日志会话方法和SQL语句(无结果)记录SQL语句和结果(行数,时序,参数等)。 LogManager是唯一不被认为过时的方法。模仿LogManager类的第三种方法受到我们自己方法的相同限制。

理想情况下,我们应该能够为任何XPO DataStoreLoggerobject提供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
© www.soinside.com 2019 - 2024. All rights reserved.