在1:n关系中,已经存在关键值为''的另一个实例正在被跟踪。

3
我正在尝试更新一个实体Tender,这个实体有一个TenderOffer列表,而这个TenderOffer又有一个Company实体。当我试图更新Tender时,我遇到了以下错误。

Message=The instance of entity type 'Company' cannot be tracked because another instance with the key value '{ID: 4}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.

Source=Microsoft.EntityFrameworkCore  StackTrace: 
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.ThrowIdentityConflict(InternalEntityEntryentry) 
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKeykey, InternalEntityEntry entry, Boolean updateDuplicate) 
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKeykey, InternalEntityEntry entry) 
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(InternalEntityEntryentry) 
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StartTracking(InternalEntityEntryentry) 
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityStateoldState, EntityState newState, Boolean acceptChanges, BooleanmodifyProperties) 
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityStateentityState, Boolean acceptChanges, Boolean modifyProperties,Nullable`1 forceStateWhenUnknownKey) 
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.PaintAction(EntityEntryGraphNode`1node) 
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph[TState](EntityEntryGraphNode`1node, Func`2 handleNode) 
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph[TState](EntityEntryGraphNode`1node, Func`2 handleNode) 
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph[TState](EntityEntryGraphNode`1node, Func`2 handleNode) 
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph[TState](EntityEntryGraphNode`1node, Func`2 handleNode) 
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.AttachGraph(InternalEntityEntryrootEntry, EntityState targetState, EntityStatestoreGeneratedWithKeySetTargetState, Boolean forceStateWhenUnknownKey)
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.SetEntityState(InternalEntityEntryentry, EntityState entityState) 
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.Update(TEntity entity) 
   at DAL.Repositories.TendersRepository.<UpdateTender>d__10.MoveNext() in C:\Progetti\DAL\Repositories\TendersRepository.cs:line 613 
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() 
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) 
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() 
   at UI.Controllers.TenderController.<SelectTenderWinner>d__13.MoveNext() in C:\Progetti\UI\Controllers\TenderController.cs:line 808
            
This exception was originally thrown at this call stack:
                [External Code] DAL.Repositories.TendersRepository.UpdateTender(ML.Models.Tender) in TendersRepository.cs
                [External Code]
                UI.Controllers.TenderController.SelectTenderWinner(int,int, bool) in TenderController.cs

我不明白为什么会出现这个追踪错误。我已经禁用了DBContext构造函数中的所有追踪行为。

base.ChangeTracker.AutoDetectChangesEnabled = false;
base.ChangeTracker.LazyLoadingEnabled = false;
base.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; 

我在所有的查询中都使用了AsNoTracking()。我无法理解为什么EF Core还是在进行跟踪。

entity_one是"Tender",entity_two是"TenderOffer"

以下是控制器中的代码:

try
{
    //id and winnerOfferID are parameters of the request

    Tender tender = await this._tenderRepository.GetTender(id, false, this.CurrentCompanyID);
    TenderOffer offer = tender.TenderOffers.FirstOrDefault(o => o.ID == winnerOfferID)
    tender.IsClosed = true;
    tender.ClosureDate = DateTime.UtcNow;
    tender.ShowWinner = showWinner;
    tender.WinnerOfferID = winnerOfferID;

    tender.TenderOffers.ForEach(o =>
    {
        if (o.ID == winnerOfferID)
        {
            o.Status = TenderOfferStatus.Winner;
        }
        else
        {
            o.Status = TenderOfferStatus.Rejected;
            o.Company = null;
        }
    });
    
    await this._tenderRepository.UpdateTender(tender);
    
    return Ok();
}
catch (Exception ex) 
{
    return StatusCode(500);
}

这是更新函数:

public async Task<Tender> UpdateTender(Tender tender)
{
        if (tender == null || tender.ID <= 0)
            throw new ArgumentNullException("Cannot update, tender is null");
        
    this._context.Tenders.Update(tender);

    await this.SaveChangesAsync();

    return tender;
}

这是 Get 函数:

public async Task<Tender> GetTender(int id, bool skipCompanyCheck = false, int? companyId = null)
{
    IQueryable<Tender> query = this._context.Tenders.AsNoTracking()
        .Include(t => t.Company).AsNoTracking()
        .Include(t => t.Company).ThenInclude(c => c.Logo).AsNoTracking();

        
    //Other include based on parameters, ALSO TENDER OFFERS
    

    tender = await query.Where(tender => tender.ID == id).FirstOrDefaultAsync();

    return tender;
}

1
仅仅因为你禁用了跟踪并不意味着你可以向集合中添加两个具有相同主键的实体 - 你希望EF如何处理这种情况?它既不能将它们都保存到表中,也不能决定哪一个是你想要的“主要”实体。 - Caius Jard
我没有添加任何东西,“Company”实体被跟踪是“Entity_Two”的创建者,当我更新“Entity_One”时,公司为空,只有“Entity_Two”的companyID被填充,我期望EF Core更新Entity_One和Entity_Two的列表而不更改公司。 - Majico
1
能否提供引发错误的实际代码。 "Entity_One","Entity_Two"等并不是很有帮助。 - Nikita Chayka
在更改之前,this._context.Tenders.Update(tender); 不需要被调用吗?阅读该方法的常见摘要。 - Seabizkit
1
你打了EF6的标签,请注意标签指南并选择正确的标签(我假设是ef-core-6.0)。 - Gert Arnold
显示剩余6条评论
1个回答

3
您可以在不跟踪的情况下检索招标,但是ChangeTracker选项并不意味着EF不能再进行更改跟踪。实际上,您需要它能够跟踪更改,以查看后续修改和SaveChanges调用。
语句this._context.Tenders.Update(tender);将对象图中的所有对象(即tender和相关子对象)标记为Modified。问题在于该图包含多个相同的Company实例,因为多个TenderOffer引用了同一家公司。
这会导致异常“已经在跟踪另一个具有键值'{ID: 4}'的实例”。
似乎可以假设EF应该知道它是同一个对象,但实际上它并不知道。这与AsNoTracking的行为有关。自EFC 3.0以来,AsNoTracking没有身份解析,这意味着当一个实体在查询结果中出现多次时,会创建多个实例。要更改此行为,您应该使用AsNoTrackingWithIdentityResolution()。(在一个LINQ语句中只需要调用一次即可。)
顺便说一下,如果您不包括Company并向TenderOffer添加一个可空的CompanyId属性,您可以将该属性简单地设置为null。当前的Update语句还将Company标记为Modified,这是没有必要的。这可以通过首先将Company实例附加到上下文来防止发生,但是设置CompanyId更容易且更轻量级。
另一种选择是使用跟踪方式获取数据,修改其属性,并在不调用Update的情况下保存更改。

2
OP,你正在尝试通过各种方法来避免更改跟踪,而实际上你的代码应该可以完美地工作,并且使用更改跟踪会简单得多。如果使用更改跟踪的代码出现问题,那么我会检查一下你的DbContext生命周期是否设置为Singleton之类的东西。它应该是PerWebRequest。 - Steve Py
这个可行,谢谢Gert,我会阅读更多关于设计的文档,感谢建议。 - Majico

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接