InfluxDB.Client.Linq 5.0.0

dotnet add package InfluxDB.Client.Linq --version 5.0.0
                    
NuGet\Install-Package InfluxDB.Client.Linq -Version 5.0.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="5.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="InfluxDB.Client.Linq" Version="5.0.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 5.0.0
                    
#r "nuget: InfluxDB.Client.Linq, 5.0.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@5.0.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=5.0.0
                    
Install as a Cake Addin
#tool nuget:?package=InfluxDB.Client.Linq&version=5.0.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
5.0.0 314 1/13/2026
4.19.0-dev.15190 473 12/5/2024
4.19.0-dev.15189 98 12/5/2024
4.19.0-dev.15188 117 12/5/2024
4.19.0-dev.15178 125 12/5/2024
4.19.0-dev.15177 132 12/5/2024
4.19.0-dev.14906 203 10/2/2024
4.19.0-dev.14897 126 10/2/2024
4.19.0-dev.14896 129 10/2/2024
4.19.0-dev.14895 130 10/2/2024
4.19.0-dev.14811 184 9/13/2024
4.18.0 215,178 9/13/2024
4.18.0-dev.14769 135 9/4/2024
4.18.0-dev.14743 120 9/3/2024
4.18.0-dev.14694 107 9/3/2024
4.18.0-dev.14693 97 9/3/2024
4.18.0-dev.14692 113 9/3/2024
4.18.0-dev.14618 124 9/2/2024
4.18.0-dev.14609 117 9/2/2024
4.18.0-dev.14592 109 9/2/2024
4.18.0-dev.14446 147 8/19/2024
4.18.0-dev.14414 133 8/12/2024
4.17.0 19,065 8/12/2024
4.17.0-dev.headers.read.1 152 7/22/2024
4.17.0-dev.14350 114 8/5/2024
4.17.0-dev.14333 105 8/5/2024
4.17.0-dev.14300 117 8/5/2024
4.17.0-dev.14291 120 8/5/2024
4.17.0-dev.14189 130 7/23/2024
4.17.0-dev.14179 136 7/22/2024
4.17.0-dev.14101 207 7/1/2024
4.17.0-dev.14100 138 7/1/2024
4.17.0-dev.14044 149 6/24/2024
4.16.0 14,101 6/24/2024
4.16.0-dev.13990 139 6/3/2024
4.16.0-dev.13973 128 6/3/2024
4.16.0-dev.13972 130 6/3/2024
4.16.0-dev.13963 131 6/3/2024
4.16.0-dev.13962 124 6/3/2024
4.16.0-dev.13881 123 6/3/2024
4.16.0-dev.13775 157 5/17/2024
4.16.0-dev.13702 124 5/17/2024
4.15.0 4,077 5/17/2024
4.15.0-dev.13674 148 5/14/2024
4.15.0-dev.13567 150 4/2/2024
4.15.0-dev.13558 157 4/2/2024
4.15.0-dev.13525 149 4/2/2024
4.15.0-dev.13524 141 4/2/2024
4.15.0-dev.13433 151 3/7/2024
4.15.0-dev.13432 149 3/7/2024
4.15.0-dev.13407 142 3/7/2024
4.15.0-dev.13390 123 3/7/2024
4.15.0-dev.13388 127 3/7/2024
4.15.0-dev.13282 117 3/6/2024
4.15.0-dev.13257 138 3/6/2024
4.15.0-dev.13113 313 2/1/2024
4.15.0-dev.13104 112 2/1/2024
4.15.0-dev.13081 153 2/1/2024
4.15.0-dev.13040 140 2/1/2024
4.15.0-dev.13039 117 2/1/2024
4.15.0-dev.12863 194 1/8/2024
4.15.0-dev.12846 154 1/8/2024
4.15.0-dev.12837 120 1/8/2024
4.15.0-dev.12726 223 12/1/2023
4.15.0-dev.12725 142 12/1/2023
4.15.0-dev.12724 149 12/1/2023
4.15.0-dev.12691 154 12/1/2023
4.15.0-dev.12658 139 12/1/2023
4.15.0-dev.12649 149 12/1/2023
4.15.0-dev.12624 141 12/1/2023
4.15.0-dev.12471 147 11/7/2023
4.15.0-dev.12462 136 11/7/2023
4.14.0 75,112 11/7/2023
4.14.0-dev.12437 147 11/7/2023
4.14.0-dev.12343 162 11/2/2023
4.14.0-dev.12310 146 11/2/2023
4.14.0-dev.12284 150 11/1/2023
4.14.0-dev.12235 149 11/1/2023
4.14.0-dev.12226 137 11/1/2023
4.14.0-dev.11972 312 8/8/2023
4.14.0-dev.11915 210 7/31/2023
4.14.0-dev.11879 219 7/28/2023
4.13.0 25,153 7/28/2023
4.13.0-dev.11854 191 7/28/2023
4.13.0-dev.11814 185 7/21/2023
4.13.0-dev.11771 190 7/19/2023
4.13.0-dev.11770 203 7/19/2023
4.13.0-dev.11728 196 7/18/2023
4.13.0-dev.11686 175 7/17/2023
4.13.0-dev.11685 203 7/17/2023
4.13.0-dev.11676 187 7/17/2023
4.13.0-dev.11479 196 6/27/2023
4.13.0-dev.11478 200 6/27/2023
4.13.0-dev.11477 202 6/27/2023
4.13.0-dev.11396 218 6/19/2023
4.13.0-dev.11395 198 6/19/2023
4.13.0-dev.11342 212 6/15/2023
4.13.0-dev.11330 223 6/12/2023
4.13.0-dev.11305 213 6/12/2023
4.13.0-dev.11296 206 6/12/2023
4.13.0-dev.11217 221 6/6/2023
4.13.0-dev.11089 212 5/30/2023
4.13.0-dev.11064 211 5/30/2023
4.13.0-dev.10998 209 5/29/2023
4.13.0-dev.10989 220 5/29/2023
4.13.0-dev.10871 215 5/8/2023
4.13.0-dev.10870 207 5/8/2023
4.13.0-dev.10819 229 4/28/2023
4.12.0 14,320 4/28/2023
4.12.0-dev.10777 208 4/27/2023
4.12.0-dev.10768 207 4/27/2023
4.12.0-dev.10759 216 4/27/2023
4.12.0-dev.10742 221 4/27/2023
4.12.0-dev.10685 201 4/27/2023
4.12.0-dev.10684 207 4/27/2023
4.12.0-dev.10643 217 4/27/2023
4.12.0-dev.10642 223 4/27/2023
4.12.0-dev.10569 218 4/27/2023
4.12.0-dev.10193 257 2/23/2023
4.11.0 26,638 2/23/2023
4.11.0-dev.10176 228 2/23/2023
4.11.0-dev.10059 350 1/26/2023
4.10.0 10,879 1/26/2023
4.10.0-dev.10033 248 1/25/2023
4.10.0-dev.10032 249 1/25/2023
4.10.0-dev.10031 248 1/25/2023
4.10.0-dev.9936 2,333 12/26/2022
4.10.0-dev.9935 253 12/26/2022
4.10.0-dev.9881 236 12/21/2022
4.10.0-dev.9880 242 12/21/2022
4.10.0-dev.9818 245 12/16/2022
4.10.0-dev.9773 236 12/12/2022
4.10.0-dev.9756 220 12/12/2022
4.10.0-dev.9693 221 12/6/2022
4.9.0 10,915 12/6/2022
4.9.0-dev.9684 246 12/6/2022
4.9.0-dev.9666 230 12/6/2022
4.9.0-dev.9617 247 12/6/2022
4.9.0-dev.9478 246 12/5/2022
4.9.0-dev.9469 249 12/5/2022
4.9.0-dev.9444 232 12/5/2022
4.9.0-dev.9411 230 12/5/2022
4.9.0-dev.9350 239 12/1/2022
4.8.0 1,810 12/1/2022
4.8.0-dev.9324 236 11/30/2022
4.8.0-dev.9232 240 11/28/2022
4.8.0-dev.9223 221 11/28/2022
4.8.0-dev.9222 245 11/28/2022
4.8.0-dev.9117 253 11/21/2022
4.8.0-dev.9108 248 11/21/2022
4.8.0-dev.9099 261 11/21/2022
4.8.0-dev.9029 255 11/16/2022
4.8.0-dev.8971 230 11/15/2022
4.8.0-dev.8961 245 11/14/2022
4.8.0-dev.8928 253 11/14/2022
4.8.0-dev.8899 258 11/14/2022
4.8.0-dev.8898 245 11/14/2022
4.8.0-dev.8839 275 11/14/2022
4.8.0-dev.8740 239 11/7/2022
4.8.0-dev.8725 223 11/7/2022
4.8.0-dev.8648 233 11/3/2022
4.7.0 25,956 11/3/2022
4.7.0-dev.8625 246 11/2/2022
4.7.0-dev.8594 251 10/31/2022
4.7.0-dev.8579 237 10/31/2022
4.7.0-dev.8557 239 10/31/2022
4.7.0-dev.8540 198 10/31/2022
4.7.0-dev.8518 244 10/31/2022
4.7.0-dev.8517 245 10/31/2022
4.7.0-dev.8509 247 10/31/2022
4.7.0-dev.8377 264 10/26/2022
4.7.0-dev.8360 243 10/25/2022
4.7.0-dev.8350 260 10/24/2022
4.7.0-dev.8335 252 10/24/2022
4.7.0-dev.8334 252 10/24/2022
4.7.0-dev.8223 294 10/19/2022
4.7.0-dev.8178 229 10/17/2022
4.7.0-dev.8170 234 10/17/2022
4.7.0-dev.8148 235 10/17/2022
4.7.0-dev.8133 251 10/17/2022
4.7.0-dev.8097 239 10/17/2022
4.7.0-dev.8034 272 10/11/2022
4.7.0-dev.8025 241 10/11/2022
4.7.0-dev.8009 269 10/10/2022
4.7.0-dev.8001 236 10/10/2022
4.7.0-dev.7959 225 10/4/2022
4.7.0-dev.7905 250 9/30/2022
4.7.0-dev.7875 237 9/29/2022
4.6.0 2,927 9/29/2022
4.6.0-dev.7832 265 9/29/2022
4.6.0-dev.7817 250 9/29/2022
4.6.0-dev.7779 249 9/27/2022
4.6.0-dev.7778 278 9/27/2022
4.6.0-dev.7734 256 9/26/2022
4.6.0-dev.7733 252 9/26/2022
4.6.0-dev.7677 263 9/20/2022
4.6.0-dev.7650 252 9/16/2022
4.6.0-dev.7626 312 9/14/2022
4.6.0-dev.7618 311 9/14/2022
4.6.0-dev.7574 236 9/13/2022
4.6.0-dev.7572 254 9/13/2022
4.6.0-dev.7528 226 9/12/2022
4.6.0-dev.7502 258 9/9/2022
4.6.0-dev.7479 282 9/8/2022
4.6.0-dev.7471 257 9/8/2022
4.6.0-dev.7447 260 9/7/2022
4.6.0-dev.7425 251 9/7/2022
4.6.0-dev.7395 237 9/6/2022
4.6.0-dev.7344 245 8/31/2022
4.6.0-dev.7329 258 8/31/2022
4.6.0-dev.7292 242 8/30/2022
4.6.0-dev.7240 262 8/29/2022
4.5.0 3,358 8/29/2022
4.5.0-dev.7216 253 8/27/2022
4.5.0-dev.7147 266 8/22/2022
4.5.0-dev.7134 258 8/17/2022
4.5.0-dev.7096 253 8/15/2022
4.5.0-dev.7070 275 8/11/2022
4.5.0-dev.7040 291 8/10/2022
4.5.0-dev.7011 253 8/3/2022
4.5.0-dev.6987 275 8/1/2022
4.5.0-dev.6962 272 7/29/2022
4.4.0 14,970 7/29/2022
4.4.0-dev.6901 281 7/25/2022
4.4.0-dev.6843 250 7/19/2022
4.4.0-dev.6804 282 7/19/2022
4.4.0-dev.6789 269 7/19/2022
4.4.0-dev.6760 269 7/19/2022
4.4.0-dev.6705 278 7/14/2022
4.4.0-dev.6663 307 6/24/2022
4.4.0-dev.6655 275 6/24/2022
4.3.0 20,676 6/24/2022
4.3.0-dev.multiple.buckets3 301 6/21/2022
4.3.0-dev.multiple.buckets2 274 6/17/2022
4.3.0-dev.multiple.buckets1 256 6/17/2022
4.3.0-dev.6631 264 6/22/2022
4.3.0-dev.6623 272 6/22/2022
4.3.0-dev.6374 276 6/13/2022
4.3.0-dev.6286 268 5/20/2022
4.2.0 2,694 5/20/2022
4.2.0-dev.6257 287 5/13/2022
4.2.0-dev.6248 279 5/12/2022
4.2.0-dev.6233 282 5/12/2022
4.2.0-dev.6194 285 5/10/2022
4.2.0-dev.6193 281 5/10/2022
4.2.0-dev.6158 2,984 5/6/2022
4.2.0-dev.6135 269 5/6/2022
4.2.0-dev.6091 274 4/28/2022
4.2.0-dev.6048 268 4/28/2022
4.2.0-dev.6047 295 4/28/2022
4.2.0-dev.5966 298 4/25/2022
4.2.0-dev.5938 294 4/19/2022
4.1.0 3,636 4/19/2022
4.1.0-dev.5910 467 4/13/2022
4.1.0-dev.5888 294 4/13/2022
4.1.0-dev.5887 296 4/13/2022
4.1.0-dev.5794 290 4/6/2022
4.1.0-dev.5725 305 3/18/2022
4.0.0 10,695 3/18/2022
4.0.0-rc3 532 3/4/2022
4.0.0-rc2 748 2/25/2022
4.0.0-rc1 364 2/18/2022
4.0.0-dev.5709 290 3/18/2022
4.0.0-dev.5684 306 3/15/2022
4.0.0-dev.5630 286 3/4/2022
4.0.0-dev.5607 290 3/3/2022
4.0.0-dev.5579 309 2/25/2022
4.0.0-dev.5556 306 2/24/2022
4.0.0-dev.5555 287 2/24/2022
4.0.0-dev.5497 293 2/23/2022
4.0.0-dev.5489 284 2/23/2022
4.0.0-dev.5460 303 2/23/2022
4.0.0-dev.5444 299 2/22/2022
4.0.0-dev.5333 281 2/17/2022
4.0.0-dev.5303 289 2/16/2022
4.0.0-dev.5280 312 2/16/2022
4.0.0-dev.5279 309 2/16/2022
4.0.0-dev.5241 403 2/15/2022
4.0.0-dev.5225 296 2/15/2022
4.0.0-dev.5217 308 2/15/2022
4.0.0-dev.5209 291 2/15/2022
4.0.0-dev.5200 295 2/14/2022
4.0.0-dev.5188 303 2/10/2022
4.0.0-dev.5180 303 2/10/2022
4.0.0-dev.5172 294 2/10/2022
4.0.0-dev.5130 303 2/10/2022
4.0.0-dev.5122 280 2/9/2022
4.0.0-dev.5103 316 2/9/2022
4.0.0-dev.5097 301 2/9/2022
4.0.0-dev.5091 291 2/9/2022
4.0.0-dev.5084 301 2/8/2022
3.4.0-dev.5263 309 2/15/2022
3.4.0-dev.4986 304 2/7/2022
3.4.0-dev.4968 319 2/4/2022
3.3.0 8,963 2/4/2022
3.3.0-dev.4889 310 2/3/2022
3.3.0-dev.4865 304 2/1/2022
3.3.0-dev.4823 320 1/19/2022
3.3.0-dev.4691 311 1/7/2022
3.3.0-dev.4557 1,522 11/26/2021
3.2.0 6,174 11/26/2021
3.2.0-dev.4533 5,026 11/24/2021
3.2.0-dev.4484 374 11/11/2021
3.2.0-dev.4475 346 11/10/2021
3.2.0-dev.4387 331 10/26/2021
3.2.0-dev.4363 344 10/22/2021
3.2.0-dev.4356 350 10/22/2021
3.1.0 2,042 10/22/2021
3.1.0-dev.4303 343 10/18/2021
3.1.0-dev.4293 348 10/15/2021
3.1.0-dev.4286 335 10/15/2021
3.1.0-dev.4240 351 10/12/2021
3.1.0-dev.4202 333 10/11/2021
3.1.0-dev.4183 374 10/11/2021
3.1.0-dev.4131 316 10/8/2021
3.1.0-dev.3999 313 10/5/2021
3.1.0-dev.3841 430 9/29/2021
3.1.0-dev.3798 336 9/17/2021
3.0.0 1,460 9/17/2021
3.0.0-dev.3726 690 8/31/2021
3.0.0-dev.3719 330 8/31/2021
3.0.0-dev.3671 343 8/20/2021
2.2.0-dev.3652 336 8/20/2021
2.1.0 1,793 8/20/2021
2.1.0-dev.3605 358 8/17/2021
2.1.0-dev.3584 350 8/16/2021
2.1.0-dev.3558 332 8/16/2021
2.1.0-dev.3527 380 7/29/2021
2.1.0-dev.3519 388 7/29/2021
2.1.0-dev.3490 327 7/20/2021
2.1.0-dev.3445 350 7/12/2021
2.1.0-dev.3434 391 7/9/2021
2.0.0 9,272 7/9/2021
2.0.0-dev.3401 369 6/25/2021
2.0.0-dev.3368 352 6/23/2021
2.0.0-dev.3361 364 6/23/2021
2.0.0-dev.3330 362 6/17/2021
2.0.0-dev.3291 374 6/16/2021
1.20.0-dev.3218 354 6/4/2021
1.19.0 1,185 6/4/2021
1.19.0-dev.3204 346 6/3/2021
1.19.0-dev.3160 337 6/2/2021
1.19.0-dev.3159 333 6/2/2021
1.19.0-dev.3084 1,006 5/7/2021
1.19.0-dev.3051 362 5/5/2021
1.19.0-dev.3044 339 5/5/2021
1.19.0-dev.3008 335 4/30/2021
1.18.0 1,496 4/30/2021
1.18.0-dev.2973 350 4/27/2021
1.18.0-dev.2930 358 4/16/2021
1.18.0-dev.2919 341 4/13/2021
1.18.0-dev.2893 344 4/12/2021
1.18.0-dev.2880 343 4/12/2021
1.18.0-dev.2856 345 4/7/2021
1.18.0-dev.2830 437 4/1/2021
1.18.0-dev.2816 349 4/1/2021
1.17.0 1,058 4/1/2021
1.17.0-dev.linq.17 962 3/18/2021
1.17.0-dev.linq.16 348 3/16/2021
1.17.0-dev.linq.15 376 3/15/2021
1.17.0-dev.linq.14 379 3/12/2021
1.17.0-dev.linq.13 418 3/11/2021
1.17.0-dev.linq.12 364 3/10/2021
1.17.0-dev.linq.11 365 3/8/2021
1.17.0-dev.2776 378 3/26/2021
1.17.0-dev.2713 387 3/25/2021
1.16.0-dev.linq.10 1,416 2/4/2021
1.15.0-dev.linq.9 388 2/4/2021
1.15.0-dev.linq.8 363 1/28/2021
1.15.0-dev.linq.7 369 1/27/2021
1.15.0-dev.linq.6 377 1/20/2021
1.15.0-dev.linq.5 422 1/19/2021
1.15.0-dev.linq.4 355 1/15/2021
1.15.0-dev.linq.3 357 1/14/2021
1.15.0-dev.linq.2 361 1/13/2021
1.15.0-dev.linq.1 397 1/12/2021