Невозможно определить, какие два объекта прикреплены к разным объектам ObjectContext. - PullRequest
0 голосов
/ 08 июля 2019

Итак, я недавно взял систему (MVC / Entity Framework), которую мы имеем, и добавил в кучу кэшированных репозиториев, чтобы ускорить ее. Казалось, что при тестировании он работал нормально, но когда мы его выдавали, мы случайно получали эту ошибку от случайных пользователей:

Exception Message: The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects.

Stack Trace: at System.Data.Entity.Core.Objects.DataClasses.RelatedEnd.ValidateContextsAreCompatible(RelatedEnd targetRelatedEnd) at System.Data.Entity.Core.Objects.DataClasses.RelatedEnd.Add(IEntityWrapper wrappedTarget, Boolean applyConstraints, Boolean addRelationshipAsUnchanged, Boolean relationshipAlreadyExists, Boolean allowModifyingOtherEndOfRelationship, Boolean forceForeignKeyChanges) at System.Data.Entity.Core.Objects.ObjectStateManager.PerformAdd(IEntityWrapper wrappedOwner, RelatedEnd relatedEnd, IEntityWrapper entityToAdd, Boolean isForeignKeyChange) at System.Data.Entity.Core.Objects.ObjectStateManager.PerformAdd(IList`1 entries) at System.Data.Entity.Core.Objects.ObjectStateManager.DetectChanges() at System.Data.Entity.Internal.InternalContext.DetectChanges(Boolean force) at System.Data.Entity.Internal.InternalContext.GetStateEntries(Func`2 predicate) at System.Data.Entity.Infrastructure.DbChangeTracker.Entries() at System.Data.Entity.DbContext.GetValidationErrors() at System.Data.Entity.Internal.InternalContext.SaveChanges() at ClinicalCMS.Domain.CMSContext.SaveChanges() in C:\Projects\BeaconEDC\ClinicalCMS.Domain\CMSContext.cs:line 255 at BeaconEDC.Areas.DataEntry.Controllers.EngineController.MarkComplete(Int64 studyId, Int64 subjectId, Int64 formIteration, StudyAssignment assignment, String caseId, StudyForm studyForm) in C:\Projects\BeaconEDC\BeaconEDC\Areas\DataEntry\Controllers\EngineController.cs:line 375 at BeaconEDC.Areas.DataEntry.Controllers.EngineController.Edit(Int64 studyId, Int64 subjectId, Int64 formIteration, Int64 studyFormId, FormCollection collection) in C:\Projects\BeaconEDC\BeaconEDC\Areas\DataEntry\Controllers\EngineController.cs:line 312 at lambda_method(Closure , ControllerBase , Object[] ) at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters) at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c.b__9_0(IAsyncResult asyncResult, ActionInvocation innerInvokeState) at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`2.CallEndDelegate(IAsyncResult asyncResult) at System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethod(IAsyncResult asyncResult) at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<>c__DisplayClass11_0.b__0() at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<>c__DisplayClass11_2.b__2() at System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethodWithFilters(IAsyncResult asyncResult) at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass3_6.b__4() at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass3_1.b__1(IAsyncResult asyncResult)

Теперь я пишу в базу данных во многих местах, но она всегда сообщает об этом в одном и том же месте. Но я ничего не вижу в той части кода, которая использует что-либо из кэшированных репозиториев. Я буквально просто создаю новый объект, добавляю к нему кучу вещей (строки, long, целые числа или даты), добавляю его в контекст и сохраняю изменения.

Чтобы еще больше расстраивать, мне никогда не удавалось воспроизвести это в Visual Studio. Встречается только на сервере.

Итак, мой вопрос, есть ли способ получить больше информации из этого исключения? Какой-нибудь способ точно сказать, о каких двух объектах идет речь?

Вот бит, где он терпит неудачу:

HatterasGlobal global = repo.HatterasGlobal(iid, caseId, "FormStatus");
if (global == null)
{
    global = new HatterasGlobal();
    global.CaseID = caseId;
    global.GlobalName = "FormStatus";
    global.Mode = 1;
    global.Value = "C";
    global.Modified = DateTime.Now;
    global.HatterasInstrumentID = iid;
    global.StudyId = studyId;
    global.SubjectId = subjectId;
    global.FormIteration = formIteration;

    repo.Add(global);
}
else
{
    global.Value = "C";
    global.Modified = DateTime.Now;

    repo.SaveChanges();
}

// set the date complete
HatterasGlobal dateGlobal = repo.HatterasGlobal(studyForm.Form.IID, caseId, "DateComplete");
if (dateGlobal == null)
{
    dateGlobal = new HatterasGlobal();
    dateGlobal.CaseID = caseId;
    dateGlobal.GlobalName = "DateComplete";
    dateGlobal.Mode = 1;
    dateGlobal.Value = DateTime.Now.ToShortDateString();
    dateGlobal.Modified = DateTime.Now;
    dateGlobal.HatterasInstrumentID = iid;
    dateGlobal.StudyId = studyId;
    dateGlobal.SubjectId = subjectId;
    dateGlobal.FormIteration = formIteration;

    repo.Add(dateGlobal);
}
else
{
    dateGlobal.Value = DateTime.Now.ToShortDateString();
    dateGlobal.Modified = DateTime.Now;

    repo.SaveChanges();
}

Строка, на которую он фактически бомбит в трассировке стека, это парень:

HatterasGlobal dateGlobal = repo.HatterasGlobal(studyForm.Form.IID, caseId, "DateComplete");

Но я подозреваю, что у него проблема с repo.Add (global). Все, что делает это:

public void Add(HatterasGlobal global)
{
    _context.HatterasGlobals.Add(global);
    _context.SaveChanges();
}

Здесь прописан контекст (Unity):

container.RegisterType<CMSContext>(new PerRequestLifetimeManager(), new InjectionConstructor());
...
container.RegisterType<IDataEntryRepository, CachedDataEntryRepository>(new PerRequestLifetimeManager(), new InjectionConstructor(typeof(CMSContext)));

Внутри репо контекст инициализируется так:

protected CMSContext _context;
public EFDataEntryRepository(CMSContext context)
{
   this._context = context;
}

(EFDataEntryRepository является базовым классом для CachedDataEntryRepository.)

Вот пример того, как я делаю кеширование:

public override Study Study(long id)
        {
            var study = HttpRuntime.Cache["Study-" + id + "-" + HttpContext.Current.User.Identity.Name] as Study;
            if (study == null)
            {
                lock (CacheLockObject)
                {
                    study = HttpRuntime.Cache["Study-" + id + "-" + HttpContext.Current.User.Identity.Name] as Study;
                    if (study == null)
                    {
                        study = base.Study(id);
                        if (study != null) HttpRuntime.Cache.Insert("Study-" + id + "-" + HttpContext.Current.User.Identity.Name, study, null, _absolute, _sliding);
                    }
                }
            }

            return study;
        }

Это помогает?

1 Ответ

0 голосов
/ 09 июля 2019

"at ClinicalCMS.Domain.CMSContext.SaveChanges () ..." Вам необходимо погрузиться еще дальше вниз по стеку исключений за "...", что станет основной проблемой.

Обычным виновником этого будет код, который передает сущности вокруг.В качестве простого примера:

public IEnumerable<RelatedEntity> LoadRelated()
{
    using (var context = new MyDbContext())
    {
        return context.RelatedEntities.ToList();
    }
}

public void SaveEntity(ParentEntity entity)
{
    using (var context = new MyDbContext())
    {
        context.ParentEntities.Add(entity);
        context.SaveChanges()
    }
}

public void SomeAction(ParentEntityViewModel viewModel)
{
    var relatedEntities = LoadRelated();
    var relatedEntity = relatedEntities
        .OrderBy(x => x.ApplicableDate)
        .First(x => x.ApplicableDate >= DateTime.Now);
    var newEntity = new ParentEntity
    {
        Name = viewModel.Name,
        RelatedEntity = relatedEntity
     };
     SaveEntity(newEntity);
}

... снова, простой пример для демонстрации.Это обычно проявляется, когда код использует такие вещи, как шаблоны репозитория, которые используют разные ограниченные контексты (т. Е. Разные DbContexts, которые загружают одни и те же сущности) или когда в рассматриваемом DbContext есть несколько объявленных экземпляров, таких как разные области действия времени жизни, в зависимости от того, используете ли выКонтейнер IoC или нет.

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

Учитывая, что вы видите ошибку в производстве, но не в тестированииЯ бы заподозрил одну из двух вещей:

  1. Это редкий сценарий, когда пользователи отключаются, а вы не воспроизводите его при тестировании.
  2. Это проблема одновременного сеанса и вашего кешированияделает что-то непослушное.

Я склоняюсь к # 2, учитывая кэширование, и сущности никогда не должны быть чем-то, что действительно должно быть когда-либо сделано.Если ваш DbContext ограничен для запроса через контекст EF, и вы загружаете сущности, а затем выбрасываете их в статический кеш, пока этот запрос активен, другие запросы, которые будут вызывать другие DbContexts, могут выбирать объекты изкеш.Эти объекты будут связаны с DbContext другого запроса и, вероятно, приведут к ошибкам, подобным тому, что вы видите.Любое кэширование, которое вы выполняете, должно выполняться только в состоянии сеанса, а не в статическом / межсессионном.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...