For the complete documentation index, see llms.txt. This page is also available as Markdown.

LINQ Queries

Write strongly typed queries against GridGain 9 with the .NET client's LINQ provider, which translates C# LINQ expressions into GridGain SQL.

GridGain .NET client provides LINQ support that is integrated with GridGain SQL APIs. You can avoid working with SQL syntax directly and write queries in C# with LINQ. C# LINQ expressions are then translated into GridGain-specific SQL. For example, the following two snippets achieve the same result:

var table = await Client.Tables.GetTableAsync("TBL1");
IQueryable<Poco> query = table!.GetRecordView<Poco>().AsQueryable()
    .Where(x => x.Key > 3)
    .OrderBy(x => x.Key);
List<Poco> queryResults = await query.ToListAsync();
var query = "select KEY, VAL from PUBLIC.TBL1 where (KEY > ?) order by KEY asc";
await using IResultSet<IIgniteTuple> resultSet = await Client.Sql.
    ExecuteAsync(transaction: null, query, 3);
var queryResults = new List<Poco>();
await foreach (IIgniteTuple row in resultSet)
{
    queryResults.Add(new Poco { Key = (long)row[0]!, Val = (string?)row[1] });
}

LINQ has the following advantages over SQL:

  • Queries are strongly typed and checked at compilation;

  • It is easier to write and maintain with IDE support (auto-completion, navigation, find usages);

  • LINQ is refactoring-friendly: rename a column and all queries are updated at once;

  • Ignite-specific SQL knowledge is not required, and most C# developers are already familiar with LINQ;

  • LINQ is safe against SQL injections;

  • Results are mapped to types naturally.

In real-world scenarios the performance of GridGain LINQ queries is on par with equivalent SQL queries. However, a small overhead still exists (due to query translation), and your mileage may vary depending on the query complexity, so it is recommended to measure the performance of your queries.

Getting Started With LINQ

Here is how you can create a simple table in GridGain:

  1. Create a table:

    await Client.Sql.ExecuteAsync(
        null, @"CREATE TABLE PUBLIC.PERSON (NAME VARCHAR PRIMARY KEY, AGE INT)");
  2. Define the classes (or records) that represent tables:

    • Member names should match column names (case-insensitive).

    • If a column name is not a valid C# identifier, use [Column("name")] attribute to specify the name.

    public record Person(string Name, int Age, string Address, string Status);
  3. Obtain a table reference:

    ITable table = await Client.Tables.GetTableAsync("PERSON");
  4. Use the GetRecordView<T>() method to get a typed view of the table:

    IRecordView<Person> view = table.GetRecordView<Person>();
  5. Use AsQueryable() to perform LINQ queries on IRecordView<T>.

    List<string> names = await view.AsQueryable()
        .Where(x => x.Age > 30)
        .Select(x => x.Name)
        .ToListAsync();

Using LINQ

Inspecting Generated SQL

Viewing generated SQL is useful for debugging and performance tuning. There are two ways to do it:

  • IgniteQueryableExtensions.ToQueryString() extension method:

  • Debug logging:

All generated SQL will be logged with Debug level to the specified logger.

Transactions

Transaction can be passed to the LINQ provider by using the AsQueryeable parameter:

Custom Query Options

Custom query options (timeout, page size) can be specified by using the second AsQueryable parameter with QueryableOptions:

Result Materialization

Materialization is the process of converting query results (IQueryable<T>) into an object or a collection of objects.

LINQ is lazy. Nothing happens (no network calls, no SQL translation) until the query is materialized. For example, the following code only constructs an expression, but does not execute anything:

Query execution and materialization can be triggered in multiple ways:

Iteration

You can iterate through query results by using foreach statement, or asynchronously by using the AsAsyncEnumerable method:

Converting to Collections

You can convert queries to collections by using the ToList and ToDictionary methods, or ToListAsync and ToDictionaryAsync methods to do it asynchronously:

Ignite-specific IResultSet

Underlying IResultSet can be obtained by using the IgniteQueryableExtensions.ToResultSetAsync() extension method:

Obtaining IResultSet can be useful for access to metadata and CollectAsync method, which provides more control over result materialization.

Supported LINQ Features

Projection

Projection is the process of converting query results into a different type. Among other things, projections are used to select a subset of columns.

For example, Person table may have many columns, but we only need Name and Age.

  • First, create a projection class:

  • Then, use Select to project query results:

Resulting SQL will select only those two columns, avoiding overfetching (a common issue that happens when ORM-generated query includes all table columns, but only a few of them are needed by the business logic).

GridGain also supports anonymous type projections:

Inner Joins

Use the standard Join method to perform joins on other tables:

Outer Joins

Outer joins are supported through the DefaultIfEmpty method. For example, not every book in a library is borrowed by a student, so a left outer join is used to retrieve all books and their current borrowers (if any):

Grouping

Grouping is supported through GroupBy method. This is equivalent to SQL GROUP BY operator. You can get both single and multiple columns in your queries. When working with multiple columns, use anonymous type:

Aggregate functions Count, Sum, Min, Max, Average can be used with groupings.

Ordering

OrderBy, OrderByDescending, ThenBy, ThenByDescending are supported. You can combine them to order by multiple columns:

Union, Intersect, Except

Multiple result sets can be combined by using the Union, Intersect, Except methods. For example:

Aggregate Functions

Below is a list of .NET aggregate functions and their SQL equivalents that are supported in GridGain:

LINQ synchronous method
LINQ asynchronous method
SQL Operator

First

FirstAsync

FIRST

FirstOrDefault

FirstOrDefaultAsync

FIRST ... LIMIT 1

Single

SingleAsync

FIRST

SingleOrDefault

SingleOrDefaultAsync

FIRST ... LIMIT 2

Max

MaxAsync

MAX

Min

MinAsync

MIN

Average

AverageAsync

AVG

Sum

SumAsync

SUM

Count

CountAsync

COUNT

LongCount

LongCountAsync

COUNT

Any

AnyAsync

ANY

All

AllAsync

ALL

Here are examples of how you can use these methods:

Math Functions

The following Math functions are supported (will be translated to SQL equivalents): Abs, Cos, Cosh, Acos, Sin, Sinh, Asin, Tan, Tanh, Atan, Ceiling, Floor, Exp, Log, Log10, Pow, Round, Sign, Sqrt, Truncate.

The following Math functions are NOT supported (no equivalent in GridGain SQL engine): Acosh, Asinh, Atanh, Atan2, Log2, Log(x, y).

Here is the example of how you can use math functions:

String Functions

The following string functions are supported: string.Compare(string), string.Compare(string, bool ignoreCase), concatenation s1 + s2 + s3, ToUpper, ToLower, Substring(start), Substring(start, len), Trim, Trim(char), TrimStart, TrimStart(char), TrimEnd, TrimEnd(char), Contains, StartsWith, EndsWith, IndexOf, Length, Replace.

Here is the example of how you can use string functions:

Regular Expressions

Regex.Replace is translated to regexp_replace function. Here is how you can use regular expressions in your code:

Regular expression engine within SQL may behave differently from .NET engine.

DML (Bulk Update and Delete)

Bulk update and delete with optional conditions are supported through ExecuteUpdateAsync and ExecuteDeleteAsync extensions methods on IQueryable<T>:

Update statement can set properties to constant values or to an expression based on other properties of the same row:

Resulting SQL:

Composing Queries

IQueryable<T> expressions can be composed dynamically. A common use case is to compose a query based on user input. For example, optional filters on different columns can be applied to a query:

Column Name Mapping

Unless custom mapping is provided with [Column], LINQ provider will use property or field names as column names, using unquoted identifiers, which are case-insensitive.

To use quoted identifiers, or to map column names to different property names, use [Column] attribute:

KeyValueView

All examples above use IRecordView<T> to perform queries; LINQ provider supports IKeyValueView<TK, TV> equally well:

Last updated

Was this helpful?