RelationQuery.Expression()
- What it contributes
- A typed authoring session that lowers expressions into canonical relation/query IR.
Cohesive.Relations Guide
Author, evaluate, and materialize a typed relation using familiar C# expressions. Start with one supplied CLR object, then add related facts only when the definition needs them.
The expression surface is the normal application API for Cohesive.Relations. It lets you describe sources, relationships, and projections with typed C# while Cohesive keeps the resulting definition available to evaluation and tooling.
This guide creates a rooted Load → LoadDto relation, executes it from a supplied object, and compiles the canonical result into a CLR DTO. It then extends the definition with a Customer relationship.
Add the current prerelease package to a .NET 10 project:
dotnet add package Cohesive.Relations --prereleaseImport the namespaces used by this walkthrough:
using System.Text.Json.Serialization;
using Cohesive.Relations.Authoring;
using Cohesive.Relations.Execution;
using Cohesive.Relations.Mapping;The current published prerelease is 0.1.0-alpha.6. Keep the application and documentation on the same prerelease while APIs are evolving.
Start with ordinary CLR types. Serialized names participate in the deterministic shape metadata discovered by the authoring session.
public sealed class Load
{
[JsonPropertyName("id")]
public required string Id { get; init; }
[JsonPropertyName("customerId")]
public required string CustomerId { get; init; }
[JsonPropertyName("status")]
public required string Status { get; init; }
}
public sealed class LoadDto
{
[JsonPropertyName("id")]
public required string Id { get; init; }
[JsonPropertyName("status")]
public required string Status { get; init; }
}Source<T>() discovers a graph-qualified shape document from the CLR contract. Explicit imported shape documents and member-path overrides remain available for tooling and schemas that should not be convention-derived.
Create an expression authoring session, declare the logical source, project the DTO, and finish with a rooted relation terminal:
var author = RelationQuery.Expression();
var loads = author.Source<Load>();
var loadDtos = author.Project(
loads,
(Load load) => new LoadDto
{
Id = load.Id,
Status = load.Status
});
var relation = loadDtos.BuildRelation(dto => dto.Id);That is the complete semantic definition. The authoring calls have already produced canonical source, projection, assignment, binding, shape, identity, and relation-output structures.
Inspect relation.Validation for structured authoring diagnostics. When persistence or transport is needed, relation.CreateDocument() creates the validated and fingerprinted canonical envelope.
A definition is reusable. An evaluation is one invocation with an explicit identity, parameter values, selected outputs, and supplied-root evidence.
Create one Load and supply it to an evaluation:
var load = new Load
{
Id = "load-42",
CustomerId = "customer-7",
Status = "Open"
};
var evaluation = author
.Evaluate(relation, new("load-dto/load-42"))
.Supply([load], static value => value.Id)
.Build();The evaluation remains target-neutral. It does not contain a database choice, source placement, adapter binding, or driver configuration.
For this one-source mapping, execute the complete canonical pipeline without configuring any I/O:
var outcome = await RelationQueryEvaluator
.CreateSuppliedOnly()
.EvaluateAsync(evaluation);
if (!outcome.IsSuccessful)
{
throw new InvalidOperationException(
$"Evaluation ended with {outcome.Status}; inspect its diagnostics.");
}CreateSuppliedOnly() is deliberately narrow: it accepts a compiled relation plan with one supplied-root source and no retained relationship traversal. It still performs canonical compilation, realization, physical planning, interpretation, and result construction.
Canonical interpretation produces relation output rows. Compile a CLR construction kernel against the exact plan, then map those rows into LoadDto values:
var mapperCompilation =
RelationDtoMapperCompiler.Default.Compile<LoadDto>(
outcome.Compilation.Plan!);
if (!mapperCompilation.IsSuccessful)
{
throw new InvalidOperationException(
string.Join(Environment.NewLine, mapperCompilation.Diagnostics));
}
var mapping = mapperCompilation.Mapper!.Map(
outcome.PhysicalExecution!);
var dto = mapping.Rows.Single().Value;
Console.WriteLine($"{dto.Id}: {dto.Status}");
// load-42: OpenThe mapper performs CLR construction and conversion over canonical output. It does not execute filters, acquire related observations, or maintain a second mapping definition.
Now introduce a related Customer and a flattened output:
public sealed class Customer
{
[JsonPropertyName("id")]
public required string Id { get; init; }
[JsonPropertyName("name")]
public required string Name { get; init; }
}
public sealed class LoadSearchDto
{
[JsonPropertyName("id")]
public required string Id { get; init; }
[JsonPropertyName("customerId")]
public required string CustomerId { get; init; }
[JsonPropertyName("customerName")]
public string? CustomerName { get; init; }
}Declare Load.CustomerId → Customer identity inline with Traverse:
var customers = author.Traverse<Load, Customer>(
loads,
load => load.CustomerId);
var searchDocuments = author.Project(
customers,
(Load load, Customer customer) => new LoadSearchDto
{
Id = load.Id,
CustomerId = load.CustomerId,
CustomerName = customer.Name
});
var enrichedRelation =
searchDocuments.BuildRelation(dto => dto.Id);The authoring session retains the convention-derived relationship in author.CreateRelationshipCatalogDocument(). The relation knows that the Customer is addressed through Load.CustomerId, including the direction and cardinality of that traversal.
At runtime, the same traversal may become a co-located PostgreSQL join, bounded Customer acquisition followed by local correlation, or in-memory interpretation over already supplied evidence. The relation does not change when that physical choice changes.
Unlike the one-source example, evaluating the enriched relation requires Customer facts. An application can supply both objects directly or configure source placement and bounded source readers. PostgreSQL and Cosmos DB currently provide production source-reader paths; the evaluator may also compose application-defined readers.
The walkthrough now has four distinct pieces that can grow independently:
From here, the same definition can support selective field demand, composed source reads, native backend compilation, dependency analysis, materialized views, and explain output.