Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compiled query that includes subclass throws exception #3464

Closed
psampaio opened this issue Oct 8, 2024 · 6 comments
Closed

Compiled query that includes subclass throws exception #3464

psampaio opened this issue Oct 8, 2024 · 6 comments

Comments

@psampaio
Copy link

psampaio commented Oct 8, 2024

I have the following query working:

IList<Product> orderItems = [];
IList<User> users = [];

var order = await session.Query<Order>()
    .Include(o => o.OrderItemId, orderItems)
    .Include(o => o.UserId, users)
    .SingleOrDefaultAsync(o => o.Id == message.OrderId, cancellationToken);

where Product derives from the abstract OrderItem. It's registered as:

For<OrderItem>()
    .AddSubClass<Product>()
    .AddSubClass<Subscription>();

I converted it to the following compiled query:

public class OrderDetails : ICompiledQuery<Order, Order?>
{
    public Guid OrderId { get; init; }

    public IList<OrderItem> OrderItems { get; } = [];
    public IList<User> Users { get; } = [];

    public Expression<Func<IMartenQueryable<Order>, Order?>> QueryIs()
    {
        return q => q
            .Include(o => o.OrderItemId, OrderItems)
            .Include(o => o.UserId, Users)
            .SingleOrDefault(o => o.Id == OrderId);
    }
}

This throws System.IndexOutOfRangeException: Column must be between 0 and 1. I've also tried this making the compiled query generic, constraining the OrderItem list (I usually know the specific type when querying) and get the same exception.

My guess is that somewhere in the chain IncludeQueryHandler > IncludeReader > CastingSelector, it's missing the right type to load, but I can't find where that is.

Before I try to dig a bit deeper, is this something that should work?

@jeremydmiller
Copy link
Member

Yes it's something that should work. It's much more likely that the issue is because of the Include() statements than anything else.

@jeremydmiller
Copy link
Member

@psampaio "Works on my box"

This test passes in master:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Marten;
using Marten.Linq;
using Marten.Testing.Harness;
using Xunit;
using Shouldly;

namespace DocumentDbTests.Bugs;

public class Bug_3464_Include_with_SingleOrDefault : BugIntegrationContext
{
    [Fact]
    public async Task can_make_the_query()
    {
        StoreOptions(opts =>
        {
            opts.Schema.For<OrderItem>().AddSubClass<Product>()
                .AddSubClass<Subscription>();
        });

        var product = new Product();
        theSession.Store(product);

        var user = new User3464();
        theSession.Store(user);

        var order = new Order { UserId = user.Id, OrderItemId = product.Id };
        theSession.Store(order);

        await theSession.SaveChangesAsync();

        IList<Product> orderItems = [];
        IList<User3464> users = [];

        var orderId = order.Id;

        var order2 = await theSession.Query<Order>()
            .Include(o => o.OrderItemId, orderItems)
            .Include(o => o.UserId, users)
            .SingleOrDefaultAsync(o => o.Id == orderId);

        order2.ShouldNotBeNull();
        orderItems.Single().Id.ShouldBe(product.Id);
        users.Single().Id.ShouldBe(user.Id);
    }
}

public class OrderDetails : ICompiledQuery<Order, Order?>
{
    public Guid OrderId { get; init; }

    public IList<OrderItem> OrderItems { get; } = [];
    public IList<User3464> Users { get; } = [];

    public Expression<Func<IMartenQueryable<Order>, Order?>> QueryIs()
    {
        return q => q
            .Include(o => o.OrderItemId, OrderItems)
            .Include(o => o.UserId, Users)
            .SingleOrDefault(o => o.Id == OrderId);
    }
}

public class Order
{
    public Guid Id { get; set; }
    public Guid OrderItemId { get; set; }
    public Guid UserId { get; set; }
}

public class User3464
{
    public Guid Id { get; set; }
}

public abstract class OrderItem
{
    public Guid Id { get; set; }

}

public class Product: OrderItem
{

}

public class Subscription: OrderItem
{

}

Can you spot anything different about what I'm doing compared to your code?

@jeremydmiller
Copy link
Member

And next time, please post the full stack trace

@jeremydmiller
Copy link
Member

You aren't maybe getting burned by having not migrated the database after adding the sub classing? Or stale generated code? Just not seeing anything wrong on my end.

@psampaio
Copy link
Author

psampaio commented Oct 9, 2024

And next time, please post the full stack trace

Sorry about that. I wanted to diagnose it myself before asking for more help, in case this was working as intended. I wasn't expecting you to proactively create a test for this. And I really appreciate it.

Going back to the issue, your test actually fails if you replace the ad-hoc query:

var order2 = await theSession.Query<Order>()
            .Include(o => o.OrderItemId, orderItems)
            .Include(o => o.UserId, users)
            .SingleOrDefaultAsync(o => o.Id == orderId);

with the compiled query:

var query = new OrderDetails { OrderId = orderId };
var order2 = await theSession.QueryAsync(query);
orderItems = query.OrderItems.OfType<Product>().ToList();
users = query.Users.ToList();

Stack trace:

System.IndexOutOfRangeException: Column must be between 0 and 1

System.IndexOutOfRangeException
Column must be between 0 and 1
   at Npgsql.NpgsqlDataReader.ThrowColumnOutOfRange(Int32 maxIndex)
   at Npgsql.NpgsqlDataReader.HandleInvalidState(ReaderState state, Int32 maxColumns)
   at Npgsql.NpgsqlDataReader.<GetInfo>g__Slow|133_0(ColumnInfo& info, PgConverter& converter, Size& bufferRequirement, Boolean& asObject, <>c__DisplayClass133_0&)
   at Npgsql.NpgsqlDataReader.GetFieldValueCore[T](Int32 ordinal)
   at Npgsql.NpgsqlDataReader.GetFieldValueAsync[T](Int32 ordinal, CancellationToken cancellationToken)
   at Marten.Generated.DocumentStorage.LightweightOrderItemSelector894903466.ResolveAsync(DbDataReader reader, CancellationToken token)
   at Marten.Linq.Includes.IncludeReader`1.ReadAsync(DbDataReader reader, CancellationToken token) in /Users/psampaio/Projects/github/marten/src/Marten/Linq/Includes/IncludeReader.cs:line 35
   at Marten.Linq.Includes.IncludeQueryHandler`1.HandleAsync(DbDataReader reader, IMartenSession session, CancellationToken token) in /Users/psampaio/Projects/github/marten/src/Marten/Linq/Includes/IncludeQueryHandler.cs:line 59
   at Marten.Internal.Sessions.QuerySession.ExecuteHandlerAsync[T](IQueryHandler`1 handler, CancellationToken token) in /Users/psampaio/Projects/github/marten/src/Marten/Internal/Sessions/QuerySession.Execution.cs:line 100
   at Marten.Internal.Sessions.QuerySession.ExecuteHandlerAsync[T](IQueryHandler`1 handler, CancellationToken token) in /Users/psampaio/Projects/github/marten/src/Marten/Internal/Sessions/QuerySession.Execution.cs:line 100
   at Marten.Internal.Sessions.QuerySession.QueryAsync[TDoc,TOut](ICompiledQuery`2 query, CancellationToken token) in /Users/psampaio/Projects/github/marten/src/Marten/Internal/Sessions/QuerySession.Querying.cs:line 101
   at DocumentDbTests.Bugs.Bug_3464_Include_with_SingleOrDefault.can_make_the_query() in /Users/psampaio/Projects/github/marten/src/DocumentDbTests/Bugs/Bug_3464_Include_with_SingleOrDefault.cs:line 45
   at Xunit.Sdk.TestInvoker`1.<>c__DisplayClass46_0.<<InvokeTestMethodAsync>b__1>d.MoveNext() in /_/src/xunit.execution/Sdk/Frameworks/Runners/TestInvoker.cs:line 253
--- End of stack trace from previous location ---
   at Xunit.Sdk.ExecutionTimer.AggregateAsync(Func`1 asyncAction) in /_/src/xunit.execution/Sdk/Frameworks/ExecutionTimer.cs:line 48
   at Xunit.Sdk.ExceptionAggregator.RunAsync(Func`1 code) in /_/src/xunit.core/Sdk/ExceptionAggregator.cs:line 90

I'm going to look into the other tests to see if I can figure it out.

@psampaio
Copy link
Author

psampaio commented Oct 9, 2024

TBH, should that compiled query even work? I can't debug into the IncludeReader generation (dynamic code?), but I wouldn't expect it to add a reader for all subclasses, if that even makes sense?

I also tried changing the compiled query to be generic, and the correct IncludeReader is used, but I get the same error.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants