Skip to main content
This page explains why the Universal Object Mapping (UOM) Assistant is built the way it is — the frameworks it targets, the exact translation directions it supports, the APIs it generates, and, most importantly, why you can trust the output it produces. If you only read one section, read Why You Can Trust the Translation. Everything else explains the engineering that makes that trust justified.
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:
  1. 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).
  2. 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.
Generic “ask an LLM to convert this code” approaches fail because they (a) hallucinate APIs and package versions, (b) silently change query semantics, and (c) cannot tell you whether the result is actually correct. UOM is engineered specifically to remove all three failure modes.

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).
You will notice the system prompts contain few-shot examples that look bidirectional (e.g. NHibernate→Mongo and a Mongo source-harness example). That is deliberate: the harness examples teach the model the shape of a runnable validation program for every framework, which is reused regardless of direction. Full bidirectional translation (Java → .NET) is on the roadmap. The four ready-made cards in the UI map directly onto the supported pairs:
  1. EF Core → Spring Data MongoDB
  2. EF Core → Spring Data Neo4j
  3. Dapper → Spring Data MongoDB
  4. 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 under services/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 Data Repository interfaces.

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:
  1. They need a booted Spring container. Repositories are runtime proxies created by classpath scanning, annotation processing, and ApplicationContext bootstrapping. Spinning up a full Spring context inside a throwaway validation sandbox is slow, memory-heavy, and fragile.
  2. 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.
  3. 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

This is a faithful translation of an EF Core 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:
Property names, ordering, and structure are all checked at compile time before a single query reaches the database.

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:
  1. Compiled in a lightweight sandbox (mvn compile / dotnet build) — see Validators & Equivalence.
  2. Executed against the real database to produce a result sample.
  3. Compared field-by-field against the source query’s result via DeepDiff.
A Repository-based design would make steps 1–3 dramatically harder. This single choice is what unlocks UOM’s correctness guarantees.

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, captures count / firstSample / lastSample, and writes them as JSON.
Keeping the harness separate means the production code you receive is not polluted with sorting, limits, counting, or JSON-serialisation scaffolding that exists only to make validation deterministic. The harness is the throwaway test rig; the query is the deliverable. (See State & Context for how these fields flow through the graph.)

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.
Why Mapping-by-Code:
  • 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-partyClassMapping<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 = 3 correction 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.
Full architecture, node-by-node, is in Architecture & LangGraph Design. The reasoning for temperature 0 and the self-repair loop is in Prompt Engineering.

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.
If all four layers cannot be satisfied within three automatic attempts, the pipeline stops and asks you (human-in-the-loop) rather than returning a result it could not verify. In other words: a translation you receive has compiled with the real toolchain and produced byte-for-byte equivalent data against a real database. That is a far stronger guarantee than “an LLM said it looks right.”

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. Developed within the Adaptive Data Management (ADaM) research group, Department of Software Engineering, Charles University (Faculty of Mathematics and Physics).