You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免ASP.NET Core与EF Core中的实体跟踪异常

EF Core跟踪冲突问题:Airline实体重复跟踪异常解决

异常信息

实体类型'Airline'的实例无法被跟踪,因为已有键值为'{CarrierCode: DY}'的实例正在被跟踪。附加现有实体时,请确保每个键值仅附加一个实体实例。

触发场景

该异常出现在PassengerController的AddBaggage()方法中foreach循环的第二次迭代,触发代码行:

await _baggageRepository.AddAsync(newBaggage);

相关代码片段

PassengerController.AddBaggage方法

[HttpPost("{id}/flight/{flightId}/add-baggage")]
public async Task<ActionResult<Baggage>> AddBaggage(Guid id, int flightId,
            [FromBody] List<AddBaggageModel> addBaggageModels)
{
    var passenger = await _passengerRepository.GetPassengerByIdAsync(id);
    var selectedFlight = await _flightRepository.GetFlightByIdAsync(flightId);
    var destination = await _destinationRepository.GetDestinationByCriteriaAsync(f =>
    f.IATAAirportCode == addBaggageModels.FirstOrDefault().FinalDestination);            

    var baggageList = new List<Baggage>();

    foreach (var baggageModel in addBaggageModels)
    {
        var newBaggage = new Baggage
                {
                    PassengerId = passenger.Id,
                    Weight = baggageModel.Weight,
                    DestinationId = destination.IATAAirportCode
                };

        //...some code
    
        else if (baggageModel.TagType == TagTypeEnum.System)
        {
            var number = _baggageRepository.GetNextSequenceValue("BaggageTagsSequence");
            var baggageTag = new BaggageTag(selectedFlight.ScheduledFlight.Airline, number);
            newBaggage.BaggageTag = baggageTag;
        }

        await _baggageRepository.AddAsync(newBaggage); // 异常触发点:第二次迭代时重复引用同一Airline实例            
        
        baggageList.Add(newBaggage);

        var orderedFlights = passenger.Flights
                    .Where(f => f.Flight.DepartureDateTime >= selectedFlight.DepartureDateTime)
                    .OrderBy(f => f.Flight.DepartureDateTime)
                    .ToList();

        foreach (var connectingFlight in orderedFlights)
        {
            //...some code

            FlightBaggage flightBaggage = new FlightBaggage
                    {
                        Flight = connectingFlight.Flight,
                        Baggage = newBaggage,
                        BaggageType = baggageType
                    };

            newBaggage.Flights.Add(flightBaggage);

            //...some code
        }
    }

    await _baggageRepository.UpdateAsync(baggageList.ToArray());

    return Ok();
}

BaggageTag构造函数

public BaggageTag(Airline? airline, int number)
{
    Airline = airline;
    LeadingDigit = 0;
    Number = number;
    TagNumber = _GetBaggageTagNumber();
    TagType = TagTypeEnum.System;
}

FlightRepository.GetFlightByIdAsync方法

public async Task<Flight> GetFlightByIdAsync(int id)
{
    var CACHE_KEY = $"Flight_{id}";

    if (!_cache.TryGetValue(CACHE_KEY, out Flight flight))
    {
        flight = await _context.Flights.AsNoTracking()
                               .Include(_ => _.ScheduledFlight)
                               .ThenInclude(_ => _.DestinationFrom)
                               .Include(_ => _.ScheduledFlight)
                               .ThenInclude(_ => _.DestinationTo)
                               .Include(_ => _.ScheduledFlight)
                               .ThenInclude(_ => _.Airline)
                               .Include(_ => _.Aircraft)
                               .ThenInclude(_ => _.AircraftType)
                               .Include(_ => _.Boarding)
                               .Include(_ => _.ListOfBookedPassengers)
                               .ThenInclude(_ => _.Passenger)
                               .Include(_ => _.ListOfCheckedBaggage)
                               .ThenInclude(_ => _.Baggage)
                               .Include(_ => _.Seats)
                               .FirstOrDefaultAsync(_ => _.Id == id);

        if (flight != null)
        {
            _cache.Set(CACHE_KEY, flight, new MemoryCacheEntryOptions
            {
                SlidingExpiration = TimeSpan.FromMinutes(10)
            });
        }
    }

    return flight;
}

PassengerRepository.GetPassengerByIdAsync方法

public async Task<Passenger> GetPassengerByIdAsync(Guid id)
{
    return await _context.Passengers.AsNoTracking()
        .Include(_ => _.PNR)
        .Include(_ => _.FrequentFlyer)
        .Include(_ => _.TravelDocuments)
        .Include(_ => _.PassengerCheckedBags)
            .ThenInclude(_ => _.BaggageTag)
        .Include(_ => _.PassengerCheckedBags)
            .ThenInclude(_ => _.SpecialBag)
        .Include(_ => _.Flights)
            .ThenInclude(_ => _.Flight)
                .ThenInclude(_ => _.ScheduledFlight)
                    .ThenInclude(_ => _.Airline)
        .Include(_ => _.Comments)
            .ThenInclude(_ => _.PredefinedComment)
        .Include(_ => _.AssignedSeats)
        .Include(_ => _.SpecialServiceRequests)
            .ThenInclude(_ => _.SSRCode)
        .FirstOrDefaultAsync(_ => _.Id == id);
}

问题分析

使用AsNoTracking()查询出的Airline实体,在第一次循环中通过BaggageTag关联到Baggage并被添加到上下文后,ChangeTracker开始跟踪该Airline实例;第二次循环时,再次使用同一个未被跟踪的Airline实例关联新的BaggageTag,尝试添加时就会触发跟踪冲突——因为上下文已经存在一个同键的Airline实例在被跟踪。

移除AsNoTracking()能解决问题,但会增加内存开销,因此需要在保留优化的前提下修复异常。


解决方案

方案1:关联主键而非整个实体(推荐)

修改BaggageTag的构造逻辑,不直接传递Airline实体,而是传入其主键(如CarrierCode),通过外键关联而非实体引用:

// 修改BaggageTag构造函数
public BaggageTag(string airlineCarrierCode, int number)
{
    AirlineCarrierCode = airlineCarrierCode; // 假设这是Airline实体的外键属性
    LeadingDigit = 0;
    Number = number;
    TagNumber = _GetBaggageTagNumber();
    TagType = TagTypeEnum.System;
}

// 在Controller循环内调用
var baggageTag = new BaggageTag(selectedFlight.ScheduledFlight.Airline.CarrierCode, number);

如果实体没有显式外键属性,可在循环外先获取或附加Airline到上下文,确保循环内使用同一个已跟踪实例:

// 循环外提前处理Airline
var airlineCarrierCode = selectedFlight.ScheduledFlight.Airline.CarrierCode;
var trackedAirline = await _context.Airlines.FindAsync(airlineCarrierCode);
if (trackedAirline == null)
{
    trackedAirline = selectedFlight.ScheduledFlight.Airline;
    _context.Airlines.Attach(trackedAirline);
}

// 循环内使用该实例
var baggageTag = new BaggageTag(trackedAirline, number);

方案2:在仓储Add方法中检查跟踪状态

在通用仓储的AddAsync方法中,提前检查关联的Airline是否已被跟踪,替换为已跟踪实例或附加未跟踪实例:

// GenericRepository.AddAsync示例
public async Task AddAsync<T>(T entity) where T : class
{
    if (entity is Baggage baggage && baggage.BaggageTag?.Airline != null)
    {
        var existingAirline = await _context.Airlines.FindAsync(baggage.BaggageTag.Airline.CarrierCode);
        if (existingAirline != null)
        {
            // 用上下文已跟踪的实例替换未跟踪的
            baggage.BaggageTag.Airline = existingAirline;
        }
        else
        {
            // 附加未跟踪的Airline实例
            _context.Airlines.Attach(baggage.BaggageTag.Airline);
        }
    }
    await _context.Set<T>().AddAsync(entity);
}

方案3:分离查询后的Airline实例(不推荐)

如果必须保留原实体传递方式,可在查询后立即分离Airline,但可能引发其他跟踪问题:

// 在FlightRepository.GetFlightByIdAsync中,查询后分离Airline
if (flight != null)
{
    _context.Entry(flight.ScheduledFlight.Airline).State = EntityState.Detached;
    _cache.Set(CACHE_KEY, flight, new MemoryCacheEntryOptions
    {
        SlidingExpiration = TimeSpan.FromMinutes(10)
    });
}

额外优化建议

AddBaggage方法中循环内多次调用AddAsync,最后又调用UpdateAsync,存在冗余操作。可以将所有Baggage收集到列表后一次性添加,减少上下文跟踪压力:

// 移除循环内的await _baggageRepository.AddAsync(newBaggage);
baggageList.Add(newBaggage);

// 循环结束后批量添加
await _baggageRepository.AddRangeAsync(baggageList);
// 无需再调用UpdateAsync,AddRange已标记实体为Added状态
await _context.SaveChangesAsync();

内容的提问来源于stack exchange,提问作者Maicuss

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 02:15:55