如何修复因从 EF Core 3 迁移到 EF Core 6 而中断的循环查询

How To Fix Query With Cycles Broken By Migration from EF Core 3 To EF Core 6

从 EF Core 3 迁移到 EF Core 6 后,此查询:

private async Task<Variation[]> GetPizzasInOrder(Uuid[] productsInOrder, CancellationToken ct)
{
    return await _clientCheckupsGatewayContext.MetaProducts
        .SelectMany(mp => mp.Variations)
        .Where(v => productsInOrder.Contains(v.Id))
        .Include(v => v.MetaProduct)
        .ToArrayAsync(ct);
}

开始抛出错误:

System.InvalidOperationException: A tracking query is attempting to project an owned entity without a corresponding owner in its result, but owned entities cannot be tracked without their owner. Either include the owner entity in the result or make the query non-tracking using 'AsNoTracking'.

更改为 'AsNoTracking()' 会出现另一个错误:

private async Task<Variation[]> GetPizzasInOrder(Uuid[] productsInOrder, CancellationToken ct)
{
    return await _clientCheckupsGatewayContext.MetaProducts
        .AsNoTracking()
        .SelectMany(mp => mp.Variations)
        .Where(v => productsInOrder.Contains(v.Id))
        .Include(v => v.MetaProduct)
        .ToArrayAsync(ct);
}

System.InvalidOperationException: The Include path 'MetaProduct->Variations' results in a cycle. Cycles are not allowed in no-tracking queries; either use a tracking query or remove the cycle.

public class MetaProduct
{
    public Uuid Id { get; }
    public IReadOnlyList<Variation> Variations => _variations.ToArray();
    private List<Variation> _variations = null!;
}

public class Variation
{
    public Uuid Id { get; }

    public MetaProduct? MetaProduct { get; }
}

关系配置:

private static void MapMetaProducts(ModelBuilder modelBuilder)
{
    var tagsConverter = new ValueConverter<string[], string>(
        v => JsonConvert.SerializeObject(v),
        v => JsonConvert.DeserializeObject<string[]>(v)
    );

    var builder = modelBuilder.Entity<MetaProduct>().ToTable("metaproducts");
    builder.HasKey(p => p.Id);

    builder.Property(p => p.Id).HasColumnName("Id");

    builder.OwnsMany(mp => mp.Variations,
        vBuilder =>
        {
            vBuilder.ToTable("metaproducts_variations");

            vBuilder.WithOwner(v => v.MetaProduct!);

            vBuilder.Property(v => v.Id).HasColumnName("Id");

            vBuilder.HasKey("Id");
        });
}

如何解决?

在我看来,您要做的只是 return 变体数组,变体 ID 在列表中。

另外,当您的变体被拥有时,它不应该有返回到 MetaProduct 的导航 属性。它实际上应该只在其所有者的上下文中检索。

如果您真的想从 Variation 导航到 MetaProduct,那么您应该重新考虑 Variation 是否真的是一个 'owned' 实体或只是一个相关实体。

问题是您正在从 MetaProducts 输入查询,然后尝试再次包含它。如果您可以取消从 Variation 到 MetaProduct 的导航,那么以下将起作用:

return await _clientCheckupsGatewayContext.MetaProducts
    .AsNoTracking()
    .SelectMany(mp => mp.Variations)
    .Where(v => productsInOrder.Contains(v.Id))
    .ToArrayAsync(ct);

如果您真的想以其他方式导航,那么您需要将 Variation 提升到相关实体(HasMany 而不是 OwnsMany),然后在您的上下文中将 Variations 公开为 DbSet。