如何避免 ASP.NET Core EF Core 中“无法跟踪实体类型 'xx' 的实例”异常

问题描述 投票:0回答:1

我在使用 Entity Framework Core 的 ASP.NET Core 应用程序中遇到问题,反复收到以下响应:

{
  "statusCode": 500,
  "message": "The instance of entity type 'Airline' cannot be tracked because another instance with the key value '{CarrierCode: DY}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached."
}

这发生在我的 PassengerController 中的 AddBaggage() 方法的 foreach 循环的第二次迭代期间,如下行:

await _baggageRepository.AddAsync(newBaggage);
。方法如下:

[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); // An exception is thrown here, on the second iteration, when we attempt to assign a reference to the same object/instance of Airline as in the first iteration in the constructor parameter.            
                
                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;
        }

我的 PassengerRepositoryFlightRepository 中也有以下代码:

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

我了解该问题源于 ChangeTracker 尝试附加已在上下文中跟踪的实体。但是,我不确定如何有效地避免这种情况。我读过有关使用

dbContext.Entry(entity).State = EntityState.Detached
分离实体的内容,但我不确定在哪里放置这行代码。

我应该将其添加到 PassengerRepository、FlightRepository 或 GenericRepository 中的 AddAsync()、UpdateAsync() 和 DeleteAsync() 等方法中吗?或者,我应该重写 AppDbContext 中的 SaveChangesAsync() 方法并将其包含在那里吗?

我非常感谢任何能够有效解决此问题的指导或替代解决方案。谢谢您的帮助!

我尝试从我的存储库中删除所有 AsNoTracking() 和 AsQueryable() 方法,并且一切正常。然而,我最初添加这些方法是为了减少内存开销,从那时起,我就一直遇到这个问题。

entity-framework asp.net-core repository-pattern .net-7.0 change-tracking
1个回答
0
投票

我想到了一些问题,但主要的怀疑是您的存储库方法正在使用

AsNoTracking()
(即 selectedFlight.ScheduledFlight.Airline)获取数据,但您试图将这些数据与此预订和关联属性的新实例相关联。当加载要关联的实体时,您不想使用
AsNoTracking()
,因为当
DbContext
可能已经在跟踪其他实例时,这将返回一个新的、分离的实体实例。这可能会导致您所看到的错误,或者由于索引限制而出现异常的情况,或者插入具有新 ID 的重复记录。 (它尝试插入关联的未跟踪实例)删除获取查询中的
AsNoTracking()
可能会解决该问题。

这是我不建议使用这样的存储库模式的原因之一,因为通过存储库的每个调用都是该存储库方法的单独关注点。有些调用可能需要相关数据,有些可能不需要。加载所有内容可能会很昂贵,因此“快速修复”可能是添加一个

AsNoTracking()
来提高性能,但这会导致意外的问题,其他代码(例如像这样的更新)将期望处理跟踪的引用。如果代码使用内置存储库模式 (DbSet),那么消费者可以更好地管理要包含的相关数据并优化
AsNoTracking()
以实现只读操作。

© www.soinside.com 2019 - 2024. All rights reserved.