Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Oct 31, 2025

Enables LINQ Where clauses to automatically translate to OData $filter parameters, eliminating manual filter string construction and providing type-safe queries. Implemented as an overload of the existing GetList() method.

Implementation

Core Components

  • EntityQueryProvider - IQueryProvider that intercepts LINQ expressions and invokes GetListAsync with translated OData parameters
  • EntityQueryable<T> - IQueryable wrapper with async execution methods (ToListAsync, FirstAsync, CountAsync, etc.)
  • ExpressionToQueryParametersVisitor - Expression tree visitor that translates LINQ to OData:
    • Comparison operators: ==, !=, >, >=, <, <=eq, ne, gt, ge, lt, le
    • Logical operators: &&, ||, !and, or, not
    • String methods: Contains(), StartsWith(), EndsWith() → OData functions
    • Pagination: Take(), Skip()$top, $skip
  • GetList<T>(asQueryable: true) overload on ApiClient that returns IQueryable<EntityType>

Key Details

  • Respects DataMember attributes for proper JSON property name serialization (e.g., Date.ValueDate/value)
  • Merges initial filter/select/expand parameters with LINQ-generated filters using and
  • Handles expression evaluation for variables and complex expressions
  • Proper string escaping (quotes, backslashes) for OData injection protection

Usage

// Before - manual filter strings
var customers = client.GetList<Customer>(
    filter: "Status eq 'Active' and CustomerClass eq 'RETAIL'",
    top: 10, skip: 20
);

// After - type-safe LINQ using GetList overload
var customers = client.GetList<Customer>(asQueryable: true)
    .Where(c => c.Status.Value == "Active" && c.CustomerClass.Value == "RETAIL")
    .Skip(20)
    .Take(10)
    .ToList();

// Async execution
var count = await ((EntityQueryable<Customer>)client.GetList<Customer>(asQueryable: true)
    .Where(c => c.Status.Value == "Active"))
    .CountAsync();

// Combining with initial parameters
var result = client.GetList<Customer>(
    asQueryable: true,
    filter: "Status eq 'Active'",
    expand: "Contacts"
).Where(c => c.CustomerClass.Value == "RETAIL")
 .ToList();

Testing

14 new tests covering filter generation, pagination, async execution, filter combination, and URL encoding. All existing tests pass.

Original prompt

Make GetList and GetListAsync methods support IQueriable and ability to propagate Where condition from LINQ Where to the rest filters


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 4 commits October 31, 2025 14:40
…ata translation

Co-authored-by: dnaumov <150417680+dnaumov@users.noreply.github.com>
Co-authored-by: dnaumov <150417680+dnaumov@users.noreply.github.com>
Co-authored-by: dnaumov <150417680+dnaumov@users.noreply.github.com>
…caping

Co-authored-by: dnaumov <150417680+dnaumov@users.noreply.github.com>
Copilot AI changed the title [WIP] Add IQueryable support to GetList and GetListAsync methods Add IQueryable support with LINQ-to-OData translation for GetList/GetListAsync Oct 31, 2025
Copilot AI requested a review from dnaumov October 31, 2025 14:49
@dnaumov
Copy link
Contributor

dnaumov commented Nov 4, 2025

@copilot Can you make it an overload to getList instead of a separate method?

Co-authored-by: dnaumov <150417680+dnaumov@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Nov 4, 2025

@copilot Can you make it an overload to getList instead of a separate method?

Done! Changed from AsQueryable() to GetList(asQueryable: true) overload. The new API is:

// Returns IQueryable for LINQ support
var customers = client.GetList<Customer>(asQueryable: true)
    .Where(c => c.Status.Value == "Active")
    .ToList();

All tests updated and passing. Commit: 32f24f4

Copilot AI changed the title Add IQueryable support with LINQ-to-OData translation for GetList/GetListAsync Add IQueryable support as GetList overload with LINQ-to-OData translation Nov 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants