无法找出哪些两个对象附​​加到不同的ObjectContext对象

时间:2019-07-08 19:42:19

标签: c# entity-framework caching

因此,我最近使用了一个已有的系统(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();
}

在这里注册上下文(统一):

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 :(得分:0)

“在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容器。

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

鉴于您在生产中看到了错误,但在测试中却没有看到,我会怀疑以下两种情况之一:

  1. 这种罕见的情况是用户跳闸,您在测试时不会重现。
  2. 这是一个并发会话问题,您的缓存做得很顽皮。

考虑到缓存,我倾向于#2,而实体从来都不是真正应该做的事情。如果您的DbContext是通过EF上下文按请求确定范围的,并且您正在加载实体,然后将它们扔到静态缓存中,而该请求处于活动状态,则将生成其他DbContext的 other 请求可能会选择实体从缓存中。这些实体将与另一个请求的DbContext关联,并可能导致类似您所看到的错误。您所做的任何缓存都只能在会话状态下进行,而不能是静态/交叉会话。

相关问题