1028 lines
40 KiB
C#
1028 lines
40 KiB
C#
using System;
|
||
using System.Collections.Concurrent;
|
||
using System.ComponentModel.DataAnnotations;
|
||
using System.Diagnostics;
|
||
using System.Reflection;
|
||
using System.Reflection.Metadata.Ecma335;
|
||
using System.Text;
|
||
using System.Threading.Tasks;
|
||
using Apache.IoTDB;
|
||
using Apache.IoTDB.DataStructure;
|
||
using JiShe.CollectBus.Common.Enums;
|
||
using JiShe.CollectBus.Common.Extensions;
|
||
using JiShe.CollectBus.Common.Helpers;
|
||
using JiShe.CollectBus.Common.Models;
|
||
using JiShe.CollectBus.IoTDB.Attributes;
|
||
using JiShe.CollectBus.IoTDB.Context;
|
||
using JiShe.CollectBus.IoTDB.Interface;
|
||
using JiShe.CollectBus.IoTDB.Model;
|
||
using JiShe.CollectBus.IoTDB.Options;
|
||
using Microsoft.Extensions.Logging;
|
||
using Volo.Abp.DependencyInjection;
|
||
using Volo.Abp.Domain.Entities;
|
||
using JiShe.CollectBus.Analyzers.Shared;
|
||
using JiShe.CollectBus.IoTDB.Exceptions;
|
||
|
||
namespace JiShe.CollectBus.IoTDB.Provider
|
||
{
|
||
/// <summary>
|
||
/// IoTDB数据源
|
||
/// </summary>
|
||
public class IoTDbProvider : IIoTDbProvider, ITransientDependency
|
||
{
|
||
private static readonly ConcurrentDictionary<Type, DeviceMetadata> MetadataCache = new();
|
||
private readonly ILogger<IoTDbProvider> _logger;
|
||
private readonly IIoTDbSessionFactory _sessionFactory;
|
||
private readonly IoTDBRuntimeContext _runtimeContext;
|
||
|
||
private IIoTDbSessionPool CurrentSession =>
|
||
_sessionFactory.GetSessionPool(_runtimeContext.UseTableSessionPool);
|
||
|
||
/// <summary>
|
||
/// IoTDbProvider
|
||
/// </summary>
|
||
/// <param name="logger"></param>
|
||
/// <param name="sessionFactory"></param>
|
||
/// <param name="runtimeContext"></param>
|
||
public IoTDbProvider(
|
||
ILogger<IoTDbProvider> logger,
|
||
IIoTDbSessionFactory sessionFactory,
|
||
IoTDBRuntimeContext runtimeContext)
|
||
{
|
||
_logger = logger;
|
||
_sessionFactory = sessionFactory;
|
||
_runtimeContext = runtimeContext;
|
||
|
||
}
|
||
|
||
/// <summary>
|
||
/// 插入数据
|
||
/// </summary>
|
||
/// <typeparam name="T"></typeparam>
|
||
/// <param name="entity"></param>
|
||
/// <returns></returns>
|
||
public async Task InsertAsync<T>(T entity) where T : IoTEntity
|
||
{
|
||
try
|
||
{
|
||
var metadata = await GetMetadata<T>();
|
||
|
||
var tablet = BuildTablet(new[] { entity }, metadata);
|
||
|
||
await CurrentSession.InsertAsync(tablet);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, $"{nameof(InsertAsync)} IoTDB插入{typeof(T).Name}的数据时发生异常");
|
||
throw;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 批量插入数据
|
||
/// </summary>
|
||
/// <typeparam name="T"></typeparam>
|
||
/// <returns></returns>
|
||
public async Task BatchInsertAsync<T>(IEnumerable<T> entities) where T : IoTEntity
|
||
{
|
||
try
|
||
{
|
||
var metadata = await GetMetadata<T>();
|
||
|
||
var batchSize = 1000;
|
||
var batches = entities.Chunk(batchSize);
|
||
|
||
foreach (var batch in batches)
|
||
{
|
||
var tablet = BuildTablet(batch, metadata);
|
||
await CurrentSession.InsertAsync(tablet);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, $"{nameof(BatchInsertAsync)} IoTDB批量插入{typeof(T).Name}的数据时发生异常");
|
||
throw;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 批量插入数据
|
||
/// </summary>
|
||
/// <typeparam name="T"></typeparam>
|
||
/// <param name="deviceMetadata">设备元数据</param>
|
||
/// <param name="entities"></param>
|
||
/// <returns></returns>
|
||
public async Task BatchInsertAsync<T>(DeviceMetadata deviceMetadata, IEnumerable<T> entities) where T : IoTEntity
|
||
{
|
||
try
|
||
{
|
||
|
||
var batchSize = 1000;
|
||
var batches = entities.Chunk(batchSize);
|
||
|
||
foreach (var batch in batches)
|
||
{
|
||
var tablet = BuildTablet(batch, deviceMetadata);
|
||
await CurrentSession.InsertAsync(tablet);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, $"{nameof(BatchInsertAsync)} IoTDB批量插入{typeof(T).Name}的数据时发生异常");
|
||
throw;
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 删除数据
|
||
/// </summary>
|
||
/// <typeparam name="T"></typeparam>
|
||
/// <param name="options"></param>
|
||
/// <returns></returns>
|
||
public async Task<object> DeleteAsync<T>(IoTDBQueryOptions options) where T : IoTEntity
|
||
{
|
||
try
|
||
{
|
||
var query = await BuildDeleteSQL<T>(options);
|
||
var result = await CurrentSession.ExecuteQueryStatementAsync(query);
|
||
|
||
if (result == null)
|
||
{
|
||
return 0;
|
||
}
|
||
|
||
if (!result.HasNext())
|
||
{
|
||
_logger.LogWarning($"{typeof(T).Name} IoTDB删除{typeof(T).Name}的数据时,没有返回受影响记录数量。");
|
||
return 0;
|
||
}
|
||
|
||
//获取唯一结果行
|
||
var row = result.Next();
|
||
await result.Close();
|
||
var dataResult = row.Values[0];
|
||
return dataResult;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, $"{nameof(DeleteAsync)} IoTDB删除{typeof(T).Name}的数据时发生异常");
|
||
throw;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取设备元数据
|
||
/// </summary>
|
||
/// <typeparam name="T"></typeparam>
|
||
/// <returns></returns>
|
||
public async Task<DeviceMetadata> GetMetadata<T>() where T : IoTEntity
|
||
{
|
||
var accessor = SourceEntityAccessorFactory.GetAccessor<T>();
|
||
|
||
var columns = CollectColumnMetadata<T>(accessor);
|
||
var metadata = BuildDeviceMetadata<T>(columns);
|
||
var metaData = MetadataCache.AddOrUpdate(
|
||
typeof(T),
|
||
addValueFactory: t => metadata, // 如果键不存在,用此值添加
|
||
updateValueFactory: (t, existingValue) =>
|
||
{
|
||
var columns = CollectColumnMetadata(accessor);
|
||
var metadata = BuildDeviceMetadata<T>(columns);
|
||
|
||
//对现有值 existingValue 进行修改,返回新值
|
||
existingValue.ColumnNames = metadata.ColumnNames;
|
||
return existingValue;
|
||
}
|
||
);
|
||
|
||
metadata.EntityType = accessor.EntityType;
|
||
|
||
return await Task.FromResult(metaData);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 查询数据
|
||
/// </summary>
|
||
/// <typeparam name="T"></typeparam>
|
||
/// <param name="options"></param>
|
||
/// <returns></returns>
|
||
public async Task<BusPagedResult<T>> QueryAsync<T>(IoTDBQueryOptions options) where T : IoTEntity, new()
|
||
{
|
||
try
|
||
{
|
||
var stopwatch2 = Stopwatch.StartNew();
|
||
|
||
var query = await BuildQuerySQL<T>(options);
|
||
var sessionDataSet = await CurrentSession.ExecuteQueryStatementAsync(query);
|
||
|
||
|
||
_logger.LogWarning($"{nameof(QueryAsync)} 主题的任务 {options.TableNameOrTreePath} 路径批次{options.PageIndex}任务数据读取完成,共消耗{stopwatch2.ElapsedMilliseconds}毫秒。");
|
||
var result = new BusPagedResult<T>
|
||
{
|
||
TotalCount = await GetTotalCount<T>(options),
|
||
Items = await ParseResults<T>(sessionDataSet, options.PageSize),
|
||
PageIndex = options.PageIndex,
|
||
PageSize = options.PageSize,
|
||
|
||
};
|
||
stopwatch2.Stop();
|
||
_logger.LogWarning($"{nameof(QueryAsync)} 主题的任务 {options.TableNameOrTreePath} 路径批次{options.PageIndex}任务数据读取完成,共消耗{stopwatch2.ElapsedMilliseconds}毫秒。");
|
||
//int totalPageCount = (int)Math.Ceiling((double)result.TotalCount / options.PageSize);
|
||
|
||
if (result.Items.Count() < result.PageSize)
|
||
{
|
||
result.HasNext = false;
|
||
}
|
||
else
|
||
{
|
||
result.HasNext = true;
|
||
}
|
||
|
||
//result.HasNext = result.Items.Count() > 0 ? result.Items.Count() < result.PageSize : false;
|
||
|
||
return result;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
CurrentSession.Dispose();
|
||
_logger.LogError(ex, $"{nameof(QueryAsync)} IoTDB查询{typeof(T).Name}的数据时发生异常");
|
||
throw;
|
||
}
|
||
}
|
||
|
||
///// <summary>
|
||
///// 构建Tablet
|
||
///// </summary>
|
||
///// <typeparam name="T"></typeparam>
|
||
///// <param name="entities">表实体</param>
|
||
///// <param name="metadata">设备元数据</param></param>
|
||
///// <returns></returns>
|
||
//private Tablet BuildTablet<T>(IEnumerable<T> entities, DeviceMetadata metadata) where T : IoTEntity
|
||
//{
|
||
// var timestamps = new List<long>();
|
||
// var values = new List<List<object>>();
|
||
// var devicePaths = new HashSet<string>();
|
||
// List<string> tempColumnNames = new List<string>();
|
||
// tempColumnNames.AddRange(metadata.ColumnNames);
|
||
|
||
// var accessor = SourceEntityAccessorFactory.GetAccessor<T>();
|
||
|
||
// var memberCache = new Dictionary<string, EntityMemberInfo>(); // 缓存优化查询
|
||
// // 预构建成员缓存(Key: NameOrPath)
|
||
// foreach (var member in accessor.MemberList)
|
||
// {
|
||
// memberCache[member.NameOrPath] = member;
|
||
// }
|
||
|
||
// if (accessor.EntityType == null || metadata.EntityType == null)
|
||
// {
|
||
// throw new ArgumentException($"{nameof(BuildTablet)} 构建存储结构{nameof(Tablet)}时 {nameof(T)}的EntityType 没有指定,属于异常情况,-101");
|
||
// }
|
||
|
||
// if (metadata.EntityType != accessor.EntityType)
|
||
// {
|
||
// throw new ArgumentException($"{nameof(BuildTablet)} 构建存储结构{nameof(Tablet)}时 {nameof(T)}的EntityType 和{nameof(DeviceMetadata)}的 EntityType 不一致,属于异常情况,-102");
|
||
// }
|
||
|
||
// if (metadata.EntityType == EntityTypeEnum.TreeModel && _runtimeContext.UseTableSessionPool == true)
|
||
// {
|
||
// throw new ArgumentException($"{nameof(BuildTablet)} 构建存储结构{nameof(Tablet)}时 tree模型不能使用table模型Session连接,属于异常情况,-103");
|
||
// }
|
||
// else if (metadata.EntityType == EntityTypeEnum.TableModel && _runtimeContext.UseTableSessionPool == false)
|
||
// {
|
||
// throw new ArgumentException($"{nameof(BuildTablet)} 构建存储结构{nameof(Tablet)}时 table模型不能使用tree模型Session连接,属于异常情况,-104");
|
||
// }
|
||
|
||
// string tableNameOrTreePath = string.Empty;
|
||
// var tableNameOrTreePathAttribute = typeof(T).GetCustomAttribute<TableNameOrTreePathAttribute>();
|
||
// if (tableNameOrTreePathAttribute != null)
|
||
// {
|
||
// tableNameOrTreePath = tableNameOrTreePathAttribute.TableNameOrTreePath;
|
||
// }
|
||
|
||
// foreach (var entity in entities)
|
||
// {
|
||
// timestamps.Add(entity.Timestamps);
|
||
// var rowValues = new List<object>();
|
||
|
||
// foreach (var measurement in metadata.ColumnNames)
|
||
// {
|
||
// if (!memberCache.TryGetValue(measurement, out var member))
|
||
// {
|
||
// throw new ArgumentException($"{nameof(BuildTablet)} 构建存储结构时{accessor.EntityName}没有找到{measurement}对应的member信息,-105");
|
||
// }
|
||
|
||
// var value = member.GetValue(entity);
|
||
|
||
// // 特性查询优化
|
||
// var attributes = member.CustomAttributes ?? Enumerable.Empty<Attribute>();
|
||
// var singleMeasuringAttr = attributes.OfType<SingleMeasuringAttribute>().FirstOrDefault();
|
||
// if (singleMeasuringAttr != null)//如果是单侧点
|
||
// {
|
||
|
||
// var tupleItem1Key = $"{member.NameOrPath}.Item1";
|
||
// if (!memberCache.TryGetValue(tupleItem1Key, out var tuple1Member))
|
||
// {
|
||
// throw new ArgumentException($"{nameof(BuildTablet)} 构建存储结构时{accessor.EntityName} 没有找到{measurement}对应的member Item1 信息,-106");
|
||
// }
|
||
// int indexOf = metadata.ColumnNames.IndexOf(measurement);
|
||
// tempColumnNames[indexOf] = (string)tuple1Member.GetValue(entity);
|
||
|
||
// var tupleItem2Key = $"{member.NameOrPath}.Item2";
|
||
// if (!memberCache.TryGetValue(tupleItem2Key, out var tuple2Member))
|
||
// {
|
||
// throw new ArgumentException($"{nameof(BuildTablet)} 构建存储结构时{accessor.EntityName} 没有找到{measurement}对应的member Item2 信息,-107");
|
||
// }
|
||
|
||
// value = tuple2Member.GetValue(entity);
|
||
// }
|
||
|
||
// if (value != null)
|
||
// {
|
||
// var tempValue = member.DeclaredTypeName.ToUpper() switch
|
||
// {
|
||
// "DATETIME" => Convert.ToDateTime(value).GetDateTimeOffset().ToUnixTimeNanoseconds(),
|
||
// _ => value
|
||
// };
|
||
|
||
// rowValues.Add(tempValue);
|
||
// }
|
||
// else
|
||
// {
|
||
// rowValues.Add(value);
|
||
// }
|
||
// }
|
||
|
||
// values.Add(rowValues);
|
||
|
||
// //如果指定了路径
|
||
// if (!string.IsNullOrWhiteSpace(tableNameOrTreePath))
|
||
// {
|
||
// devicePaths.Add(tableNameOrTreePath);
|
||
// }
|
||
// else
|
||
// {
|
||
// if (!_runtimeContext.UseTableSessionPool)//树模型
|
||
// {
|
||
// devicePaths.Add(DevicePathBuilder.GetDevicePath(entity));
|
||
// }
|
||
// else
|
||
// {
|
||
// devicePaths.Add(DevicePathBuilder.GetTableName<T>());
|
||
// }
|
||
// }
|
||
// }
|
||
|
||
// if (devicePaths.Count > 1)
|
||
// {
|
||
// throw new Exception($"{nameof(BuildTablet)} 构建Tablet《{typeof(T).Name}》时,批量插入的设备路径不一致。");
|
||
// }
|
||
|
||
// return _runtimeContext.UseTableSessionPool
|
||
// ? BuildTableSessionTablet(metadata, devicePaths.First(), tempColumnNames, values, timestamps)
|
||
// : BuildSessionTablet(metadata, devicePaths.First(), tempColumnNames,values, timestamps);
|
||
//}
|
||
|
||
/// <summary>
|
||
/// 构建Tablet
|
||
/// </summary>
|
||
/// <typeparam name="T"></typeparam>
|
||
/// <param name="entities">表实体集合</param>
|
||
/// <param name="metadata">设备元数据</param></param>
|
||
/// <returns></returns>
|
||
private Tablet BuildTablet<T>(IEnumerable<T> entities, DeviceMetadata metadata) where T : IoTEntity
|
||
{
|
||
// 前置校验
|
||
ValidateMetadataAndAccessor<T>(metadata, out var accessor);
|
||
|
||
// 初始化数据结构
|
||
var (timestamps, values, devicePaths) = (new List<long>(), new List<List<object>>(), new HashSet<string>());
|
||
var tempColumnNames = new List<string>(metadata.ColumnNames);
|
||
var memberCache = BuildMemberCache(accessor);
|
||
var tableNameOrTreePath = GetTableNameOrTreePath<T>();
|
||
|
||
// 处理每个实体
|
||
foreach (var entity in entities)
|
||
{
|
||
ProcessEntity(entity, accessor, metadata, memberCache, tempColumnNames, timestamps, values);
|
||
UpdateDevicePaths(entity, tableNameOrTreePath, devicePaths);
|
||
}
|
||
|
||
// 后置校验与返回
|
||
ValidateDevicePaths(devicePaths);
|
||
// return CreateFinalTablet(metadata, devicePaths.First(), tempColumnNames, values, timestamps);
|
||
return _runtimeContext.UseTableSessionPool
|
||
? BuildTableSessionTablet(metadata, devicePaths.First(), tempColumnNames, values, timestamps)
|
||
: BuildSessionTablet(metadata, devicePaths.First(), tempColumnNames, values, timestamps);
|
||
}
|
||
|
||
private void ValidateMetadataAndAccessor<T>(DeviceMetadata metadata, out ISourceEntityAccessor<T> accessor) where T : IoTEntity
|
||
{
|
||
accessor = SourceEntityAccessorFactory.GetAccessor<T>();
|
||
|
||
if (accessor.EntityType == null || metadata.EntityType == null)
|
||
{
|
||
throw new IoTException($"{nameof(BuildTablet)} 构建IoTDB数据结构时,EntityType未指定", -101);
|
||
}
|
||
|
||
if (metadata.EntityType != accessor.EntityType)
|
||
{
|
||
throw new IoTException($"{nameof(BuildTablet)} 构建IoTDB数据结构时,EntityType不一致", -102);
|
||
}
|
||
|
||
bool isTableModel = accessor.EntityType == EntityTypeEnum.TableModel;
|
||
if (_runtimeContext.UseTableSessionPool != isTableModel)
|
||
{
|
||
throw new IoTException($"{nameof(BuildTablet)} 构建IoTDB数据结构时,Session类型不匹配: 预期{(isTableModel ? "Table" : "Tree")}模型", isTableModel ? -104 : -103);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理实体并获取值
|
||
/// </summary>
|
||
/// <typeparam name="T"></typeparam>
|
||
/// <param name="entity"></param>
|
||
/// <param name="accessor"></param>
|
||
/// <param name="metadata"></param>
|
||
/// <param name="memberCache"></param>
|
||
/// <param name="tempColumnNames"></param>
|
||
/// <param name="timestamps"></param>
|
||
/// <param name="values"></param>
|
||
/// <exception cref="IoTException"></exception>
|
||
private void ProcessEntity<T>(
|
||
T entity,
|
||
ISourceEntityAccessor<T> accessor,
|
||
DeviceMetadata metadata,
|
||
Dictionary<string, EntityMemberInfo> memberCache,
|
||
List<string> tempColumnNames,
|
||
List<long> timestamps,
|
||
List<List<object>> values) where T : IoTEntity
|
||
{
|
||
timestamps.Add(entity.Timestamps);
|
||
var rowValues = new object[metadata.ColumnNames.Count];
|
||
|
||
Parallel.ForEach(metadata.ColumnNames, (measurement, state, index) =>
|
||
{
|
||
if (!memberCache.TryGetValue(measurement, out var member))
|
||
{
|
||
throw new IoTException($"{nameof(BuildTablet)} 构建IoTDB数据结构时,找不到成员: {measurement}", -105);
|
||
}
|
||
|
||
object value = ResolveMemberValue(entity, member, memberCache, tempColumnNames, (int)index);
|
||
rowValues[index] = ConvertValueByType(member, value);
|
||
});
|
||
|
||
values.Add(rowValues.ToList());
|
||
}
|
||
|
||
private object ResolveMemberValue<T>(
|
||
T entity,
|
||
EntityMemberInfo member,
|
||
Dictionary<string, EntityMemberInfo> memberCache,
|
||
List<string> tempColumnNames,
|
||
int columnIndex) where T : IoTEntity
|
||
{
|
||
// 单测点逻辑
|
||
if (member.CustomAttributes?.OfType<SingleMeasuringAttribute>().FirstOrDefault() is { } attr)
|
||
{
|
||
var tuple1Key = $"{member.NameOrPath}.Item1";
|
||
var tuple2Key = $"{member.NameOrPath}.Item2";
|
||
|
||
if (!memberCache.TryGetValue(tuple1Key, out var tuple1) || !memberCache.TryGetValue(tuple2Key, out var tuple2))
|
||
{
|
||
throw new IoTException($"{nameof(BuildTablet)} 构建IoTDB数据结构时,单侧点元组成员缺失", -106);
|
||
}
|
||
|
||
tempColumnNames[columnIndex] = (string)tuple1.GetValue(entity);
|
||
return tuple2.GetValue(entity);
|
||
}
|
||
return member.GetValue(entity);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置实体的成员值
|
||
/// </summary>
|
||
/// <param name="member"></param>
|
||
/// <param name="value"></param>
|
||
/// <returns></returns>
|
||
private object ConvertValueByType(EntityMemberInfo member, object value)
|
||
{
|
||
return member.DeclaredTypeName switch
|
||
{
|
||
"DATETIME" => Convert.ToDateTime(value).GetDateTimeOffset().ToUnixTimeNanoseconds(),
|
||
_ => value
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理设备路径
|
||
/// </summary>
|
||
/// <typeparam name="T"></typeparam>
|
||
/// <param name="entity"></param>
|
||
/// <param name="tableNameOrTreePath"></param>
|
||
/// <param name="devicePaths"></param>
|
||
private void UpdateDevicePaths<T>(
|
||
T entity,
|
||
string tableNameOrTreePath,
|
||
HashSet<string> devicePaths) where T : IoTEntity
|
||
{
|
||
if (!string.IsNullOrEmpty(tableNameOrTreePath))
|
||
{
|
||
devicePaths.Add(tableNameOrTreePath);
|
||
return;
|
||
}
|
||
|
||
var path = _runtimeContext.UseTableSessionPool
|
||
? DevicePathBuilder.GetTableName<T>()
|
||
: DevicePathBuilder.GetDevicePath(entity);
|
||
devicePaths.Add(path);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证设备路径
|
||
/// </summary>
|
||
/// <param name="devicePaths"></param>
|
||
private void ValidateDevicePaths(HashSet<string> devicePaths)
|
||
{
|
||
if (devicePaths.Count == 0)
|
||
{
|
||
throw new IoTException($"{nameof(BuildTablet)} 构建IoTDB数据结构时,设备路径集合为空", -108);
|
||
}
|
||
|
||
if (devicePaths.Count > 1)
|
||
{
|
||
var paths = string.Join(", ", devicePaths.Take(3));
|
||
{
|
||
throw new IoTException($"{nameof(BuildTablet)} 构建IoTDB数据结构时,设备路径不一致。检测到路径: {paths}...", -109);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 缓存优化:避免重复反射
|
||
/// </summary>
|
||
/// <typeparam name="T"></typeparam>
|
||
/// <returns></returns>
|
||
private string GetTableNameOrTreePath<T>()
|
||
{
|
||
return AttributeCache<T>.TableNameOrTreePath;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 特性缓存辅助类
|
||
/// </summary>
|
||
/// <typeparam name="T"></typeparam>
|
||
private static class AttributeCache<T>
|
||
{
|
||
public static readonly string TableNameOrTreePath;
|
||
|
||
static AttributeCache()
|
||
{
|
||
var attr = typeof(T).GetCustomAttribute<TableNameOrTreePathAttribute>();
|
||
TableNameOrTreePath = attr?.TableNameOrTreePath ?? string.Empty;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 构建tree模型的Tablet
|
||
/// </summary>
|
||
/// <param name="metadata">已解析的设备数据元数据</param>
|
||
/// <param name="devicePath">设备路径</param>
|
||
/// <param name="columns">数据列集合</param>
|
||
/// <param name="values">数据集合</param>
|
||
/// <param name="timestamps">时间戳集合</param>
|
||
/// <returns></returns>
|
||
private Tablet BuildSessionTablet(DeviceMetadata metadata, string devicePath, List<string> columns, List<List<object>> values, List<long> timestamps)
|
||
{
|
||
//todo 树模型需要去掉TAG类型和ATTRIBUTE类型的字段,只需要保留FIELD类型字段即可
|
||
|
||
return new Tablet(
|
||
devicePath,
|
||
columns,
|
||
metadata.DataTypes,
|
||
values,
|
||
timestamps
|
||
);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 构建表模型的Tablet
|
||
/// </summary>
|
||
/// <param name="metadata">已解析的设备数据元数据</param>
|
||
/// <param name="tableName">表名称</param>
|
||
/// <param name="columns">数据列集合</param>
|
||
/// <param name="values">数据集合</param>
|
||
/// <param name="timestamps">时间戳集合</param>
|
||
/// <returns></returns>
|
||
private Tablet BuildTableSessionTablet(DeviceMetadata metadata, string tableName, List<string> columns,List<List<object>> values, List<long> timestamps)
|
||
{
|
||
var tablet = new Tablet(
|
||
tableName,
|
||
columns,
|
||
metadata.ColumnCategories,
|
||
metadata.DataTypes,
|
||
values,
|
||
timestamps
|
||
);
|
||
|
||
return tablet;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 构建查询语句
|
||
/// </summary>
|
||
/// <typeparam name="T"></typeparam>
|
||
/// <param name="options"></param>
|
||
/// <returns></returns>
|
||
private async Task<string> BuildQuerySQL<T>(IoTDBQueryOptions options) where T : IoTEntity
|
||
{
|
||
var metadata = await GetMetadata<T>();
|
||
var sb = new StringBuilder("SELECT TIME as Timestamps,");
|
||
sb.AppendJoin(", ", metadata.ColumnNames);
|
||
sb.Append($" FROM {options.TableNameOrTreePath}");
|
||
|
||
if (options.Conditions.Any())
|
||
{
|
||
sb.Append(" WHERE ");
|
||
sb.AppendJoin(" AND ", options.Conditions.Select(TranslateCondition));
|
||
}
|
||
|
||
sb.Append($" LIMIT {options.PageSize} OFFSET {options.PageIndex * options.PageSize}");
|
||
return sb.ToString();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 构建删除语句
|
||
/// </summary>
|
||
/// <typeparam name="T"></typeparam>
|
||
/// <param name="options"></param>
|
||
/// <returns></returns>
|
||
private async Task<string> BuildDeleteSQL<T>(IoTDBQueryOptions options) where T : IoTEntity
|
||
{
|
||
var metadata = await GetMetadata<T>();
|
||
var sb = new StringBuilder();
|
||
|
||
if (!_runtimeContext.UseTableSessionPool)
|
||
{
|
||
sb.Append("DELETE ");
|
||
}
|
||
else
|
||
{
|
||
sb.Append("DROP ");
|
||
}
|
||
|
||
sb.Append($" FROM {options.TableNameOrTreePath}");
|
||
|
||
sb.AppendJoin(", ", metadata.ColumnNames);
|
||
|
||
if (options.Conditions.Any())
|
||
{
|
||
sb.Append(" WHERE ");
|
||
sb.AppendJoin(" AND ", options.Conditions.Select(TranslateCondition));
|
||
}
|
||
|
||
return sb.ToString();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将查询条件转换为SQL语句
|
||
/// </summary>
|
||
/// <param name="condition"></param>
|
||
/// <returns></returns>
|
||
/// <exception cref="NotSupportedException"></exception>
|
||
private string TranslateCondition(QueryCondition condition)
|
||
{
|
||
return condition.Operator switch
|
||
{
|
||
">" => condition.IsNumber ? $"{condition.Field} > {condition.Value}" : $"{condition.Field} > '{condition.Value}'",
|
||
"<" => condition.IsNumber ? $"{condition.Field} < {condition.Value}" : $"{condition.Field} < '{condition.Value}'",
|
||
"=" => condition.IsNumber ? $"{condition.Field} = {condition.Value}" : $"{condition.Field} = '{condition.Value}'",
|
||
_ => throw new NotSupportedException($"{nameof(TranslateCondition)} 将查询条件转换为SQL语句时操作符 {condition.Operator} 属于异常情况")
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取查询条件的总数量
|
||
/// </summary>
|
||
/// <typeparam name="T"></typeparam>
|
||
/// <param name="options"></param>
|
||
/// <returns></returns>
|
||
private async Task<int> GetTotalCount<T>(IoTDBQueryOptions options) where T : IoTEntity
|
||
{
|
||
var countQuery = $"SELECT COUNT(*) FROM {options.TableNameOrTreePath}";
|
||
if (options.Conditions.Any())
|
||
{
|
||
countQuery += " WHERE " + string.Join(" AND ", options.Conditions.Select(TranslateCondition));
|
||
}
|
||
|
||
var result = await CurrentSession.ExecuteQueryStatementAsync(countQuery);
|
||
if (result == null)
|
||
{
|
||
return 0;
|
||
}
|
||
|
||
if (!result.HasNext())
|
||
{
|
||
return 0;
|
||
}
|
||
|
||
|
||
var count = Convert.ToInt32(result.Next().Values[0]);
|
||
await result.Close();
|
||
|
||
return count;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 解析查询结果
|
||
/// </summary>
|
||
/// <typeparam name="T"></typeparam>
|
||
/// <param name="dataSet"></param>
|
||
/// <param name="pageSize"></param>
|
||
/// <returns></returns>
|
||
private async Task<IEnumerable<T>> ParseResults<T>(SessionDataSet dataSet, int pageSize) where T : IoTEntity, new()
|
||
{
|
||
var results = new List<T>();
|
||
var metadata = await GetMetadata<T>();
|
||
|
||
var accessor = SourceEntityAccessorFactory.GetAccessor<T>();
|
||
var memberCache = BuildMemberCache(accessor);
|
||
|
||
var columns = new List<string>() { "Timestamps" };
|
||
var dataTypes = new List<TSDataType>() { TSDataType.TIMESTAMP };
|
||
columns.AddRange(metadata.ColumnNames);
|
||
dataTypes.AddRange(metadata.DataTypes);
|
||
|
||
|
||
while (dataSet.HasNext() && results.Count < pageSize)
|
||
{
|
||
var record = dataSet.Next();
|
||
var entity = new T
|
||
{
|
||
Timestamps = record.Timestamps
|
||
};
|
||
|
||
foreach (var measurement in columns)
|
||
{
|
||
int indexOf = columns.IndexOf(measurement);
|
||
var value = record.Values[indexOf];
|
||
TSDataType tSDataType = dataTypes[indexOf];
|
||
|
||
if (!memberCache.TryGetValue(measurement, out var member) && !(value is System.DBNull))
|
||
{
|
||
throw new Exception($"{nameof(ParseResults)} 解析查询结果 {accessor.EntityName} 属性赋值出现异常,没有找到{measurement}对应的 member信息");
|
||
}
|
||
|
||
dynamic tempValue = GetTSDataValue(tSDataType, value);
|
||
|
||
if (measurement.ToLower().EndsWith("time"))
|
||
{
|
||
member.Setter(entity, TimestampHelper.ConvertToDateTime(tempValue, TimestampUnit.Nanoseconds));
|
||
}
|
||
else
|
||
{
|
||
member.Setter(entity, tempValue);
|
||
}
|
||
|
||
}
|
||
|
||
results.Add(entity);
|
||
|
||
}
|
||
await dataSet.Close();
|
||
return results;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取设备元数据的列
|
||
/// </summary>
|
||
/// <param name="accessor"></param>
|
||
/// <returns></returns>
|
||
private List<ColumnInfo> CollectColumnMetadata<T>(ISourceEntityAccessor<T> accessor)
|
||
{
|
||
var columns = new List<ColumnInfo>();
|
||
var memberCache = BuildMemberCache(accessor);
|
||
|
||
foreach (var member in accessor.MemberList)
|
||
{
|
||
// 过滤元组子项
|
||
if (member.NameOrPath.Contains(".Item")) continue;
|
||
|
||
// 类型名称处理
|
||
Type declaredType = member.DeclaredType;
|
||
var underlyingType = Nullable.GetUnderlyingType(declaredType);
|
||
string declaredTypeName = underlyingType?.Name ?? member.DeclaredTypeName;
|
||
|
||
// 特性查询优化
|
||
var attributes = member.CustomAttributes ?? Enumerable.Empty<Attribute>();
|
||
var tagAttr = attributes.OfType<TAGColumnAttribute>().FirstOrDefault();
|
||
var attrColumn = attributes.OfType<ATTRIBUTEColumnAttribute>().FirstOrDefault();
|
||
var fieldColumn = attributes.OfType<FIELDColumnAttribute>().FirstOrDefault();
|
||
var singleMeasuringAttr = attributes.OfType<SingleMeasuringAttribute>().FirstOrDefault();
|
||
|
||
// 构建ColumnInfo
|
||
ColumnInfo? column = null;
|
||
if (tagAttr != null)
|
||
{
|
||
column = new ColumnInfo(member.NameOrPath, ColumnCategory.TAG, GetDataTypeFromTypeName(declaredTypeName), false);
|
||
}
|
||
else if (attrColumn != null)
|
||
{
|
||
column = new ColumnInfo(member.NameOrPath, ColumnCategory.ATTRIBUTE, GetDataTypeFromTypeName(declaredTypeName), false);
|
||
}
|
||
else if (fieldColumn != null)
|
||
{
|
||
column = new ColumnInfo(member.NameOrPath, ColumnCategory.FIELD, GetDataTypeFromTypeName(declaredTypeName), false);
|
||
}
|
||
|
||
// 单测模式处理
|
||
if (singleMeasuringAttr != null && column == null)
|
||
{
|
||
var tupleItemKey = $"{member.NameOrPath}.Item2";
|
||
if (!memberCache.TryGetValue(tupleItemKey, out var tupleMember))
|
||
{
|
||
throw new Exception($"{nameof(CollectColumnMetadata)} {accessor.EntityName} {member.NameOrPath} 单侧点属性解析异常");
|
||
}
|
||
column = new ColumnInfo(member.NameOrPath, ColumnCategory.FIELD, GetDataTypeFromTypeName(tupleMember.DeclaredTypeName), true);
|
||
}
|
||
|
||
if (column.HasValue) columns.Add(column.Value);
|
||
}
|
||
return columns;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 构建设备元数据
|
||
/// </summary>
|
||
/// <param name="typeInfo">待解析的类</param>
|
||
/// <param name="columns">已处理好的数据列</param>
|
||
/// <returns></returns>
|
||
private DeviceMetadata BuildDeviceMetadata<T>(List<ColumnInfo> columns) where T : IoTEntity
|
||
{
|
||
var metadata = new DeviceMetadata();
|
||
|
||
//先检查是不是单侧点模型
|
||
if (columns.Any(c => c.IsSingleMeasuring))
|
||
{
|
||
metadata.IsSingleMeasuring = true;
|
||
}
|
||
|
||
//按业务逻辑顺序处理(TAG -> ATTRIBUTE -> FIELD)
|
||
var groupedColumns = columns
|
||
.GroupBy(c => c.Category)
|
||
.ToDictionary(g => g.Key, g => g.ToList());
|
||
|
||
ProcessCategory(groupedColumns, ColumnCategory.TAG, metadata);
|
||
ProcessCategory(groupedColumns, ColumnCategory.ATTRIBUTE, metadata);
|
||
ProcessCategory(groupedColumns, ColumnCategory.FIELD, metadata);
|
||
|
||
return metadata;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理不同列类型的逻辑
|
||
/// </summary>
|
||
/// <param name="groupedColumns"></param>
|
||
/// <param name="category"></param>
|
||
/// <param name="metadata"></param>
|
||
private void ProcessCategory(IReadOnlyDictionary<ColumnCategory, List<ColumnInfo>> groupedColumns, ColumnCategory category, DeviceMetadata metadata)
|
||
{
|
||
if (groupedColumns.TryGetValue(category, out var cols))
|
||
{
|
||
metadata.ColumnNames.AddRange(cols.Select(c => c.Name));
|
||
metadata.ColumnCategories.AddRange(cols.Select(c => c.Category));
|
||
metadata.DataTypes.AddRange(cols.Select(c => c.DataType));
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 数据列结构
|
||
/// </summary>
|
||
private readonly struct ColumnInfo
|
||
{
|
||
/// <summary>
|
||
/// 列名
|
||
/// </summary>
|
||
public string Name { get; }
|
||
|
||
/// <summary>
|
||
/// 是否是单测点
|
||
/// </summary>
|
||
public bool IsSingleMeasuring { get; }
|
||
|
||
/// <summary>
|
||
/// 列类型
|
||
/// </summary>
|
||
public ColumnCategory Category { get; }
|
||
|
||
/// <summary>
|
||
/// 数据类型
|
||
/// </summary>
|
||
public TSDataType DataType { get; }
|
||
|
||
public ColumnInfo(string name, ColumnCategory category, TSDataType dataType, bool isSingleMeasuring)
|
||
{
|
||
Name = name;
|
||
Category = category;
|
||
DataType = dataType;
|
||
IsSingleMeasuring = isSingleMeasuring;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据类型名称获取对应的 IoTDB 数据类型
|
||
/// </summary>
|
||
/// <param name="typeName">类型名称(不区分大小写)</param>
|
||
/// <returns>对应的 TSDataType,默认返回 TSDataType.STRING</returns>
|
||
private TSDataType GetDataTypeFromTypeName(string typeName)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(typeName))
|
||
return TSDataType.STRING;
|
||
|
||
return DataTypeMap.TryGetValue(typeName.Trim(), out var dataType)
|
||
? dataType
|
||
: TSDataType.STRING;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据类型名称获取 IoTDB 数据类型
|
||
/// </summary>
|
||
private readonly IReadOnlyDictionary<string, TSDataType> DataTypeMap =
|
||
new Dictionary<string, TSDataType>(StringComparer.OrdinalIgnoreCase)
|
||
{
|
||
["BOOLEAN"] = TSDataType.BOOLEAN,
|
||
["INT32"] = TSDataType.INT32,
|
||
["INT64"] = TSDataType.INT64,
|
||
["FLOAT"] = TSDataType.FLOAT,
|
||
["DOUBLE"] = TSDataType.DOUBLE,
|
||
["TEXT"] = TSDataType.TEXT,
|
||
["NULLTYPE"] = TSDataType.NONE,
|
||
["DATETIME"] = TSDataType.TIMESTAMP,
|
||
["DATE"] = TSDataType.DATE,
|
||
["BLOB"] = TSDataType.BLOB,
|
||
["DECIMAL"] = TSDataType.STRING,
|
||
["STRING"] = TSDataType.STRING
|
||
};
|
||
|
||
/// <summary>
|
||
/// 根据类型名称获取 IoTDB 数据默认值
|
||
/// </summary>
|
||
private readonly IReadOnlyDictionary<string, object> DataTypeDefaultValueMap =
|
||
new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
|
||
{
|
||
["BOOLEAN"] = false,
|
||
["INT32"] = 0,
|
||
["INT64"] = 0,
|
||
["FLOAT"] = 0.0f,
|
||
["DOUBLE"] = 0.0d,
|
||
["TEXT"] = string.Empty,
|
||
["NULLTYPE"] = null,
|
||
["DATETIME"] = null,
|
||
["DATE"] = null,
|
||
["BLOB"] = null,
|
||
["DECIMAL"] = "0.0",
|
||
["STRING"] = string.Empty
|
||
};
|
||
|
||
/// <summary>
|
||
/// IoTDB 数据类型与.net类型映射
|
||
/// </summary>
|
||
/// <param name="tSDataType"></param>
|
||
/// <param name="value"></param>
|
||
/// <returns></returns>
|
||
private dynamic GetTSDataValue(TSDataType tSDataType, object value) =>
|
||
tSDataType switch
|
||
{
|
||
TSDataType.BOOLEAN => Convert.ToBoolean(value),
|
||
TSDataType.INT32 => Convert.ToInt32(value),
|
||
TSDataType.INT64 => Convert.ToInt64(value),
|
||
TSDataType.FLOAT => Convert.ToDouble(value),
|
||
TSDataType.DOUBLE => Convert.ToDouble(value),
|
||
TSDataType.TEXT => Convert.ToString(value),
|
||
TSDataType.NONE => null,
|
||
TSDataType.TIMESTAMP => Convert.ToInt64(value),
|
||
TSDataType.DATE => Convert.ToDateTime(value),
|
||
TSDataType.BLOB => Convert.ToByte(value),
|
||
TSDataType.STRING => Convert.ToString(value),
|
||
_ => Convert.ToString(value)
|
||
};
|
||
|
||
/// <summary>
|
||
/// 缓存实体属性信息
|
||
/// </summary>
|
||
/// <typeparam name="T"></typeparam>
|
||
/// <param name="accessor"></param>
|
||
/// <returns></returns>
|
||
private Dictionary<string, EntityMemberInfo> BuildMemberCache<T>(ISourceEntityAccessor<T> accessor)
|
||
{
|
||
var cache = new Dictionary<string, EntityMemberInfo>(StringComparer.Ordinal);
|
||
foreach (var member in accessor.MemberList)
|
||
{
|
||
cache[member.NameOrPath] = member;
|
||
}
|
||
return cache;
|
||
}
|
||
}
|
||
}
|