Linq2OData follows an OData-centric naming convention for its fluent API methods to maintain consistency with OData terminology.
| OData Query Parameter | API Method | Rationale |
|---|---|---|
$filter |
.Filter() |
Matches OData terminology |
$expand |
.Expand() |
Matches OData terminology |
$top |
.Top() |
Matches OData terminology |
$skip |
.Skip() |
Matches OData terminology |
$count |
.Count() |
Matches OData terminology |
$orderby |
.Order() |
Shortened form of OData $orderby |
$orderby=x desc |
.OrderDescending() |
Shortened form with direction |
While LINQ uses Where, OrderBy, Take, etc., this library uses OData-centric names:
// ❌ NOT using LINQ naming
.Where(s => s.IsActive) // LINQ
.OrderBy(s => s.Name) // LINQ
.Take(10) // LINQ
// ✅ Using OData-centric naming
.Filter(s => s.IsActive) // OData
.Order(s => s.Name) // OData
.Top(10) // ODataBenefits:
- Consistency: All methods match OData terminology
- Clarity: Developers working with OData understand the direct mapping
- Predictability: Method names directly correspond to query parameters
- Documentation: Easier to map to OData specification
For secondary operations, we keep familiar LINQ patterns:
.Order(s => s.Country)
.ThenBy(s => s.Name) // Kept ThenBy (LINQ familiar)
.ThenByDescending(s => s.Date) // Kept ThenByDescending
.Expand(s => s.Products)
.ThenExpand(p => p.Select(...)) // Kept ThenExpand patternWhy keep "Then" methods?
- Widely recognized LINQ pattern
- Clear semantic meaning (secondary/subsequent operation)
- Good IntelliSense discoverability
client.Query<Entity>()
.Filter(e => e.IsActive) // OData: $filter
.Expand(e => e.NavProperty) // OData: $expand
.Order(e => e.PropertyName) // OData: $orderby
.OrderDescending(e => e.Price) // OData: $orderby=Price desc
.Top(20) // OData: $top
.Skip(10) // OData: $skip
.Count(true) // OData: $count
.ExecuteAsync();// After Expand
.Expand(e => e.Products)
.ThenExpand(p => p.Select(x => x.Category)) // Nested expand
// After Order
.Order(e => e.Country)
.ThenBy(e => e.Name) // Additional ordering
.ThenByDescending(e => e.Date) // Descending ordering-
Primary methods = OData terminology
- Entry points for operations
- Map directly to OData query parameters
-
Chaining methods = LINQ patterns
- Secondary operations
- Familiar to .NET developers
- Clear semantic meaning
var results = await client
.Query<Supplier>()
.Filter(s => s.Country == "USA") // OData-centric
.Expand(s => s.Products) // OData-centric
.ThenExpand(p => p.Select(x => x.Category)) // LINQ-familiar
.Order(s => s.Rating) // OData-centric
.ThenByDescending(s => s.JoinDate) // LINQ-familiar
.ThenBy(s => s.Name) // LINQ-familiar
.Top(20) // OData-centric
.Skip(10) // OData-centric
.Count(true) // OData-centric
.ExecuteAsync();Generates:
$filter=(Country eq 'USA')
&$expand=Products($expand=Category)
&$orderby=Rating,JoinDate desc,Name
&$top=20
&$skip=10
&$count=true
If you were using the LINQ-style naming (OrderBy/OrderByDescending), update to:
// Before
.OrderBy(s => s.Name)
.OrderByDescending(s => s.Price)
// After
.Order(s => s.Name)
.OrderDescending(s => s.Price)
// ThenBy/ThenByDescending remain unchanged
.ThenBy(s => s.Name)
.ThenByDescending(s => s.Date)✅ Primary operations: OData-centric (Filter, Expand, Order, Top, Skip, Count)
✅ Chaining operations: LINQ-familiar (ThenBy, ThenExpand)
✅ Result: Best of both worlds - predictable OData mapping with familiar .NET patterns