首页 > 解决方案 > 无法确定哪两个对象附​​加到不同的 ObjectContext 对象

问题描述

所以我最近采用了我们拥有的一个系统(MVC/实体框架),并添加了一堆缓存存储库来加速它。它在测试中似乎工作正常,但是当我们推出它时,我们随机从随机用户那里得到这个错误:

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)

现在,我在很多地方写入数据库,但它总是在同一个地方报告这一点。但是我在代码的那部分中没有看到使用任何缓存存储库中的任何内容。我实际上只是创建一个新对象,向其中添加一堆东西(字符串、长整数、整数或日期),将其添加到上下文中,然后保存更改。

更令人沮丧的是,我从来没有能够在 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();
}

这是 Context 注册的地方(Unity):

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

在 repo 中,上下文被初始化如下:

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;
        }

这有帮助吗?

标签: c#entity-frameworkcaching

解决方案


“在 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);
}

...再次,一个简单的演示示例。这通常在代码使用诸如存储库模式之类的东西时表现出来,这些模式要么利用不同的有界上下文(即加载相同实体的不同 DbContext),要么所讨论的 DbContext 声明了多个实例,例如不同的生命周期范围,具体取决于您是否使用是否有 IoC 容器。

上面的示例使用了 2 个不同的 DbContext 实例。相关实体在第一个中加载,但 Save 使用第二个。当我们创建父实体并关联仍然由第一个实体跟踪的实体然后将其提供给第二个实体时,我们会收到您所看到的错误。

鉴于您在生产中看到错误但在测试中没有看到错误,我会怀疑以下两件事之一:

  1. 这是一种罕见的情况,即用户在测试时没有重现您会绊倒的情况。
  2. 这是一个并发会话问题,您的缓存正在做一些顽皮的事情。

我倾向于#2,因为缓存和实体从来都不是真正应该做的事情。如果您的 DbContext 通过 EF 上下文对每个请求进行限定,并且您正在加载实体然后将它们扔到静态缓存中,而该请求处于活动状态,则将产生其他 DbContext 的其他请求可能会从缓存中挑选出实体。这些实体将与其他请求的 DbContext 相关联,并且可能会导致您所看到的错误。您所做的任何缓存都应该只在会话状态下完成,而不是静态/跨会话。


推荐阅读