New to the project? Start with the User Guide, then read Prompt Engineering and Context Engineering for how the LLM is steered. This page is the “why”, those two are the “how”.
1. The Problem UOM Solves
Migrating a data-access layer from one ecosystem to another (for example, from a relational .NET stack to a document or graph Java stack) is normally a slow, error-prone, manual job. Two things make it hard:- Schema translation — a SQL table with foreign keys does not map one-to-one onto a MongoDB document (which prefers embedding related data) or a Neo4j graph (which prefers relationships between nodes).
- Query translation — a LINQ/SQL/HQL query must be rewritten into an equivalent MongoDB or Cypher query that returns the same data, not just code that compiles.
2. Why These Frameworks and These Translation Pairs
2.1 Source frameworks (.NET / relational)
UOM ingests schema and query code from the three dominant .NET data-access frameworks, each representing a different style of data access:
Covering all three proves the pipeline is not over-fit to a single query dialect: it handles LINQ expression trees, raw parameterised SQL, and HQL alike.
2.2 Target frameworks (Java / NoSQL)
These two cover the two major non-relational paradigms. A relational→relational port is comparatively trivial; relational→document and relational→graph are where the genuinely hard, interesting mapping problems live.
2.3 Current direction: one-way .NET → Java Spring Data
Today the pipeline supports translation in one direction only: from a .NET source framework to a Java Spring Data target framework. This is enforced in code —
SourceFramework contains only the three .NET frameworks and TargetFramework only the two Java frameworks (see services/orchestrator/src/react_agent/constants.py).Java → .NET) is on the roadmap.
The four ready-made cards in the UI map directly onto the supported pairs:
- EF Core → Spring Data MongoDB
- EF Core → Spring Data Neo4j
- Dapper → Spring Data MongoDB
- NHibernate → Spring Data MongoDB
2.4 Newest framework versions, pinned exactly
UOM does not translate to “MongoDB in general” — it translates to a specific, current stack, and it tells the model exactly which one. The pinned versions live in the sandbox project files underservices/orchestrator/src/context/snippets/:
Because the exact
.csproj / pom.xml is injected into the prompt and is the same file used to compile the validation sandbox, the model generates code for precisely the API surface it will be compiled against. This is the single biggest reason the output rarely uses hallucinated or deprecated APIs — see Context Engineering for the mechanism.
3. Template APIs vs. Repository Patterns
A foundational decision: UOM generates programmatic Template APIs, never Spring DataRepository interfaces.
- Spring Data MongoDB →
MongoTemplatewith theCriteria/QueryAPI. - Spring Data Neo4j →
Neo4jTemplatewith the programmatic Cypher-DSLStatementbuilders.
3.1 Why not MongoRepository / Neo4jRepository?
Repository interfaces (e.g. interface OrderRepo extends MongoRepository<Order, String>) look convenient, but they are the wrong tool for an automated, verifiable pipeline:
- They need a booted Spring container. Repositories are runtime proxies created by classpath scanning, annotation processing, and
ApplicationContextbootstrapping. Spinning up a full Spring context inside a throwaway validation sandbox is slow, memory-heavy, and fragile. - Derived-query methods hide logic in method names.
findByPickingCompletedWhenBetween(...)encodes the query in a string parsed at runtime. The compiler cannot verify it; a typo surfaces only when the app runs against a live database. That defeats the entire point of compile-time validation. - They are hard to introspect. UOM needs to extract the concrete query object (the Mongo filter document / the Cypher statement) to run it and compare results. A repository proxy gives you results, not an inspectable query plan.
MongoTemplate and Neo4jTemplate, by contrast, are plain objects you instantiate directly. The generated query is an ordinary Java value (Query / Statement) that compiles in a bare main(), can be executed against the live database, and can be serialised for equivalence checking.
3.2 What a generated MongoDB query looks like
Where(ol => ol.PickingCompletedWhen >= from && ... <= to) LINQ query — same intent, same field, no booted container required.
3.3 Cypher-DSL for Spring Data Neo4j
For Neo4j, UOM mandates the programmatic Cypher-DSL, not raw string concatenation. String-built Cypher is injection-prone, easy to misformat, and invisible to the compiler. The Cypher-DSL builds the query as a typed object tree:3.4 The payoff: everything is compile-checkable and runnable
Because both targets emit Template-API code with explicit entry points, every translation can be:- Compiled in a lightweight sandbox (
mvn compile/dotnet build) — see Validators & Equivalence. - Executed against the real database to produce a result sample.
- Compared field-by-field against the source query’s result via DeepDiff.
4. Why a Validation Harness Is Generated Alongside the Query
For every query translation the model emits two artifacts, kept strictly separate:translated_query_code— the clean, production query you actually want.*_validation_harness_code— a self-contained runnable program (with a declared entry-point class/method) that executes the query, capturescount/firstSample/lastSample, and writes them as JSON.
5. NHibernate Mapping-by-Code
On the .NET side, UOM uses NHibernate’s native Mapping-by-Code (ClassMapping<T>) rather than legacy .hbm.xml files or the third-party Fluent NHibernate package.
- Self-contained in one C# file — no external XML to parse, generate, or keep in sync, which keeps both code generation and compilation simple.
- Compile-checked — mapping mistakes surface from
dotnet build, not as silent runtime configuration failures. - First-party —
ClassMapping<T>ships with NHibernate itself, so no extra dependency (unlike Fluent NHibernate).
6. Determinism by Design
UOM is built around a deterministic LangGraph state machine, not a free-roaming “agent that decides when it’s done.” The earlier ReAct-agent approach is deprecated. The reasons, in brief:- Explicit validation gates: compile → execute → DeepDiff → LLM judge. A translation cannot be returned unless it passes the gates appropriate to its type.
- Bounded retries: at most
MAX_TRANSLATION_LOOPS = 3correction loops before handing off to a human, so the pipeline never spins forever. - Temperature 0 on every generation/evaluation model, so the same input yields the same output.
7. Why You Can Trust the Translation
This is the heart of the project. UOM does not ask you to trust the LLM. It asks you to trust a pipeline that checks the LLM. A translation is only presented to you after surviving four independent layers of verification:1
Grounded generation (no guessing)
The model is given the exact source schema, the exact target
.csproj/pom.xml, ground-truth database mappings, and verified few-shot harness examples before it writes a line. It is generating against a known, pinned API surface — not its training-data memory. See Context Engineering.2
It must compile (syntactic correctness)
The translated schema and query are compiled in an isolated Daytona sandbox with the real SDKs (.NET 10, Java 25). Code that does not build is rejected and sent back for repair — you never receive non-compiling output silently.
3
It must return the same data (semantic correctness)
The source query and the translated query are both executed against live databases, and their result sets (
count, first row, last row) are compared with DeepDiff. Tolerances handle harmless differences (field ordering, float precision, reversed sort order). If the data differs, the translation is rejected — see Validators & Equivalence.4
An independent LLM judge signs off
A separate evaluation model reviews the compiler output and the DeepDiff result and issues a structured
ACCEPT / REJECT. Only ACCEPT ends the run successfully.8. Acknowledgements & Lineage
UOM extends ORMorpher, originally developed by Milan Abrahám (Master’s thesis, later published at IEEE/ACM ASE 2025). ORMorpher used Roslyn-based compiler heuristics and Integer Linear Programming to select optimal .NET frameworks. UOM generalises this to cross-paradigm (relational → document/graph) translation by replacing rigid rules with an iterative, validated LLM pipeline.- ORMorpher thesis: Framework-Agnostic Query Adaptation
- ORMorpher source: github.com/milan252525/orm-convertor