Иногда "Ошибка базового провайдера при ошибках Open" при использовании EF4 (модель edmx)

Я надеюсь, что кто-то может помочь мне с решением следующей ошибки. Приложение, в котором происходит ошибка, работает в процессе производства, и я никогда не испытываю ошибку самостоятельно. Однако около 20 раз в день я получаю сообщение об ошибке:

Исходный провайдер не смог открыть Open. --- > System.InvalidOperationException: соединение не было закрыто. состояние соединения подключается.

Здесь трассировка стека

Исправление System.Data.EntityException: базовый поставщик не работает в Open. --- > System.InvalidOperationException: соединение не было закрыто. Состояние соединения подключается. в System.Data.ProviderBase.DbConnectionBusy.OpenConnection(DbConnection externalConnection, DbConnectionFactory connectionFactory) при System.Data.SqlClient.SqlConnection.Open() at HibernatingRhinos.Profiler.Appender.ProfiledDataAccess.ProfiledConnection.Open() в System.Data.EntityClient.EntityConnection.OpenStoreConnectionIf(Boolean openCondition, DbConnection storeConnectionToOpen, DbConnection originalConnection, String exceptionCode, String попыткаOperation, Логическое & closeStoreConnectionOnFailure) --- Конец внутреннего исключения трассировка стека --- при System.Data.EntityClient.EntityConnection.OpenStoreConnectionIf(Boolean openCondition, DbConnection storeConnectionToOpen, DbConnection originalConnection, String exceptionCode, String попыткаOperation, Логическое & closeStoreConnectionOnFailure) в System.Data.EntityClient.EntityConnection.Open() at System.Data.Objects.ObjectContext.EnsureConnection() в System.Data.Objects.ObjectQuery 1.GetResults(Nullable 1 forMergeOption) при System.Data.Objects.ObjectQuery 1.System.Collections.Generic.IEnumerable<T>.GetEnumerator() at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable 1 источник) в System.Data.Objects.ELinq.ObjectQueryProvider.b__1 [TResult] (IEnumerable 1 sequence) at System.Data.Objects.ELinq.ObjectQueryProvider.ExecuteSingle[TResult](IEnumerable 1 query, Expression queryRoot) в System.Data.Objects.ELinq.ObjectQueryProvider.System.Linq.IQueryProvider.Execute [S] (Выражение выражение) в System.Linq.Queryable.FirstOrDefault [TSource] (источник IQueryable`1)
в GuideSites.DomainModel.Repositories.ClinicTermRepository.GetClinicTermByGuideSiteId(Int32 guideSiteId) в C:\Projects\GuideSites\GuideSites.DomainModel\Хранилища\ClinicTermRepository.cs: линия 20 на GuideSites.Web.Frontend.Helpers.VerifyUrlHelper.RedirectOldUrls() в C:\Projects\GuideSites\GuideSites.Web.Frontend\Helpers\VerifyUrlHelper.cs: линия 91 на GuideSites.Web.Frontend.MvcApplication.Application_BeginRequest (Объект отправителя, EventArgs e) в C:\Projects\GuideSites\GuideSites.Web.Frontend\Global.asax.cs: строка 412 в System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() на System.Web.HttpApplication.ExecuteStep(шаг IExecutionStep, Логическое & completedSynchronously)

Я использую EF4 через EDMX-модель, и я, как я подключаюсь к базе данных (MS SQL 2008), через контекст объекта запроса для HttpContext, поэтому соединения с базой данных не открываются и не закрываются для каждого кусок данных, который мне нужен на заданной странице.

Мой класс контекста базы данных выглядит следующим образом:

public class DatabaseContext : IDisposable
{
    private const string ContextName = "context";
    private static dbEntities _dbEntities;

    public dbEntities GetDatabaseContext()
    {
        SqlConnection.ClearAllPools();

        if (HttpContext.Current == null)
            return _dbEntities ?? (_dbEntities = new dbEntities());

        if (HttpContext.Current.Items[ContextName] == null)
            HttpContext.Current.Items[ContextName] = new dbEntities();

        _dbEntities = (dbEntities)HttpContext.Current.Items[ContextName];
        if (_dbEntities.Connection.State == ConnectionState.Closed)
        {
            _dbEntities.Connection.Open();
            return _dbEntities;
        }

        return _dbEntities;
    }


    public void RemoveContext()
    {
        if (HttpContext.Current != null && HttpContext.Current.Items[ContextName] != null)
        {
            ((dbEntities)HttpContext.Current.Items[ContextName]).Dispose();
            HttpContext.Current.Items[ContextName] = null;
        }

        if (_dbEntities != null)
        {
            _dbEntities.Dispose();
            _dbEntities = null;
        }
    }


    public void Dispose()
    {
        RemoveContext();
    }

}

В моих репозиториях я использую контекст базы данных следующим образом:

public class SomeRepository
{
    private static readonly object Lock = new object();
    private readonly dbEntities _dbEntities;

    public SomeRepository()
    {
        var databaseContext = new DatabaseContext();
        _dbEntities = databaseContext.GetDatabaseContext();
    }


    public IEnumerable<SomeRecord> GetSomeData(int id)
    {
        lock (Lock)
        {
            return
                _dbEntities.SomeData.Where(c => c.Id == id);
        }
    }
 }

Дело блокировки (Lock) было чем-то, что я читал, должно помочь этой проблеме, но в моем случае это не так. И вообще было сложно найти потоки, которые точно описывают мою проблему, не говоря уже о решении проблемы.

Приложение представляет собой приложение ASP.NET MVC3, и оно устанавливается как одно приложение, работающее на 9 разных веб-сайтах (домен определяет контент, который будет передан клиенту). На 9 веб-сайтах ежедневно не более 2000 просмотров страниц, поэтому база данных должна быть подчеркнута в этой учетной записи.

Я надеюсь, что кто-то может помочь, и, пожалуйста, дайте мне знать, есть ли что-то, о чем я забыл упомянуть.

Ответ 1

Согласно моему комментарию, Dispose() должен быть вызван чем-то в конце запроса. Вы можете сделать это с помощью HttpModule следующим образом:

public class ContextDisposer : IHttpModule
{
    private readonly DatabaseContext _context = new DatabaseContext();

    public void Init(HttpApplication context)
    {
        context.EndRequest += (sender, e) => this.DisposeContext(sender, e);
    }

    private static bool DoesRequestCompletionRequireDisposing(
        string requestPath)
    {
        string fileExtension = Path.GetExtension(requestPath)
            .ToUpperInvariant();

        switch (fileExtension)
        {
            case ".ASPX":
            case string.Empty:
            case null:
                return true;
        }

        return false;
    }

    private void DisposeContext(object sender, EventArgs e)
    {
        // This gets fired for every request to the server, but there no 
        // point trying to dispose anything if the request is for (e.g.) a 
        // gif, so only call Dispose() if necessary:
        string requestedFilePath = ((HttpApplication)sender).Request.FilePath;

        if (DoesRequestCompletionRequireDisposing(requestedFilePath))
        {
            this._context.Dispose();
        }
    }
}

Затем вы подключаете модуль к конвейеру запроса (вы помещаете его в system.web и system.webserver, поэтому он включен для IIS и веб-сервера VS dev):

<system.web>
    <httpModules>
        <add name="ContextDisposer" 
             type="MyNamespace.ContextDisposer" />
    </httpModules>
</system.web>

<system.webServer>
    <modules runAllManagedModulesForAllRequests="true">
        <add name="ContextDisposer" 
             type="MyNamespace.ContextDisposer" />
    </modules>
</system.webServer>