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

Time:11-20

After migration from EF Core 3 to EF Core 6 this query:

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

started to throw error:

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'.

Changing to 'AsNoTracking()' gives another error:

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

Relationship configuration:

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

How to fix it?

CodePudding user response:

It seems to me that all you're trying to do is return an array of Variations where the Variation Id is in the list.

Also, your variation should not have a navigation property back to MetaProduct when it is owned. It should really only be retrieved in the context of its owner.

If you really want to navigate from Variation to MetaProduct then you should reconsider whether Variation really is an 'owned' entity or just a related entity.

The problem is that you are entering the query from MetaProducts but then trying to Include it again. If you can do away with the navigation from Variation to MetaProduct then the following will work:

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

If you really want to navigate the other way, then you'd need to promote Variation to a related entity (HasMany instead of OwnsMany), and then expose Variations as a DbSet on your context.

  • Related