【问题标题】:Use MiniProfiler with DevExpress XPO (ORM)将 MiniProfiler 与 DevExpress XPO (ORM) 一起使用
【发布时间】:2019-03-14 22:41:23
【问题描述】:

我正在尝试设置我的项目,以便 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();
}

此代码只是将SqlConnectionProfiledDbConnection 包装在一起,然后将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源代码作为示例...因为我没有订阅源没有包括它我不知道如何实现它。

【问题讨论】:

    标签: c# devexpress mvc-mini-profiler xpo miniprofiler


    【解决方案1】:

    9 天后,我想出了一个低摩擦但不理想的解决方案,它包含两个继承自 SimpleDataLayerThreadSafeDataLayer 的新类:

    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 类似乎更像是一种黑客攻击而不是一个适当的后代类),所以我最终遇到了与连接提供程序方法相同的问题。然后我尝试连接到框架的其他部分,甚至是它自己的跟踪机制。这是卓有成效的,产生了两个简洁的类:XpoNLogLoggerXpoConsoleLogger,但最终不允许我在 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");
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-09-13
      • 2010-09-07
      • 1970-01-01
      • 1970-01-01
      • 2012-08-20
      • 2022-01-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多