首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >将MiniProfiler与DevExpress XPO一起使用(ORM)

将MiniProfiler与DevExpress XPO一起使用(ORM)
EN

Stack Overflow用户
提问于 2019-03-15 06:41:23
回答 1查看 282关注 0票数 0

我正在尝试设置我的项目,以便MiniProfiler能够分析XPO的SQL调用。这应该是一项非常简单的工作,因为MiniProfiler只包装了一个普通的连接,但这种简单的方法不起作用。下面是本应正常工作的代码:

代码语言:javascript
复制
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属性设置为这个连接。

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

代码语言:javascript
复制
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源代码作为示例……由于我没有源代码,因为我的订阅没有包括它,所以我对如何实现它感到困惑。

EN

回答 1

Stack Overflow用户

发布于 2019-03-24 03:18:58

9天后,我提出了一个低摩擦的解决方案,它由两个继承自SimpleDataLayerThreadSafeDataLayer的新类组成

ProfiledThreadSafeDataLayer.cs

代码语言:javascript
复制
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

代码语言:javascript
复制
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()扩展方法:

代码语言:javascript
复制
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属性:

代码语言:javascript
复制
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类方法的调用。令我惊讶的是,这些调用都不是虚拟的(继承自SessionUnitOfWork类似乎更像是一个hack,而不是一个适当的子类),所以我最终遇到了与连接提供程序方法相同的问题。然后,我尝试连接到框架的其他部分,甚至是它自己的跟踪机制。这是卓有成效的,产生了两个整洁的类:XpoNLogLoggerXpoConsoleLogger,但最终不允许我在MiniProfiler中显示结果,因为它提供了已经分析和计时的结果,而我发现无法将这些结果包含/插入到MiniProfiler步骤/自定义计时中。

上面显示的数据层后代解决方案只解决了问题的一部分。首先,它不记录直接SQL调用、存储过程调用和会话方法,这可能很昂贵(毕竟,它甚至不记录从数据库检索到的对象的水合过程)。XPO实现了两种(也许三种)截然不同的跟踪机制。一个记录SQL语句和结果(行数、计时、参数等)使用标准的.NET跟踪和其他日志会话方法,以及使用DevExpress的LogManager类的SQL语句(没有结果)。LogManager是唯一未被认为已过时的方法。第三种方法是模仿DataStoreLogger类,它与我们自己的方法有相同的局限性。

理想情况下,我们应该只需要为任何XPO对象提供一个ProfiledDbConnection,就可以获得MiniProfiler的所有Session分析功能。

我仍然在研究一种方法来包装或继承一些XPO的框架类,以便为基于XPO的项目提供更完整/更好的MiniProfiler分析体验。如果我发现任何有用的东西,我会更新这个案例。

XPO日志记录类

在研究的过程中,我创建了两个非常有用的类:

XpoNLogLogger.cs

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
DevExpress.Xpo.Logger.LogManager.SetTransport(new XpoNLogLogger(), "SQL;Session;DataCache");
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/55173087

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档