Включение одного подэлемента из свойства списка при использовании ThenInclude()

У меня есть функция, целью которой является получение аукциона по его идентификатору из базы данных и включение его самой высокой ставки и типа самой высокой ставки.

Я смог получить самую высокую ставку, используя эту линию

var result = _context.Auctions.
      Include(a => a.AuctionBids.OrderByDescending(b => b.BidAmount).Take(1)).
      FirstOrDefault(a => a.AuctionId == id);

Когда я добавил остальные, чтобы включить тип ставки, который я использовал

  var result = _context.Auctions.
      Include(a => a.AuctionBids.OrderByDescending(b => b.BidAmount).Take(1)).ThenInclude(b => b.AuctionBidType).AsNoTracking().
      FirstOrDefault(a => a.AuctionId == id);

Но потом я получаю сообщение об ошибке

Microsoft.Data.SqlClient.SqlException (0x80131904): Invalid column name 'AuctionBidTypeID'.
Invalid column name 'AuctionBidID'.
Invalid column name 'AuctionBidTypeID'.
Invalid column name 'BidAmount'.
Invalid column name 'BidDate'.
Invalid column name 'ExternalUserLoginID'.

Удаление .Take(1) работает, как и ожидалось, возвращая все ставки. Я пытался перемещаться .Take(1), но я просто получаю нулевой ответ или ошибку компилятора. Все, что указывает мне правильное направление, ценится

Я также проверил, используя First() вместо Take(1), это дает эту ошибку

System.InvalidOperationException: The expression 'a.AuctionBids.AsQueryable().OrderByDescending(b => b.BidAmount).First()' is invalid inside an 'Include' operation, since it does not represent a property access: 't => t.MyProperty'. To target navigations declared on derived types, use casting ('t => ((Derived)t).MyProperty') or the 'as' operator ('t => (t as Derived).MyProperty'). Collection navigation access can be filtered by composing Where, OrderBy(Descending), ThenBy(Descending), Skip or Take operations. For more information on including related data, see http://go.microsoft.com/fwlink/?LinkID=746393.

Упрощенные классы

Это упрощенные версии моих моделей

public partial class Auction
    {
        public Auction()
        {
            AuctionBids = new HashSet<AuctionBid>();
        }

        public long AuctionId { get; set; }
        public long AuctionTypeId { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public string LongDescription { get; set; }
        public decimal BidIncrement { get; set; }
        public decimal BidWarningCap { get; set; }
        public decimal Reserve { get; set; }
        public int CreatedById { get; set; }
        public DateTime CreatedDate { get; set; }
        public virtual AuctionType AuctionType { get; set; }
        public virtual ICollection<AuctionBid> AuctionBids { get; set; }
    }
public partial class AuctionBid
    {
        public long AuctionBidId { get; set; }
        public long AuctionId { get; set; }
        public int ExternalUserLoginId { get; set; }
        public long AuctionBidTypeId { get; set; }
        public DateTime BidDate { get; set; }
        public decimal BidAmount { get; set; }
        public virtual Auction Auction { get; set; }
        public virtual AuctionBidType AuctionBidType { get; set; }
    }
    public partial class AuctionBidType
    {
        public AuctionBidType()
        {
            AuctionBids = new HashSet<AuctionBid>();
        }

        public long AuctionBidTypeId { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public virtual ICollection<AuctionBid> AuctionBids { get; set; }
    }

🤔 А знаете ли вы, что...
C# имеет обширную библиотеку классов .NET, что упрощает разработку приложений.


1
51
1

Ответ:

Решено

Похоже, это ошибка в EF-core 5. Сгенерированный SQL-запрос (с SQL Server) содержит неверные псевдонимы в одной строке:

SELECT [t].[AuctionId], [t].[AuctionTypeId], [t].[BidIncrement], [t].[BidWarningCap], [t].[CreatedById], [t].[CreatedDate], [t].[Description], [t].[LongDescription], [t].[Name], [t].[Reserve], [t1].[AuctionBidId], [t1].[AuctionBidTypeId], [t1].[AuctionId], [t1].[BidAmount], [t1].[BidDate], [t1].[ExternalUserLoginId], [t1].[AuctionBidTypeId0], [t1].[Description], [t1].[Name]
  FROM (
      SELECT TOP(1) [a].[AuctionId], [a].[AuctionTypeId], [a].[BidIncrement], [a].[BidWarningCap], [a].[CreatedById], [a].[CreatedDate], [a].[Description], [a].[LongDescription], [a].[Name], [a].[Reserve]
      FROM [Auctions] AS [a]
      WHERE [a].[AuctionId] = CAST(1 AS bigint)
  ) AS [t]
  OUTER APPLY (
-- Wrong aliases in line below. [t] should be [t0]
      SELECT [t].[AuctionBidId], [t].[AuctionBidTypeId], [t].[AuctionId], [t].[BidAmount], [t].[BidDate], [t].[ExternalUserLoginId], [a1].[AuctionBidTypeId] AS [AuctionBidTypeId0], [a1].[Description], [a1].[Name]
      FROM (
          SELECT TOP(1) [a0].[AuctionBidId], [a0].[AuctionBidTypeId], [a0].[AuctionId], [a0].[BidAmount], [a0].[BidDate], [a0].[ExternalUserLoginId]
          FROM [AuctionBid] AS [a0]
          WHERE [t].[AuctionId] = [a0].[AuctionId]
          ORDER BY [a0].[BidAmount] DESC
      ) AS [t0] -- This alias
      INNER JOIN [AuctionBidType] AS [a1] ON [t].[AuctionBidTypeId] = [a1].[AuctionBidTypeId]
  ) AS [t1]
  ORDER BY [t].[AuctionId], [t1].[BidAmount] DESC, [t1].[AuctionBidId], [t1].[AuctionBidTypeId0]

Когда я пытаюсь выполнить аналогичный запрос в другой модели базы данных, берется правильный псевдоним, но я не могу сделать вывод, какая часть запроса или имена классов/свойств смущают EF.

К счастью, вы можете использовать обходной путь (по крайней мере, я могу с вашей моделью):

var result = _context.Auctions.AsNoTracking()
    .Include(a => a.AuctionBids.OrderByDescending(b => b.BidAmount).Take(1))
        .ThenInclude(b => b.AuctionBidType)
    .Where(a => a.AuctionId == id)
    .AsEnumerable()
    .FirstOrDefault();

Похоже, что FirstOrDefault(a => a.AuctionId == id) — это та часть, которая сбивает с толку генератор запросов. Замена его на Where запускает запрос. Тогда остается только переключиться на вычисление в памяти (.AsEnumerable()) и применить там .FirstOrDefault().

В EF-core 6 эта ошибка исправлена. Псевдоним [t1] используется там, где в приведенном выше запросе есть [t] и [t0].