InfluxDB.Client.Linq 4.16.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package InfluxDB.Client.Linq --version 4.16.0
                    
NuGet\Install-Package InfluxDB.Client.Linq -Version 4.16.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="InfluxDB.Client.Linq" Version="4.16.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="InfluxDB.Client.Linq" Version="4.16.0" />
                    
Directory.Packages.props
<PackageReference Include="InfluxDB.Client.Linq" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add InfluxDB.Client.Linq --version 4.16.0
                    
#r "nuget: InfluxDB.Client.Linq, 4.16.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package InfluxDB.Client.Linq@4.16.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=InfluxDB.Client.Linq&version=4.16.0
                    
Install as a Cake Addin
#tool nuget:?package=InfluxDB.Client.Linq&version=4.16.0
                    
Install as a Cake Tool

InfluxDB.Client.Linq

The library supports to use a LINQ expression to query the InfluxDB.

Documentation

This section contains links to the client library documentation.

Usage

How to start

First, add the library as a dependency for your project:

# For actual version please check: https://www.nuget.org/packages/InfluxDB.Client.Linq/

dotnet add package InfluxDB.Client.Linq --version 1.17.0-dev.linq.17

Next, you should add additional using statement to your program:

using InfluxDB.Client.Linq;

The LINQ query depends on QueryApiSync, you could create an instance of QueryApiSync by:

var client = new InfluxDBClient("http://localhost:8086", "my-token");
var queryApi = client.GetQueryApiSync();

In the following examples we assume that the Sensor entity is defined as:

class Sensor
{
    [Column("sensor_id", IsTag = true)] 
    public string SensorId { get; set; }

    /// <summary>
    /// "production" or "testing"
    /// </summary>
    [Column("deployment", IsTag = true)]
    public string Deployment { get; set; }

    /// <summary>
    /// Value measured by sensor
    /// </summary>
    [Column("data")]
    public float Value { get; set; }

    [Column(IsTimestamp = true)] 
    public DateTime Timestamp { get; set; }
}

Time Series

The InfluxDB uses concept of TimeSeries - a collection of data that shares a measurement, tag set, and bucket. You always operate on each time-series, if you querying data with Flux.

Imagine that you have following data:

sensor,deployment=production,sensor_id=id-1 data=15
sensor,deployment=testing,sensor_id=id-1 data=28
sensor,deployment=testing,sensor_id=id-1 data=12
sensor,deployment=production,sensor_id=id-1 data=89

The corresponding time series are:

  • sensor,deployment=production,sensor_id=id-1
  • sensor,deployment=testing,sensor_id=id-1

If you query your data with following Flux:

from(bucket: "my-bucket")
  |> range(start: 0)
  |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
  |> drop(columns: ["_start", "_stop", "_measurement"])
  |> limit(n:1)

The result will be one item for each time-series:

sensor,deployment=production,sensor_id=id-1 data=15
sensor,deployment=testing,sensor_id=id-1 data=28

and this is also way how this LINQ driver works.

The driver supposes that you are querying over one time-series.

There is a way how to change this configuration:

Enable querying multiple time-series

var settings = new QueryableOptimizerSettings{QueryMultipleTimeSeries = true};
var query = from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", _queryApi, settings)
    select s;

The group() function is way how to query multiple time-series and gets correct results.

The following query works correctly:

from(bucket: "my-bucket")
  |> range(start: 0)
  |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
  |> drop(columns: ["_start", "_stop", "_measurement"])
  |> group()
  |> limit(n:1)

and corresponding result:

sensor,deployment=production,sensor_id=id-1 data=15

Do not used this functionality if it is not required because it brings a performance costs caused by sorting:

Group does not guarantee sort order

The group() does not guarantee sort order of output records. To ensure data is sorted correctly, use orderby expression.

Client Side Evaluation

The library attempts to evaluate a query on the server as much as possible. The client side evaluations is required for aggregation function if there is more then one time series.

If you want to count your data with following Flux:

from(bucket: "my-bucket")
  |> range(start: 0)
  |> drop(columns: ["_start", "_stop", "_measurement"])
  |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
  |> stateCount(fn: (r) => true, column: "linq_result_column") 
  |> last(column: "linq_result_column") 
  |> keep(columns: ["linq_result_column"])

The result will be one count for each time-series:

#group,false,false,false
#datatype,string,long,long
#default,_result,,
,result,table,linq_result_column
,,0,1
,,0,1

and client has to aggregate this multiple results into one scalar value.

Operators that could cause client side evaluation:

  • Count
  • CountLong

TL;DR

Perform Query

The LINQ query requires bucket and organization as a source of data. Both of them could be name or ID.

var query = (from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", queryApi)
    where s.SensorId == "id-1"
    where s.Value > 12
    where s.Timestamp > new DateTime(2019, 11, 16, 8, 20, 15, DateTimeKind.Utc)
    where s.Timestamp < new DateTime(2021, 01, 10, 5, 10, 0, DateTimeKind.Utc)
    orderby s.Timestamp
    select s)
    .Take(2)
    .Skip(2);

var sensors = query.ToList();

Flux Query:

from(bucket: "my-bucket") 
    |> range(start: 2019-11-16T08:20:15Z, stop: 2021-01-10T05:10:00Z) 
    |> filter(fn: (r) => (r["sensor_id"] == "id-1")) 
    |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
    |> drop(columns: ["_start", "_stop", "_measurement"])
    |> filter(fn: (r) => (r["data"] > 12)) 
    |> limit(n: 2, offset: 2)

Filtering

The range() and filter() are pushdown functions that allow push their data manipulation down to the underlying data source rather than storing and manipulating data in memory. Using pushdown functions at the beginning of query we greatly reduce the amount of server memory necessary to run a query.

The LINQ provider needs to aligns fields within each input table that have the same timestamp to column-wise format:

From
_time _value _measurement _field
1970-01-01T00:00:00.000000001Z 1.0 "m1" "f1"
1970-01-01T00:00:00.000000001Z 2.0 "m1" "f2"
1970-01-01T00:00:00.000000002Z 3.0 "m1" "f1"
1970-01-01T00:00:00.000000002Z 4.0 "m1" "f2"
To
_time _measurement f1 f2
1970-01-01T00:00:00.000000001Z "m1" 1.0 2.0
1970-01-01T00:00:00.000000002Z "m1" 3.0 4.0

For that reason we need to use the pivot() function. The pivot is heavy and should be used at the end of our Flux query.

There is an also possibility to disable appending pivot by:

var optimizerSettings =
    new QueryableOptimizerSettings
    {
        AlignFieldsWithPivot = false
    };
    
var query = from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", queryApi, optimizerSettings)
    select s;

Mapping LINQ filters

For the best performance on the both side - server, LINQ provider we maps the LINQ expressions to FLUX query following way:

Filter by Timestamp

Mapped to range().

var query = from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", queryApi)
    where s.Timestamp >= new DateTime(2019, 11, 16, 8, 20, 15, DateTimeKind.Utc)
    select s;

var sensors = query.ToList();

Flux Query:

from(bucket: "my-bucket") 
    |> range(start: 2019-11-16T08:20:15ZZ) 
    |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
    |> drop(columns: ["_start", "_stop", "_measurement"])
Filter by Tag

Mapped to filter() before pivot().

var query = from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", queryApi)
    where s.SensorId == "id-1"
    select s;

Flux Query:

from(bucket: "my-bucket") 
    |> range(start: 0)
    |> filter(fn: (r) => (r["sensor_id"] == "id-1"))  
    |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
    |> drop(columns: ["_start", "_stop", "_measurement"])
Filter by Field

The filter by field has to be after the pivot() because we want to select all fields from pivoted table.

var query = from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", queryApi)
    where s.Value < 28
    select s;

Flux Query:

from(bucket: "my-bucket") 
    |> range(start: 0)
    |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")  
    |> drop(columns: ["_start", "_stop", "_measurement"])
    |> filter(fn: (r) => (r["data"] < 28))

If we move the filter() for fields before the pivot() then we will gets wrong results:

Data
m1 f1=1,f2=2 1
m1 f1=3,f2=4 2
Without filter
from(bucket: "my-bucket") 
    |> range(start: 0)
    |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value") 
    |> drop(columns: ["_start", "_stop", "_measurement"])

Results:

_time f1 f2
1970-01-01T00:00:00.000000001Z 1.0 2.0
1970-01-01T00:00:00.000000002Z 3.0 4.0
Filter before pivot()

filter: f1 > 0

from(bucket: "my-bucket") 
    |> range(start: 0) 
    |> filter(fn: (r) => (r["_field"] == "f1" and r["_value"] > 0))
    |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value") 
    |> drop(columns: ["_start", "_stop", "_measurement"])

Results:

_time f1
1970-01-01T00:00:00.000000001Z 1.0
1970-01-01T00:00:00.000000002Z 3.0

Time Range Filtering

The time filtering expressions are mapped to Flux range() function. This function has start and stop parameters with following behaviour: start <= _time < stop:

Results include records with _time values greater than or equal to the specified start time and less than the specified stop time.

This means that we have to add one nanosecond to start if we want timestamp greater than and also add one nanosecond to stop if we want to timestamp lesser or equal than.

Example 1:
var query = from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", queryApi)
    where s.Timestamp > new DateTime(2019, 11, 16, 8, 20, 15, DateTimeKind.Utc)
    where s.Timestamp < new DateTime(2021, 01, 10, 5, 10, 0, DateTimeKind.Utc)
    select s;

var sensors = query.ToList();

Flux Query:

start_shifted = int(v: time(v: "2019-11-16T08:20:15Z")) + 1

from(bucket: "my-bucket") 
    |> range(start: time(v: start_shifted), stop: 2021-01-10T05:10:00Z)
    |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value") 
    |> drop(columns: ["_start", "_stop", "_measurement"])
Example 2:
var query = from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", queryApi)
    where s.Timestamp >= new DateTime(2019, 11, 16, 8, 20, 15, DateTimeKind.Utc)
    where s.Timestamp <= new DateTime(2021, 01, 10, 5, 10, 0, DateTimeKind.Utc)
    select s;

var sensors = query.ToList();

Flux Query:

stop_shifted = int(v: time(v: "2021-01-10T05:10:00Z")) + 1

from(bucket: "my-bucket") 
    |> range(start: 2019-11-16T08:20:15Z, stop: time(v: stop_shifted)) 
    |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
    |> drop(columns: ["_start", "_stop", "_measurement"])
Example 3:
var query = from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", queryApi)
    where s.Timestamp >= new DateTime(2019, 11, 16, 8, 20, 15, DateTimeKind.Utc)
    select s;

var sensors = query.ToList();

Flux Query:

from(bucket: "my-bucket") 
    |> range(start: 2019-11-16T08:20:15ZZ) 
    |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
    |> drop(columns: ["_start", "_stop", "_measurement"])
Example 4:
var query = from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", queryApi)
    where s.Timestamp <= new DateTime(2021, 01, 10, 5, 10, 0, DateTimeKind.Utc)
    select s;

var sensors = query.ToList();

Flux Query:

stop_shifted = int(v: time(v: "2021-01-10T05:10:00Z")) + 1

from(bucket: "my-bucket") 
    |> range(start: 0, stop: time(v: stop_shifted))
    |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value") 
    |> drop(columns: ["_start", "_stop", "_measurement"])
Example 5:
var query = from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", queryApi)
    where s.Timestamp == new DateTime(2019, 11, 16, 8, 20, 15, DateTimeKind.Utc)
    select s;

var sensors = query.ToList();

Flux Query:

stop_shifted = int(v: time(v: "2019-11-16T08:20:15Z")) + 1

from(bucket: "my-bucket") 
    |> range(start: 2019-11-16T08:20:15Z, stop: time(v: stop_shifted)) 
    |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
    |> drop(columns: ["_start", "_stop", "_measurement"])

There is also a possibility to specify the default value for start and stop parameter. This is useful when you need to include data with future timestamps when no time bounds are explicitly set.

var settings = new QueryableOptimizerSettings
{
    RangeStartValue = DateTime.UtcNow.AddHours(-24),
    RangeStopValue = DateTime.UtcNow.AddHours(1)
};
var query = from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", queryApi, settings)
    select s;

TD;LR

Supported LINQ operators

Equal

var query = from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", queryApi)
    where s.SensorId == "id-1"
    select s;

Flux Query:

from(bucket: "my-bucket") 
    |> range(start: 0)
    |> filter(fn: (r) => (r["sensor_id"] == "id-1"))  
    |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
    |> drop(columns: ["_start", "_stop", "_measurement"])

Not Equal

var query = from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", queryApi)
    where s.SensorId != "id-1"
    select s;

Flux Query:

from(bucket: "my-bucket") 
    |> range(start: 0)
    |> filter(fn: (r) => (r["sensor_id"] != "id-1")) 
    |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
    |> drop(columns: ["_start", "_stop", "_measurement"])

Less Than

var query = from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", queryApi)
    where s.Value < 28
    select s;

Flux Query:

from(bucket: "my-bucket") 
    |> range(start: 0) 
    |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value") 
    |> drop(columns: ["_start", "_stop", "_measurement"])
    |> filter(fn: (r) => (r["data"] < 28))

Less Than Or Equal

var query = from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", queryApi)
    where s.Value <= 28
    select s;

Flux Query:

from(bucket: "my-bucket") 
    |> range(start: 0) 
    |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value") 
    |> drop(columns: ["_start", "_stop", "_measurement"])
    |> filter(fn: (r) => (r["data"] <= 28))

Greater Than

var query = from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", queryApi)
    where s.Value > 28
    select s;

Flux Query:

from(bucket: "my-bucket") 
    |> range(start: 0) 
    |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
    |> drop(columns: ["_start", "_stop", "_measurement"])
    |> filter(fn: (r) => (r["data"] > 28))

Greater Than Or Equal

var query = from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", queryApi)
    where s.Value >= 28
    select s;

Flux Query:

from(bucket: "my-bucket") 
    |> range(start: 0) 
    |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value") 
    |> drop(columns: ["_start", "_stop", "_measurement"])
    |> filter(fn: (r) => (r["data"] >= 28))

And

var query = from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", queryApi)
    where s.Value >= 28 && s.SensorId != "id-1"
    select s;

Flux Query:

from(bucket: "my-bucket") 
    |> range(start: 0) 
    |> filter(fn: (r) => (r["sensor_id"] != "id-1"))
    |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value") 
    |> drop(columns: ["_start", "_stop", "_measurement"])
    |> filter(fn: (r) => (r["data"] >= 28))

Or

var query = from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", queryApi)
    where s.Value >= 28 || s.Value <= 5
    select s;

Flux Query:

from(bucket: "my-bucket") 
    |> range(start: 0) 
    |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value") 
    |> drop(columns: ["_start", "_stop", "_measurement"])
    |> filter(fn: (r) => ((r["data"] >= 28) or (r["data"] <=> 28)))

Any

The following code demonstrates how to use the Any operator to determine whether a collection contains any elements. By default the InfluxDB.Client doesn't supports to store a subcollection in your DomainObject.

Imagine that you have following entities:

class SensorCustom
{
    public Guid Id { get; set; }
    
    public float Data { get; set; }
    
    public DateTimeOffset Time { get; set; }
    
    public virtual ICollection<SensorAttribute> Attributes { get; set; }
}

class SensorAttribute
{
    public string Name { get; set; }
    public string Value { get; set; }
}

To be able to store SensorCustom entity in InfluxDB and retrieve it from database you should implement IDomainObjectMapper. The converter tells to the Client how to map DomainObject into PointData and how to map FluxRecord to DomainObject.

Entity Converter:

private class SensorEntityConverter : IDomainObjectMapper
{
    //
    // Parse incoming FluxRecord to DomainObject
    //
    public T ConvertToEntity<T>(FluxRecord fluxRecord)
    {
        if (typeof(T) != typeof(SensorCustom))
        {
            throw new NotSupportedException($"This converter doesn't supports: {typeof(SensorCustom)}");
        }

        //
        // Create SensorCustom entity and parse `SeriesId`, `Value` and `Time`
        //
        var customEntity = new SensorCustom
        {
            Id = Guid.Parse(Convert.ToString(fluxRecord.GetValueByKey("series_id"))!),
            Data = Convert.ToDouble(fluxRecord.GetValueByKey("data")),
            Time = fluxRecord.GetTime().GetValueOrDefault().ToDateTimeUtc(),
            Attributes = new List<SensorAttribute>()
        };
        
        foreach (var (key, value) in fluxRecord.Values)
        {
            //
            // Parse SubCollection values
            //
            if (key.StartsWith("property_"))
            {
                var attribute = new SensorAttribute
                {
                    Name = key.Replace("property_", string.Empty), Value = Convert.ToString(value)
                };
                
                customEntity.Attributes.Add(attribute);
            }
        }

        return (T) Convert.ChangeType(customEntity, typeof(T));
    }

    //
    // Convert DomainObject into PointData
    //
    public PointData ConvertToPointData<T>(T entity, WritePrecision precision)
    {
        if (!(entity is SensorCustom ce))
        {
            throw new NotSupportedException($"This converter doesn't supports: {typeof(SensorCustom)}");
        }

        //
        // Map `SeriesId`, `Value` and `Time` to Tag, Field and Timestamp
        //
        var point = PointData
            .Measurement("custom_measurement")
            .Tag("series_id", ce.Id.ToString())
            .Field("data", ce.Data)
            .Timestamp(ce.Time, precision);

        //
        // Map subattributes to Fields
        //
        foreach (var attribute in ce.Attributes ?? new List<SensorAttribute>())
        {
            point = point.Field($"property_{attribute.Name}", attribute.Value);
        }

        return point;
    }
}

The Converter could be passed to QueryApiSync, QueryApi or WriteApi by:

// Create Converter
var converter = new SensorEntityConverter();

// Get Query and Write API
var queryApi = client.GetQueryApiSync(converter);
var writeApi = client.GetWriteApi(converter);

The LINQ provider needs to know how properties of DomainObject are stored in InfluxDB - their name and type (tag, field, timestamp).

If you use a IDomainObjectMapper instead of InfluxDB Attributes you should implement IMemberNameResolver:

private class SensorMemberResolver: IMemberNameResolver
{
    //
    // Tell to LINQ providers how is property of DomainObject mapped - Tag, Field, Timestamp, ... ?
    //
    public MemberType ResolveMemberType(MemberInfo memberInfo)
    {
        //
        // Mapping of subcollection
        //
        if (memberInfo.DeclaringType == typeof(SensorAttribute))
        {
            return memberInfo.Name switch
            {
                "Name" => MemberType.NamedField,
                "Value" => MemberType.NamedFieldValue,
                _ => MemberType.Field
            };
        }

        //
        // Mapping of "root" domain
        //
        return memberInfo.Name switch
        {
            "Time" => MemberType.Timestamp,
            "Id" => MemberType.Tag,
            _ => MemberType.Field
        };
    }

    //
    // Tell to LINQ provider how is property of DomainObject named 
    //
    public string GetColumnName(MemberInfo memberInfo)
    {
        return memberInfo.Name switch
        {
            "Id" => "series_id",
            "Data" => "data",
            _ => memberInfo.Name
        };
    }

    //
    // Tell to LINQ provider how is named property that is flattened
    //
    public string GetNamedFieldName(MemberInfo memberInfo, object value)
    {
        return "attribute_" + Convert.ToString(value);
    }
}

Now We are able to provide a required information to the LINQ provider by memberResolver parameter:

var memberResolver = new SensorMemberResolver();

var query = from s in InfluxDBQueryable<SensorCustom>.Queryable("my-bucket", "my-org", queryApi, memberResolver)
    where s.Attributes.Any(a => a.Name == "quality" && a.Value == "good")
    select s;

Flux Query:

from(bucket: "my-bucket")
    |> range(start: 0)
    |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value") 
    |> drop(columns: ["_start", "_stop", "_measurement"])
    |> filter(fn: (r) => (r["attribute_quality"] == "good"))

For more info see CustomDomainMappingAndLinq example.

Take

var query = (from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", queryApi)
    select s)
    .Take(10);

Flux Query:

from(bucket: "my-bucket") 
    |> range(start: 0) 
    |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value") 
    |> drop(columns: ["_start", "_stop", "_measurement"])
    |> limit(n: 10)

Note: the limit() function can be align before pivot() function by:

var optimizerSettings =
    new QueryableOptimizerSettings
    {
        AlignLimitFunctionAfterPivot = false
    };

Performance: The pivot() is a “heavy” function. Using limit() before pivot() is much faster but works only if you have consistent data series. See #318 for more details.

TakeLast

var query = (from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", queryApi)
    select s)
    .TakeLast(10);

Flux Query:

from(bucket: "my-bucket") 
    |> range(start: 0) 
    |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value") 
    |> drop(columns: ["_start", "_stop", "_measurement"])
    |> tail(n: 10)

Note: the tail() function can be align before pivot() function by:

var optimizerSettings =
    new QueryableOptimizerSettings
    {
        AlignLimitFunctionAfterPivot = false
    };

Performance: The pivot() is a “heavy” function. Using tail() before pivot() is much faster but works only if you have consistent data series. See #318 for more details.

Skip

var query = (from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", queryApi)
    select s)
    .Take(10)
    .Skip(50);

Flux Query:

from(bucket: "my-bucket") 
    |> range(start: 0) 
    |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value") 
    |> drop(columns: ["_start", "_stop", "_measurement"])
    |> limit(n: 10, offset: 50)

OrderBy

Example 1:
var query = from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", queryApi)
    orderby s.Deployment
    select s;

Flux Query:

from(bucket: "my-bucket") 
    |> range(start: 0) 
    |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value") 
    |> drop(columns: ["_start", "_stop", "_measurement"])
    |> sort(columns: ["deployment"], desc: false)
Example 2:
var query = from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", queryApi)
    orderby s.Timestamp descending 
    select s;

Flux Query:

from(bucket: "my-bucket") 
    |> range(start: 0) 
    |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value") 
    |> drop(columns: ["_start", "_stop", "_measurement"])
    |> sort(columns: ["_time"], desc: true)

Count

Possibility of partial client side evaluation

var query = from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", queryApi)
    select s;

var sensors = query.Count();

Flux Query:

from(bucket: "my-bucket") 
    |> range(start: 0) 
    |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value") 
    |> drop(columns: ["_start", "_stop", "_measurement"])
    |> stateCount(fn: (r) => true, column: "linq_result_column") 
    |> last(column: "linq_result_column") 
    |> keep(columns: ["linq_result_column"])

LongCount

Possibility of partial client side evaluation

var query = from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", queryApi)
    select s;

var sensors = query.LongCount();

Flux Query:

from(bucket: "my-bucket") 
    |> range(start: 0)
    |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value") 
    |> drop(columns: ["_start", "_stop", "_measurement"])
    |> stateCount(fn: (r) => true, column: "linq_result_column") 
    |> last(column: "linq_result_column") 
    |> keep(columns: ["linq_result_column"])

Contains

int[] values = {15, 28};

var query = from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", queryApi)
    where values.Contains(s.Value)
    select s;

var sensors = query.Count();

Flux Query:

from(bucket: "my-bucket")
    |> range(start: 0)
    |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
    |> drop(columns: ["_start", "_stop", "_measurement"])
    |> filter(fn: (r) => contains(value: r["data"], set: [15, 28]))

Custom LINQ operators

AggregateWindow

The AggregateWindow applies an aggregate function to fixed windows of time. Can be used only for a field which is defined as timestamp - [Column(IsTimestamp = true)]. For more info about aggregateWindow() function see Flux's documentation - https://docs.influxdata.com/flux/v0.x/stdlib/universe/aggregatewindow/.

var query = from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", queryApi)
    where s.Timestamp.AggregateWindow(TimeSpan.FromSeconds(20), TimeSpan.FromSeconds(40), "mean")
    select s;

Flux Query:

from(bucket: "my-bucket") 
    |> range(start: 0) 
    |> aggregateWindow(every: 20s, period: 40s, fn: mean) 
    |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value") 
    |> drop(columns: ["_start", "_stop", "_measurement"])

Domain Converter

There is also possibility to use custom domain converter to transform data from/to your DomainObject.

Instead of following Influx attributes:

[Measurement("temperature")]
private class Temperature
{
    [Column("location", IsTag = true)] public string Location { get; set; }

    [Column("value")] public double Value { get; set; }

    [Column(IsTimestamp = true)] public DateTime Time { get; set; }
}

you could create own instance of IDomainObjectMapper and use it with QueryApiSync, QueryApi and WriteApi.

var converter = new DomainEntityConverter();
var queryApi = client.GetQueryApiSync(converter)

To satisfy LINQ Query Provider you have to implement IMemberNameResolver:

var resolver = new MemberNameResolver();

var query = from s in InfluxDBQueryable<SensorCustom>.Queryable("my-bucket", "my-org", queryApi, nameResolver)
    where s.Attributes.Any(a => a.Name == "quality" && a.Value == "good")
    select s;

for more details see Any operator and for full example see: CustomDomainMappingAndLinq.

How to debug output Flux Query

var query = (from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", _queryApi)
        where s.SensorId == "id-1"
        where s.Value > 12
        where s.Timestamp > new DateTime(2019, 11, 16, 8, 20, 15, DateTimeKind.Utc)
        where s.Timestamp < new DateTime(2021, 01, 10, 5, 10, 0, DateTimeKind.Utc)
        orderby s.Timestamp
        select s)
    .Take(2)
    .Skip(2);
    
Console.WriteLine("==== Debug LINQ Queryable Flux output ====");
var influxQuery = ((InfluxDBQueryable<Sensor>) query).ToDebugQuery();
foreach (var statement in influxQuery.Extern.Body)
{
    var os = statement as OptionStatement;
    var va = os?.Assignment as VariableAssignment;
    var name = va?.Id.Name;
    var value = va?.Init.GetType().GetProperty("Value")?.GetValue(va.Init, null);

    Console.WriteLine($"{name}={value}");
}
Console.WriteLine();
Console.WriteLine(influxQuery._Query);

How to filter by Measurement

By default, as an optimization step, Flux queries generated by LINQ will automatically drop the Start, Stop and Measurement columns:

from(bucket: "my-bucket")
  |> range(start: 0)
  |> drop(columns: ["_start", "_stop", "_measurement"])
  ...

This is because typical POCO classes do not include them:

[Measurement("temperature")]
private class Temperature
{
    [Column("location", IsTag = true)] public string Location { get; set; }
    [Column("value")] public double Value { get; set; }
    [Column(IsTimestamp = true)] public DateTime Time { get; set; }
}

It is, however, possible to utilize the Measurement column in LINQ queries by enabling it in the query optimization settings:

var optimizerSettings =
    new QueryableOptimizerSettings
    {
        DropMeasurementColumn = false,
        
        // Note we can also enable the start and stop columns
        //DropStartColumn = false,
        //DropStopColumn = false
    };

var queryable =
    new InfluxDBQueryable<InfluxPoint>("my-bucket", "my-org", queryApi, new DefaultMemberNameResolver(), optimizerSettings);

var latest =
    await queryable.Where(p => p.Measurement == "temperature")
                   .OrderByDescending(p => p.Time)
                   .ToInfluxQueryable()
                   .GetAsyncEnumerator()
                   .FirstOrDefaultAsync();

private class InfluxPoint
{
    [Column(IsMeasurement = true)] public string Measurement { get; set; }
    [Column("location", IsTag = true)] public string Location { get; set; }
    [Column("value")] public double Value { get; set; }
    [Column(IsTimestamp = true)] public DateTime Time { get; set; }
}

Asynchronous Queries

The LINQ driver also supports asynchronous querying. For asynchronous queries you have to initialize InfluxDBQueryable with asynchronous version of QueryApi and transform IQueryable<T> to IAsyncEnumerable<T>:

var client = new InfluxDBClient("http://localhost:8086", "my-token");
var queryApi = client.GetQueryApi();

var query = from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", queryApi)
    select s;

IAsyncEnumerable<Sensor> enumerable = query
    .ToInfluxQueryable()
    .GetAsyncEnumerator();
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 is compatible. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (3)

Showing the top 3 NuGet packages that depend on InfluxDB.Client.Linq:

Package Downloads
DeerNet.InfluxDb2

Package Description

ToolNET.InfluxDB.SDK

时序数据库InfluxDB操作SDK

MicroHeart.InfluxDB

Package Description

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
4.19.0-dev.15190 299 12/5/2024
4.19.0-dev.15189 79 12/5/2024
4.19.0-dev.15188 91 12/5/2024
4.19.0-dev.15178 93 12/5/2024
4.19.0-dev.15177 99 12/5/2024
4.19.0-dev.14906 161 10/2/2024
4.19.0-dev.14897 92 10/2/2024
4.19.0-dev.14896 89 10/2/2024
4.19.0-dev.14895 100 10/2/2024
4.19.0-dev.14811 149 9/13/2024
4.18.0 159,045 9/13/2024
4.18.0-dev.14769 107 9/4/2024
4.18.0-dev.14743 98 9/3/2024
4.18.0-dev.14694 85 9/3/2024
4.18.0-dev.14693 80 9/3/2024
4.18.0-dev.14692 89 9/3/2024
4.18.0-dev.14618 93 9/2/2024
4.18.0-dev.14609 85 9/2/2024
4.18.0-dev.14592 83 9/2/2024
4.18.0-dev.14446 113 8/19/2024
4.18.0-dev.14414 107 8/12/2024
4.17.0 14,344 8/12/2024
4.17.0-dev.headers.read.1 129 7/22/2024
4.17.0-dev.14350 92 8/5/2024
4.17.0-dev.14333 81 8/5/2024
4.17.0-dev.14300 84 8/5/2024
4.17.0-dev.14291 86 8/5/2024
4.17.0-dev.14189 101 7/23/2024
4.17.0-dev.14179 104 7/22/2024
4.17.0-dev.14101 182 7/1/2024
4.17.0-dev.14100 105 7/1/2024
4.17.0-dev.14044 116 6/24/2024
4.16.0 13,588 6/24/2024
4.16.0-dev.13990 106 6/3/2024
4.16.0-dev.13973 94 6/3/2024
4.16.0-dev.13972 101 6/3/2024
4.16.0-dev.13963 103 6/3/2024
4.16.0-dev.13962 99 6/3/2024
4.16.0-dev.13881 92 6/3/2024
4.16.0-dev.13775 127 5/17/2024
4.16.0-dev.13702 102 5/17/2024
4.15.0 3,798 5/17/2024
4.15.0-dev.13674 114 5/14/2024
4.15.0-dev.13567 119 4/2/2024
4.15.0-dev.13558 121 4/2/2024
4.15.0-dev.13525 109 4/2/2024
4.15.0-dev.13524 104 4/2/2024
4.15.0-dev.13433 118 3/7/2024
4.15.0-dev.13432 114 3/7/2024
4.15.0-dev.13407 114 3/7/2024
4.15.0-dev.13390 97 3/7/2024
4.15.0-dev.13388 97 3/7/2024
4.15.0-dev.13282 98 3/6/2024
4.15.0-dev.13257 110 3/6/2024
4.15.0-dev.13113 273 2/1/2024
4.15.0-dev.13104 93 2/1/2024
4.15.0-dev.13081 111 2/1/2024
4.15.0-dev.13040 105 2/1/2024
4.15.0-dev.13039 97 2/1/2024
4.15.0-dev.12863 155 1/8/2024
4.15.0-dev.12846 122 1/8/2024
4.15.0-dev.12837 100 1/8/2024
4.15.0-dev.12726 192 12/1/2023
4.15.0-dev.12725 113 12/1/2023
4.15.0-dev.12724 117 12/1/2023
4.15.0-dev.12691 126 12/1/2023
4.15.0-dev.12658 109 12/1/2023
4.15.0-dev.12649 118 12/1/2023
4.15.0-dev.12624 112 12/1/2023
4.15.0-dev.12471 127 11/7/2023
4.15.0-dev.12462 110 11/7/2023
4.14.0 70,664 11/7/2023
4.14.0-dev.12437 124 11/7/2023
4.14.0-dev.12343 131 11/2/2023
4.14.0-dev.12310 118 11/2/2023
4.14.0-dev.12284 118 11/1/2023
4.14.0-dev.12235 121 11/1/2023
4.14.0-dev.12226 113 11/1/2023
4.14.0-dev.11972 288 8/8/2023
4.14.0-dev.11915 178 7/31/2023
4.14.0-dev.11879 190 7/28/2023
4.13.0 23,852 7/28/2023
4.13.0-dev.11854 158 7/28/2023
4.13.0-dev.11814 160 7/21/2023
4.13.0-dev.11771 162 7/19/2023
4.13.0-dev.11770 173 7/19/2023
4.13.0-dev.11728 161 7/18/2023
4.13.0-dev.11686 148 7/17/2023
4.13.0-dev.11685 164 7/17/2023
4.13.0-dev.11676 163 7/17/2023
4.13.0-dev.11479 163 6/27/2023
4.13.0-dev.11478 161 6/27/2023
4.13.0-dev.11477 164 6/27/2023
4.13.0-dev.11396 177 6/19/2023
4.13.0-dev.11395 159 6/19/2023
4.13.0-dev.11342 172 6/15/2023
4.13.0-dev.11330 184 6/12/2023
4.13.0-dev.11305 174 6/12/2023
4.13.0-dev.11296 172 6/12/2023
4.13.0-dev.11217 180 6/6/2023
4.13.0-dev.11089 169 5/30/2023
4.13.0-dev.11064 173 5/30/2023
4.13.0-dev.10998 164 5/29/2023
4.13.0-dev.10989 176 5/29/2023
4.13.0-dev.10871 175 5/8/2023
4.13.0-dev.10870 155 5/8/2023
4.13.0-dev.10819 182 4/28/2023
4.12.0 14,164 4/28/2023
4.12.0-dev.10777 173 4/27/2023
4.12.0-dev.10768 169 4/27/2023
4.12.0-dev.10759 173 4/27/2023
4.12.0-dev.10742 174 4/27/2023
4.12.0-dev.10685 161 4/27/2023
4.12.0-dev.10684 165 4/27/2023
4.12.0-dev.10643 171 4/27/2023
4.12.0-dev.10642 173 4/27/2023
4.12.0-dev.10569 176 4/27/2023
4.12.0-dev.10193 210 2/23/2023
4.11.0 26,128 2/23/2023
4.11.0-dev.10176 176 2/23/2023
4.11.0-dev.10059 296 1/26/2023
4.10.0 9,179 1/26/2023
4.10.0-dev.10033 207 1/25/2023
4.10.0-dev.10032 206 1/25/2023
4.10.0-dev.10031 206 1/25/2023
4.10.0-dev.9936 2,281 12/26/2022
4.10.0-dev.9935 199 12/26/2022
4.10.0-dev.9881 191 12/21/2022
4.10.0-dev.9880 196 12/21/2022
4.10.0-dev.9818 196 12/16/2022
4.10.0-dev.9773 183 12/12/2022
4.10.0-dev.9756 182 12/12/2022
4.10.0-dev.9693 178 12/6/2022
4.9.0 10,526 12/6/2022
4.9.0-dev.9684 195 12/6/2022
4.9.0-dev.9666 188 12/6/2022
4.9.0-dev.9617 202 12/6/2022
4.9.0-dev.9478 192 12/5/2022
4.9.0-dev.9469 199 12/5/2022
4.9.0-dev.9444 185 12/5/2022
4.9.0-dev.9411 178 12/5/2022
4.9.0-dev.9350 186 12/1/2022
4.8.0 1,702 12/1/2022
4.8.0-dev.9324 192 11/30/2022
4.8.0-dev.9232 192 11/28/2022
4.8.0-dev.9223 178 11/28/2022
4.8.0-dev.9222 198 11/28/2022
4.8.0-dev.9117 201 11/21/2022
4.8.0-dev.9108 194 11/21/2022
4.8.0-dev.9099 201 11/21/2022
4.8.0-dev.9029 204 11/16/2022
4.8.0-dev.8971 186 11/15/2022
4.8.0-dev.8961 201 11/14/2022
4.8.0-dev.8928 200 11/14/2022
4.8.0-dev.8899 214 11/14/2022
4.8.0-dev.8898 197 11/14/2022
4.8.0-dev.8839 214 11/14/2022
4.8.0-dev.8740 187 11/7/2022
4.8.0-dev.8725 184 11/7/2022
4.8.0-dev.8648 191 11/3/2022
4.7.0 25,621 11/3/2022
4.7.0-dev.8625 199 11/2/2022
4.7.0-dev.8594 204 10/31/2022
4.7.0-dev.8579 194 10/31/2022
4.7.0-dev.8557 190 10/31/2022
4.7.0-dev.8540 170 10/31/2022
4.7.0-dev.8518 188 10/31/2022
4.7.0-dev.8517 202 10/31/2022
4.7.0-dev.8509 194 10/31/2022
4.7.0-dev.8377 204 10/26/2022
4.7.0-dev.8360 196 10/25/2022
4.7.0-dev.8350 210 10/24/2022
4.7.0-dev.8335 206 10/24/2022
4.7.0-dev.8334 203 10/24/2022
4.7.0-dev.8223 247 10/19/2022
4.7.0-dev.8178 187 10/17/2022
4.7.0-dev.8170 186 10/17/2022
4.7.0-dev.8148 192 10/17/2022
4.7.0-dev.8133 199 10/17/2022
4.7.0-dev.8097 187 10/17/2022
4.7.0-dev.8034 218 10/11/2022
4.7.0-dev.8025 195 10/11/2022
4.7.0-dev.8009 213 10/10/2022
4.7.0-dev.8001 205 10/10/2022
4.7.0-dev.7959 185 10/4/2022
4.7.0-dev.7905 200 9/30/2022
4.7.0-dev.7875 189 9/29/2022
4.6.0 2,810 9/29/2022
4.6.0-dev.7832 211 9/29/2022
4.6.0-dev.7817 204 9/29/2022
4.6.0-dev.7779 209 9/27/2022
4.6.0-dev.7778 224 9/27/2022
4.6.0-dev.7734 209 9/26/2022
4.6.0-dev.7733 207 9/26/2022
4.6.0-dev.7677 211 9/20/2022
4.6.0-dev.7650 207 9/16/2022
4.6.0-dev.7626 266 9/14/2022
4.6.0-dev.7618 264 9/14/2022
4.6.0-dev.7574 188 9/13/2022
4.6.0-dev.7572 203 9/13/2022
4.6.0-dev.7528 183 9/12/2022
4.6.0-dev.7502 212 9/9/2022
4.6.0-dev.7479 231 9/8/2022
4.6.0-dev.7471 211 9/8/2022
4.6.0-dev.7447 210 9/7/2022
4.6.0-dev.7425 199 9/7/2022
4.6.0-dev.7395 196 9/6/2022
4.6.0-dev.7344 197 8/31/2022
4.6.0-dev.7329 203 8/31/2022
4.6.0-dev.7292 196 8/30/2022
4.6.0-dev.7240 210 8/29/2022
4.5.0 2,977 8/29/2022
4.5.0-dev.7216 196 8/27/2022
4.5.0-dev.7147 210 8/22/2022
4.5.0-dev.7134 209 8/17/2022
4.5.0-dev.7096 208 8/15/2022
4.5.0-dev.7070 219 8/11/2022
4.5.0-dev.7040 242 8/10/2022
4.5.0-dev.7011 207 8/3/2022
4.5.0-dev.6987 225 8/1/2022
4.5.0-dev.6962 224 7/29/2022
4.4.0 14,851 7/29/2022
4.4.0-dev.6901 217 7/25/2022
4.4.0-dev.6843 206 7/19/2022
4.4.0-dev.6804 225 7/19/2022
4.4.0-dev.6789 215 7/19/2022
4.4.0-dev.6760 215 7/19/2022
4.4.0-dev.6705 228 7/14/2022
4.4.0-dev.6663 260 6/24/2022
4.4.0-dev.6655 216 6/24/2022
4.3.0 19,086 6/24/2022
4.3.0-dev.multiple.buckets3 243 6/21/2022
4.3.0-dev.multiple.buckets2 221 6/17/2022
4.3.0-dev.multiple.buckets1 205 6/17/2022
4.3.0-dev.6631 215 6/22/2022
4.3.0-dev.6623 221 6/22/2022
4.3.0-dev.6374 224 6/13/2022
4.3.0-dev.6286 226 5/20/2022
4.2.0 2,579 5/20/2022
4.2.0-dev.6257 232 5/13/2022
4.2.0-dev.6248 227 5/12/2022
4.2.0-dev.6233 230 5/12/2022
4.2.0-dev.6194 236 5/10/2022
4.2.0-dev.6193 226 5/10/2022
4.2.0-dev.6158 2,934 5/6/2022
4.2.0-dev.6135 223 5/6/2022
4.2.0-dev.6091 228 4/28/2022
4.2.0-dev.6048 223 4/28/2022
4.2.0-dev.6047 242 4/28/2022
4.2.0-dev.5966 251 4/25/2022
4.2.0-dev.5938 242 4/19/2022
4.1.0 3,520 4/19/2022
4.1.0-dev.5910 422 4/13/2022
4.1.0-dev.5888 240 4/13/2022
4.1.0-dev.5887 239 4/13/2022
4.1.0-dev.5794 240 4/6/2022
4.1.0-dev.5725 248 3/18/2022
4.0.0 9,944 3/18/2022
4.0.0-rc3 490 3/4/2022
4.0.0-rc2 672 2/25/2022
4.0.0-rc1 307 2/18/2022
4.0.0-dev.5709 237 3/18/2022
4.0.0-dev.5684 251 3/15/2022
4.0.0-dev.5630 238 3/4/2022
4.0.0-dev.5607 239 3/3/2022
4.0.0-dev.5579 249 2/25/2022
4.0.0-dev.5556 247 2/24/2022
4.0.0-dev.5555 238 2/24/2022
4.0.0-dev.5497 234 2/23/2022
4.0.0-dev.5489 235 2/23/2022
4.0.0-dev.5460 248 2/23/2022
4.0.0-dev.5444 244 2/22/2022
4.0.0-dev.5333 231 2/17/2022
4.0.0-dev.5303 235 2/16/2022
4.0.0-dev.5280 254 2/16/2022
4.0.0-dev.5279 250 2/16/2022
4.0.0-dev.5241 353 2/15/2022
4.0.0-dev.5225 236 2/15/2022
4.0.0-dev.5217 251 2/15/2022
4.0.0-dev.5209 234 2/15/2022
4.0.0-dev.5200 242 2/14/2022
4.0.0-dev.5188 244 2/10/2022
4.0.0-dev.5180 245 2/10/2022
4.0.0-dev.5172 237 2/10/2022
4.0.0-dev.5130 239 2/10/2022
4.0.0-dev.5122 233 2/9/2022
4.0.0-dev.5103 252 2/9/2022
4.0.0-dev.5097 244 2/9/2022
4.0.0-dev.5091 243 2/9/2022
4.0.0-dev.5084 243 2/8/2022
3.4.0-dev.5263 257 2/15/2022
3.4.0-dev.4986 249 2/7/2022
3.4.0-dev.4968 264 2/4/2022
3.3.0 8,822 2/4/2022
3.3.0-dev.4889 250 2/3/2022
3.3.0-dev.4865 254 2/1/2022
3.3.0-dev.4823 269 1/19/2022
3.3.0-dev.4691 258 1/7/2022
3.3.0-dev.4557 1,465 11/26/2021
3.2.0 6,045 11/26/2021
3.2.0-dev.4533 4,962 11/24/2021
3.2.0-dev.4484 323 11/11/2021
3.2.0-dev.4475 295 11/10/2021
3.2.0-dev.4387 272 10/26/2021
3.2.0-dev.4363 292 10/22/2021
3.2.0-dev.4356 293 10/22/2021
3.1.0 1,920 10/22/2021
3.1.0-dev.4303 284 10/18/2021
3.1.0-dev.4293 291 10/15/2021
3.1.0-dev.4286 271 10/15/2021
3.1.0-dev.4240 302 10/12/2021
3.1.0-dev.4202 278 10/11/2021
3.1.0-dev.4183 309 10/11/2021
3.1.0-dev.4131 270 10/8/2021
3.1.0-dev.3999 273 10/5/2021
3.1.0-dev.3841 363 9/29/2021
3.1.0-dev.3798 281 9/17/2021
3.0.0 1,336 9/17/2021
3.0.0-dev.3726 628 8/31/2021
3.0.0-dev.3719 269 8/31/2021
3.0.0-dev.3671 282 8/20/2021
2.2.0-dev.3652 284 8/20/2021
2.1.0 1,683 8/20/2021
2.1.0-dev.3605 295 8/17/2021
2.1.0-dev.3584 286 8/16/2021
2.1.0-dev.3558 277 8/16/2021
2.1.0-dev.3527 317 7/29/2021
2.1.0-dev.3519 323 7/29/2021
2.1.0-dev.3490 270 7/20/2021
2.1.0-dev.3445 300 7/12/2021
2.1.0-dev.3434 335 7/9/2021
2.0.0 9,143 7/9/2021
2.0.0-dev.3401 309 6/25/2021
2.0.0-dev.3368 297 6/23/2021
2.0.0-dev.3361 309 6/23/2021
2.0.0-dev.3330 305 6/17/2021
2.0.0-dev.3291 306 6/16/2021
1.20.0-dev.3218 310 6/4/2021
1.19.0 1,061 6/4/2021
1.19.0-dev.3204 289 6/3/2021
1.19.0-dev.3160 281 6/2/2021
1.19.0-dev.3159 274 6/2/2021
1.19.0-dev.3084 940 5/7/2021
1.19.0-dev.3051 305 5/5/2021
1.19.0-dev.3044 285 5/5/2021
1.19.0-dev.3008 283 4/30/2021
1.18.0 1,369 4/30/2021
1.18.0-dev.2973 302 4/27/2021
1.18.0-dev.2930 295 4/16/2021
1.18.0-dev.2919 287 4/13/2021
1.18.0-dev.2893 282 4/12/2021
1.18.0-dev.2880 297 4/12/2021
1.18.0-dev.2856 287 4/7/2021
1.18.0-dev.2830 381 4/1/2021
1.18.0-dev.2816 290 4/1/2021
1.17.0 932 4/1/2021
1.17.0-dev.linq.17 903 3/18/2021
1.17.0-dev.linq.16 285 3/16/2021
1.17.0-dev.linq.15 313 3/15/2021
1.17.0-dev.linq.14 325 3/12/2021
1.17.0-dev.linq.13 357 3/11/2021
1.17.0-dev.linq.12 304 3/10/2021
1.17.0-dev.linq.11 303 3/8/2021
1.17.0-dev.2776 325 3/26/2021
1.17.0-dev.2713 337 3/25/2021
1.16.0-dev.linq.10 1,351 2/4/2021
1.15.0-dev.linq.9 327 2/4/2021
1.15.0-dev.linq.8 298 1/28/2021
1.15.0-dev.linq.7 309 1/27/2021
1.15.0-dev.linq.6 322 1/20/2021
1.15.0-dev.linq.5 357 1/19/2021
1.15.0-dev.linq.4 303 1/15/2021
1.15.0-dev.linq.3 287 1/14/2021
1.15.0-dev.linq.2 302 1/13/2021
1.15.0-dev.linq.1 326 1/12/2021