【问题标题】:Cannot figure out which two objects are attached to different ObjectContext objects无法确定哪两个对象附​​加到不同的 ObjectContext 对象
【发布时间】:2019-11-18 08:45:26
【问题描述】:

所以我最近采用了一个我们拥有的系统(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;
        }

这有帮助吗?

【问题讨论】:

  • 抛出这个的代码是什么样的?我认为它与您的缓存存储库无关,看起来您正在混合上下文
  • 您必须包含一些代码。对于新实体,“向其中添加一堆东西(字符串、长整数、整数或日期)”不会导致此错误。
  • 嘿,对不起,让我看看我可以在那里注入什么代码。我正在使用 Unity 注入上下文,所以它应该都来自同一个上下文。

标签: c# entity-framework caching


【解决方案1】:

“在 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 上下文对每个请求进行限定,并且您正在加载实体然后将它们扔到静态缓存中,而该请求处于活动状态,other 将产生其他 DbContexts 的请求可能会挑选出实体从缓存中。这些实体将与其他请求的 DbContext 相关联,并可能导致您所看到的错误。您所做的任何缓存都应该只在会话状态下完成,而不是静态/跨会话。

【讨论】:

  • 我明白......虽然我确保在将有问题的记录写入数据库时​​不使用其他实体。我只通过 ID 引用它们。我认为我的缓存做错了什么,我就是不知道是什么!
  • 您能否添加声明“repo”实例的代码,以便我们了解您所说的“缓存”存储库是什么意思。 Add 方法没有像我所怀疑的那样缓存实体,因此看起来存储库本身(及其上下文)可以被缓存/全局。另外,如何在 Repos 中声明/初始化 DbContext?
  • 已添加。还添加了完整的堆栈跟踪。就 CachedRepository 而言,我不会缓存整个存储库……只是某些经常访问的对象。这还不包括上面写的对象。
  • 好的,谢谢.. 我仍然看不到这个“缓存”实现是由什么组成的。 “CachedDataEntryRepository”做了哪些基本存储库没有做的事情?存储库上下文的范围仅限于请求,但根据您正在执行的“缓存”操作,我怀疑实体引用正在针对未处理的上下文进行缓存。最后,什么是“formIteration”?是字符串还是实体?
  • FormIteration 很长。我将在上面放置一个缓存位的示例。基本上我做了这样的事情:ardalis.com/introducing-the-cachedrepository-pattern
猜你喜欢
  • 2012-02-23
  • 2012-08-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多