-
Notifications
You must be signed in to change notification settings - Fork 0
Add result mapping function for nested queries #4
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
Open
magesoe
wants to merge
4
commits into
main
Choose a base branch
from
claude/add-query-result-mapper-011CUrHZytSyZRx4i5onBwAi
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Add result mapping function for nested queries #4
magesoe
wants to merge
4
commits into
main
from
claude/add-query-result-mapper-011CUrHZytSyZRx4i5onBwAi
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This commit adds functionality to map query results to dynamic objects with selected columns and linked entities: - Add Alias property to ExpandBuilder for tracking LinkEntity aliases - Generate unique aliases for all LinkEntity objects in QueryExpressionBuilder - Create ResultMapper class that transforms Entity objects into dynamic objects with only selected columns - Add EntityExtensions for extracting aliased attribute values from linked entities - Add GetResultMapper() method to QueryExpressionBuilder for creating mapping functions - Support nested linked entities with deep object mapping - Add comprehensive unit tests for all result mapper functionality The mapper handles: - Simple column selections from main entity - Linked entities with aliases - Multi-level nested linked entities - Missing values (returns null) - Multiple linked entities at the same level
This commit enhances the result mapper to support strongly-typed projections instead of dynamic objects:
- Add QueryProjection<TEntity> class for type-safe value extraction using expressions
- Add LinkedEntityProjection class for projecting nested linked entities
- Add generic GetResultMapper<TResult> method that returns Func<Entity, TResult>
- Add GetAliasMap() method to retrieve relationship-to-alias mappings
- Add HasAliasedValues() extension method to check for linked entity presence
- Deprecate dynamic ResultMapper in favor of strongly-typed approach
- Add comprehensive unit tests with DTO classes
Benefits:
- Full IntelliSense support for result properties
- Compile-time type checking
- Clear contract for what data is available
- Easier to refactor and maintain
- Support for nested linked entities with type safety
Usage example:
var mapper = builder.GetResultMapper(proj => new AccountDto
{
Name = proj.Get(a => a.Name),
Contact = proj.GetLinked(a => a.account_primary_contact,
contact => new ContactDto
{
FirstName = contact.Get<Contact, string>(c => c.FirstName)
})
});
This commit adds a complete C# source generator that analyzes queries at compile-time and generates strongly-typed result classes based on selected columns:
**Source Generator Features:**
- Analyzes QueryExpressionBuilder chains at compile-time
- Detects .Project() calls to trigger generation
- Generates result classes with properties for each selected column
- Generates nested classes for linked entities (Expand calls)
- Supports multi-level nested relationships
- Provides full IntelliSense and compile-time type safety
**New Components:**
- src/QueryBuilder.SourceGenerator/ - Complete source generator implementation
- QueryResultGenerator.cs - Main generator with syntax analysis
- Uses Roslyn APIs to parse query builder syntax trees
- Generates classes in DataverseQuery.Generated namespace
- src/QueryBuilder/ProjectionBuilder.cs - Builder for projections
- Wraps QueryExpressionBuilder for projection scenarios
- Provides .To<TResult>() method for type-safe mapping
- QueryExpressionBuilder.Project() - Marker method for generator
- Returns ProjectionBuilder<TEntity>
- Triggers source generator analysis
- test/QueryBuilder.Tests/SourceGeneratorProjectionTests.cs - Comprehensive tests
- Tests simple column selection
- Tests linked entity projections
- Tests nested linked entities
- Tests multiple linked entities
- PROJECTION_EXAMPLES.md - Complete usage documentation
**Usage Example:**
var projection = new QueryExpressionBuilder<Account>()
.Select(e => e.Name, e => e.AccountNumber)
.Expand(a => a.account_primary_contact,
c => c.Select(x => x.FirstName, x => x.LastName))
.Project();
var mapper = projection.To(proj => new
{
Name = proj.Get(a => a.Name),
AccountNumber = proj.Get(a => a.AccountNumber),
PrimaryContact = proj.GetLinked(
a => a.account_primary_contact,
contact => new
{
FirstName = contact.Get<Contact, string>(c => c.FirstName),
LastName = contact.Get<Contact, string>(c => c.LastName)
})
});
// Result has full IntelliSense support!
var results = entities.Select(mapper).ToList();
foreach (var result in results)
{
Console.WriteLine(result.Name); // Compile-time checked!
Console.WriteLine(result.PrimaryContact.FirstName); // Type-safe!
}
**Benefits:**
- Zero runtime overhead - all generation at compile-time
- Exact types matching selected columns
- Impossible to access properties not selected
- IntelliSense everywhere
- Refactoring-safe
This enhances the source generator to automatically create mapper extension methods, eliminating ALL boilerplate code:
**What's Auto-Generated Now:**
- Result classes (Query{N}Result) with exact selected properties
- Nested classes for linked entities
- **NEW: Extension methods (ToQuery{N}Result()) with complete mapper logic**
**Before (manual mapper required):**
```csharp
var projection = builder.Select(e => e.Name).Project();
var mapper = projection.To(proj => new
{
Name = proj.Get(a => a.Name)
});
```
**After (zero boilerplate):**
```csharp
var projection = builder.Select(e => e.Name).Project();
var mapper = projection.ToQuery0Result(); // Auto-generated!
```
**Generated Extension Method Example:**
```csharp
public static class Query0ResultExtensions
{
public static Func<Entity, Query0Result> ToQuery0Result(
this ProjectionBuilder<Account> projection)
{
return projection.To(proj => new Query0Result
{
Name = proj.Get(e => e.Name),
account_primary_contact = proj.GetLinked(
e => e.account_primary_contact,
linked => new Query0Result_account_primary_contact
{
FirstName = linked.Get<Contact, string>(x => x.FirstName),
LastName = linked.Get<Contact, string>(x => x.LastName)
})
});
}
}
```
**Changes:**
- Enhanced GenerateCode() to call GenerateExtensionMethod()
- Added GenerateExtensionMethod() to create ToQuery{N}Result() methods
- Extension methods handle main entity properties and nested linked entities
- Updated tests with comments showing auto-generated usage
- Updated PROJECTION_EXAMPLES.md with "Quick Start" section highlighting auto-generated mappers
**Benefits:**
- Absolutely ZERO mapping boilerplate code
- Just call .ToQuery{N}Result() and you're done
- Complete type safety maintained
- Full IntelliSense for result properties
- Handles nested linked entities automatically
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Generated with claude, not checked code yet.
Goal is to provide a mapper along with the query to get the result in a strongly typed way