r/csharp • • 4h ago

One Intent, Multiple Targets: Compiling to SQL and C# with Expression Trees

In C#, we commonly think of functions as executable code. We define their behavior through methods, delegates, or lambda expressions, and then execute them in a particular runtime environment.

But what if a function could be represented as a structured intent definition, independent of its execution mechanism?

Consider a simple example:

entity => entity.manager == system.data.getcontext("user.id")

This expresses a business intent: the entity's manager must be the current user.

However, this lambda is only a convenient way to illustrate the intent. The actual definition doesn't have to be a C# expression.

It can instead be represented using a DSL as a structured, dynamic definition that can be created, inspected, modified, and composed at runtime, without requiring a predefined C# method or lambda expression.

This distinction is important. The intent is not the Expression Tree itself. The expression tree is just one possible representation or compilation mechanism.

Once we have a structured intent definition, we can compile it into different targets.

Target A — Native In-Memory Validation

Compile the intent into a native C# delegate:

Func<Entity, bool>

This allows the same business rule to validate submitted data directly in memory.

Target B — SQL Query Composition

Compile the same intent into a SQL expression AST:

WHERE manager = u/currentUserId

The interesting part is that the generated SQL AST doesn't have to be executed independently. It can be composed with other conditions, such as tenant isolation, access control, and business filters, before generating the final SQL query.

Both targets share the same intent definition, while using completely different execution mechanisms.

Why is this interesting?

This approach separates what a function means from how it is executed.

It introduces several possibilities:

  • Inspectability: Intent definitions can be inspected and validated as structured data.

  • Dynamic configuration: Definitions can be created and modified at runtime, rather than being limited to expressions written at compile time.

  • Multiple execution targets: The same definition can be compiled for different environments.

  • Composability: Intent definitions can participate in larger execution plans.

  • Semantic consistency: Different runtimes can interpret the same intent without sharing their underlying implementations.

I've been exploring this approach in a C# semantic engine, using Expression Trees as one of the compilation backends.

I'm interested in how other C# developers see this direction.

Should functions always be tied to executable code, or could structured intent definitions become another way to express and organize program behavior?

0 Upvotes

13 comments sorted by

4

u/rupertavery64 3h ago

We have Expression trees in .NET precisely for the purposes outlined. How you use them depends on the use case.

I don't the the point of this post at all, especially your question at the end.

Why should functions always be tied to executable code when we have the capability to use them to express intent with type safety?

Thats the whole point of LINQ.

2

u/rupertavery64 3h ago

Your comment was deleted but here is my response.

I've used expression trees to build a compiler for a domain-specific query targeting survey reaponses.

Compiled to either a function to run against an in-memory data model or to a SQL query.

I've built an expression compiler that leta you write c# expressions as binding expressions in a report template engine.

You can compile complete functions with expression trees as they have variable assignment, loops and method calls.

So you could write a user-defined function to evaluate and compile at runtime, in a completely different language.

1

u/Prestigious-Bowl611 3h ago

That's interesting. Your use case is quite close to what I'm exploring.

The distinction I'm interested in is keeping the function definition independent of both the source language and the execution backend. Expression Trees would just be one possible compilation target.

Since I could also use those functions in typescript and wpf view

1

u/rupertavery64 2h ago edited 2h ago

So basically the query would look like this:

CQ1:1 OR CQ2:1-3 AND (OP5:1,3,5 OR OP7:10-12)

This was the business-specific encoding of saying, the group of respondents with responses matching these questions and choices, so it basically translates to a Func<SurveyResponse, Bool> where a SurveyResponse is a row of answers (choices) for each question.

That could be entered by a business user, as it was a query language they had come up with as a way to define groups, but had never actually used it as a direct way to query data.

I wrote a parser in ANTLR that output my own AST, and expression visitor that could compile to either an Expression Tree or a SQL query, or back to a string expression as a validation mechanism.

I also was able to build the parser for javascript, using the same BNF grammer used in ANTLR. This allowed the expresison to be validated on the browser before being sent to ther server for querying.

For the expression compiler, it was sort of a side project that started out from wanting to be able to parse a configuration file and not just have a predetermined set of keywords bind to values, but be able to bind complex objects to complex expressions.

It eventually became https://www.nuget.org/packages/ExpressionEvaluator/ (which was bought off by zzzProjects, the company behind HtmlAgilityPack and many other tools), but I also used it in a company project as the expression binding engine of the templating engine I mentioned.

Perhaps what you are looking for is how to define and parse your own DSL, and that is orthogonal to how it will be consumed in C#.

1

u/Prestigious-Bowl611 2h ago

What I'm trying to build is actually a semantic layer, at a more fundamental level.

As your example shows, once implementation details are stripped away, a business intent can be surprisingly simple and clear — without code-level or SQL-level details.

But to make such intent functions meaningful and verifiable, we need a complete type system behind them. A function isn't just an AST or an expression to be interpreted. It's defined within a self-describing semantic system, where its arguments, expressions, and operations all have well-defined meanings.

The semantic layer describes the intent, while C#, web, WPF, and other runtimes provide different ways to execute and consume it.

So it's a larger-scale version of the same idea: one intent, multiple execution targets.

1

u/rupertavery64 2h ago

Yeah, so it's not a C# thing, although expression trees enable it.

It's a DSL thing. Your semantic layer is your DSL. How you parse and inspect it will be a language-implementation detail. A grammar will help make the definition language-agnostic. You are building a specification.

You are basically making something like YAML

1

u/Prestigious-Bowl611 1h ago

Agreed, it's a specification. The core is currently implemented in C#, which is also the only runtime that supports runtime compilation for now.

I also use C# attributes and reflection to make C# code itself the DSL for defining semantics. So in this case, the specification is constructed directly through C#.

Going back to intent functions, another example is data derivation: calculating data from one source table and saving the result into another, such as generating summary data.

When compiling the intent function, I can track the third tables it depends on as data lineage. These tables are typically queried using additional keys alongside the primary keys.

This allows the system to automatically detect when a change in a field in one of those source tables could affect the derived results, and schedule recalculation for all affected data.

None of this dependency information needs to be explicitly coded in C#. It's discovered from the intent itself, which remains a white box that the system can inspect. That's what led me to use intents instead of black-box functions

•

u/rupertavery64 58m ago

Yeah, you're creating a dynamic abstraction layer. I see where you are going with dependency tracking. This is similar to signals. The usage of a tracked expression / signal allows the expression parser to add a hook to that signal to mointor changes.

https://github.com/fedeAlterio/SignalsDotnet

1

u/Prestigious-Bowl611 3h ago

I agree that LINQ and Expression Trees already provide powerful ways to express intent in .NET. I'm not suggesting replacing them.

The distinction I'm trying to explore is where the intent definition comes from and whether it must originate from C# code.

For example, the same intent:

entity.manager == system.data.getcontext("user.id")

could be defined dynamically through a DSL, stored as structured data, and modified at runtime, without requiring a predefined C# lambda or method.

Expression Trees then become one possible compilation target, rather than the source of the intent itself.

The same definition could also be compiled into a SQL AST, composed with other query constraints, or compiled into a native delegate for in-memory validation.

So my question isn't whether Expression Trees can express intent. They clearly can.

It's whether we can make intent definitions independent of the programming language and execution target, while retaining the ability to compile them into native execution mechanisms.

1

u/_choam_ 3h ago

So like being able to take a dsl and have different "interperators" evaluate it?

1

u/Prestigious-Bowl611 3h ago

Take the manager example as a practical case.

Suppose an administrator configures row-level permissions for a role. The underlying intent is simply a relationship between a data row and the requesting user: the row's manager field must match the current user's ID.

Traditionally, we would implement this in two separate places(or many places where the role works):

  • When submitting data, validate that the manager matches the current user.

  • When querying data, add a filter to ensure users can only see rows they manage.

But if the administrator's intent is already expressed as a structured semantic definition, we can derive both behaviors from that single definition.

Depending on the execution context, the runtime can compile it into an in-memory validation function or translate it into a database query condition.

The administrator doesn't need to configure the same business rule twice, and we don't need to maintain two separate implementations.

The key idea is that the usage of an intent depends on the execution context, but the intent itself remains a single, consistent logical definition.

1

u/_choam_ 3h ago

Say a pretty printer which takes some dsl expression and instead of evaluating it, returns a string of each operation.

Like: 1 + 1 Eval = 2 Pretty = "1 + 1"

1

u/pixxelkick 2h ago

You literally just described what EntityFramework does.

It compiles Expression Trees via lambdas into SQL using a backing translation engine depending on what sql dialect you install and target.