Подтвердить что ты не робот

EF. Контекст не может использоваться во время создания исключения во время HTTP-запросов

Я получаю "Контекст не может использоваться во время создания модели". в моем веб-приложении на одной из моих веб-страниц. Эта веб-страница отправляется на сервер каждые 2-3 секунды для обновления экрана. Из моего тестирования я обнаружил, что если у меня есть два или более экземпляра браузера, открытых на этой странице, через несколько минут я получаю исключение из контекста, которое не создается во время создания модели, из глубины репозитория.

Этот код вызывает "службу" для извлечения необходимых данных. Этот код выполняется в пользовательском атрибуте авторизации класса MVC Controller.

// Code in custom "Authorization" attribute on the controller
int? stationId = stationCookieValue;  // Read value from cookie
RoomStationModel roomStationModel = RoomStationService.GetRoomStation(stationId); // Error occurs inside this call

Вот "RoomStationModel"

public class RoomStationModel
{
    [Key]
    public int RoomStationId { get; set; }

    public int? RoomId { get; set; }
    [ForeignKey("RoomId")]
    public virtual RoomModel Room { get; set; }
    /* Some other data properties.... */
 }

public class RoomModel
{
    [Key]
    public int RoomId { get; set; }

    public virtual ICollection<RoomStationModel> Stations { get; set; }
}

Вот код для вызова службы выше:

public RoomStationModel GetRoomStation(int? roomStationId)
{
    RoomStationModel roomStationModel = null;
    if (roomStationId.HasValue)
    {
        using (IRepository<RoomStationModel> roomStationRepo = new Repository<RoomStationModel>(Context))
        {
            roomStationModel = roomStationRepo.FirstOrDefault(rs => rs.RoomStationId == roomStationId.Value, false, new string[] { "Room" });
        }
    }

    return roomStationModel;
}

Здесь репозиторий.... где происходит ошибка

    public class Repository<TObject> : IRepository<TObject> where TObject : class
    {
        protected MyContext Context = null;

        public Repository(IDataContext context)
        {
            Context = context as MyContext;
        }

        protected DbSet<TObject> DbSet { get { return Context.Set<TObject>(); } }

    public virtual TObject FirstOrDefault(Expression<Func<TObject, bool>> predicate, bool track = true, string[] children = null)
    {
        var objectSet = DbSet.AsQueryable();

        if (children != null)
            foreach (string child in children)
                objectSet = objectSet.Include(child);

        if (track)
            return objectSet.Where(predicate).FirstOrDefault<TObject>(predicate);

        return objectSet.Where(predicate).AsNoTracking().FirstOrDefault<TObject>(predicate);
    }
}

Снимок экрана с ошибкой: Screenshot of error occurring

StackTrace

  at System.Data.Entity.Internal.LazyInternalContext.InitializeContext()
   at System.Data.Entity.Internal.InternalContext.Initialize()
   at System.Data.Entity.Internal.InternalContext.GetEntitySetAndBaseTypeForType(Type entityType)
   at System.Data.Entity.Internal.Linq.InternalSet`1.Initialize()
   at System.Data.Entity.Internal.Linq.InternalSet`1.Include(String path)
   at System.Data.Entity.Infrastructure.DbQuery`1.Include(String path)
   at System.Data.Entity.DbExtensions.Include[T](IQueryable`1 source, String path)
   at Vanguard.AssetManager.Data.Repository`1.FirstOrDefault(Expression`1 predicate, Boolean track, String[] children) in C:\Work\VanguardAssetManager\Main\Vanguard.AssetManager.Data\Repository.cs:line 100
   at Vanguard.AssetManager.Services.Business.RoomStationService.GetRoomStation(Nullable`1 roomStationId) in C:\Work\VanguardAssetManager\Main\Vanguard.AssetManager.Services\Business\RoomStationService.cs:line 61
   at Vanguard.AssetManager.Web.Attributes.RoomStationAuthorizeAttribute.OnAuthorization(AuthorizationContext filterContext) in C:\Work\VanguardAssetManager\Main\Vanguard.AssetManager.Web\Attributes\RoomStationAuthorizeAttribute.cs:line 52
   at System.Web.Mvc.ControllerActionInvoker.InvokeAuthorizationFilters(ControllerContext controllerContext, IList`1 filters, ActionDescriptor actionDescriptor)
   at System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName)

EF Версия: 4.1 (сначала код)

4b9b3361

Ответ 1

Ваш репозиторий недолговечен (вы создаете его для каждого вызова GetRoomStation(), но ваш фактический контекст кажется долговечным (свойство RoomServiceStation.Context). Это означает, что каждый вызов вашего веб-приложения будет использоваться в том же контексте.

Это сценарий "EF в N-уровне", в котором вы пытаетесь сохранить что-то с точки зрения состояния (контекст) в архитектурной модели без учета состояния веб-приложения. Все эти запросы направляются в один и тот же контекст для разных потоков, и вы получаете условие гонки.

Один поток может инициировать первоначальную инициализацию вашего контекста в ответ на запрос, а другой - пытаться использовать контекст. Второй запрос предполагает, что контекст готов к использованию, и вы получаете это исключение. Вы даже можете получить это, если у вас есть несколько контекстов, пытающихся "развернуться" одновременно с предлагаемым в другом потоке SO.

Вы можете сделать несколько вещей. Вы можете попробовать пессимистичную блокировку доступа к вашему контексту, но вы ставите ненужное узкое место. Вы можете попытаться создать какой-то "до того, как клиенты вызовут меня, инициализируют контекстный" код, но вам нужно найти подходящее место для этого, возможно, используя метод "грубой силы" предложенный в потоке MSDN.

Лучше всего просто создать новый контекст для каждого запроса в вашем внутреннем сервисе. Есть некоторые накладные расходы, да, но минимальные. Накладные расходы, вероятно, менее вероятны для того, чтобы убить производительность, чем пессимистическая блокировка, и не будут подвержены событиям перезапуска приложений, масштабирующими ваше веб-приложение на ферме и т.д.

Если вы полагаетесь на отслеживание изменений или другой контекстный характер контекста, вы потеряете это преимущество. В этом случае вам придется придумать другой механизм отслеживания и сведения к минимуму ударов базы данных.

Из статьи MSDN это суммируется (внимание мое):

Если вы упорядочиваете объекты с одного уровня на другой, рекомендуется шаблон должен поддерживать контекст на среднем уровне достаточно долго для вызова одного вызова службы. Последующие вызовы вызовут новый экземпляр контекста для выполнения каждой задачи.

Поток на EF/WCF/N-уровне может также дать вам некоторые идеи, а Jorge сообщение в блоге # 5 говорит о EF в N-Tiers (вся серия может быть хорошей прочитанной). И, кстати, я столкнулся с одним и тем же: многие клиенты одновременно сталкиваются с контекстом, что приводит к этой проблеме.

Ответ 2

Я столкнулся с этой ошибкой и, похоже, решил ее, предоставив переопределение методу Dispose() в контроллере. Похоже, что принудительное закрытие соединения с базой данных, прежде чем пытаться открыть новую, подрывает эту ошибку.

protected override void Dispose(bool disposing)
{
   if(disposing)
   {
        _fooRepository.Dispose();
   }
   base.Dispose(disposing);
}

Ответ 3

Это выглядит как одна из двух вещей: какое-то гоночное состояние или проблема контекста. Вы должны убедиться, что контекст инициализируется поточно-безопасным способом и что контекст не обращается к различным потокам, чтобы предотвратить условия гонки. Жесткой причиной этой ошибки является также доступ к самой модели в переопределении OnModelCreation.

Ответ 4

Сегодня я столкнулся с этой проблемой. Проблема заключалась в том, что я случайно использовал один и тот же экземпляр моего DbContext для разных запросов. Первый запрос создаст экземпляр и начнет наращивать модель, и второй запрос будет включен и попытаться извлечь данные, пока он все еще строится.

Моя ошибка была глупой. Я случайно использовал HttpContext.Current.Cache вместо HttpContext.Current.Items:)