Conversation
|
Example usecase: public class User
{
public int Id { get; set; }
public string FirstName { get; set; } = "";
public string LastName { get; set; } = "";
[Expressive(Projectable = true)]
public string FullName
{
get => field ?? (LastName + ", " + FirstName);
init => field = value;
}
} |
src/ExpressiveSharp.MongoDB/Infrastructure/ExpressiveMongoIgnoreConvention.cs
Fixed
Show fixed
Hide fixed
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Pull request overview
This PR introduces Projectable properties via [Expressive(Projectable = true)] to ensure computed members participate correctly in “project-into-same-type” projection middleware scenarios (e.g., HotChocolate projections / AutoMapper ProjectTo), while still allowing ExpressiveSharp to translate the underlying formula into provider SQL/query expressions.
Changes:
- Added a new
[Expressive(Projectable = true)]mode with generator support, pattern recognition, and new diagnostics (EXP0021–EXP0029). - Added runtime/provider integration coverage via new EF Core SQLite + MongoDB integration tests and resolver tests ensuring registry keying and rewrite correctness.
- Added end-user documentation and site navigation entries for Projectable properties, projection middleware usage, and diagnostics reference.
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/ExpressiveSharp.Tests/Services/ExpressiveResolverTests.cs | Adds resolver-level tests ensuring Projectable expressions resolve by property getter and only capture the formula. |
| tests/ExpressiveSharp.MongoDB.IntegrationTests/Tests/ProjectableMongoIgnoreTests.cs | Adds Mongo integration coverage for ignoring Projectable properties in BSON and ensuring formula behavior survives round-trip. |
| tests/ExpressiveSharp.IntegrationTests/Tests/ProjectableExpressiveTests.cs | Adds provider-agnostic runtime semantics tests (formula vs. stored value) and expression expansion tests. |
| tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ProjectableTests.SimpleProjectableProperty_ManualBackingField.verified.txt | Verified generator output for manual nullable backing-field Projectable pattern. |
| tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ProjectableTests.SimpleProjectableProperty_FieldKeyword.verified.txt | Verified generator output for C# field keyword Projectable pattern. |
| tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ProjectableTests.ProjectableWithSetAccessor.verified.txt | Verified generator output for Projectable properties using set instead of init. |
| tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ProjectableTests.cs | Adds generator tests for happy paths, registry-key correctness, and negative/diagnostic cases. |
| tests/ExpressiveSharp.EntityFrameworkCore.IntegrationTests/Tests/Sqlite/ProjectableExpressiveSqlTests.cs | Adds EF Core SQLite tests for model ignoring + SQL inlining + member-init materialization behavior. |
| src/ExpressiveSharp.MongoDB/Infrastructure/ExpressiveMongoIgnoreConvention.cs | Introduces a Mongo convention to unmap [Expressive]/Projectable properties from BSON mapping. |
| src/ExpressiveSharp.MongoDB/ExpressiveMongoCollection.cs | Ensures the Mongo ignore convention is registered when using the collection wrapper entry point. |
| src/ExpressiveSharp.Generator/Models/ExpressiveAttributeData.cs | Adds Projectable attribute argument parsing into generator model. |
| src/ExpressiveSharp.Generator/Interpretation/ProjectablePatternRecognizer.cs | Adds IOperation-based pattern recognition for Projectable getter/setter shapes. |
| src/ExpressiveSharp.Generator/Interpretation/ExpressiveInterpreter.cs | Routes Projectable properties to specialized interpretation pipeline. |
| src/ExpressiveSharp.Generator/Interpretation/ExpressiveInterpreter.BodyProcessors.cs | Implements Projectable property validation, pattern extraction, and formula emission via existing pipeline. |
| src/ExpressiveSharp.Generator/Infrastructure/Diagnostics.cs | Adds new Projectable diagnostics EXP0021–EXP0029. |
| src/ExpressiveSharp.Abstractions/ExpressiveAttribute.cs | Adds public API surface bool Projectable { get; set; } with semantics documentation. |
| docs/reference/projectable-properties.md | New reference page detailing motivation, semantics, syntax, and restrictions. |
| docs/reference/expressive-attribute.md | Documents the new Projectable attribute property and links to the full reference. |
| docs/reference/diagnostics.md | Adds Projectable diagnostics overview and per-diagnostic guidance (EXP0021–EXP0029). |
| docs/recipes/projection-middleware.md | New recipe page for HotChocolate/AutoMapper projection middleware scenarios. |
| docs/guide/migration-from-projectables.md | Updates migration guide to include Projectable as a UseMemberBody replacement option. |
| docs/.vitepress/config.mts | Adds sidebar links for the new reference/recipe pages. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/ExpressiveSharp.Generator/Interpretation/ProjectablePatternRecognizer.cs
Outdated
Show resolved
Hide resolved
- Replace Unicode em-dashes with double-hyphens to match reference/recipe style - Rename "Further reading" → "See Also" in the projection-middleware recipe - Update UseMemberBody migration row to mention both replacement options - Add MongoDB BSON unmapping convention note Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
842a365 to
90c7a82
Compare
|
Hi @koenbeuk, that was quick! And yeh, I see what you meant by "odd-ball" Part of the rason I like this, is because (believe it or not) I wrote my own source generator on top of [Projectable(UseMemberBody = nameof(FullNameExpr))]
public partial string FullName { get; init; }
private string FullNameExpr => LastName + ", " + FirstName;And generated exactly: public partial string FullName
{
get => field ?? FullNameExpr;
init => field = value;
}The reason we had to do that, was because we use EF Core's lazy loading proxies in CQRS Command context for updates, and we needed projectable properties to evaluate correctly when lazily accessed. So this new approach automatically handles that for us. While it looks a bit odd at first glance, I think it works. I also like the fact, that unlike Out of curiousity was there a technical reason why you chose this syntax over |
|
Another question which may have an obvious answer. Is |
No technical reason. This PR is an attempt to provide proper support for the scenario you ran into.
Good point! but keeping the explicit flag preserves the intent signal at the declaration site and guards against accidental opt‑in plus a code analyzer can flag incorrect usage |
- Reject static backing fields in Pattern B of ProjectablePatternRecognizer. A static field would share materialized state across all instances, breaking per-entity semantics. Adds a snapshot test for the EXP0022 diagnostic. - Register ExpressiveMongoIgnoreConvention from the AsExpressive extension path (not just the ExpressiveMongoCollection<T> constructor). Document the ordering constraint with MongoDB's eager class-map caching, and recommend calling ExpressiveMongoIgnoreConvention.EnsureRegistered() at startup. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Allows a special form of Expressive members, projectables! (No relation to the original project).
Problem: Expressive properties silently return garbage when consumed by projection middleware (HotChocolate, AutoMapper ProjectTo, Mapperly). The middleware drops read-only members from its Select(src => new Entity { ... }) binding because they fail a CanWrite check, so the query fetches nothing and the getter runs against empty defaults.
Solution:
[Expressive(Projectable = true)]opts the property into a writable field ?? () shape. The middleware sees a writable target and emits the binding; ExpressiveSharp extracts the formula and pushes it into SQL; the result materializes through init.