Support schema types that share one dotnet type (EF property bags) by lukemurray · Pull Request #581 · EntityGraphQL/EntityGraphQL · GitHub
Skip to content

Support schema types that share one dotnet type (EF property bags) - #581

Open
lukemurray wants to merge 1 commit into
mainfrom
feature/property-bag-fields
Open

lukemurray wants to merge 1 commit into
mainfrom
feature/property-bag-fields

Conversation

@lukemurray

Copy link
Copy Markdown
Collaborator

Lets several schema types share one dotnet type - the case for EF Core shared-type entities, where tables known only at runtime are property bags (Dictionary<string, object>) with no class per table. Each field names its schema type with Returns("Type"), and the schema now takes it from the field.

var author = schema.AddType<Dictionary<string, object>>("Author", null);
var priority = schema.AddType<Dictionary<string, object>>("Priority", null);
priority.AddField("score", p => EF.Property<int>(p, "score"), null);
priority.AddField("author", p => EF.Property<Dictionary<string, object>>(p, "author"), null).Returns("Author");
schema.Query().AddField("priorities", db => db.Set<Dictionary<string, object>>("priorities"), null)
    .Returns("Priority").UseFilter().UseSort(useSchemaFields: true);

What was broken, and the fix

Before Fix
UseFilter() Looked its context up by dotnet type, getting the first schema type registered for it - Field 'score' not found on type 'Author' Starts from the filtered field's schema type and follows each field's through references (author.name) and collection methods (priorities.any(...)), tracked on EqlCompileContext. Falls back to the dotnet-type lookup where the field's schema type is not of the expression's - a paged field's Page/Connection wrapper over its collection - so paging and aggregate filters are unchanged.
UseSort() on a field with Returns("Type") Value cannot be null. (Parameter 'type') Returns(string) put the schema type's dotnet type in ReturnType.TypeDotnet, so a list field had no element type. It now keeps the field's own type (List<T> etc.), as GqlTypeInfo does everywhere else.
A field returning a dictionary Treated as a list of its key/value pairs - Could not find extension method Select A field returning the named type itself is a single object, even when that type is enumerable.

ExpressionUtil.BuildEntityQueryExpression gains an overload taking the schema type; the existing signature is kept, so this is binary compatible.

One behaviour change, called out in the changelog: ReturnType.TypeDotnet of a field that used Returns("Type") is now the field's own dotnet type rather than the named type's. Code reading it as the element type should use GetEnumerableOrArrayType().

Version

main was at 6.3.1, which has not been released (NuGet's latest is 6.3.0). This adds to the API, so it bumps to 6.4.0, and the unreleased 6.3.1 changelog section becomes 6.4.0's.

Tests

  • SharedDotnetTypeTests (in memory): selection, both directions of a relation, filter (direct, through a reference, through any), sort, and ReturnType after Returns.
  • SharedTypeEntityTests (EF Core over SQLite, which throws if anything does not translate): the same over real shared-type entities.
  • Without the library changes, 12 of the 13 new tests fail (one-to-many already worked); the whole suite passes on net8.0, net9.0 and net10.0.

Found while building dynamic per-app schemas for Tredle, where each app's tables come from its own migrations.

Also noticed: .config/dotnet-tools.json names the CSharpier command dotnet-csharpier, but CSharpier 1.0.0 ships it as csharpier, so dotnet tool restore fails. Not changed here.

🤖 Generated with Claude Code

Tables known only at runtime map to EF shared-type entities - property
bags, Dictionary<string, object>, with no class per table - so several
schema types share one dotnet type and each field names its type with
Returns("Type"). Three places took the type from the dotnet type instead:

- UseFilter looked its context up by dotnet type and got the first schema
  type registered for it. The filter now starts from the filtered field's
  schema type and follows each field's through references and collection
  methods, falling back to the dotnet type where the field's schema type
  is not of the expression's (a paged field's Page/Connection wrapper).
- Returns(string) put the schema type's dotnet type in ReturnType, leaving
  a list field with no element type, so UseSort failed on it. It now keeps
  the field's own dotnet type, as GqlTypeInfo does everywhere else.
- A dictionary is enumerable, so a field returning one was a list. A field
  returning the named type itself is now a single object.

Bumps to 6.4.0: main's 6.3.1 has not been released, and this adds to the
API.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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

Successfully merging this pull request may close these issues.

1 participant