2025-04-21 09:45:30 +08:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Concurrent;
|
2025-04-22 22:11:55 +08:00
|
|
|
|
using System.ComponentModel.DataAnnotations;
|
2025-04-29 23:48:47 +08:00
|
|
|
|
using System.Diagnostics;
|
2025-04-17 20:28:50 +08:00
|
|
|
|
using System.Reflection;
|
2025-04-22 22:11:55 +08:00
|
|
|
|
using System.Reflection.Metadata.Ecma335;
|
2025-04-17 20:28:50 +08:00
|
|
|
|
using System.Text;
|
2025-04-22 16:44:47 +08:00
|
|
|
|
using System.Threading.Tasks;
|
2025-04-17 20:28:50 +08:00
|
|
|
|
using Apache.IoTDB;
|
2025-04-02 14:06:40 +08:00
|
|
|
|
using Apache.IoTDB.DataStructure;
|
2025-04-22 16:44:47 +08:00
|
|
|
|
using JiShe.CollectBus.Common.Enums;
|
|
|
|
|
|
using JiShe.CollectBus.Common.Extensions;
|
|
|
|
|
|
using JiShe.CollectBus.Common.Helpers;
|
2025-04-15 16:05:07 +08:00
|
|
|
|
using JiShe.CollectBus.Common.Models;
|
2025-05-08 14:42:13 +08:00
|
|
|
|
using JiShe.CollectBus.IoTDB.Attributes;
|
2025-04-17 20:28:50 +08:00
|
|
|
|
using JiShe.CollectBus.IoTDB.Context;
|
|
|
|
|
|
using JiShe.CollectBus.IoTDB.Interface;
|
2025-04-21 14:20:49 +08:00
|
|
|
|
using JiShe.CollectBus.IoTDB.Model;
|
2025-04-17 20:28:50 +08:00
|
|
|
|
using JiShe.CollectBus.IoTDB.Options;
|
2025-04-03 15:38:31 +08:00
|
|
|
|
using Microsoft.Extensions.Logging;
|
2025-04-21 14:20:49 +08:00
|
|
|
|
using Volo.Abp.DependencyInjection;
|
2025-04-21 09:45:30 +08:00
|
|
|
|
using Volo.Abp.Domain.Entities;
|
2025-05-07 16:37:26 +08:00
|
|
|
|
using JiShe.CollectBus.Analyzers.Shared;
|
2025-05-08 22:44:01 +08:00
|
|
|
|
using JiShe.CollectBus.IoTDB.Exceptions;
|
2025-05-09 17:54:52 +08:00
|
|
|
|
using System.Diagnostics.Metrics;
|
|
|
|
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
|
|
using static System.Runtime.InteropServices.JavaScript.JSType;
|
|
|
|
|
|
using System.Text.RegularExpressions;
|
2025-04-02 14:06:40 +08:00
|
|
|
|
|
2025-04-17 20:28:50 +08:00
|
|
|
|
namespace JiShe.CollectBus.IoTDB.Provider
|
2025-04-02 14:06:40 +08:00
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// IoTDB数据源
|
|
|
|
|
|
/// </summary>
|
2025-04-22 23:44:37 +08:00
|
|
|
|
public class IoTDbProvider : IIoTDbProvider, ITransientDependency
|
2025-04-02 14:06:40 +08:00
|
|
|
|
{
|
2025-04-21 10:17:40 +08:00
|
|
|
|
private static readonly ConcurrentDictionary<Type, DeviceMetadata> MetadataCache = new();
|
|
|
|
|
|
private readonly ILogger<IoTDbProvider> _logger;
|
|
|
|
|
|
private readonly IIoTDbSessionFactory _sessionFactory;
|
2025-04-21 22:57:49 +08:00
|
|
|
|
private readonly IoTDBRuntimeContext _runtimeContext;
|
2025-04-07 16:44:25 +08:00
|
|
|
|
|
2025-04-21 10:17:40 +08:00
|
|
|
|
private IIoTDbSessionPool CurrentSession =>
|
2025-04-11 11:56:23 +08:00
|
|
|
|
_sessionFactory.GetSessionPool(_runtimeContext.UseTableSessionPool);
|
|
|
|
|
|
|
2025-04-21 10:17:40 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// IoTDbProvider
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="logger"></param>
|
|
|
|
|
|
/// <param name="sessionFactory"></param>
|
|
|
|
|
|
/// <param name="runtimeContext"></param>
|
|
|
|
|
|
public IoTDbProvider(
|
|
|
|
|
|
ILogger<IoTDbProvider> logger,
|
|
|
|
|
|
IIoTDbSessionFactory sessionFactory,
|
2025-04-21 22:57:49 +08:00
|
|
|
|
IoTDBRuntimeContext runtimeContext)
|
2025-04-07 16:44:25 +08:00
|
|
|
|
{
|
|
|
|
|
|
_logger = logger;
|
|
|
|
|
|
_sessionFactory = sessionFactory;
|
|
|
|
|
|
_runtimeContext = runtimeContext;
|
|
|
|
|
|
|
2025-04-02 14:06:40 +08:00
|
|
|
|
}
|
2025-04-11 17:06:30 +08:00
|
|
|
|
|
2025-04-02 14:06:40 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 插入数据
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <typeparam name="T"></typeparam>
|
|
|
|
|
|
/// <param name="entity"></param>
|
|
|
|
|
|
/// <returns></returns>
|
2025-04-07 16:44:25 +08:00
|
|
|
|
public async Task InsertAsync<T>(T entity) where T : IoTEntity
|
2025-04-02 14:06:40 +08:00
|
|
|
|
{
|
2025-04-21 22:57:49 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-04-22 16:44:47 +08:00
|
|
|
|
var metadata = await GetMetadata<T>();
|
2025-04-02 14:06:40 +08:00
|
|
|
|
|
2025-04-21 22:57:49 +08:00
|
|
|
|
var tablet = BuildTablet(new[] { entity }, metadata);
|
2025-05-09 17:54:52 +08:00
|
|
|
|
if (tablet == null || tablet.Count <= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogError($"{nameof(InsertAsync)} IoTDB插入{typeof(T).Name}的数据时 tablet 为null");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-04-11 11:56:23 +08:00
|
|
|
|
|
2025-05-09 17:54:52 +08:00
|
|
|
|
await CurrentSession.InsertAsync(tablet.First());
|
2025-04-21 22:57:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2025-04-29 23:48:47 +08:00
|
|
|
|
_logger.LogError(ex, $"{nameof(InsertAsync)} IoTDB插入{typeof(T).Name}的数据时发生异常");
|
2025-04-21 22:57:49 +08:00
|
|
|
|
throw;
|
|
|
|
|
|
}
|
2025-04-02 14:06:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 批量插入数据
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <typeparam name="T"></typeparam>
|
|
|
|
|
|
/// <returns></returns>
|
2025-04-07 16:44:25 +08:00
|
|
|
|
public async Task BatchInsertAsync<T>(IEnumerable<T> entities) where T : IoTEntity
|
2025-04-02 14:06:40 +08:00
|
|
|
|
{
|
2025-04-21 22:57:49 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-04-22 16:44:47 +08:00
|
|
|
|
var metadata = await GetMetadata<T>();
|
2025-04-02 14:06:40 +08:00
|
|
|
|
|
2025-04-21 22:57:49 +08:00
|
|
|
|
var batchSize = 1000;
|
|
|
|
|
|
var batches = entities.Chunk(batchSize);
|
2025-04-02 14:06:40 +08:00
|
|
|
|
|
2025-04-21 22:57:49 +08:00
|
|
|
|
foreach (var batch in batches)
|
|
|
|
|
|
{
|
|
|
|
|
|
var tablet = BuildTablet(batch, metadata);
|
2025-05-09 17:54:52 +08:00
|
|
|
|
if (tablet == null || tablet.Count <= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogError($"{nameof(InsertAsync)} IoTDB插入{typeof(T).Name}的数据时 tablet 为null");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
foreach (var item in tablet)
|
|
|
|
|
|
{
|
|
|
|
|
|
await CurrentSession.InsertAsync(item);
|
|
|
|
|
|
}
|
2025-04-21 22:57:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
2025-04-02 14:06:40 +08:00
|
|
|
|
{
|
2025-04-29 23:48:47 +08:00
|
|
|
|
_logger.LogError(ex, $"{nameof(BatchInsertAsync)} IoTDB批量插入{typeof(T).Name}的数据时发生异常");
|
2025-04-21 22:57:49 +08:00
|
|
|
|
throw;
|
2025-04-02 14:06:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-22 16:44:47 +08:00
|
|
|
|
/// <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
|
2025-05-13 14:51:38 +08:00
|
|
|
|
{
|
|
|
|
|
|
var batchSize = 1000;
|
2025-04-22 16:44:47 +08:00
|
|
|
|
var batches = entities.Chunk(batchSize);
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var batch in batches)
|
|
|
|
|
|
{
|
|
|
|
|
|
var tablet = BuildTablet(batch, deviceMetadata);
|
2025-05-09 17:54:52 +08:00
|
|
|
|
if (tablet == null || tablet.Count <= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogError($"{nameof(InsertAsync)} IoTDB插入{typeof(T).Name}的数据时 tablet 为null");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
foreach (var item in tablet)
|
|
|
|
|
|
{
|
|
|
|
|
|
await CurrentSession.InsertAsync(item);
|
|
|
|
|
|
}
|
2025-04-22 16:44:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2025-04-29 23:48:47 +08:00
|
|
|
|
_logger.LogError(ex, $"{nameof(BatchInsertAsync)} IoTDB批量插入{typeof(T).Name}的数据时发生异常");
|
2025-04-22 16:44:47 +08:00
|
|
|
|
throw;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-03 15:38:31 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 删除数据
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <typeparam name="T"></typeparam>
|
|
|
|
|
|
/// <param name="options"></param>
|
|
|
|
|
|
/// <returns></returns>
|
2025-04-22 16:44:47 +08:00
|
|
|
|
public async Task<object> DeleteAsync<T>(IoTDBQueryOptions options) where T : IoTEntity
|
2025-04-03 15:38:31 +08:00
|
|
|
|
{
|
2025-04-21 22:57:49 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-04-22 16:44:47 +08:00
|
|
|
|
var query = await BuildDeleteSQL<T>(options);
|
2025-04-29 23:48:47 +08:00
|
|
|
|
var result = await CurrentSession.ExecuteQueryStatementAsync(query);
|
|
|
|
|
|
|
|
|
|
|
|
if (result == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
2025-04-21 22:57:49 +08:00
|
|
|
|
|
2025-04-29 23:48:47 +08:00
|
|
|
|
if (!result.HasNext())
|
2025-04-21 22:57:49 +08:00
|
|
|
|
{
|
2025-04-29 23:48:47 +08:00
|
|
|
|
_logger.LogWarning($"{typeof(T).Name} IoTDB删除{typeof(T).Name}的数据时,没有返回受影响记录数量。");
|
2025-04-21 22:57:49 +08:00
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
2025-04-03 15:38:31 +08:00
|
|
|
|
|
2025-04-21 22:57:49 +08:00
|
|
|
|
//获取唯一结果行
|
2025-04-29 23:48:47 +08:00
|
|
|
|
var row = result.Next();
|
|
|
|
|
|
await result.Close();
|
|
|
|
|
|
var dataResult = row.Values[0];
|
|
|
|
|
|
return dataResult;
|
2025-04-21 22:57:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
2025-04-03 15:38:31 +08:00
|
|
|
|
{
|
2025-04-29 23:48:47 +08:00
|
|
|
|
_logger.LogError(ex, $"{nameof(DeleteAsync)} IoTDB删除{typeof(T).Name}的数据时发生异常");
|
2025-04-21 22:57:49 +08:00
|
|
|
|
throw;
|
2025-04-03 15:38:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-22 16:44:47 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取设备元数据
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <typeparam name="T"></typeparam>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public async Task<DeviceMetadata> GetMetadata<T>() where T : IoTEntity
|
|
|
|
|
|
{
|
2025-05-07 16:37:26 +08:00
|
|
|
|
var accessor = SourceEntityAccessorFactory.GetAccessor<T>();
|
|
|
|
|
|
|
2025-05-08 10:28:23 +08:00
|
|
|
|
var columns = CollectColumnMetadata<T>(accessor);
|
2025-05-09 17:54:52 +08:00
|
|
|
|
var tmpMetadata = BuildDeviceMetadata<T>(columns, accessor);
|
|
|
|
|
|
|
|
|
|
|
|
string tableNameOrTreePath = string.Empty;
|
|
|
|
|
|
var tableNameOrTreePathAttribute = typeof(T).GetCustomAttribute<TableNameOrTreePathAttribute>();
|
|
|
|
|
|
if (tableNameOrTreePathAttribute != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
tableNameOrTreePath = tableNameOrTreePathAttribute.TableNameOrTreePath;
|
|
|
|
|
|
}
|
|
|
|
|
|
tmpMetadata.EntityName = accessor.EntityName;
|
|
|
|
|
|
tmpMetadata.EntityType = accessor.EntityType;
|
|
|
|
|
|
tmpMetadata.TableNameOrTreePath = tableNameOrTreePath;
|
|
|
|
|
|
|
2025-04-22 16:44:47 +08:00
|
|
|
|
var metaData = MetadataCache.AddOrUpdate(
|
|
|
|
|
|
typeof(T),
|
2025-05-09 17:54:52 +08:00
|
|
|
|
addValueFactory: t => tmpMetadata, // 如果键不存在,用此值添加
|
2025-04-22 16:44:47 +08:00
|
|
|
|
updateValueFactory: (t, existingValue) =>
|
|
|
|
|
|
{
|
2025-05-08 10:28:23 +08:00
|
|
|
|
var columns = CollectColumnMetadata(accessor);
|
2025-05-09 17:54:52 +08:00
|
|
|
|
var metadata = BuildDeviceMetadata(columns, accessor);
|
2025-04-22 16:44:47 +08:00
|
|
|
|
|
|
|
|
|
|
//对现有值 existingValue 进行修改,返回新值
|
2025-05-09 17:54:52 +08:00
|
|
|
|
string tableNameOrTreePath = string.Empty;
|
|
|
|
|
|
var tableNameOrTreePathAttribute = typeof(T).GetCustomAttribute<TableNameOrTreePathAttribute>();
|
|
|
|
|
|
if (tableNameOrTreePathAttribute != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
tableNameOrTreePath = tableNameOrTreePathAttribute.TableNameOrTreePath;
|
|
|
|
|
|
}
|
2025-04-22 16:44:47 +08:00
|
|
|
|
existingValue.ColumnNames = metadata.ColumnNames;
|
2025-05-09 17:54:52 +08:00
|
|
|
|
existingValue.DataTypes = metadata.DataTypes;
|
2025-04-22 16:44:47 +08:00
|
|
|
|
return existingValue;
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-05-09 17:54:52 +08:00
|
|
|
|
//var metaData = MetadataCache.GetOrAdd(typeof(T), type =>
|
|
|
|
|
|
//{
|
|
|
|
|
|
// var columns = CollectColumnMetadata(accessor);
|
|
|
|
|
|
// var metadata = BuildDeviceMetadata(columns, accessor);
|
|
|
|
|
|
// string tableNameOrTreePath = string.Empty;
|
|
|
|
|
|
// var tableNameOrTreePathAttribute = typeof(T).GetCustomAttribute<TableNameOrTreePathAttribute>();
|
|
|
|
|
|
// if (tableNameOrTreePathAttribute != null)
|
|
|
|
|
|
// {
|
|
|
|
|
|
// tableNameOrTreePath = tableNameOrTreePathAttribute.TableNameOrTreePath;
|
|
|
|
|
|
// }
|
|
|
|
|
|
// metadata.EntityName = accessor.EntityName;
|
|
|
|
|
|
// metadata.EntityType = accessor.EntityType;
|
|
|
|
|
|
// metadata.TableNameOrTreePath = tableNameOrTreePath;
|
|
|
|
|
|
// return metadata;
|
|
|
|
|
|
//});
|
2025-05-08 17:21:20 +08:00
|
|
|
|
|
2025-04-22 16:44:47 +08:00
|
|
|
|
return await Task.FromResult(metaData);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-03 15:38:31 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 查询数据
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <typeparam name="T"></typeparam>
|
|
|
|
|
|
/// <param name="options"></param>
|
|
|
|
|
|
/// <returns></returns>
|
2025-04-22 16:44:47 +08:00
|
|
|
|
public async Task<BusPagedResult<T>> QueryAsync<T>(IoTDBQueryOptions options) where T : IoTEntity, new()
|
2025-04-03 15:38:31 +08:00
|
|
|
|
{
|
2025-04-21 22:57:49 +08:00
|
|
|
|
try
|
2025-04-03 15:38:31 +08:00
|
|
|
|
{
|
2025-04-29 23:48:47 +08:00
|
|
|
|
var stopwatch2 = Stopwatch.StartNew();
|
|
|
|
|
|
|
2025-04-22 22:11:55 +08:00
|
|
|
|
var query = await BuildQuerySQL<T>(options);
|
2025-04-21 22:57:49 +08:00
|
|
|
|
var sessionDataSet = await CurrentSession.ExecuteQueryStatementAsync(query);
|
2025-04-03 15:38:31 +08:00
|
|
|
|
|
2025-05-08 14:42:13 +08:00
|
|
|
|
|
2025-04-21 22:57:49 +08:00
|
|
|
|
var result = new BusPagedResult<T>
|
|
|
|
|
|
{
|
|
|
|
|
|
TotalCount = await GetTotalCount<T>(options),
|
2025-04-22 16:44:47 +08:00
|
|
|
|
Items = await ParseResults<T>(sessionDataSet, options.PageSize),
|
|
|
|
|
|
PageIndex = options.PageIndex,
|
|
|
|
|
|
PageSize = options.PageSize,
|
2025-04-22 22:11:55 +08:00
|
|
|
|
|
2025-04-21 22:57:49 +08:00
|
|
|
|
};
|
2025-04-29 23:48:47 +08:00
|
|
|
|
stopwatch2.Stop();
|
2025-05-12 17:08:09 +08:00
|
|
|
|
|
2025-04-29 23:48:47 +08:00
|
|
|
|
//int totalPageCount = (int)Math.Ceiling((double)result.TotalCount / options.PageSize);
|
2025-04-21 22:57:49 +08:00
|
|
|
|
|
2025-04-29 23:48:47 +08:00
|
|
|
|
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;
|
2025-04-22 16:44:47 +08:00
|
|
|
|
|
2025-04-21 22:57:49 +08:00
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2025-04-22 17:58:14 +08:00
|
|
|
|
CurrentSession.Dispose();
|
2025-04-29 23:48:47 +08:00
|
|
|
|
_logger.LogError(ex, $"{nameof(QueryAsync)} IoTDB查询{typeof(T).Name}的数据时发生异常");
|
2025-04-21 22:57:49 +08:00
|
|
|
|
throw;
|
|
|
|
|
|
}
|
2025-04-03 15:38:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-02 14:06:40 +08:00
|
|
|
|
/// <summary>
|
2025-04-07 16:44:25 +08:00
|
|
|
|
/// 构建Tablet
|
2025-04-02 14:06:40 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <typeparam name="T"></typeparam>
|
2025-05-09 17:54:52 +08:00
|
|
|
|
/// <param name="entities">表实体</param>
|
2025-04-07 16:44:25 +08:00
|
|
|
|
/// <param name="metadata">设备元数据</param></param>
|
2025-04-02 14:06:40 +08:00
|
|
|
|
/// <returns></returns>
|
2025-05-09 17:54:52 +08:00
|
|
|
|
private List<Tablet> BuildTablet<T>(IEnumerable<T> entities, DeviceMetadata metadata) where T : IoTEntity
|
2025-04-02 14:06:40 +08:00
|
|
|
|
{
|
2025-05-09 17:54:52 +08:00
|
|
|
|
var entitiyList = entities.ToList();
|
|
|
|
|
|
if (entitiyList == null || entitiyList.Count <= 0)
|
2025-05-08 17:21:20 +08:00
|
|
|
|
{
|
2025-05-09 17:54:52 +08:00
|
|
|
|
return null;
|
2025-05-08 17:21:20 +08:00
|
|
|
|
}
|
2025-04-21 09:45:30 +08:00
|
|
|
|
|
2025-05-09 17:54:52 +08:00
|
|
|
|
//var accessor = SourceEntityAccessorFactory.GetAccessor<T>();
|
2025-05-08 22:44:01 +08:00
|
|
|
|
|
2025-05-09 17:54:52 +08:00
|
|
|
|
//var memberCache = BuildMemberCache(accessor);
|
2025-05-08 22:44:01 +08:00
|
|
|
|
|
2025-05-09 17:54:52 +08:00
|
|
|
|
if (metadata.EntityType == null)
|
2025-04-21 09:45:30 +08:00
|
|
|
|
{
|
2025-05-09 17:54:52 +08:00
|
|
|
|
throw new ArgumentException($"{nameof(BuildTablet)} 构建存储结构{nameof(Tablet)}时 {nameof(T)}的EntityType 没有指定,属于异常情况,-101");
|
2025-04-21 09:45:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-09 17:54:52 +08:00
|
|
|
|
if (metadata.EntityType == EntityTypeEnum.Other)
|
2025-04-21 09:45:30 +08:00
|
|
|
|
{
|
2025-05-09 17:54:52 +08:00
|
|
|
|
throw new ArgumentException($"{nameof(BuildTablet)} 构建存储结构{nameof(Tablet)}时 {nameof(T)}的EntityType 不属于IoTDB数据模型实体,属于异常情况,-102");
|
2025-04-21 09:45:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-09 17:54:52 +08:00
|
|
|
|
if (metadata.EntityType == EntityTypeEnum.TreeModel && _runtimeContext.UseTableSessionPool == true)
|
2025-04-02 14:06:40 +08:00
|
|
|
|
{
|
2025-05-09 17:54:52 +08:00
|
|
|
|
throw new ArgumentException($"{nameof(BuildTablet)} 构建存储结构{nameof(Tablet)}时 tree模型不能使用table模型Session连接,属于异常情况,-103");
|
2025-04-21 09:45:30 +08:00
|
|
|
|
}
|
2025-05-09 17:54:52 +08:00
|
|
|
|
else if (metadata.EntityType == EntityTypeEnum.TableModel && _runtimeContext.UseTableSessionPool == false)
|
2025-04-21 14:20:49 +08:00
|
|
|
|
{
|
2025-05-09 17:54:52 +08:00
|
|
|
|
throw new ArgumentException($"{nameof(BuildTablet)} 构建存储结构{nameof(Tablet)}时 table模型不能使用tree模型Session连接,属于异常情况,-104");
|
|
|
|
|
|
}
|
|
|
|
|
|
string tableNameOrTreePath = string.Empty;
|
|
|
|
|
|
if (_runtimeContext.UseTableSessionPool)//表模型
|
|
|
|
|
|
{
|
|
|
|
|
|
//如果指定了路径
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(metadata.TableNameOrTreePath))
|
2025-05-08 22:44:01 +08:00
|
|
|
|
{
|
2025-05-09 17:54:52 +08:00
|
|
|
|
tableNameOrTreePath = metadata.TableNameOrTreePath;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
tableNameOrTreePath = DevicePathBuilder.GetTableName<T>();
|
2025-05-08 22:44:01 +08:00
|
|
|
|
}
|
2025-04-21 14:20:49 +08:00
|
|
|
|
|
2025-05-09 17:54:52 +08:00
|
|
|
|
return new List<Tablet>() { BuildTablet(entitiyList, metadata, tableNameOrTreePath) };
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
2025-05-08 22:44:01 +08:00
|
|
|
|
{
|
2025-05-09 17:54:52 +08:00
|
|
|
|
//树模型的时候,实体的设备Id可能会不同,因此需要根据不同路径进行存储。
|
|
|
|
|
|
var tabletList = new List<Tablet>();
|
|
|
|
|
|
var groupEntities = entitiyList.GroupBy(d => d.DevicePath).ToList();
|
|
|
|
|
|
foreach (var group in groupEntities)
|
2025-05-08 22:44:01 +08:00
|
|
|
|
{
|
2025-05-09 17:54:52 +08:00
|
|
|
|
tabletList.Add(BuildTablet(group.ToList(), metadata, group.Key));
|
2025-05-08 22:44:01 +08:00
|
|
|
|
}
|
2025-05-08 10:28:23 +08:00
|
|
|
|
|
2025-05-09 17:54:52 +08:00
|
|
|
|
return tabletList;
|
2025-05-08 22:44:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-09 17:54:52 +08:00
|
|
|
|
private Tablet BuildTablet<T>(List<T> entities, DeviceMetadata metadata, string tableNameOrTreePath) where T : IoTEntity
|
2025-05-08 22:44:01 +08:00
|
|
|
|
{
|
2025-05-09 17:54:52 +08:00
|
|
|
|
// 预分配内存结构
|
|
|
|
|
|
var rowCount = entities.Count;
|
|
|
|
|
|
var timestamps = new long[rowCount];
|
|
|
|
|
|
var values = new object[rowCount][];
|
|
|
|
|
|
for (var i = 0; i < values.Length; i++)
|
2025-05-08 22:44:01 +08:00
|
|
|
|
{
|
2025-05-09 17:54:52 +08:00
|
|
|
|
values[i] = new object[metadata.ColumnNames.Count];
|
2025-05-08 22:44:01 +08:00
|
|
|
|
}
|
2025-04-02 14:06:40 +08:00
|
|
|
|
|
2025-05-09 17:54:52 +08:00
|
|
|
|
List<string> tempColumnNames = new List<string>();
|
|
|
|
|
|
tempColumnNames.AddRange(metadata.ColumnNames);
|
2025-04-21 14:20:49 +08:00
|
|
|
|
|
2025-05-09 17:54:52 +08:00
|
|
|
|
// 顺序处理数据(保证线程安全)
|
|
|
|
|
|
for (var row = 0; row < rowCount; row++)
|
2025-05-08 22:44:01 +08:00
|
|
|
|
{
|
2025-05-09 17:54:52 +08:00
|
|
|
|
var entity = entities[row];
|
|
|
|
|
|
timestamps[row] = entity.Timestamps;
|
2025-04-21 14:20:49 +08:00
|
|
|
|
|
2025-05-09 17:54:52 +08:00
|
|
|
|
for (int i = 0; i < metadata.ColumnNames.Count; i++)
|
2025-05-08 22:44:01 +08:00
|
|
|
|
{
|
2025-05-09 17:54:52 +08:00
|
|
|
|
var processor = metadata.Processors[i];
|
|
|
|
|
|
if (processor.IsSingleMeasuring)
|
|
|
|
|
|
{
|
|
|
|
|
|
tempColumnNames[i] = (string)processor.SingleMeasuringNameGetter(entity);
|
|
|
|
|
|
}
|
2025-05-12 17:08:09 +08:00
|
|
|
|
|
2025-05-09 17:54:52 +08:00
|
|
|
|
// 获取并转换值
|
|
|
|
|
|
values[row][i] = processor.ValueGetter(entity);
|
2025-05-08 22:44:01 +08:00
|
|
|
|
}
|
2025-04-03 15:38:31 +08:00
|
|
|
|
}
|
2025-04-07 16:44:25 +08:00
|
|
|
|
|
2025-05-09 17:54:52 +08:00
|
|
|
|
return _runtimeContext.UseTableSessionPool
|
|
|
|
|
|
? BuildTableSessionTablet(metadata, tableNameOrTreePath, tempColumnNames, values.Select(d => d.ToList()).ToList(), timestamps.ToList())
|
|
|
|
|
|
: BuildSessionTablet(metadata, tableNameOrTreePath, tempColumnNames, values.Select(d => d.ToList()).ToList(), timestamps.ToList());
|
2025-05-08 22:44:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-07 16:44:25 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 构建tree模型的Tablet
|
|
|
|
|
|
/// </summary>
|
2025-04-21 09:45:30 +08:00
|
|
|
|
/// <param name="metadata">已解析的设备数据元数据</param>
|
|
|
|
|
|
/// <param name="devicePath">设备路径</param>
|
2025-05-08 22:44:01 +08:00
|
|
|
|
/// <param name="columns">数据列集合</param>
|
2025-04-21 09:45:30 +08:00
|
|
|
|
/// <param name="values">数据集合</param>
|
|
|
|
|
|
/// <param name="timestamps">时间戳集合</param>
|
2025-04-07 16:44:25 +08:00
|
|
|
|
/// <returns></returns>
|
2025-05-08 22:44:01 +08:00
|
|
|
|
private Tablet BuildSessionTablet(DeviceMetadata metadata, string devicePath, List<string> columns, List<List<object>> values, List<long> timestamps)
|
2025-04-07 16:44:25 +08:00
|
|
|
|
{
|
2025-04-21 09:45:30 +08:00
|
|
|
|
//todo 树模型需要去掉TAG类型和ATTRIBUTE类型的字段,只需要保留FIELD类型字段即可
|
|
|
|
|
|
|
2025-04-07 16:44:25 +08:00
|
|
|
|
return new Tablet(
|
|
|
|
|
|
devicePath,
|
2025-05-08 22:44:01 +08:00
|
|
|
|
columns,
|
2025-04-07 16:44:25 +08:00
|
|
|
|
metadata.DataTypes,
|
|
|
|
|
|
values,
|
|
|
|
|
|
timestamps
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 构建表模型的Tablet
|
|
|
|
|
|
/// </summary>
|
2025-04-21 09:45:30 +08:00
|
|
|
|
/// <param name="metadata">已解析的设备数据元数据</param>
|
|
|
|
|
|
/// <param name="tableName">表名称</param>
|
2025-05-08 22:44:01 +08:00
|
|
|
|
/// <param name="columns">数据列集合</param>
|
2025-04-21 09:45:30 +08:00
|
|
|
|
/// <param name="values">数据集合</param>
|
|
|
|
|
|
/// <param name="timestamps">时间戳集合</param>
|
2025-04-07 16:44:25 +08:00
|
|
|
|
/// <returns></returns>
|
2025-05-09 17:54:52 +08:00
|
|
|
|
private Tablet BuildTableSessionTablet(DeviceMetadata metadata, string tableName, List<string> columns, List<List<object>> values, List<long> timestamps)
|
2025-04-07 16:44:25 +08:00
|
|
|
|
{
|
|
|
|
|
|
var tablet = new Tablet(
|
2025-04-21 09:45:30 +08:00
|
|
|
|
tableName,
|
2025-05-08 22:44:01 +08:00
|
|
|
|
columns,
|
2025-04-07 16:44:25 +08:00
|
|
|
|
metadata.ColumnCategories,
|
|
|
|
|
|
metadata.DataTypes,
|
|
|
|
|
|
values,
|
|
|
|
|
|
timestamps
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
return tablet;
|
2025-04-02 14:06:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2025-04-03 15:38:31 +08:00
|
|
|
|
/// 构建查询语句
|
2025-04-02 14:06:40 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <typeparam name="T"></typeparam>
|
|
|
|
|
|
/// <param name="options"></param>
|
|
|
|
|
|
/// <returns></returns>
|
2025-04-22 16:44:47 +08:00
|
|
|
|
private async Task<string> BuildQuerySQL<T>(IoTDBQueryOptions options) where T : IoTEntity
|
2025-04-02 14:06:40 +08:00
|
|
|
|
{
|
2025-04-22 16:44:47 +08:00
|
|
|
|
var metadata = await GetMetadata<T>();
|
2025-05-12 15:14:19 +08:00
|
|
|
|
var sb = new StringBuilder("SELECT ");
|
2025-04-03 15:38:31 +08:00
|
|
|
|
sb.AppendJoin(", ", metadata.ColumnNames);
|
2025-04-07 16:44:25 +08:00
|
|
|
|
sb.Append($" FROM {options.TableNameOrTreePath}");
|
2025-04-02 14:06:40 +08:00
|
|
|
|
|
2025-04-03 15:38:31 +08:00
|
|
|
|
if (options.Conditions.Any())
|
2025-04-02 14:06:40 +08:00
|
|
|
|
{
|
2025-04-03 15:38:31 +08:00
|
|
|
|
sb.Append(" WHERE ");
|
|
|
|
|
|
sb.AppendJoin(" AND ", options.Conditions.Select(TranslateCondition));
|
|
|
|
|
|
}
|
2025-04-02 14:06:40 +08:00
|
|
|
|
|
2025-04-22 16:44:47 +08:00
|
|
|
|
sb.Append($" LIMIT {options.PageSize} OFFSET {options.PageIndex * options.PageSize}");
|
2025-04-03 15:38:31 +08:00
|
|
|
|
return sb.ToString();
|
2025-04-02 14:06:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2025-04-03 15:38:31 +08:00
|
|
|
|
/// 构建删除语句
|
2025-04-02 14:06:40 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <typeparam name="T"></typeparam>
|
|
|
|
|
|
/// <param name="options"></param>
|
|
|
|
|
|
/// <returns></returns>
|
2025-04-22 16:44:47 +08:00
|
|
|
|
private async Task<string> BuildDeleteSQL<T>(IoTDBQueryOptions options) where T : IoTEntity
|
2025-04-02 14:06:40 +08:00
|
|
|
|
{
|
2025-04-22 16:44:47 +08:00
|
|
|
|
var metadata = await GetMetadata<T>();
|
2025-04-07 16:44:25 +08:00
|
|
|
|
var sb = new StringBuilder();
|
2025-04-03 15:38:31 +08:00
|
|
|
|
|
2025-04-07 16:44:25 +08:00
|
|
|
|
if (!_runtimeContext.UseTableSessionPool)
|
|
|
|
|
|
{
|
|
|
|
|
|
sb.Append("DELETE ");
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
sb.Append("DROP ");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-08 17:44:42 +08:00
|
|
|
|
sb.Append($" FROM {options.TableNameOrTreePath}");
|
2025-04-03 15:38:31 +08:00
|
|
|
|
|
|
|
|
|
|
sb.AppendJoin(", ", metadata.ColumnNames);
|
2025-04-02 14:06:40 +08:00
|
|
|
|
|
|
|
|
|
|
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)
|
2025-05-14 11:33:31 +08:00
|
|
|
|
{
|
2025-04-02 14:06:40 +08:00
|
|
|
|
return condition.Operator switch
|
|
|
|
|
|
{
|
2025-05-14 11:33:31 +08:00
|
|
|
|
">" => $"{condition.Field} > {condition.Value}",
|
|
|
|
|
|
"<" => $"{condition.Field} < {condition.Value}",
|
|
|
|
|
|
"=" => $"{condition.Field} = {condition.Value}",
|
2025-04-22 16:44:47 +08:00
|
|
|
|
_ => throw new NotSupportedException($"{nameof(TranslateCondition)} 将查询条件转换为SQL语句时操作符 {condition.Operator} 属于异常情况")
|
2025-04-02 14:06:40 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
2025-05-14 11:33:31 +08:00
|
|
|
|
|
2025-04-02 14:06:40 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取查询条件的总数量
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <typeparam name="T"></typeparam>
|
|
|
|
|
|
/// <param name="options"></param>
|
|
|
|
|
|
/// <returns></returns>
|
2025-04-22 16:44:47 +08:00
|
|
|
|
private async Task<int> GetTotalCount<T>(IoTDBQueryOptions options) where T : IoTEntity
|
2025-04-02 14:06:40 +08:00
|
|
|
|
{
|
2025-04-07 16:44:25 +08:00
|
|
|
|
var countQuery = $"SELECT COUNT(*) FROM {options.TableNameOrTreePath}";
|
2025-04-02 14:06:40 +08:00
|
|
|
|
if (options.Conditions.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
countQuery += " WHERE " + string.Join(" AND ", options.Conditions.Select(TranslateCondition));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-11 11:56:23 +08:00
|
|
|
|
var result = await CurrentSession.ExecuteQueryStatementAsync(countQuery);
|
2025-04-29 23:48:47 +08:00
|
|
|
|
if (result == null)
|
2025-04-22 23:44:37 +08:00
|
|
|
|
{
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-29 23:48:47 +08:00
|
|
|
|
if (!result.HasNext())
|
|
|
|
|
|
{
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-04-22 23:44:37 +08:00
|
|
|
|
var count = Convert.ToInt32(result.Next().Values[0]);
|
|
|
|
|
|
await result.Close();
|
|
|
|
|
|
|
|
|
|
|
|
return count;
|
2025-04-02 14:06:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 解析查询结果
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <typeparam name="T"></typeparam>
|
|
|
|
|
|
/// <param name="dataSet"></param>
|
|
|
|
|
|
/// <param name="pageSize"></param>
|
|
|
|
|
|
/// <returns></returns>
|
2025-04-22 16:44:47 +08:00
|
|
|
|
private async Task<IEnumerable<T>> ParseResults<T>(SessionDataSet dataSet, int pageSize) where T : IoTEntity, new()
|
2025-04-02 14:06:40 +08:00
|
|
|
|
{
|
|
|
|
|
|
var results = new List<T>();
|
2025-04-22 16:44:47 +08:00
|
|
|
|
var metadata = await GetMetadata<T>();
|
2025-04-02 14:06:40 +08:00
|
|
|
|
|
2025-05-08 17:21:20 +08:00
|
|
|
|
var accessor = SourceEntityAccessorFactory.GetAccessor<T>();
|
2025-05-08 22:44:01 +08:00
|
|
|
|
var memberCache = BuildMemberCache(accessor);
|
2025-05-12 17:08:09 +08:00
|
|
|
|
|
2025-05-07 17:20:10 +08:00
|
|
|
|
|
2025-04-02 14:06:40 +08:00
|
|
|
|
while (dataSet.HasNext() && results.Count < pageSize)
|
|
|
|
|
|
{
|
|
|
|
|
|
var record = dataSet.Next();
|
|
|
|
|
|
var entity = new T
|
|
|
|
|
|
{
|
2025-04-02 17:23:52 +08:00
|
|
|
|
Timestamps = record.Timestamps
|
2025-04-02 14:06:40 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-05-12 15:14:19 +08:00
|
|
|
|
for (int i = 0; i < metadata.Processors.Count; i++)
|
2025-04-02 14:06:40 +08:00
|
|
|
|
{
|
2025-05-12 15:14:19 +08:00
|
|
|
|
var value = record.Values[i];
|
2025-05-12 17:08:09 +08:00
|
|
|
|
if (!(value is System.DBNull))
|
|
|
|
|
|
{
|
|
|
|
|
|
metadata.Processors[i].ValueSetter(entity, value);
|
|
|
|
|
|
}
|
2025-04-02 14:06:40 +08:00
|
|
|
|
}
|
2025-05-12 17:08:09 +08:00
|
|
|
|
|
2025-04-02 14:06:40 +08:00
|
|
|
|
results.Add(entity);
|
2025-04-22 23:44:37 +08:00
|
|
|
|
|
2025-04-02 14:06:40 +08:00
|
|
|
|
}
|
2025-04-22 23:44:37 +08:00
|
|
|
|
await dataSet.Close();
|
2025-04-02 14:06:40 +08:00
|
|
|
|
return results;
|
|
|
|
|
|
}
|
2025-04-03 15:38:31 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取设备元数据的列
|
|
|
|
|
|
/// </summary>
|
2025-05-08 10:28:23 +08:00
|
|
|
|
/// <param name="accessor"></param>
|
2025-04-03 15:38:31 +08:00
|
|
|
|
/// <returns></returns>
|
2025-05-08 10:28:23 +08:00
|
|
|
|
private List<ColumnInfo> CollectColumnMetadata<T>(ISourceEntityAccessor<T> accessor)
|
2025-04-03 15:38:31 +08:00
|
|
|
|
{
|
|
|
|
|
|
var columns = new List<ColumnInfo>();
|
2025-05-09 17:54:52 +08:00
|
|
|
|
var memberCache = BuildMemberCache(accessor);
|
2025-05-08 17:21:20 +08:00
|
|
|
|
|
|
|
|
|
|
foreach (var member in accessor.MemberList)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 过滤元组子项
|
|
|
|
|
|
if (member.NameOrPath.Contains(".Item")) continue;
|
|
|
|
|
|
|
2025-05-12 17:08:09 +08:00
|
|
|
|
// 类型名称处理
|
|
|
|
|
|
string declaredTypeName = member.DeclaredTypeName;
|
2025-05-08 17:21:20 +08:00
|
|
|
|
|
|
|
|
|
|
// 特性查询优化
|
|
|
|
|
|
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)
|
2025-05-08 14:42:13 +08:00
|
|
|
|
{
|
2025-05-12 17:08:09 +08:00
|
|
|
|
column = new ColumnInfo(member.NameOrPath, ColumnCategory.TAG, GetDataTypeFromTypeName(member.DeclaredTypeName), false, member.DeclaredTypeName);
|
2025-05-08 14:42:13 +08:00
|
|
|
|
}
|
2025-05-08 17:21:20 +08:00
|
|
|
|
else if (attrColumn != null)
|
2025-04-21 14:20:49 +08:00
|
|
|
|
{
|
2025-05-12 17:08:09 +08:00
|
|
|
|
column = new ColumnInfo(member.NameOrPath, ColumnCategory.ATTRIBUTE, GetDataTypeFromTypeName(member.DeclaredTypeName), false, member.DeclaredTypeName);
|
2025-04-21 14:20:49 +08:00
|
|
|
|
}
|
2025-05-08 17:21:20 +08:00
|
|
|
|
else if (fieldColumn != null)
|
2025-04-21 14:20:49 +08:00
|
|
|
|
{
|
2025-05-12 17:08:09 +08:00
|
|
|
|
column = new ColumnInfo(member.NameOrPath, ColumnCategory.FIELD, GetDataTypeFromTypeName(member.DeclaredTypeName), false, member.DeclaredTypeName);
|
2025-04-21 14:20:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-08 17:21:20 +08:00
|
|
|
|
// 单测模式处理
|
|
|
|
|
|
if (singleMeasuringAttr != null && column == null)
|
2025-04-10 23:31:43 +08:00
|
|
|
|
{
|
2025-05-08 17:21:20 +08:00
|
|
|
|
var tupleItemKey = $"{member.NameOrPath}.Item2";
|
|
|
|
|
|
if (!memberCache.TryGetValue(tupleItemKey, out var tupleMember))
|
2025-05-08 14:42:13 +08:00
|
|
|
|
{
|
2025-05-08 17:21:20 +08:00
|
|
|
|
throw new Exception($"{nameof(CollectColumnMetadata)} {accessor.EntityName} {member.NameOrPath} 单侧点属性解析异常");
|
2025-05-08 14:42:13 +08:00
|
|
|
|
}
|
2025-05-09 17:54:52 +08:00
|
|
|
|
column = new ColumnInfo(member.NameOrPath, ColumnCategory.FIELD, GetDataTypeFromTypeName(tupleMember.DeclaredTypeName), true, tupleMember.DeclaredTypeName);
|
2025-04-10 23:31:43 +08:00
|
|
|
|
}
|
2025-04-11 17:06:30 +08:00
|
|
|
|
|
2025-05-08 17:21:20 +08:00
|
|
|
|
if (column.HasValue) columns.Add(column.Value);
|
2025-04-03 15:38:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
return columns;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 构建设备元数据
|
|
|
|
|
|
/// </summary>
|
2025-04-21 09:45:30 +08:00
|
|
|
|
/// <param name="typeInfo">待解析的类</param>
|
|
|
|
|
|
/// <param name="columns">已处理好的数据列</param>
|
2025-04-03 15:38:31 +08:00
|
|
|
|
/// <returns></returns>
|
2025-05-09 17:54:52 +08:00
|
|
|
|
private DeviceMetadata BuildDeviceMetadata<T>(List<ColumnInfo> columns, ISourceEntityAccessor<T> accessor) where T : IoTEntity
|
2025-04-03 15:38:31 +08:00
|
|
|
|
{
|
|
|
|
|
|
var metadata = new DeviceMetadata();
|
|
|
|
|
|
|
2025-04-11 11:56:23 +08:00
|
|
|
|
//先检查是不是单侧点模型
|
|
|
|
|
|
if (columns.Any(c => c.IsSingleMeasuring))
|
|
|
|
|
|
{
|
|
|
|
|
|
metadata.IsSingleMeasuring = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//按业务逻辑顺序处理(TAG -> ATTRIBUTE -> FIELD)
|
2025-04-03 15:38:31 +08:00
|
|
|
|
var groupedColumns = columns
|
|
|
|
|
|
.GroupBy(c => c.Category)
|
|
|
|
|
|
.ToDictionary(g => g.Key, g => g.ToList());
|
2025-04-21 14:20:49 +08:00
|
|
|
|
|
2025-04-03 15:38:31 +08:00
|
|
|
|
ProcessCategory(groupedColumns, ColumnCategory.TAG, metadata);
|
|
|
|
|
|
ProcessCategory(groupedColumns, ColumnCategory.ATTRIBUTE, metadata);
|
|
|
|
|
|
ProcessCategory(groupedColumns, ColumnCategory.FIELD, metadata);
|
|
|
|
|
|
|
2025-05-09 17:54:52 +08:00
|
|
|
|
// 新增处理器初始化
|
|
|
|
|
|
foreach (var item in metadata.ColumnNames)
|
|
|
|
|
|
{
|
|
|
|
|
|
ColumnInfo column = columns.FirstOrDefault(d => d.Name == item);
|
|
|
|
|
|
|
|
|
|
|
|
var processor = new ColumnProcessor
|
|
|
|
|
|
{
|
|
|
|
|
|
ColumnName = column.Name,
|
|
|
|
|
|
IsSingleMeasuring = column.IsSingleMeasuring,
|
2025-05-12 15:14:19 +08:00
|
|
|
|
GetConverter = GetterConverter(column.DeclaredTypeName.ToUpper()),
|
|
|
|
|
|
SetConverter = SetterConverter(column.Name.ToUpper()),
|
|
|
|
|
|
TSDataType = column.DataType,
|
2025-05-09 17:54:52 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 处理单测点
|
|
|
|
|
|
if (column.IsSingleMeasuring)
|
|
|
|
|
|
{
|
|
|
|
|
|
var item1Member = accessor.MemberList
|
|
|
|
|
|
.First(m => m.NameOrPath == $"{column.Name}.Item1");
|
|
|
|
|
|
|
|
|
|
|
|
processor.SingleMeasuringNameGetter = (obj) =>
|
|
|
|
|
|
{
|
|
|
|
|
|
// 获取原始值并转为字符串
|
|
|
|
|
|
object rawValue = item1Member.Getter(obj);
|
|
|
|
|
|
string value = rawValue?.ToString();
|
|
|
|
|
|
|
2025-05-12 15:14:19 +08:00
|
|
|
|
ValidateSingleMeasuringName(value);
|
2025-05-09 17:54:52 +08:00
|
|
|
|
|
|
|
|
|
|
return value;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var item2Member = accessor.MemberList
|
|
|
|
|
|
.First(m => m.NameOrPath == $"{column.Name}.Item2");
|
2025-05-12 17:08:09 +08:00
|
|
|
|
processor.ValueGetter = (obj) =>
|
|
|
|
|
|
{
|
2025-05-09 17:54:52 +08:00
|
|
|
|
object rawValue = item2Member.Getter(obj);
|
2025-05-12 15:14:19 +08:00
|
|
|
|
return processor.GetConverter(rawValue);
|
2025-05-09 17:54:52 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// 获取对应的成员访问器
|
|
|
|
|
|
var member = accessor.MemberList.First(m => m.NameOrPath == column.Name);
|
2025-05-12 17:08:09 +08:00
|
|
|
|
processor.ValueGetter = (obj) =>
|
|
|
|
|
|
{
|
2025-05-09 17:54:52 +08:00
|
|
|
|
object rawValue = member.Getter(obj);
|
2025-05-12 15:14:19 +08:00
|
|
|
|
return processor.GetConverter(rawValue);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
//对应的属性成员进行赋值
|
|
|
|
|
|
processor.ValueSetter = (obj, value) =>
|
|
|
|
|
|
{
|
|
|
|
|
|
dynamic tempValue = GetTSDataValue(processor.TSDataType, value);
|
|
|
|
|
|
var rawValue = processor.SetConverter(value);
|
|
|
|
|
|
member.Setter(obj, rawValue);
|
2025-05-09 17:54:52 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
metadata.Processors.Add(processor);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-03 15:38:31 +08:00
|
|
|
|
return metadata;
|
2025-04-02 17:23:52 +08:00
|
|
|
|
}
|
2025-04-03 15:38:31 +08:00
|
|
|
|
|
2025-05-12 15:14:19 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 验证单测点名称格式
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void ValidateSingleMeasuringName(string value)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(value))
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 规则1: 严格检查ASCII字母和数字(0-9, A-Z, a-z)
|
|
|
|
|
|
bool hasInvalidChars = value.Any(c =>
|
|
|
|
|
|
!((c >= 'A' && c <= 'Z') ||
|
|
|
|
|
|
(c >= 'a' && c <= 'z') ||
|
|
|
|
|
|
(c >= '0' && c <= '9')));
|
|
|
|
|
|
|
|
|
|
|
|
// 规则2: 首字符不能是数字
|
|
|
|
|
|
bool startsWithDigit = value[0] >= '0' && value[0] <= '9';
|
|
|
|
|
|
|
|
|
|
|
|
// 规则3: 全字符串不能都是数字
|
|
|
|
|
|
bool allDigits = value.All(c => c >= '0' && c <= '9');
|
|
|
|
|
|
|
|
|
|
|
|
// 按优先级抛出具体异常
|
|
|
|
|
|
if (hasInvalidChars)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new InvalidOperationException(
|
|
|
|
|
|
$"SingleMeasuring name '{value}' 包含非法字符,只允许字母和数字");
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (startsWithDigit)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new InvalidOperationException(
|
|
|
|
|
|
$"SingleMeasuring name '{value}' 不能以数字开头");
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (allDigits)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new InvalidOperationException(
|
|
|
|
|
|
$"SingleMeasuring name '{value}' 不能全为数字");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 取值的处理器
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="declaredTypeName"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
private Func<object, object> GetterConverter(string declaredTypeName)
|
2025-05-09 17:54:52 +08:00
|
|
|
|
{
|
|
|
|
|
|
return declaredTypeName switch
|
|
|
|
|
|
{
|
2025-05-12 17:08:09 +08:00
|
|
|
|
"DATETIME" => value => value != null ? ((DateTime)value).GetDateTimeOffset().ToUnixTimeNanoseconds() : null,
|
2025-05-09 17:54:52 +08:00
|
|
|
|
_ => value => value
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-12 15:14:19 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 设置值的处理
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="columnName"></param>
|
|
|
|
|
|
/// <returns></returns>
|
2025-05-12 17:08:09 +08:00
|
|
|
|
private Func<object, object> SetterConverter(string columnName) =>
|
2025-05-12 15:14:19 +08:00
|
|
|
|
columnName.ToLower().EndsWith("time")
|
2025-05-12 17:08:09 +08:00
|
|
|
|
? value => value != null ? TimestampHelper.ConvertToDateTime(Convert.ToInt64(value), TimestampUnit.Nanoseconds) : null
|
2025-05-12 15:14:19 +08:00
|
|
|
|
: value => value;
|
|
|
|
|
|
|
2025-04-03 15:38:31 +08:00
|
|
|
|
/// <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
|
|
|
|
|
|
{
|
2025-04-10 23:31:43 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 列名
|
|
|
|
|
|
/// </summary>
|
2025-04-03 15:38:31 +08:00
|
|
|
|
public string Name { get; }
|
2025-04-10 23:31:43 +08:00
|
|
|
|
|
2025-05-09 17:54:52 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 声明的类型的名称
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public string DeclaredTypeName { get; }
|
|
|
|
|
|
|
2025-04-10 23:31:43 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 是否是单测点
|
|
|
|
|
|
/// </summary>
|
2025-04-11 11:56:23 +08:00
|
|
|
|
public bool IsSingleMeasuring { get; }
|
2025-04-10 23:31:43 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 列类型
|
|
|
|
|
|
/// </summary>
|
2025-04-03 15:38:31 +08:00
|
|
|
|
public ColumnCategory Category { get; }
|
2025-04-10 23:31:43 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 数据类型
|
|
|
|
|
|
/// </summary>
|
2025-04-03 15:38:31 +08:00
|
|
|
|
public TSDataType DataType { get; }
|
|
|
|
|
|
|
2025-05-09 17:54:52 +08:00
|
|
|
|
public ColumnInfo(string name, ColumnCategory category, TSDataType dataType, bool isSingleMeasuring, string declaredTypeName)
|
2025-04-03 15:38:31 +08:00
|
|
|
|
{
|
|
|
|
|
|
Name = name;
|
|
|
|
|
|
Category = category;
|
|
|
|
|
|
DataType = dataType;
|
2025-04-10 23:31:43 +08:00
|
|
|
|
IsSingleMeasuring = isSingleMeasuring;
|
2025-05-09 17:54:52 +08:00
|
|
|
|
DeclaredTypeName = declaredTypeName;
|
2025-04-03 15:38:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 根据类型名称获取对应的 IoTDB 数据类型
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="typeName">类型名称(不区分大小写)</param>
|
|
|
|
|
|
/// <returns>对应的 TSDataType,默认返回 TSDataType.STRING</returns>
|
|
|
|
|
|
private TSDataType GetDataTypeFromTypeName(string typeName)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(typeName))
|
2025-04-07 16:44:25 +08:00
|
|
|
|
return TSDataType.STRING;
|
2025-04-03 15:38:31 +08:00
|
|
|
|
|
|
|
|
|
|
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,
|
2025-04-21 22:57:49 +08:00
|
|
|
|
["DATETIME"] = TSDataType.TIMESTAMP,
|
2025-04-03 15:38:31 +08:00
|
|
|
|
["DATE"] = TSDataType.DATE,
|
|
|
|
|
|
["BLOB"] = TSDataType.BLOB,
|
2025-04-08 17:44:42 +08:00
|
|
|
|
["DECIMAL"] = TSDataType.STRING,
|
2025-04-03 15:38:31 +08:00
|
|
|
|
["STRING"] = TSDataType.STRING
|
|
|
|
|
|
};
|
2025-04-08 17:44:42 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 根据类型名称获取 IoTDB 数据默认值
|
|
|
|
|
|
/// </summary>
|
2025-04-11 11:56:23 +08:00
|
|
|
|
private readonly IReadOnlyDictionary<string, object> DataTypeDefaultValueMap =
|
2025-04-08 17:44:42 +08:00
|
|
|
|
new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
|
|
|
|
|
|
{
|
|
|
|
|
|
["BOOLEAN"] = false,
|
|
|
|
|
|
["INT32"] = 0,
|
|
|
|
|
|
["INT64"] = 0,
|
|
|
|
|
|
["FLOAT"] = 0.0f,
|
|
|
|
|
|
["DOUBLE"] = 0.0d,
|
|
|
|
|
|
["TEXT"] = string.Empty,
|
|
|
|
|
|
["NULLTYPE"] = null,
|
2025-04-21 22:57:49 +08:00
|
|
|
|
["DATETIME"] = null,
|
2025-04-08 17:44:42 +08:00
|
|
|
|
["DATE"] = null,
|
|
|
|
|
|
["BLOB"] = null,
|
|
|
|
|
|
["DECIMAL"] = "0.0",
|
|
|
|
|
|
["STRING"] = string.Empty
|
|
|
|
|
|
};
|
2025-04-22 22:11:55 +08:00
|
|
|
|
|
|
|
|
|
|
/// <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)
|
|
|
|
|
|
};
|
2025-05-08 22:44:01 +08:00
|
|
|
|
|
|
|
|
|
|
/// <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;
|
|
|
|
|
|
}
|
2025-05-09 17:54:52 +08:00
|
|
|
|
|
|
|
|
|
|
private static readonly Regex _asciiAlphanumericRegex = new Regex(@"^[a-zA-Z0-9]*$", RegexOptions.Compiled);
|
2025-04-02 14:06:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|