🧭 OData Walked So GraphQL Could Run (and Sometimes Trip)

🧭 OData Walked So GraphQL Could Run (and Sometimes Trip)
Photo by Walkator / Unsplash

🧓 The Old Guard Meets the Hype Train

OData has been around since before microservices were cool — quietly powering enterprise APIs while everyone else was busy reinventing REST for the 47th time.

Then along comes GraphQL, all swagger and curly braces, promising flexibility, perfect queries, and eternal happiness (as long as you don’t mind writing resolvers for everything).

So… should you leave your OData comfort zone and join the GraphQL hype train?

Let’s find out. Spoiler: you might not need to pack your bags just yet.


🧩 OData — The Veteran That Still Hits Hard

OData (Open Data Protocol) is like that dependable senior engineer — no nonsense, fully standardized, and allergic to drama.
It exposes your data model through REST endpoints and lets you filter, expand, and page through records with a simple query string.

GET /api/customers?$filter=firstName eq 'Orlando'&$expand=orders&$top=10

OData Controller Example (C#)

[HttpGet]
[Route("api/customers")]
public IActionResult Get(ODataQueryOptions<Customer> options)
    => ODataCustomResult.ObtainResult(options, _dbContext.Customers.AsQueryable(), _odataSettings);

You get $filter, $expand, $orderby, $top, $skip, $select — all powered by IQueryable, so your SQL stays efficient and clean.

Pros

  • 💪 Tight integration with EF Core — just works™.
  • 📖 Standards-based and widely supported in enterprise tools.
  • 🧮 Handles filtering, projections, and paging out of the box.
  • 🔐 Easy to secure and cache.

Cons

  • 🔠 URLs look like alien hieroglyphs.
  • 🧩 Lives mostly inside the Microsoft ecosystem.
  • 🕸️ Not flexible enough for today’s front-end-hungry SPAs.

🌐 Where OData Still Rules

OData shines when:

  • Your data lives in SQL Server or any relational DB.
  • You’re building internal or business APIs (think ERP, CRM).
  • You want to expose structured, queryable data fast.
  • Your users are analysts or other backend systems, not mobile clients.

Examples:

  • HR APIs powering Power BI dashboards.
  • Banking transaction lookups with server-side filtering.
  • Inventory APIs with OData queryable endpoints feeding internal reports.

⚡ GraphQL — The Ambitious Overachiever

GraphQL is the new kid in town: opinionated, expressive, and not afraid to ask for just the fields it wants.

It flips the OData model — instead of the server deciding what to return, the client calls the shots.

GraphQL Query Example

query {
  customers(filter: { firstName: "Orlando" }) {
    customerId
    firstName
    lastName
    orders {
      orderDate
      totalDue
    }
  }
}

C# with HotChocolate

builder.Services.AddGraphQLServer()
    .AddQueryType<Query>()
    .AddFiltering()
    .AddSorting();

public class Query
{
    [UseFiltering]
    [UseSorting]
    public IQueryable<Customer> GetCustomers([Service] AppDbContext db) => db.Customers;
}

Pros

  • 🧠 You only fetch what you need — no wasted payloads.
  • 🚪 Single endpoint for all queries and mutations.
  • 💡 Built-in introspection (self-documenting API).
  • 🌍 Works beautifully with modern frontends (React, Flutter, Vue).

Cons

  • 🧮 Resolver design can go sideways fast (N+1 city).
  • 🕸️ Caching and rate-limiting are… let’s say “custom.”
  • 🧩 Requires schema management and extra tooling.

🌍 Where GraphQL Earns Its Keep

GraphQL is perfect when:

  • You serve multiple frontends with different data needs.
  • You aggregate multiple microservices.
  • You want evolution, not versioning (deprecate fields, don’t break clients).
  • You want your frontend team to stop opening backend tickets. 😅

Examples:

  • E-commerce APIs combining product, inventory, and review data.
  • Multi-service dashboards with live metrics.
  • Public APIs with rapidly changing client requirements.

⚖️ The Showdown

FeatureODataGraphQL
Query modelQuery parameters ($filter, $expand)Declarative query language
SchemaImplicit via EF modelExplicit via Schema Definition Language
VersioningURL or headersDeprecate fields gracefully
ToolingSwagger, PostmanGraphiQL, Apollo Studio
IntegrationNative in ASP.NET CoreVia libraries like HotChocolate
PerformanceExcellent (IQueryable FTW)Depends on resolver design
Ideal ForInternal, database-centric APIsMulti-client, flexible frontends

🧭 Architecture Comparison

OData Request Flow

@startuml
actor Client
participant "OData API (.NET)" as API
participant "Entity Framework" as EF
database "SQL Server" as DB

Client -> API: GET /customers?$expand=orders
API -> EF: Build IQueryable
EF -> DB: SELECT Customers JOIN Orders
DB --> EF: Result set
EF --> API: Entities + Relations
API --> Client: JSON (Customers + Orders)
@enduml

GraphQL Request Flow

@startuml
actor Client
participant "GraphQL Server" as GQL
participant "Resolvers" as Resolver
participant "Entity Framework" as EF
database "SQL Server" as DB

Client -> GQL: POST /graphql { customers { orders { totalDue } } }
GQL -> Resolver: Parse + Validate Query
Resolver -> EF: Fetch Data via IQueryable
EF -> DB: SELECT with JOIN or batch
DB --> EF: Data result
EF --> Resolver: Entity objects
Resolver --> GQL: Structured Response
GQL --> Client: JSON (Only requested fields)
@enduml

🧮 C4 Container Diagram — The Big Picture

@startuml
!include <C4/C4_Container>

Person(dev, "Developer / Client", "Consumes the API")
System_Boundary(system, "API Layer") {
  Container(odataapi, "OData API", "ASP.NET Core + EF Core", "Exposes REST endpoints with query parameters ($filter, $expand)")
  Container(graphqlapi, "GraphQL Server", "HotChocolate + EF Core", "Exposes schema-driven queries via a single endpoint")
}
ContainerDb(database, "SQL Server", "Relational Database", "Holds normalized data")

Rel(dev, odataapi, "Queries data via REST + OData")
Rel(dev, graphqlapi, "Queries data via GraphQL schema")
Rel(odataapi, database, "Executes IQueryable queries")
Rel(graphqlapi, database, "Executes resolvers and joins")

SHOW_LEGEND()
@enduml

🧰 My Take: OData Still Reigns (and I’ve Got Receipts)

I’ll be honest — I use OData way more than GraphQL.
If you’ve read my original post — OData! Oh my!! So powerful indeed!!! — you know it’s one of my go-to tools for building reliable enterprise APIs.

That custom envelope pattern I introduced there became a core part of my architecture: consistent pagination, clear metadata, clean responses.

And yes — I’m working on Custom Envelope v2 right now.
It’ll have better serialization, lazy evaluation, and performance update (plus some surprises I’m saving for launch 👀).

Spoiler: it’ll make your OData responses shine — and yes, it’ll play nice with GraphQL hybrids too.


🚀 The Hybrid Reality

The truth? You don’t have to pick sides.
In fact, the best architectures use both:

  • OData internally — structured, fast, predictable.
  • GraphQL externally — flexible, client-friendly, sexy (in the “developer conference demo” kind of way).

You can even stack them:

GraphQL on top of OData — clean REST underneath, pretty schema up top.
A perfect full-stack truce.

🧭 Conclusion

“OData walked so GraphQL could run — and sometimes running means tripping over your own resolvers.”

If your users are analysts, .NET systems, or Power BI dashboards — OData is your solid old friend.
If they’re frontend devs who like fancy schemas and hate waiting on backend tickets — GraphQL’s their jam.

The best engineers don’t pick religions — they pick the right tool for the mission.

Happy coding!!!