Using mvc-mini-profiler database profiling with Entity Framework Code First
Asked Answered
I

3

24

I'm using the mvc-mini-profiler in my project built with ASP.Net MVC 3 and Entity Framework code-first.

Everything works great until I attempt to add database profiling by wrapping the connection in the ProfiledDbConnection as described in the documentation. Since I'm using a DbContext, the way I am attempting to provide the connection is through the constructor using a static factory method:

public class MyDbContext : DbContext
{                
    public MyDbContext() : base(GetProfilerConnection(), true)
    { }

    private static DbConnection GetProfilerConnection()
    {
        // Code below errors
        //return ProfiledDbConnection.Get(new SqlConnection(ConfigurationManager.ConnectionStrings["MyConnectionName"].ConnectionString));

        // Code below works fine...
        return new SqlConnection(ConfigurationManager.ConnectionStrings["MyConnectionName"].ConnectionString);
    }

    //...
}

When using the ProfiledDbConnection, I get the following error:

ProviderIncompatibleException: The provider did not return a ProviderManifestToken string.

Stack Trace:

[ArgumentException: The connection is not of type 'System.Data.SqlClient.SqlConnection'.]
   System.Data.SqlClient.SqlProviderUtilities.GetRequiredSqlConnection(DbConnection connection) +10486148
   System.Data.SqlClient.SqlProviderServices.GetDbProviderManifestToken(DbConnection connection) +77
   System.Data.Common.DbProviderServices.GetProviderManifestToken(DbConnection connection) +44

[ProviderIncompatibleException: The provider did not return a ProviderManifestToken string.]
System.Data.Common.DbProviderServices.GetProviderManifestToken(DbConnection connection) +11092901
   System.Data.Common.DbProviderServices.GetProviderManifestToken(DbConnection connection) +11092745
   System.Data.Entity.DbModelBuilder.Build(DbConnection providerConnection) +221
   System.Data.Entity.Internal.LazyInternalContext.CreateModel(LazyInternalContext internalContext) +61
   System.Data.Entity.Internal.RetryLazy`2.GetValue(TInput input) +1203482
   System.Data.Entity.Internal.LazyInternalContext.InitializeContext() +492
   System.Data.Entity.Internal.InternalContext.GetEntitySetAndBaseTypeForType(Type entityType) +26
   System.Data.Entity.Internal.Linq.InternalSet`1.Initialize() +89
   System.Data.Entity.Internal.Linq.InternalSet`1.get_InternalContext() +21
   System.Data.Entity.Infrastructure.DbQuery`1.System.Linq.IQueryable.get_Provider() +44
   System.Linq.Queryable.Where(IQueryable`1 source, Expression`1 predicate) +135

I have stepped through and the type returned by ProfiledDbConnection.Get is of type ProfiledDbConnection (Even if the current MiniProfiler is null).

The MiniProfiler.Start() method is called within the Global Application_BeginRequest() method before the DbContext is instantiated. I am also calling the Start method for every request regardless but calling stop if the user is not in the correct role:

    protected void Application_BeginRequest()
    {
        // We don't know who the user is at this stage so need to start for everyone
        MiniProfiler.Start();
    }

    protected void Application_AuthorizeRequest(Object sender, EventArgs e)
    {
        // Now stop the profiler if the user is not a developer
        if (!AuthorisationHelper.IsDeveloper())
        {
            MvcMiniProfiler.MiniProfiler.Stop(discardResults: true);
        }
    }

    protected void Application_EndRequest()
    {
        MiniProfiler.Stop();
    }

I'm not sure if this affects things but I'm also using StructureMap as IoC for the DbContext using the following initialiser:

For<MyDbContext>().Singleton().HybridHttpOrThreadLocalScoped();

I understand that there is a similar question on here with a good explanation of what's happening for that user, however it doesn't seem to solve my problem.

EDIT:

For clarity. I am attempting to pass the connection as ProfiledDbConnection in order to profile the generated sql from Entity Framework Code First.

Profiled Sql

The Entity Framework is expecting a connection with type SqlConnection which of course this isn't.

Here is an example of my connection string (notice the providerName)

<add name="MyDbContext" connectionString="Server=.\SQLEXPRESS; Database=MyDatabase;Trusted_Connection=true;MultipleActiveResultSets=true" providerName="System.Data.SqlClient" />

I attempted to create my own version of the ProfiledDbConnection inheriting from SqlConnection but it is a sealed class.

If there is some way of telling Entity Framework about the custom connection type then perhaps this would work. I tried setting the providerName in the connection string to MvcMiniProfiler.Data.ProfiledDbConnection but that didn't work.

So. Perhaps an evolution of the question would be: How can you pass a custom connection type to Entity Framework Code First?

Imprint answered 1/7, 2011 at 15:35 Comment(0)
D
13

This is now fully supported, check out the latest source or grab the package from nuget.

You will need the MiniProfiler.EF package if you are using nuget. (1.9.1 and up)

Supporting this involved a large set of modifications to the underlying proxy object to support acting as EF code first proxies.

To add this support:

During your Application_Start run:

MiniProfilerEF.Initialize();

Note: EF Code First will store table metadata in a table called: EdmMetadata. This metadata uses the provider as part of the entity key. If you initialized your provider as a non-profiled provider, you will have to re-build this metadata. Deleting all the rows from EdmMetadata may do the trick, alternatively some smarter providers are able to handle this transparently.

Dollfuss answered 19/7, 2011 at 7:56 Comment(10)
Sam you're a superstar! Thanks for the fixImprint
Does this method require the EdmMetadata table? We are using Code First with a database not created by EF. I implemented the method above (though with normal SQL Server, as described on Scott Hanselman's blog), but SQL queries are not showing up in the profiler.Tea
@Tea Have you checked that you are not hitting this problemSalchunas
Perhaps you should mention that the remove and add nodes in the web.config should be added under <system.data> then <DbProviderFactories>Prostration
Your answer assumes too much knowledge about EF and MiniProfiler and/or is out of date. As a newb to both libraries, I'm little better off having read your answer. As @Per suggested, please indicate where in web.config that setting goes. Also, please indicate possibilities where the code snippet should go. App start? Is this what comes in the MiniProfiler.cs file? If so, then point that out too.Indestructible
@Indestructible I would chuck the code in app start, but it is totally up to you. added the locationDollfuss
@Sam can you please clarify if you need to keep the previous code MiniProfiler.Start or replace it entirely with MiniProfilerEF.Initialize(). BTW does 1.9 work with MVC 2 database first method?Granduncle
@Granduncle initialize and start are unrelated, one is related to initialization (called once per app lifecycle) ... the other to the profiling session in progress (one per request)Dollfuss
Thanks - just that your answers only show the new MiniProfilerEF.Initialize() line, so I was not sure if we only needed one or needed both.Granduncle
Is there a way to use this when you do not generate the database via Code First. I created Entities based upon an existing database.Simplicidentate
S
2

I was still having problems getting this to work and found that I needed to rename or remove the connection string to get Database.DefaultConnectionFactory to work.

Please refer to this answer for more detail.

Salchunas answered 23/7, 2011 at 13:53 Comment(0)
S
-1

This error in my experience has always been an invalid connection string, or a lack of connection to the DB, like "A network service error occurred while connecting...".

Also note that the DbContext just needs the "connectionStringKey" in the constructor, like

public MyDbContext() : 
     base("MyConnectionName", true)
    { }
Streamliner answered 12/7, 2011 at 21:2 Comment(1)
Thanks, this will get rid of the exception, however what I am attempting is to pass the connection as an instance of the ProfiledDbConnection in order to profile the sql.Imprint

© 2022 - 2024 — McMap. All rights reserved.