InfluxDB.Client.Linq 4.17.0-dev.14101

This is a prerelease version of InfluxDB.Client.Linq.
There is a newer version of this package available.
See the version list below for details.
dotnet add package InfluxDB.Client.Linq --version 4.17.0-dev.14101
                    
NuGet\Install-Package InfluxDB.Client.Linq -Version 4.17.0-dev.14101
                    
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.17.0-dev.14101" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="InfluxDB.Client.Linq" Version="4.17.0-dev.14101" />
                    
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.17.0-dev.14101
                    
#r "nuget: InfluxDB.Client.Linq, 4.17.0-dev.14101"
                    
#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.
#addin nuget:?package=InfluxDB.Client.Linq&version=4.17.0-dev.14101&prerelease
                    
Install InfluxDB.Client.Linq as a Cake Addin
#tool nuget:?package=InfluxDB.Client.Linq&version=4.17.0-dev.14101&prerelease
                    
Install InfluxDB.Client.Linq 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

MicroHeart.InfluxDB

Package Description

ToolNET.InfluxDB.SDK

时序数据库InfluxDB操作SDK

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
4.19.0-dev.15190 205 12/5/2024
4.19.0-dev.15189 55 12/5/2024
4.19.0-dev.15188 52 12/5/2024
4.19.0-dev.15178 58 12/5/2024
4.19.0-dev.15177 57 12/5/2024
4.19.0-dev.14906 126 10/2/2024
4.19.0-dev.14897 61 10/2/2024
4.19.0-dev.14896 57 10/2/2024
4.19.0-dev.14895 59 10/2/2024
4.19.0-dev.14811 93 9/13/2024
4.18.0 95,106 9/13/2024
4.18.0-dev.14769 73 9/4/2024
4.18.0-dev.14743 66 9/3/2024
4.18.0-dev.14694 63 9/3/2024
4.18.0-dev.14693 59 9/3/2024
4.18.0-dev.14692 57 9/3/2024
4.18.0-dev.14618 57 9/2/2024
4.18.0-dev.14609 57 9/2/2024
4.18.0-dev.14592 61 9/2/2024
4.18.0-dev.14446 82 8/19/2024
4.18.0-dev.14414 75 8/12/2024
4.17.0 12,488 8/12/2024
4.17.0-dev.headers.read.1 90 7/22/2024
4.17.0-dev.14350 54 8/5/2024
4.17.0-dev.14333 52 8/5/2024
4.17.0-dev.14300 47 8/5/2024
4.17.0-dev.14291 51 8/5/2024
4.17.0-dev.14189 64 7/23/2024
4.17.0-dev.14179 62 7/22/2024
4.17.0-dev.14101 148 7/1/2024
4.17.0-dev.14100 69 7/1/2024
4.17.0-dev.14044 74 6/24/2024
4.16.0 11,850 6/24/2024
4.16.0-dev.13990 72 6/3/2024
4.16.0-dev.13973 64 6/3/2024
4.16.0-dev.13972 64 6/3/2024
4.16.0-dev.13963 71 6/3/2024
4.16.0-dev.13962 67 6/3/2024
4.16.0-dev.13881 67 6/3/2024
4.16.0-dev.13775 81 5/17/2024
4.16.0-dev.13702 71 5/17/2024
4.15.0 3,130 5/17/2024
4.15.0-dev.13674 81 5/14/2024
4.15.0-dev.13567 86 4/2/2024
4.15.0-dev.13558 71 4/2/2024
4.15.0-dev.13525 77 4/2/2024
4.15.0-dev.13524 67 4/2/2024
4.15.0-dev.13433 80 3/7/2024
4.15.0-dev.13432 78 3/7/2024
4.15.0-dev.13407 75 3/7/2024
4.15.0-dev.13390 72 3/7/2024
4.15.0-dev.13388 70 3/7/2024
4.15.0-dev.13282 77 3/6/2024
4.15.0-dev.13257 80 3/6/2024
4.15.0-dev.13113 238 2/1/2024
4.15.0-dev.13104 74 2/1/2024
4.15.0-dev.13081 74 2/1/2024
4.15.0-dev.13040 72 2/1/2024
4.15.0-dev.13039 76 2/1/2024
4.15.0-dev.12863 124 1/8/2024
4.15.0-dev.12846 90 1/8/2024
4.15.0-dev.12837 82 1/8/2024
4.15.0-dev.12726 166 12/1/2023
4.15.0-dev.12725 85 12/1/2023
4.15.0-dev.12724 82 12/1/2023
4.15.0-dev.12691 85 12/1/2023
4.15.0-dev.12658 80 12/1/2023
4.15.0-dev.12649 83 12/1/2023
4.15.0-dev.12624 82 12/1/2023
4.15.0-dev.12471 109 11/7/2023
4.15.0-dev.12462 82 11/7/2023
4.14.0 65,258 11/7/2023
4.14.0-dev.12437 84 11/7/2023
4.14.0-dev.12343 97 11/2/2023
4.14.0-dev.12310 83 11/2/2023
4.14.0-dev.12284 85 11/1/2023
4.14.0-dev.12235 84 11/1/2023
4.14.0-dev.12226 83 11/1/2023
4.14.0-dev.11972 225 8/8/2023
4.14.0-dev.11915 123 7/31/2023
4.14.0-dev.11879 134 7/28/2023
4.13.0 22,919 7/28/2023
4.13.0-dev.11854 106 7/28/2023
4.13.0-dev.11814 117 7/21/2023
4.13.0-dev.11771 107 7/19/2023
4.13.0-dev.11770 116 7/19/2023
4.13.0-dev.11728 107 7/18/2023
4.13.0-dev.11686 105 7/17/2023
4.13.0-dev.11685 104 7/17/2023
4.13.0-dev.11676 120 7/17/2023
4.13.0-dev.11479 106 6/27/2023
4.13.0-dev.11478 107 6/27/2023
4.13.0-dev.11477 113 6/27/2023
4.13.0-dev.11396 112 6/19/2023
4.13.0-dev.11395 99 6/19/2023
4.13.0-dev.11342 113 6/15/2023
4.13.0-dev.11330 124 6/12/2023
4.13.0-dev.11305 114 6/12/2023
4.13.0-dev.11296 115 6/12/2023
4.13.0-dev.11217 118 6/6/2023
4.13.0-dev.11089 111 5/30/2023
4.13.0-dev.11064 120 5/30/2023
4.13.0-dev.10998 113 5/29/2023
4.13.0-dev.10989 118 5/29/2023
4.13.0-dev.10871 119 5/8/2023
4.13.0-dev.10870 106 5/8/2023
4.13.0-dev.10819 131 4/28/2023
4.12.0 13,906 4/28/2023
4.12.0-dev.10777 120 4/27/2023
4.12.0-dev.10768 126 4/27/2023
4.12.0-dev.10759 122 4/27/2023
4.12.0-dev.10742 119 4/27/2023
4.12.0-dev.10685 110 4/27/2023
4.12.0-dev.10684 113 4/27/2023
4.12.0-dev.10643 115 4/27/2023
4.12.0-dev.10642 118 4/27/2023
4.12.0-dev.10569 116 4/27/2023
4.12.0-dev.10193 157 2/23/2023
4.11.0 24,325 2/23/2023
4.11.0-dev.10176 126 2/23/2023
4.11.0-dev.10059 235 1/26/2023
4.10.0 7,796 1/26/2023
4.10.0-dev.10033 149 1/25/2023
4.10.0-dev.10032 148 1/25/2023
4.10.0-dev.10031 146 1/25/2023
4.10.0-dev.9936 2,226 12/26/2022
4.10.0-dev.9935 145 12/26/2022
4.10.0-dev.9881 138 12/21/2022
4.10.0-dev.9880 135 12/21/2022
4.10.0-dev.9818 144 12/16/2022
4.10.0-dev.9773 134 12/12/2022
4.10.0-dev.9756 141 12/12/2022
4.10.0-dev.9693 136 12/6/2022
4.9.0 10,082 12/6/2022
4.9.0-dev.9684 138 12/6/2022
4.9.0-dev.9666 143 12/6/2022
4.9.0-dev.9617 138 12/6/2022
4.9.0-dev.9478 131 12/5/2022
4.9.0-dev.9469 147 12/5/2022
4.9.0-dev.9444 131 12/5/2022
4.9.0-dev.9411 125 12/5/2022
4.9.0-dev.9350 134 12/1/2022
4.8.0 1,622 12/1/2022
4.8.0-dev.9324 137 11/30/2022
4.8.0-dev.9232 139 11/28/2022
4.8.0-dev.9223 137 11/28/2022
4.8.0-dev.9222 144 11/28/2022
4.8.0-dev.9117 150 11/21/2022
4.8.0-dev.9108 136 11/21/2022
4.8.0-dev.9099 145 11/21/2022
4.8.0-dev.9029 137 11/16/2022
4.8.0-dev.8971 140 11/15/2022
4.8.0-dev.8961 144 11/14/2022
4.8.0-dev.8928 146 11/14/2022
4.8.0-dev.8899 148 11/14/2022
4.8.0-dev.8898 143 11/14/2022
4.8.0-dev.8839 155 11/14/2022
4.8.0-dev.8740 133 11/7/2022
4.8.0-dev.8725 138 11/7/2022
4.8.0-dev.8648 135 11/3/2022
4.7.0 25,379 11/3/2022
4.7.0-dev.8625 144 11/2/2022
4.7.0-dev.8594 146 10/31/2022
4.7.0-dev.8579 146 10/31/2022
4.7.0-dev.8557 138 10/31/2022
4.7.0-dev.8540 129 10/31/2022
4.7.0-dev.8518 133 10/31/2022
4.7.0-dev.8517 143 10/31/2022
4.7.0-dev.8509 141 10/31/2022
4.7.0-dev.8377 145 10/26/2022
4.7.0-dev.8360 153 10/25/2022
4.7.0-dev.8350 150 10/24/2022
4.7.0-dev.8335 147 10/24/2022
4.7.0-dev.8334 149 10/24/2022
4.7.0-dev.8223 189 10/19/2022
4.7.0-dev.8178 144 10/17/2022
4.7.0-dev.8170 141 10/17/2022
4.7.0-dev.8148 150 10/17/2022
4.7.0-dev.8133 147 10/17/2022
4.7.0-dev.8097 134 10/17/2022
4.7.0-dev.8034 155 10/11/2022
4.7.0-dev.8025 141 10/11/2022
4.7.0-dev.8009 158 10/10/2022
4.7.0-dev.8001 162 10/10/2022
4.7.0-dev.7959 140 10/4/2022
4.7.0-dev.7905 147 9/30/2022
4.7.0-dev.7875 142 9/29/2022
4.6.0 2,729 9/29/2022
4.6.0-dev.7832 153 9/29/2022
4.6.0-dev.7817 150 9/29/2022
4.6.0-dev.7779 166 9/27/2022
4.6.0-dev.7778 162 9/27/2022
4.6.0-dev.7734 152 9/26/2022
4.6.0-dev.7733 152 9/26/2022
4.6.0-dev.7677 159 9/20/2022
4.6.0-dev.7650 159 9/16/2022
4.6.0-dev.7626 213 9/14/2022
4.6.0-dev.7618 204 9/14/2022
4.6.0-dev.7574 146 9/13/2022
4.6.0-dev.7572 146 9/13/2022
4.6.0-dev.7528 138 9/12/2022
4.6.0-dev.7502 153 9/9/2022
4.6.0-dev.7479 168 9/8/2022
4.6.0-dev.7471 156 9/8/2022
4.6.0-dev.7447 149 9/7/2022
4.6.0-dev.7425 140 9/7/2022
4.6.0-dev.7395 140 9/6/2022
4.6.0-dev.7344 145 8/31/2022
4.6.0-dev.7329 139 8/31/2022
4.6.0-dev.7292 138 8/30/2022
4.6.0-dev.7240 148 8/29/2022
4.5.0 2,801 8/29/2022
4.5.0-dev.7216 143 8/27/2022
4.5.0-dev.7147 148 8/22/2022
4.5.0-dev.7134 149 8/17/2022
4.5.0-dev.7096 153 8/15/2022
4.5.0-dev.7070 159 8/11/2022
4.5.0-dev.7040 181 8/10/2022
4.5.0-dev.7011 159 8/3/2022
4.5.0-dev.6987 161 8/1/2022
4.5.0-dev.6962 165 7/29/2022
4.4.0 14,766 7/29/2022
4.4.0-dev.6901 162 7/25/2022
4.4.0-dev.6843 156 7/19/2022
4.4.0-dev.6804 163 7/19/2022
4.4.0-dev.6789 159 7/19/2022
4.4.0-dev.6760 154 7/19/2022
4.4.0-dev.6705 169 7/14/2022
4.4.0-dev.6663 195 6/24/2022
4.4.0-dev.6655 154 6/24/2022
4.3.0 16,694 6/24/2022
4.3.0-dev.multiple.buckets3 182 6/21/2022
4.3.0-dev.multiple.buckets2 148 6/17/2022
4.3.0-dev.multiple.buckets1 154 6/17/2022
4.3.0-dev.6631 150 6/22/2022
4.3.0-dev.6623 158 6/22/2022
4.3.0-dev.6374 162 6/13/2022
4.3.0-dev.6286 167 5/20/2022
4.2.0 2,482 5/20/2022
4.2.0-dev.6257 164 5/13/2022
4.2.0-dev.6248 163 5/12/2022
4.2.0-dev.6233 168 5/12/2022
4.2.0-dev.6194 166 5/10/2022
4.2.0-dev.6193 160 5/10/2022
4.2.0-dev.6158 2,875 5/6/2022
4.2.0-dev.6135 171 5/6/2022
4.2.0-dev.6091 173 4/28/2022
4.2.0-dev.6048 172 4/28/2022
4.2.0-dev.6047 174 4/28/2022
4.2.0-dev.5966 175 4/25/2022
4.2.0-dev.5938 176 4/19/2022
4.1.0 3,431 4/19/2022
4.1.0-dev.5910 367 4/13/2022
4.1.0-dev.5888 175 4/13/2022
4.1.0-dev.5887 179 4/13/2022
4.1.0-dev.5794 179 4/6/2022
4.1.0-dev.5725 185 3/18/2022
4.0.0 9,228 3/18/2022
4.0.0-rc3 427 3/4/2022
4.0.0-rc2 578 2/25/2022
4.0.0-rc1 239 2/18/2022
4.0.0-dev.5709 175 3/18/2022
4.0.0-dev.5684 187 3/15/2022
4.0.0-dev.5630 187 3/4/2022
4.0.0-dev.5607 179 3/3/2022
4.0.0-dev.5579 183 2/25/2022
4.0.0-dev.5556 187 2/24/2022
4.0.0-dev.5555 175 2/24/2022
4.0.0-dev.5497 173 2/23/2022
4.0.0-dev.5489 185 2/23/2022
4.0.0-dev.5460 180 2/23/2022
4.0.0-dev.5444 174 2/22/2022
4.0.0-dev.5333 181 2/17/2022
4.0.0-dev.5303 175 2/16/2022
4.0.0-dev.5280 187 2/16/2022
4.0.0-dev.5279 188 2/16/2022
4.0.0-dev.5241 283 2/15/2022
4.0.0-dev.5225 177 2/15/2022
4.0.0-dev.5217 182 2/15/2022
4.0.0-dev.5209 174 2/15/2022
4.0.0-dev.5200 177 2/14/2022
4.0.0-dev.5188 178 2/10/2022
4.0.0-dev.5180 178 2/10/2022
4.0.0-dev.5172 182 2/10/2022
4.0.0-dev.5130 173 2/10/2022
4.0.0-dev.5122 181 2/9/2022
4.0.0-dev.5103 188 2/9/2022
4.0.0-dev.5097 187 2/9/2022
4.0.0-dev.5091 180 2/9/2022
4.0.0-dev.5084 183 2/8/2022
3.4.0-dev.5263 191 2/15/2022
3.4.0-dev.4986 182 2/7/2022
3.4.0-dev.4968 196 2/4/2022
3.3.0 8,724 2/4/2022
3.3.0-dev.4889 185 2/3/2022
3.3.0-dev.4865 191 2/1/2022
3.3.0-dev.4823 197 1/19/2022
3.3.0-dev.4691 195 1/7/2022
3.3.0-dev.4557 1,402 11/26/2021
3.2.0 5,944 11/26/2021
3.2.0-dev.4533 4,900 11/24/2021
3.2.0-dev.4484 261 11/11/2021
3.2.0-dev.4475 235 11/10/2021
3.2.0-dev.4387 210 10/26/2021
3.2.0-dev.4363 225 10/22/2021
3.2.0-dev.4356 223 10/22/2021
3.1.0 1,830 10/22/2021
3.1.0-dev.4303 225 10/18/2021
3.1.0-dev.4293 227 10/15/2021
3.1.0-dev.4286 205 10/15/2021
3.1.0-dev.4240 243 10/12/2021
3.1.0-dev.4202 202 10/11/2021
3.1.0-dev.4183 246 10/11/2021
3.1.0-dev.4131 214 10/8/2021
3.1.0-dev.3999 222 10/5/2021
3.1.0-dev.3841 299 9/29/2021
3.1.0-dev.3798 220 9/17/2021
3.0.0 1,244 9/17/2021
3.0.0-dev.3726 560 8/31/2021
3.0.0-dev.3719 207 8/31/2021
3.0.0-dev.3671 219 8/20/2021
2.2.0-dev.3652 214 8/20/2021
2.1.0 1,595 8/20/2021
2.1.0-dev.3605 219 8/17/2021
2.1.0-dev.3584 220 8/16/2021
2.1.0-dev.3558 209 8/16/2021
2.1.0-dev.3527 257 7/29/2021
2.1.0-dev.3519 256 7/29/2021
2.1.0-dev.3490 207 7/20/2021
2.1.0-dev.3445 229 7/12/2021
2.1.0-dev.3434 265 7/9/2021
2.0.0 9,053 7/9/2021
2.0.0-dev.3401 247 6/25/2021
2.0.0-dev.3368 231 6/23/2021
2.0.0-dev.3361 241 6/23/2021
2.0.0-dev.3330 239 6/17/2021
2.0.0-dev.3291 241 6/16/2021
1.20.0-dev.3218 258 6/4/2021
1.19.0 973 6/4/2021
1.19.0-dev.3204 228 6/3/2021
1.19.0-dev.3160 213 6/2/2021
1.19.0-dev.3159 209 6/2/2021
1.19.0-dev.3084 873 5/7/2021
1.19.0-dev.3051 239 5/5/2021
1.19.0-dev.3044 233 5/5/2021
1.19.0-dev.3008 228 4/30/2021
1.18.0 1,275 4/30/2021
1.18.0-dev.2973 247 4/27/2021
1.18.0-dev.2930 226 4/16/2021
1.18.0-dev.2919 224 4/13/2021
1.18.0-dev.2893 210 4/12/2021
1.18.0-dev.2880 229 4/12/2021
1.18.0-dev.2856 223 4/7/2021
1.18.0-dev.2830 320 4/1/2021
1.18.0-dev.2816 226 4/1/2021
1.17.0 821 4/1/2021
1.17.0-dev.linq.17 840 3/18/2021
1.17.0-dev.linq.16 219 3/16/2021
1.17.0-dev.linq.15 252 3/15/2021
1.17.0-dev.linq.14 256 3/12/2021
1.17.0-dev.linq.13 286 3/11/2021
1.17.0-dev.linq.12 237 3/10/2021
1.17.0-dev.linq.11 234 3/8/2021
1.17.0-dev.2776 255 3/26/2021
1.17.0-dev.2713 268 3/25/2021
1.16.0-dev.linq.10 1,276 2/4/2021
1.15.0-dev.linq.9 254 2/4/2021
1.15.0-dev.linq.8 228 1/28/2021
1.15.0-dev.linq.7 245 1/27/2021
1.15.0-dev.linq.6 264 1/20/2021
1.15.0-dev.linq.5 282 1/19/2021
1.15.0-dev.linq.4 245 1/15/2021
1.15.0-dev.linq.3 220 1/14/2021
1.15.0-dev.linq.2 236 1/13/2021
1.15.0-dev.linq.1 259 1/12/2021