时序数据库针对的业务场景并没有关系数据库广,生态相对不是那么好。对我们.net core来说更少了 网上搜关于InfluxDB的资料有是有,但是大都是2.0以下的,很坑的是现在新版本都不支持SQL语法了连接方式也是加了Token,网上的资料大都废了,搞的我钻研了挺久。
下面我只介绍代码写法和软件用法就不说安装了 安装比较简单。
基本概念
一、与传统数据库中的名词做比较
二、InfluxDB中独有的概念
Point
Point由时间戳(time)、数据(field)、标签(tags)组成。
Point相当于传统数据库里的一行数据,如下表所示:
二、数据模型建议
需要用来检索的字段设为Tags
一、服务端启后,访问127.0.0.1:8086(第一次访问需要创建账号等)
二、依次点Data->Buckets->Create Bucket(创建数据库)
三、获取鉴权Token连接的时候使用(注意设置读写权限)
NuGet安装:InfluxDB.Client、InfluxDB.Client.Core
代码如下(示例):
public interface IDataStorage
{
public Guid EquipId { get; set; }
public string KeyName { get; set; }
public DateTime DateTime { get; set; }
public DataType Type { get; set; }
public bool? Value_Boolean { get; set; }
public string Value_String { get; set; }
public long? Value_Long { get; set; }
public DateTime? Value_DateTime { get; set; }
public double? Value_Double { get; set; }
public string Value_Json { get; set; }
public string Value_XML { get; set; }
public byte[] Value_Binary { get; set; }
}
///
/// 数据保存模型
///
public class TelemetryData: IDataStorage
{
public Guid EquipId { get; set; }
public string KeyName { get; set; }
public DateTime DateTime { get; set; }
public DataType Type { get; set; }
public bool? Value_Boolean { get; set; }
public string Value_String { get; set; }
public long? Value_Long { get; set; }
public DateTime? Value_DateTime { get; set; }
public double? Value_Double { get; set; }
public string Value_Json { get; set; }
public string Value_XML { get; set; }
public byte[] Value_Binary { get; set; }
}
///
/// 输入数据模型
///
public class PlayloadInput
{
///
/// 时序时间
///
public DateTime ts { get; set; } = DateTime.Now;
///
///装置ID
///
public Guid EquipId { get; set; }
///
/// 数据内容
///
public Dictionary<string, object> MsgBody { get; set; }
}
///
/// 返回数据模型
///
public class PlayloadOutput
{
public string KeyName { get; set; }
public DateTime DateTime { get; set; }
public DataType DataType { get; set; }
public object Value { get; set; }
}
//数据类型
public enum DataType
{
Boolean,
String,
Long,
Double,
Json,
XML,
Binary,
DateTime
}
//聚合函数
public enum Aggregate
{
///
/// 不使用
///
None,
///
/// 平均数
///
Mean,
///
/// 中值
///
Median,
///
/// 最后一个值
///
Last,
///
/// 第一个值
///
First,
///
/// 最大
///
Max,
///
/// 最小
///
Min,
///
/// 合计
///
Sum,
///
/// 条数
///
Count
}
粘贴从可视化软件复制的Token
private const string cfg = "http://192.168.1.163:8086/?org=fjzn&bucket=fjzn&token=ozt7aE6xGIuEdZX6hbyLtV7n461KSKAbHnPMuPgvrDIcH5pRxfKAvZtR_c0wFCmRcDsoP2qRMNefbHMAMzeyvA==&&latest=-72h";
public async Task<bool> StoreTelemetryAsync(PlayloadInput msg)
{
bool result = false;
try
{
List<PointData> lst = new List<PointData>();
msg.MsgBody.ToList().ForEach(kp =>
{
if (kp.Value != null)
{
TelemetryData tdata = new TelemetryData() { DateTime = msg.ts, EquipId = msg.EquipId, KeyName = kp.Key, Value_DateTime = new DateTime(1970, 1, 1) };
tdata.CheckType(kp);
var point = PointData.Measurement(nameof(TelemetryData))
.Tag("EquipId", tdata.EquipId.ToString());
switch (tdata.Type)
{
case DataType.Boolean:
if (tdata.Value_Boolean.HasValue) point = point.Field(tdata.KeyName, tdata.Value_Boolean.Value);
break;
case DataType.String:
point = point.Field(tdata.KeyName, tdata.Value_String);
break;
case DataType.Long:
if (tdata.Value_Long.HasValue) point = point.Field(tdata.KeyName, tdata.Value_Long.Value);
break;
case DataType.Double:
if (tdata.Value_Double.HasValue) point = point.Field(tdata.KeyName, tdata.Value_Double.Value);
break;
case DataType.Json:
point = point.Field(tdata.KeyName, tdata.Value_Json);
break;
case DataType.XML:
point = point.Field(tdata.KeyName, tdata.Value_XML);
break;
case DataType.Binary:
point = point.Field(tdata.KeyName, Hex.ToHexString(tdata.Value_Binary));
break;
case DataType.DateTime:
point = point.Field(tdata.KeyName, tdata.Value_DateTime.GetValueOrDefault().Subtract(new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalMilliseconds);
break;
default:
break;
}
if (point.HasFields())
{
point = point.Timestamp(msg.ts.ToUniversalTime(), WritePrecision.Ns);
lst.Add(point);
}
}
});
using InfluxDBClient influx = InfluxDBClientFactory.Create(cfg);
var writeApi = influx.GetWriteApiAsync();
//await writeApi.WritePointsAsync(lst, bucket: _bucket, org: _org);//指定组织和Db
await writeApi.WritePointsAsync(lst);
Console.WriteLine($"数据入库完成,共数据{lst.Count}条");
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString(), $"{msg.EquipId}数据处理失败{ex.Message} {ex.InnerException?.Message} ");
}
return result;
}
public static class DataExtension
{
///
/// 数据类型判断
///
///
///
///
internal static void CheckType<T>(this T tdata, KeyValuePair<string, object> kp) where T : IDataStorage
{
var tc = Type.GetTypeCode(kp.Value.GetType());
switch (tc)
{
case TypeCode.Boolean:
tdata.Type = DataType.Boolean;
tdata.Value_Boolean = (bool)kp.Value;
break;
case TypeCode.Single:
tdata.Type = DataType.Double;
tdata.Value_Double = double.Parse(kp.Value.ToString(), System.Globalization.NumberStyles.Float);
break;
case TypeCode.Double:
case TypeCode.Decimal:
tdata.Type = DataType.Double;
tdata.Value_Double = (double)kp.Value;
break;
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.UInt64:
case TypeCode.Byte:
case TypeCode.SByte:
tdata.Type = DataType.Long;
tdata.Value_Long = (Int64)Convert.ChangeType(kp.Value, TypeCode.Int64);
break;
case TypeCode.String:
case TypeCode.Char:
tdata.Type = DataType.String;
tdata.Value_String = (string)kp.Value;
break;
case TypeCode.DateTime:
tdata.Type = DataType.DateTime;
tdata.Value_DateTime = ((DateTime)kp.Value);
break;
case TypeCode.DBNull:
case TypeCode.Empty:
break;
case TypeCode.Object:
default:
if (kp.Value.GetType() == typeof(byte[]))
{
tdata.Type = DataType.Binary;
tdata.Value_Binary = (byte[])kp.Value;
}
else if (kp.Value.GetType() == typeof(System.Xml.XmlDocument))
{
tdata.Type = DataType.XML;
tdata.Value_XML = ((System.Xml.XmlDocument)kp.Value).InnerXml;
}
else if (kp.Value.GetType() == typeof(System.Text.Json.JsonElement))
{
var kvx = kp.Value as System.Text.Json.JsonElement?;
if (kvx.HasValue)
{
switch (kvx.Value.ValueKind)
{
case System.Text.Json.JsonValueKind.Undefined:
case System.Text.Json.JsonValueKind.Object:
break;
case System.Text.Json.JsonValueKind.Array:
break;
case System.Text.Json.JsonValueKind.String:
tdata.Type = DataType.String;
tdata.Value_String = kvx.Value.GetString();
break;
case System.Text.Json.JsonValueKind.Number:
tdata.Type = DataType.Double;
tdata.Value_Double = kvx.Value.GetDouble();
break;
case System.Text.Json.JsonValueKind.True:
case System.Text.Json.JsonValueKind.False:
tdata.Type = DataType.Boolean;
tdata.Value_Boolean = kvx.Value.GetBoolean();
break;
case System.Text.Json.JsonValueKind.Null:
break;
default:
break;
}
}
}
else
{
tdata.Type = DataType.Json;
tdata.Value_Json = Newtonsoft.Json.JsonConvert.SerializeObject(kp.Value);
}
break;
}
}
}
1.调用测试
Dictionary<string, object> keys = new Dictionary<string, object>();
keys.Add("TimeStamp", i);
keys.Add("EqCode", i);
keys.Add("StartTime", i);
keys.Add("EndTime", i);
keys.Add("KeepSeconds", i);
keys.Add("KeepWarCount", i);
keys.Add("WarName", i);
keys.Add("WarLevel", i);
keys.Add("WarType", i);
keys.Add("WarRmk", i);
keys.Add("Value", i);
keys.Add("Threshold", i);
///写入测试
var rlt = await influx.StoreTelemetryAsync(new PlayloadInput()
{
EquipId = new Guid("01735c40-8a67-48a2-86cd-0839990bede8"),
MsgBody = keys
});
//指定Tags
public async Task<List<TelemetryDataDto>> GetTelemetryLatest(Guid equipId)
{
using InfluxDBClient influx = InfluxDBClientFactory.Create(cfg);
var query = influx.GetQueryApi();
var exp = @$"
from(bucket: ""{_bucket}"")
|> range(start: {_latest})
|> filter(fn: (r) => r[""_measurement""] == ""{nameof(TelemetryData)}"")
|> filter(fn: (r) => r[""EquipId""] == ""{equipId}"")
|> last()";
var v = query.QueryAsync(exp);
return FluxToDtoAsync(v);
}
public Task<List<PlayloadOutput>> GetTelemetryLatest(Guid equipId, List<string> keys)
{
using InfluxDBClient influx = InfluxDBClientFactory.Create(cfg);
var query = influx.GetQueryApi();
var kvs = from k in keys
select $"r[\"_field\"] == \"{k}\"";
string exp = @$"
from(bucket: ""{_bucket}"")
|> range(start: {_latest})
|> filter(fn: (r) => r[""_measurement""] == ""{nameof(TelemetryData)}"")
|> filter(fn: (r) => r[""EquipId""] == ""{equipId}"")
|> filter(fn: (r) => {string.Join(" or ", kvs)})
|> group(columns: [""_field""])
|> last()";
var v = query.QueryAsync(exp);
return FluxToDtoAsync(v);
}
public Task<List<PlayloadOutput>> LoadTelemetryAsync(Guid equipId, List<string> keys, DateTime begin, DateTime end, TimeSpan every, Aggregate aggregate)
{
using InfluxDBClient influx = InfluxDBClientFactory.Create(cfg);
var query = influx.GetQueryApi();
var sb = new StringBuilder();
sb.AppendLine(@$"from(bucket: ""{_bucket}"")");
sb.AppendLine($"|> range(start: {begin:o},stop:{end:o})");
sb.AppendLine(@$"|> filter(fn: (r) => r[""_measurement""] == ""{nameof(TelemetryData)}"")");
sb.AppendLine(@$"|> filter(fn: (r) => r[""EquipId""] == ""{equipId}"")");
if (keys.Count>0)
{
var kvs = from k in keys
select $"r[\"_field\"] == \"{k}\"";
sb.AppendLine(@$"|> filter(fn: (r) => {string.Join(" or ", kvs)})");
}
if (every > TimeSpan.Zero&& aggregate != Aggregate.Count)
{
sb.AppendLine(@$"|> group(columns: [""_field""])");
sb.AppendLine($@"|> aggregateWindow(every: {(long)every.TotalMilliseconds}ms, fn: {Enum.GetName(aggregate).ToLower()}, createEmpty: false)");
sb.AppendLine(@$"|> yield(name: ""{Enum.GetName(aggregate).ToLower()}"")");
}
else if (aggregate == Aggregate.Count)//统计个数不需要间隔时间 所以单独处理
{
sb.AppendLine(@$"|> count(column:""_value"")");
sb.AppendLine(@$"|> yield()");
}
else
{
sb.AppendLine(@$"|> yield()");
}
Console.WriteLine(sb.ToString());
var v = query.QueryAsync(sb.ToString(), _org);
return FluxToDtoAsync(v);
}
#region 私有的
private async Task<List<PlayloadOutput>> FluxToDtoAsync(Task<List<FluxTable>> v)
{
List<PlayloadOutput> dt = new List<PlayloadOutput>();
(await v)?.ForEach(ft =>
{
ft.Records.ForEach(fr =>
{
dt.Add(new PlayloadOutput()
{
KeyName = fr.GetField(),
DateTime = fr.GetTimeInDateTime().GetValueOrDefault(DateTime.MinValue).ToLocalTime(),
Value = fr.GetValue(),
DataType = InfluxType(ft.Columns.Find(fv => fv.Label == "_value")?.DataType)
});
});
});
return dt;
}
DataType InfluxType(string itype)
{
DataType data = DataType.String;
switch (itype)
{
case "long":
data = DataType.Long;
break;
case "double":
data = DataType.Double;
break;
case "boolean":
case "bool":
data = DataType.Boolean;
break;
case "dateTime:RFC3339":
data = DataType.DateTime;
break;
case "string":
default:
data = DataType.String;
break;
}
return data;
}
#endregion
//扩展
//值判断过滤
from(bucket: "fjzn")
|> range(start:-72h)
|> filter(fn: (r) => r["_measurement"] == "TelemetryData")
|> filter(fn: (r) => r["EquipId"] == "01735c40-8a67-48a2-86cd-0839990bede7")
|> filter(fn: (r) => (r["_field"] == "EndTime" and r._value>37) or r["_field"] == "EqCode")
from(bucket: "fjzn")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["_measurement"] == "TelemetryData")
|> unique(column:"EquipId")
|> drop(columns:["EquipId"])
|> aggregateWindow(every: 5m, fn: sum, createEmpty: false)
|> yield(name: "sum")
就写到这吧没空写了,要是有人有什么问题就评论一下,如果还有国产数据库TDengine(Todelin)的需求,我也可以写一下,为.net core生态贡献自己的绵薄之力。参考IoTSharp(开源地址:https://gitee.com/dotnetchina/IoTSharp)