Итак, я недавно взял систему (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;
}
Это помогает?