MVC Mini Profiler与Microsoft Enterprise Library

10

我相信可以对Enterprise Library SQL命令进行性能分析,但是我还没有找到如何包装连接的方法。以下是我想出的代码:

Database db = DatabaseFactory.CreateDatabase();
DbCommand dbCommand = db.GetStoredProcCommand(PROC);

ProfiledDbCommand cmd = new ProfiledDbCommand(dbCommand, dbCommand.Connection, MvcMiniProfiler.MiniProfiler.Current);
db.AddInParameter(cmd, "foo", DbType.Int64, 0);

DataSet ds = db.ExecuteDataSet(cmd);

这会导致以下异常:

无法将类型为 'MvcMiniProfiler.Data.ProfiledDbCommand' 的对象强制转换为类型 'System.Data.SqlClient.SqlCommand'。


到目前为止,我还没有找到如何将MVC Mini ProfilerEnterprise Library一起使用的方法... - Regent
@goalie7960,请查看我更新的答案,解决了这个问题。 - Jethro
3个回答

13

异常来自Entlib Database.DoLoadDataSet中的这一行。

    ((IDbDataAdapter) adapter).SelectCommand = command; 
在这种情况下,适配器的类型为SqlDataAdapter,它期望一个SqlCommand。由ProfiledDbProviderFactory创建的命令的类型是ProfiledDbCommand,正如你在异常中看到的。
此解决方案通过覆盖ProfiledDbProviderFactory中的CreateDataAdapter和CreateCommand方法,为EntLib提供了通用的DbDataAdapter。它似乎工作得很好,但如果我忽略了这个 hack 可能带来的任何不良后果(或可能引起的不适),我深表歉意。下面是具体步骤:
1.创建两个新类ProfiledDbProviderFactoryForEntLib和DbDataAdapterForEntLib。
public class ProfiledDbProviderFactoryForEntLib : ProfiledDbProviderFactory
{
    private DbProviderFactory _tail;
    public static ProfiledDbProviderFactory Instance = new ProfiledDbProviderFactoryForEntLib();

    public ProfiledDbProviderFactoryForEntLib(): base(null, null)
    {
    }

    public void InitProfiledDbProviderFactory(IDbProfiler profiler, DbProviderFactory tail)
    {
        base.InitProfiledDbProviderFactory(profiler, tail);
        _tail = tail;
    }

    public override DbDataAdapter CreateDataAdapter()
    {
        return new DbDataAdapterForEntLib(base.CreateDataAdapter());
    }

    public override DbCommand CreateCommand()
    {
        return _tail.CreateCommand(); 
    }        
}

public class DbDataAdapterForEntLib : DbDataAdapter
{
    private DbDataAdapter _dbDataAdapter;
    public DbDataAdapterForEntLib(DbDataAdapter adapter)
    : base(adapter)
   {
        _dbDataAdapter = adapter;
   }
}
在 Web.config 中,向 DbProviderFactories 添加 ProfiledDbProviderFactoryForEntLib 并将 ProfiledDbProviderFactoryForEntLib 设置为连接字符串的 providerName。
<configuration>
    <configSections>
        <section name="dataConfiguration" type="..."  />
    </configSections>
    <connectionStrings>
        <add name="SqlServerConnectionString" connectionString="Data Source=xyz;Initial Catalog=dbname;User ID=u;Password=p"
  providerName="ProfiledDbProviderFactoryForEntLib" />
    </connectionStrings>
    <system.data>
        <DbProviderFactories>
          <add name="EntLib DB Provider"
           invariant="ProfiledDbProviderFactoryForEntLib"
           description="Profiled DB provider for EntLib"
           type="MvcApplicationEntlib.ProfiledDbProviderFactoryForEntLib,     MvcApplicationEntlib, Version=1.0.0.0, Culture=neutral"/>
        </DbProviderFactories>
    </system.data>
    <dataConfiguration defaultDatabase="..." />
    <appSettings>... <system.web>... etc ...
</configuration>

(MvcApplicationEntlib是我的测试项目的名称)

  • 在任何对数据库的调用之前设置ProfiledDbProviderFactoryForEntLib(对于敏感于黑客攻击的读者,请注意,这是一个丑陋的地方)

  • //In Global.asax.cs 
        protected void Application_Start()
        {
    
            ProfiledDbProviderFactoryForEntLib profiledProfiledDbProviderFactoryFor = ((ProfiledDbProviderFactoryForEntLib)DbProviderFactories.GetFactory("ProfiledDbProviderFactoryForEntLib"));
            DbProviderFactory factory = DbProviderFactories.GetFactory("System.Data.SqlClient"); //or whatever predefined factory you want to profile
            profiledProfiledDbProviderFactoryFor.InitProfiledDbProviderFactory(MiniProfiler.Current, factory); 
        ...
    

    这样做可能有更好的方式或在其他地方进行操作。这里MiniProfiler.Current将为空,因为没有对其进行性能分析。

  • 像一开始那样调用存储过程即可。

  • public class HomeController : Controller
        {
            public ActionResult Index()
            { 
                Database db = DatabaseFactory.CreateDatabase();
                DbCommand dbCommand = db.GetStoredProcCommand("spGetSomething");
                DbCommand cmd = new ProfiledDbCommand(dbCommand, dbCommand.Connection, MiniProfiler.Current);
                DataSet ds = db.ExecuteDataSet(cmd);
                ...
    

    编辑: 好的,不太确定您想要如何使用它。要跳过手动创建ProfiledDbCommand,请为每个请求初始化ProfiledDbProviderFactory并添加miniprofiler。

    1. 在Global.asax.cs中,删除对Application_Start所做的更改(即上面第3步中的工厂设置),改为将其添加到Application_BeginRequest中。

    ProfiledDbProviderFactoryForEntLib profiledProfiledDbProviderFactoryFor = ((ProfiledDbProviderFactoryForEntLib) DbProviderFactories.GetFactory("ProfiledDbProviderFactoryForEntLib"));
    DbProviderFactory factory = DbProviderFactories.GetFactory("System.Data.SqlClient");
    profiledProfiledDbProviderFactoryFor.InitProfiledDbProviderFactory(MvcMiniProfiler.MiniProfiler.Start(), factory);
    
  • 删除 ProfiledDbProviderFactoryForEntLib 中的 CreateCommand 方法,让 ProfiledDbProviderFactory 创建 profiled 命令。

  • 执行您的存储过程时不要创建 ProfiledDbCommand,就像这样:

  • Database db = DatabaseFactory.CreateDatabase();
    DbCommand dbCommand = db.GetStoredProcCommand("spGetSomething");
    DataSet ds = db.ExecuteDataSet(dbCommand);
    

    那很好用。我无法理解提供程序模型。不过,有一件事,是否有一种方法可以删除DbCommand cmd = new ProfiledDbCommand(dbCommand, dbCommand.Connection, MiniProfiler.Current); 并使db.GetStoredProcCommand()返回一个经过配置的命令?我知道我可以通过帮助方法来实现,但是配置解决方案会更好。 - goalie7960

    0

    这是一个相当旧的帖子,但我认为值得提一下我是如何解决这个问题的。

    由于我们的应用程序实现方式,按照Jonas的解决方案并不容易,所以我选择了修改EntLib Datablock(在我们的项目中已经修改过)的路线。

    事情就像简单地修改Database类中的DoExecuteXXX方法,使它们调用miniprofiler一样,而之前我已经将其更改为公开SqlProfiler。

    只需要这样做:

    if (_miniProfiler != null)
        _miniProfiler.ExecuteStart(command, StackExchange.Profiling.Data.ExecuteType.NonQuery);
    

    在每个方法的开头和

    if (_miniProfiler != null)
        _miniProfiler.ExecuteFinish(command, StackExchange.Profiling.Data.ExecuteType.NonQuery); 
    

    在 finally 块中做了这个技巧。只需将执行类型更改为要更改的方法所适用的类型。

    miniprofiler 实例是在 DatabaseClass 的构造函数中获取的。

    if (MiniProfiler.Current != null)
        _miniProfiler = MiniProfiler.Current.SqlProfiler;
    

    -2
    你可以尝试这样做。
    Database db = DatabaseFactory.CreateDatabase();
    DbCommand dbCommand = db.GetStoredProcCommand(PROC);
    
    ProfiledDbCommand cmd = new ProfiledDbCommand(dbCommand, dbCommand.Connection, MvcMiniProfiler.MiniProfiler.Current);
    db.AddInParameter(cmd, "foo", DbType.Int64, 0);
    
    DbConnection dbc = MvcMiniProfiler.Data.ProfiledDbConnection.Get(dbCommand.Connection, MiniProfiler.Current);
    DataSet ds = db.ExecuteDataSet(dbc);
    

    更新:

    经过大量搜索EntLib数据源代码,SqlDatabase,看起来并没有真正实现这一点的简单方法。抱歉。我知道我是。

    实际上,这似乎是Mvc-Mini-Profiler的一个问题。如果您查看MVC-Mini-Profiler项目主页上的Issues 19 Link,您将看到其他人也遇到了同样的问题。

    我花了很长时间通过Reflector和ProfiledDbProviderFactory浏览System.Data.SqlClient,试图看看问题所在,似乎以下是问题所在。这在ProfiledDbProviderFactory类中找到。

     public override DbDataAdapter CreateDataAdapter()        
     {
         return tail.CreateDataAdapter();        
     }
    

    现在问题并不在于这段代码本身,而是当Microsoft.Practices.EnterpriseLibrary.Data.Database类调用一个DbDataAdapter实例(在我们的情况下是SqlClientFactory)时,它会创建一个SqlClientFactory Command,然后尝试将此命令的Connection设置为ProfiledDbConnection,这就是出错的地方。

    我尝试创建自己的工厂类来解决这个问题,但我太迷失了。除非SO团队先解决了这个问题,否则也许等我有空闲时间再试着解决吧。 :)


    这与我发布的有何不同? - goalie7960
    请查看我的更新答案。并且我也在想如何与EntLib一起使用Mini Profiler,需要花些时间详细了解。 - Jethro
    3
    这段代码无法编译。你不能将DbConnection传递给ExecuteDataSet方法。另外,DbCommand的Connection属性为空。 - goalie7960
    您更新的答案实际上没有使用Enterprise Library。 - Regent
    1
    @Jethro 或许把这个问题直接发布到该项目的问题跟踪器上会更好? - goalie7960
    显示剩余8条评论

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接