Cross-Schema Data Loaders

EF Core cannot JOIN across two DbContext instances, so a GraphQL field that resolves an entity owned by a different schema/context has no SQL join available and would otherwise run one query per parent row (N+1). CrossSchemaLoader<TContext, TEntity> (in Trax.Api.GraphQL) batches those lookups: every key requested in a GraphQL request collapses into a single WHERE id IN (...) against the owning context.

CrossSchemaLoader

A GreenDonut.BatchDataLoader<int, TEntity> keyed by integer Id. It injects the pooled IDbContextFactory<TContext> and creates a short-lived context per batch (a DbContext is not thread-safe). You do not subclass it; you reference the closed type in an edge resolver.

[ExtendObjectType(typeof(Loan))]
public sealed class LoanToBookEdge
{
    public async Task<Book?> GetBook(
        [Parent] Loan loan,
        CrossSchemaLoader<CatalogDbContext, Book> books,
        CancellationToken ct
    ) => await books.LoadAsync(loan.BookId, ct);
}

The target entity must have an integer Id primary key. Because HotChocolate projection only loads selected columns, a query selecting the edge must also select its foreign key (here bookId).

CrossSchemaEdge and registration

Declare every edge in one manifest (the single source of truth the architecture guards verify), and register one loader per distinct target pair:

public static readonly IReadOnlyList<CrossSchemaEdge> All =
[
    new(Source: typeof(Loan), Fk: nameof(Loan.BookId), Target: typeof(Book),
        TargetContext: typeof(CatalogDbContext), FieldName: "book"),
];
 
// In your service registration:
services.AddCrossSchemaLoader<CatalogDbContext, Book>();

Cross-schema edge resolvers live in their own project (the one place allowed to reference more than one domain context), keeping the domains isolated from each other.

MemberDescription
CrossSchemaLoader<TContext, TEntity>Batched loader resolving TEntity (integer Id PK) from TContext.
CrossSchemaEdgeManifest entry: Source, Fk, Target, TargetContext, FieldName.
AddCrossSchemaLoader<TContext, TEntity>()Registers the loader for one target pair.