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

Error when using QueryContext for dataloaders for abstract classes #8024

Open
cenx1 opened this issue Feb 14, 2025 · 2 comments
Open

Error when using QueryContext for dataloaders for abstract classes #8024

cenx1 opened this issue Feb 14, 2025 · 2 comments

Comments

@cenx1
Copy link

cenx1 commented Feb 14, 2025

Product

Hot Chocolate

Version

15.0.3

Link to minimal reproduction

https://github.com/cenx1/HC15AbstractInheritanceProblem

Steps to reproduce

Use entities that inherit from an abstract class:

[InterfaceType]
public abstract class FarmAnimal
{
    public int Id { get; set; }
    public int FarmerId { get; set; }
    public virtual Farmer FarmerOwner { get; set; } = default!;
}

public class Cow : FarmAnimal
{
    public double AmountOfMilk { get; set; }
}

public class Pig : FarmAnimal
{
    public double AmountOfMeat { get; set; }
}

public class Cat : FarmAnimal
{
    public int Age { get; set; }
}

public class Farmer
{
    public int Id { get; set; }
    public string Name { get; set; } = default!;

    public virtual ICollection<FarmAnimal> FarmAnimals { get; set; } = [];
}

Use DbContext for the entities:

public class AnimalContext : DbContext
{
    public virtual DbSet<FarmAnimal> FarmAnimals { get; set; }
    public virtual DbSet<Farmer> Farmers { get; set; }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<FarmAnimal>(entity => { entity.UseTpcMappingStrategy(); });
        modelBuilder.Entity<Cow>(entity => { entity.ToTable("Cow"); });
        modelBuilder.Entity<Pig>(entity => { entity.ToTable("Pig"); });
        modelBuilder.Entity<Cat>(entity => { entity.ToTable("Cat"); });
    }
}

Create a dataloader that uses QueryContext that fetches a collection of entities that inherits from an abstract class:

[DataLoader]
public static async Task<Dictionary<int, Page<FarmAnimal>>> GetFarmAnimalsByFarmerIdAsync(
    IReadOnlyList<int> farmerIds,
    QueryContext<FarmAnimal> queryContext,
    PagingArguments pagingArgs,
    AnimalContext context,
    CancellationToken cancellationToken
)
{
    var result = await context.FarmAnimals
        .Where(fa => farmerIds.Contains(fa.FarmerId))
        .OrderBy(fa => fa.Id)
        .With(queryContext) // I believe this is the culprit
        .ToBatchPageAsync(x => x.FarmerId, pagingArgs, cancellationToken);

    return result;
}

Execute a query that queries farm animals on farmer:

query a {
  farmer {
    id
    name
    farmAnimals(pagingArguments: { first: 3 }) {
      id
      __typename
      ... on Cat {
        age
      }
      ... on Cow {
        amountOfMilk
      }
      ... on Pig {
        amountOfMeat
      }
    }
  }
}

What is expected?

The response returns something like:

{
  "data": {
    "farmer": {
      "id": "RmFybWVyOjE=",
      "name": "John Doe",
      "farmAnimals": {
        "edges": [
          {
            "node": {
              "id": 1,
              "__typename": "Cow",
              "amountOfMilk": 10
            }
          },
          {
            "node": {
              "id": 2,
              "__typename": "Pig",
              "amountOfMeat": 5
            }
          },
          {
            "node": {
              "id": 3,
              "__typename": "Cat",
              "age": 3
            }
          }
        ]
      }
    }
  }
}

What is actually happening?

Response:

{
  "errors": [
    {
      "message": "Unexpected Execution Error",
      "locations": [
        {
          "line": 5,
          "column": 5
        }
      ],
      "path": [
        "farmer",
        "farmAnimals"
      ],
      "extensions": {
        "message": "The LINQ expression 'DbSet<FarmAnimal>()\r\n    .Where(f => __farmerIds_0\r\n        .Contains(f.FarmerId))\r\n    .GroupBy(f => (f is Cow) ? (FarmAnimal)(FarmAnimal)new Cow{ \r\n        Id = ((Cow)f).Id, \r\n        AmountOfMilk = ((Cow)f).AmountOfMilk, \r\n        FarmerId = f.FarmerId \r\n    }\r\n     : (f is Pig) ? (FarmAnimal)(FarmAnimal)new Pig{ \r\n        Id = ((Pig)f).Id, \r\n        AmountOfMeat = ((Pig)f).AmountOfMeat, \r\n        FarmerId = f.FarmerId \r\n    }\r\n     : (f is Cat) ? (FarmAnimal)(FarmAnimal)new Cat{ \r\n        Id = ((Cat)f).Id, \r\n        Age = ((Cat)f).Age, \r\n        FarmerId = f.FarmerId \r\n    }\r\n     : null.FarmerId)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.",
      }
    }
  ],
  "data": {
    "farmer": {
      "id": "RmFybWVyOjE=",
      "name": "John Doe",
      "farmAnimals": null
    }
  }
}

Relevant log output

Additional context

No response

@michaelstaib
Copy link
Member

Yes, this is a known limitation. Batched paging with projections on abstract types will not work. So, far we have not found a solution for it.

@michaelstaib michaelstaib added this to the HC-16.0.0 milestone Feb 17, 2025
@cenx1
Copy link
Author

cenx1 commented Feb 18, 2025

Alright, thanks for answering.

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

No branches or pull requests

2 participants