将Feature/XP.Common和Feature/XP.Hardware分支合并至Develop/XP.forHardwareAndCommon,完善XPapp注册和相关硬件类库通用类库功能。
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using XP.Common.Database.Models;
|
||||
|
||||
namespace XP.Common.Database.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 分页计算辅助工具
|
||||
/// </summary>
|
||||
public static class PaginationHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 计算分页偏移量(SQLite OFFSET)
|
||||
/// </summary>
|
||||
public static int CalculateOffset(PaginationRequest pagination)
|
||||
{
|
||||
if (pagination.PageIndex < 1) pagination.PageIndex = 1;
|
||||
if (pagination.PageSize < 1) pagination.PageSize = 20;
|
||||
return (pagination.PageIndex - 1) * pagination.PageSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证并修正分页参数
|
||||
/// </summary>
|
||||
public static PaginationRequest ValidateAndFix(PaginationRequest pagination)
|
||||
{
|
||||
var fixedPagination = new PaginationRequest
|
||||
{
|
||||
PageIndex = pagination.PageIndex < 1 ? 1 : pagination.PageIndex,
|
||||
PageSize = pagination.PageSize < 1 ? 20 : (pagination.PageSize > 1000 ? 1000 : pagination.PageSize),
|
||||
OrderBy = pagination.OrderBy ?? string.Empty
|
||||
};
|
||||
return fixedPagination;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace XP.Common.Database.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 通用CRUD SQL构建器(适配SQLite)
|
||||
/// </summary>
|
||||
public static class SqlBuilderHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 构建插入SQL
|
||||
/// </summary>
|
||||
/// <param name="tableName">表名</param>
|
||||
/// <param name="columns">列名集合</param>
|
||||
/// <returns>插入SQL(参数名:@列名)</returns>
|
||||
public static string BuildInsertSql(string tableName, IEnumerable<string> columns)
|
||||
{
|
||||
if (string.IsNullOrEmpty(tableName)) throw new ArgumentNullException(nameof(tableName));
|
||||
var columnList = columns?.ToList() ?? throw new ArgumentNullException(nameof(columns));
|
||||
if (columnList.Count == 0) throw new ArgumentException("列名集合不能为空");
|
||||
|
||||
var columnsStr = string.Join(", ", columnList);
|
||||
var paramsStr = string.Join(", ", columnList.Select(c => $"@{c}"));
|
||||
return $"INSERT INTO {tableName} ({columnsStr}) VALUES ({paramsStr})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建更新SQL
|
||||
/// </summary>
|
||||
/// <param name="tableName">表名</param>
|
||||
/// <param name="updateColumns">更新列名</param>
|
||||
/// <param name="whereColumns">条件列名(如Id)</param>
|
||||
/// <returns>更新SQL</returns>
|
||||
public static string BuildUpdateSql(string tableName, IEnumerable<string> updateColumns, IEnumerable<string> whereColumns)
|
||||
{
|
||||
if (string.IsNullOrEmpty(tableName)) throw new ArgumentNullException(nameof(tableName));
|
||||
var updateList = updateColumns?.ToList() ?? throw new ArgumentNullException(nameof(updateColumns));
|
||||
var whereList = whereColumns?.ToList() ?? throw new ArgumentNullException(nameof(whereColumns));
|
||||
if (updateList.Count == 0) throw new ArgumentException("更新列名集合不能为空");
|
||||
if (whereList.Count == 0) throw new ArgumentException("条件列名集合不能为空");
|
||||
|
||||
var updateStr = string.Join(", ", updateList.Select(c => $"{c}=@{c}"));
|
||||
var whereStr = string.Join(" AND ", whereList.Select(c => $"{c}=@{c}"));
|
||||
return $"UPDATE {tableName} SET {updateStr} WHERE {whereStr}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建删除SQL
|
||||
/// </summary>
|
||||
public static string BuildDeleteSql(string tableName, IEnumerable<string> whereColumns)
|
||||
{
|
||||
if (string.IsNullOrEmpty(tableName)) throw new ArgumentNullException(nameof(tableName));
|
||||
var whereList = whereColumns?.ToList() ?? throw new ArgumentNullException(nameof(whereColumns));
|
||||
if (whereList.Count == 0) throw new ArgumentException("条件列名集合不能为空");
|
||||
|
||||
var whereStr = string.Join(" AND ", whereList.Select(c => $"{c}=@{c}"));
|
||||
return $"DELETE FROM {tableName} WHERE {whereStr}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建查询SQL
|
||||
/// </summary>
|
||||
public static string BuildSelectSql(string tableName, IEnumerable<string> columns, IEnumerable<string>? whereColumns = null, string orderBy = "")
|
||||
{
|
||||
if (string.IsNullOrEmpty(tableName)) throw new ArgumentNullException(nameof(tableName));
|
||||
var columnList = columns?.ToList() ?? throw new ArgumentNullException(nameof(columns));
|
||||
if (columnList.Count == 0) columnList.Add("*");
|
||||
|
||||
var columnsStr = string.Join(", ", columnList);
|
||||
var sql = new StringBuilder($"SELECT {columnsStr} FROM {tableName}");
|
||||
|
||||
// 添加WHERE条件
|
||||
if (whereColumns != null && whereColumns.Any())
|
||||
{
|
||||
var whereStr = string.Join(" AND ", whereColumns.Select(c => $"{c}=@{c}"));
|
||||
sql.Append($" WHERE {whereStr}");
|
||||
}
|
||||
|
||||
// 添加排序
|
||||
if (!string.IsNullOrEmpty(orderBy))
|
||||
{
|
||||
sql.Append($" ORDER BY {orderBy}");
|
||||
}
|
||||
|
||||
return sql.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace XP.Common.Database.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// SQLite参数化查询辅助工具
|
||||
/// </summary>
|
||||
public static class SqliteParameterHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建参数字典
|
||||
/// </summary>
|
||||
/// <param name="keyValues">参数名-值对(如 ("Id", 1), ("Name", "Test"))</param>
|
||||
/// <returns>参数字典</returns>
|
||||
public static Dictionary<string, object> CreateParameters(params (string Key, object Value)[] keyValues)
|
||||
{
|
||||
var parameters = new Dictionary<string, object>();
|
||||
foreach (var (key, value) in keyValues)
|
||||
{
|
||||
parameters.Add(key, value);
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 合并参数字典
|
||||
/// </summary>
|
||||
public static Dictionary<string, object> MergeParameters(params Dictionary<string, object>[] paramLists)
|
||||
{
|
||||
var merged = new Dictionary<string, object>();
|
||||
foreach (var paramList in paramLists)
|
||||
{
|
||||
foreach (var (key, value) in paramList)
|
||||
{
|
||||
if (!merged.ContainsKey(key))
|
||||
{
|
||||
merged.Add(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using XP.Common.Database.Interfaces;
|
||||
|
||||
namespace XP.Common.Database.Implementations
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据库操作执行结果实体
|
||||
/// </summary>
|
||||
public class DbExecuteResult : IDbExecuteResult
|
||||
{
|
||||
public bool IsSuccess { get; set; }
|
||||
public int RowsAffected { get; set; }
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public Exception? Exception { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 快速创建成功结果
|
||||
/// </summary>
|
||||
public static DbExecuteResult Success(string message = "执行成功", int rowsAffected = 0)
|
||||
{
|
||||
return new DbExecuteResult
|
||||
{
|
||||
IsSuccess = true,
|
||||
Message = message,
|
||||
RowsAffected = rowsAffected
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 快速创建失败结果
|
||||
/// </summary>
|
||||
public static DbExecuteResult Fail(string message, Exception? ex = null, int rowsAffected = 0)
|
||||
{
|
||||
return new DbExecuteResult
|
||||
{
|
||||
IsSuccess = false,
|
||||
Message = message,
|
||||
Exception = ex,
|
||||
RowsAffected = rowsAffected
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,527 @@
|
||||
using Microsoft.Data.Sqlite;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using XP.Common.Configs;
|
||||
using XP.Common.Database.Interfaces;
|
||||
using XP.Common.Database.Models;
|
||||
using XP.Common.Helpers;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
|
||||
using CustomDbTransaction = XP.Common.Database.Interfaces.IDbTransaction;
|
||||
|
||||
namespace XP.Common.Database.Implementations
|
||||
{
|
||||
/// <summary>
|
||||
/// SQLite 核心操作实现(适配IDbContext)
|
||||
/// </summary>
|
||||
public class SqliteContext : IDbContext
|
||||
{
|
||||
private readonly SqliteConfig _config;
|
||||
private readonly ILoggerService _logger;
|
||||
private SqliteConnection? _connection;
|
||||
private bool _isDisposed = false;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数(DI注入配置+日志)
|
||||
/// </summary>
|
||||
public SqliteContext(SqliteConfig config, ILoggerService logger)
|
||||
{
|
||||
_config = config ?? throw new ArgumentNullException(nameof(config));
|
||||
_logger = logger.ForModule("Sqlite.Context");
|
||||
|
||||
// 自动创建数据库目录
|
||||
if (_config.CreateIfNotExists)
|
||||
{
|
||||
var dbDir = Path.GetDirectoryName(_config.DbFilePath);
|
||||
if (!string.IsNullOrEmpty(dbDir) && !Directory.Exists(dbDir))
|
||||
{
|
||||
Directory.CreateDirectory(dbDir);
|
||||
_logger.Info("自动创建数据库目录:{DirPath}", dbDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region 连接管理
|
||||
public IDbExecuteResult OpenConnection()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_connection != null && _connection.State == ConnectionState.Open)
|
||||
{
|
||||
return DbExecuteResult.Success("连接已打开");
|
||||
}
|
||||
|
||||
_connection = new SqliteConnection(_config.GetConnectionString());
|
||||
_connection.Open();
|
||||
_logger.Debug("SQLite连接已打开:{DbPath}", _config.DbFilePath);
|
||||
|
||||
// 启用WAL模式提升性能
|
||||
if (_config.EnableWalMode)
|
||||
{
|
||||
using var cmd = new SqliteCommand("PRAGMA journal_mode=WAL;", _connection);
|
||||
cmd.ExecuteNonQuery();
|
||||
_logger.Debug("SQLite已启用WAL模式");
|
||||
}
|
||||
|
||||
return DbExecuteResult.Success("连接打开成功");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "SQLite连接打开失败:{DbPath}", _config.DbFilePath);
|
||||
return DbExecuteResult.Fail("连接打开失败", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IDbExecuteResult> OpenConnectionAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_connection != null && _connection.State == ConnectionState.Open)
|
||||
{
|
||||
return DbExecuteResult.Success("连接已打开");
|
||||
}
|
||||
|
||||
_connection = new SqliteConnection(_config.GetConnectionString());
|
||||
await _connection.OpenAsync();
|
||||
_logger.Debug("SQLite连接已异步打开:{DbPath}", _config.DbFilePath);
|
||||
|
||||
if (_config.EnableWalMode)
|
||||
{
|
||||
using var cmd = new SqliteCommand("PRAGMA journal_mode=WAL;", _connection);
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
return DbExecuteResult.Success("连接异步打开成功");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "SQLite连接异步打开失败:{DbPath}", _config.DbFilePath);
|
||||
return DbExecuteResult.Fail("连接异步打开失败", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取有效连接(自动打开)
|
||||
/// </summary>
|
||||
private SqliteConnection GetValidConnection()
|
||||
{
|
||||
if (_connection == null || _connection.State != ConnectionState.Open)
|
||||
{
|
||||
var result = OpenConnection();
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
throw new InvalidOperationException($"获取SQLite连接失败:{result.Message}");
|
||||
}
|
||||
}
|
||||
return _connection!;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 事务管理
|
||||
public (IDbExecuteResult Result, CustomDbTransaction? Transaction) BeginTransaction()
|
||||
{
|
||||
try
|
||||
{
|
||||
var conn = GetValidConnection();
|
||||
// 系统的SqliteTransaction
|
||||
var innerSystemTrans = conn.BeginTransaction();
|
||||
// 封装为自定义的SqliteTransaction
|
||||
var customTrans = new SqliteTransaction(innerSystemTrans, _logger);
|
||||
_logger.Debug("SQLite事务已开始");
|
||||
// 返回:自定义IDbTransaction(解决返回类型不匹配)
|
||||
return (DbExecuteResult.Success("事务开始成功"), customTrans);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "SQLite事务开始失败");
|
||||
return (DbExecuteResult.Fail("事务开始失败", ex), null);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 通用SQL执行
|
||||
public IDbExecuteResult ExecuteNonQuery(string sql, Dictionary<string, object>? parameters = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
LogSql(sql, parameters);
|
||||
using var cmd = CreateCommand(sql, parameters);
|
||||
var rowsAffected = cmd.ExecuteNonQuery();
|
||||
_logger.Debug("SQL执行成功,影响行数:{Rows}", rowsAffected);
|
||||
return DbExecuteResult.Success("执行成功", rowsAffected);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "SQL执行失败:{Sql}", sql);
|
||||
return DbExecuteResult.Fail("执行失败", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IDbExecuteResult> ExecuteNonQueryAsync(string sql, Dictionary<string, object>? parameters = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
LogSql(sql, parameters);
|
||||
using var cmd = CreateCommand(sql, parameters);
|
||||
var rowsAffected = await cmd.ExecuteNonQueryAsync();
|
||||
_logger.Debug("SQL异步执行成功,影响行数:{Rows}", rowsAffected);
|
||||
return DbExecuteResult.Success("异步执行成功", (int)rowsAffected);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "SQL异步执行失败:{Sql}", sql);
|
||||
return DbExecuteResult.Fail("异步执行失败", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public (IDbExecuteResult Result, T? Value) ExecuteScalar<T>(string sql, Dictionary<string, object>? parameters = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
LogSql(sql, parameters);
|
||||
using var cmd = CreateCommand(sql, parameters);
|
||||
var resultObj = cmd.ExecuteScalar();
|
||||
T? value = default;
|
||||
if (resultObj != null && resultObj != DBNull.Value)
|
||||
{
|
||||
if (resultObj is T directValue)
|
||||
{
|
||||
value = directValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 类型不匹配时安全转换,处理值类型/引用类型
|
||||
value = (T)Convert.ChangeType(resultObj, Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T));
|
||||
}
|
||||
}
|
||||
var logValue = value ?? (object)"null";
|
||||
_logger.Debug("SQL标量查询成功,返回值:{Value}", logValue);
|
||||
return (DbExecuteResult.Success("标量查询成功"), value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "SQL标量查询失败:{Sql}", sql);
|
||||
return (DbExecuteResult.Fail("标量查询失败", ex), default);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(IDbExecuteResult Result, T? Value)> ExecuteScalarAsync<T>(string sql, Dictionary<string, object>? parameters = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
LogSql(sql, parameters);
|
||||
using var cmd = CreateCommand(sql, parameters);
|
||||
var resultObj = await cmd.ExecuteScalarAsync();
|
||||
T? value = default;
|
||||
if (resultObj != null && resultObj != DBNull.Value)
|
||||
{
|
||||
if (resultObj is T directValue)
|
||||
{
|
||||
value = directValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 类型不匹配时安全转换,处理值类型/引用类型
|
||||
value = (T)Convert.ChangeType(resultObj, Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T));
|
||||
}
|
||||
}
|
||||
var logValue = value ?? (object)"null";
|
||||
_logger.Debug("SQL异步标量查询成功,返回值:{Value}", logValue);
|
||||
return (DbExecuteResult.Success("异步标量查询成功"), value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "SQL异步标量查询失败:{Sql}", sql);
|
||||
return (DbExecuteResult.Fail("异步标量查询失败", ex), default);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 查询结果映射
|
||||
public (IDbExecuteResult Result, DataTable? Data) ExecuteDataTable(string sql, Dictionary<string, object>? parameters = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
LogSql(sql, parameters);
|
||||
using var cmd = CreateCommand(sql, parameters);
|
||||
using var reader = cmd.ExecuteReader();
|
||||
|
||||
var dt = new DataTable();
|
||||
// 添加列
|
||||
for (int i = 0; i < reader.FieldCount; i++)
|
||||
{
|
||||
dt.Columns.Add(reader.GetName(i), reader.GetFieldType(i));
|
||||
}
|
||||
// 添加行
|
||||
while (reader.Read())
|
||||
{
|
||||
var row = dt.NewRow();
|
||||
for (int i = 0; i < reader.FieldCount; i++)
|
||||
{
|
||||
row[i] = reader.IsDBNull(i) ? DBNull.Value : reader.GetValue(i);
|
||||
}
|
||||
dt.Rows.Add(row);
|
||||
}
|
||||
|
||||
_logger.Debug("SQL查询成功,返回DataTable行数:{Rows}", dt.Rows.Count);
|
||||
return (DbExecuteResult.Success("查询成功"), dt);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "SQL查询DataTable失败:{Sql}", sql);
|
||||
return (DbExecuteResult.Fail("查询失败", ex), null);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(IDbExecuteResult Result, DataTable? Data)> ExecuteDataTableAsync(string sql, Dictionary<string, object>? parameters = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
LogSql(sql, parameters);
|
||||
using var cmd = CreateCommand(sql, parameters);
|
||||
using var reader = await cmd.ExecuteReaderAsync();
|
||||
|
||||
var dt = new DataTable();
|
||||
// 添加列
|
||||
for (int i = 0; i < reader.FieldCount; i++)
|
||||
{
|
||||
dt.Columns.Add(reader.GetName(i), reader.GetFieldType(i));
|
||||
}
|
||||
// 添加行(异步读取)
|
||||
while (await reader.ReadAsync())
|
||||
{
|
||||
var row = dt.NewRow();
|
||||
for (int i = 0; i < reader.FieldCount; i++)
|
||||
{
|
||||
row[i] = await reader.IsDBNullAsync(i) ? DBNull.Value : reader.GetValue(i);
|
||||
}
|
||||
dt.Rows.Add(row);
|
||||
}
|
||||
_logger.Debug("SQL异步查询成功,返回DataTable行数:{Rows}", dt.Rows.Count);
|
||||
return (DbExecuteResult.Success("异步查询成功"), dt);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "SQL异步查询DataTable失败:{Sql}", sql);
|
||||
return (DbExecuteResult.Fail("异步查询失败", ex), null);
|
||||
}
|
||||
}
|
||||
|
||||
public (IDbExecuteResult Result, List<T> Data) QueryList<T>(string sql, Dictionary<string, object>? parameters = null) where T : new()
|
||||
{
|
||||
try
|
||||
{
|
||||
var (result, dt) = ExecuteDataTable(sql, parameters);
|
||||
if (!result.IsSuccess || dt == null)
|
||||
{
|
||||
return (result, new List<T>());
|
||||
}
|
||||
|
||||
var list = MapDataTableToEntityList<T>(dt);
|
||||
return (DbExecuteResult.Success("实体列表查询成功"), list);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "SQL查询实体列表失败:{Sql}", sql);
|
||||
return (DbExecuteResult.Fail("实体列表查询失败", ex), new List<T>());
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(IDbExecuteResult Result, List<T> Data)> QueryListAsync<T>(string sql, Dictionary<string, object>? parameters = null) where T : new()
|
||||
{
|
||||
try
|
||||
{
|
||||
var (result, dt) = await ExecuteDataTableAsync(sql, parameters);
|
||||
if (!result.IsSuccess || dt == null)
|
||||
{
|
||||
return (result, new List<T>());
|
||||
}
|
||||
|
||||
var list = MapDataTableToEntityList<T>(dt);
|
||||
return (DbExecuteResult.Success("异步实体列表查询成功"), list);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "SQL异步查询实体列表失败:{Sql}", sql);
|
||||
return (DbExecuteResult.Fail("异步实体列表查询失败", ex), new List<T>());
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 分页查询
|
||||
public (IDbExecuteResult Result, PaginationResponse<T> Data) QueryPaged<T>(string sql, PaginationRequest pagination, Dictionary<string, object>? parameters = null) where T : new()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 1. 查询总条数
|
||||
var countSql = $"SELECT COUNT(*) FROM ({sql}) AS TotalCountQuery";
|
||||
var (countResult, totalCount) = ExecuteScalar<int>(countSql, parameters);
|
||||
if (!countResult.IsSuccess)
|
||||
{
|
||||
return (countResult, new PaginationResponse<T>());
|
||||
}
|
||||
|
||||
// 2. 构建分页SQL(SQLite分页:LIMIT/OFFSET)
|
||||
var offset = (pagination.PageIndex - 1) * pagination.PageSize;
|
||||
var pagedSql = $"{sql} {(!string.IsNullOrEmpty(pagination.OrderBy) ? $"ORDER BY {pagination.OrderBy}" : "")} LIMIT {pagination.PageSize} OFFSET {offset}";
|
||||
|
||||
// 3. 查询当前页数据
|
||||
var (dataResult, list) = QueryList<T>(pagedSql, parameters);
|
||||
if (!dataResult.IsSuccess)
|
||||
{
|
||||
return (dataResult, new PaginationResponse<T>());
|
||||
}
|
||||
|
||||
// 4. 封装分页结果
|
||||
var pagedResponse = new PaginationResponse<T>
|
||||
{
|
||||
PageIndex = pagination.PageIndex,
|
||||
PageSize = pagination.PageSize,
|
||||
TotalCount = totalCount,
|
||||
Data = list
|
||||
};
|
||||
|
||||
_logger.Debug("SQL分页查询成功:页码{Page},条数{Size},总条数{Total}",
|
||||
pagination.PageIndex, pagination.PageSize, totalCount);
|
||||
return (DbExecuteResult.Success("分页查询成功"), pagedResponse);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "SQL分页查询失败:{Sql}", sql);
|
||||
return (DbExecuteResult.Fail("分页查询失败", ex), new PaginationResponse<T>());
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(IDbExecuteResult Result, PaginationResponse<T> Data)> QueryPagedAsync<T>(string sql, PaginationRequest pagination, Dictionary<string, object>? parameters = null) where T : new()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 1. 查询总条数
|
||||
var countSql = $"SELECT COUNT(*) FROM ({sql}) AS TotalCountQuery";
|
||||
var (countResult, totalCount) = await ExecuteScalarAsync<int>(countSql, parameters);
|
||||
if (!countResult.IsSuccess)
|
||||
{
|
||||
return (countResult, new PaginationResponse<T>());
|
||||
}
|
||||
|
||||
// 2. 构建分页SQL
|
||||
var offset = (pagination.PageIndex - 1) * pagination.PageSize;
|
||||
var pagedSql = $"{sql} {(!string.IsNullOrEmpty(pagination.OrderBy) ? $"ORDER BY {pagination.OrderBy}" : "")} LIMIT {pagination.PageSize} OFFSET {offset}";
|
||||
|
||||
// 3. 查询当前页数据
|
||||
var (dataResult, list) = await QueryListAsync<T>(pagedSql, parameters);
|
||||
if (!dataResult.IsSuccess)
|
||||
{
|
||||
return (dataResult, new PaginationResponse<T>());
|
||||
}
|
||||
|
||||
// 4. 封装分页结果
|
||||
var pagedResponse = new PaginationResponse<T>
|
||||
{
|
||||
PageIndex = pagination.PageIndex,
|
||||
PageSize = pagination.PageSize,
|
||||
TotalCount = totalCount,
|
||||
Data = list
|
||||
};
|
||||
|
||||
_logger.Debug("SQL异步分页查询成功:页码{Page},条数{Size},总条数{Total}",
|
||||
pagination.PageIndex, pagination.PageSize, totalCount);
|
||||
return (DbExecuteResult.Success("异步分页查询成功"), pagedResponse);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "SQL异步分页查询失败:{Sql}", sql);
|
||||
return (DbExecuteResult.Fail("异步分页查询失败", ex), new PaginationResponse<T>());
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 私有辅助方法
|
||||
/// <summary>
|
||||
/// 创建SQLite命令(带参数)
|
||||
/// </summary>
|
||||
private SqliteCommand CreateCommand(string sql, Dictionary<string, object>? parameters = null)
|
||||
{
|
||||
var cmd = new SqliteCommand(sql, GetValidConnection());
|
||||
cmd.CommandTimeout = _config.ConnectionTimeout;
|
||||
|
||||
// 添加参数(防SQL注入)
|
||||
if (parameters != null && parameters.Count > 0)
|
||||
{
|
||||
foreach (var (key, value) in parameters)
|
||||
{
|
||||
var paramValue = value ?? DBNull.Value;
|
||||
cmd.Parameters.AddWithValue($"@{key}", paramValue);
|
||||
}
|
||||
}
|
||||
|
||||
return cmd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 记录SQL日志(调试模式)
|
||||
/// </summary>
|
||||
private void LogSql(string sql, Dictionary<string, object>? parameters = null)
|
||||
{
|
||||
if (!_config.EnableSqlLogging) return;
|
||||
|
||||
var paramStr = parameters == null
|
||||
? "无参数"
|
||||
: string.Join(", ", parameters.Select(kv => $"{kv.Key}={kv.Value}"));
|
||||
_logger.Debug("执行SQL:{Sql} | 参数:{Params}", sql, paramStr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DataTable映射为实体列表
|
||||
/// </summary>
|
||||
private List<T> MapDataTableToEntityList<T>(DataTable dt) where T : new()
|
||||
{
|
||||
var list = new List<T>();
|
||||
var props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
||||
|
||||
foreach (DataRow row in dt.Rows)
|
||||
{
|
||||
var entity = new T();
|
||||
foreach (var prop in props)
|
||||
{
|
||||
if (dt.Columns.Contains(prop.Name) && row[prop.Name] != DBNull.Value)
|
||||
{
|
||||
var value = Convert.ChangeType(row[prop.Name], prop.PropertyType);
|
||||
prop.SetValue(entity, value);
|
||||
}
|
||||
}
|
||||
list.Add(entity);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 资源释放
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
|
||||
// 关闭连接
|
||||
if (_connection != null)
|
||||
{
|
||||
if (_connection.State == ConnectionState.Open)
|
||||
{
|
||||
_connection.Close();
|
||||
_logger.Debug("SQLite连接已关闭(Dispose)");
|
||||
}
|
||||
_connection.Dispose();
|
||||
}
|
||||
|
||||
_isDisposed = true;
|
||||
_logger.Debug("SQLiteContext资源已释放");
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using XP.Common.Database.Interfaces;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
|
||||
namespace XP.Common.Database.Implementations
|
||||
{
|
||||
/// <summary>
|
||||
/// SQLite 事务实现
|
||||
/// </summary>
|
||||
public class SqliteTransaction : IDbTransaction
|
||||
{
|
||||
private readonly Microsoft.Data.Sqlite.SqliteTransaction _innerTransaction;
|
||||
private readonly ILoggerService _logger;
|
||||
private bool _isDisposed = false;
|
||||
|
||||
public SqliteTransaction(Microsoft.Data.Sqlite.SqliteTransaction innerTrans, ILoggerService logger)
|
||||
{
|
||||
_innerTransaction = innerTrans ?? throw new ArgumentNullException(nameof(innerTrans));
|
||||
_logger = logger.ForModule("Sqlite.Transaction");
|
||||
}
|
||||
|
||||
public IDbExecuteResult Commit()
|
||||
{
|
||||
try
|
||||
{
|
||||
_innerTransaction.Commit();
|
||||
_logger.Debug("SQLite事务提交成功");
|
||||
return DbExecuteResult.Success("事务提交成功");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "SQLite事务提交失败");
|
||||
return DbExecuteResult.Fail("事务提交失败", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public IDbExecuteResult Rollback()
|
||||
{
|
||||
try
|
||||
{
|
||||
_innerTransaction.Rollback();
|
||||
_logger.Debug("SQLite事务回滚成功");
|
||||
return DbExecuteResult.Success("事务回滚成功");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "SQLite事务回滚失败");
|
||||
return DbExecuteResult.Fail("事务回滚失败", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
_innerTransaction.Dispose();
|
||||
_isDisposed = true;
|
||||
_logger.Debug("SQLite事务资源已释放");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using XP.Common.Database.Models;
|
||||
|
||||
namespace XP.Common.Database.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// 通用数据库操作接口(适配任意数据库,SQLite为具体实现)
|
||||
/// </summary>
|
||||
public interface IDbContext : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 打开数据库连接
|
||||
/// </summary>
|
||||
IDbExecuteResult OpenConnection();
|
||||
|
||||
/// <summary>
|
||||
/// 异步打开数据库连接
|
||||
/// </summary>
|
||||
Task<IDbExecuteResult> OpenConnectionAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 开始事务
|
||||
/// </summary>
|
||||
(IDbExecuteResult Result, IDbTransaction? Transaction) BeginTransaction();
|
||||
|
||||
/// <summary>
|
||||
/// 执行增删改SQL(无返回值)
|
||||
/// </summary>
|
||||
/// <param name="sql">SQL语句(参数化)</param>
|
||||
/// <param name="parameters">参数集合(key=参数名,value=参数值)</param>
|
||||
IDbExecuteResult ExecuteNonQuery(string sql, Dictionary<string, object>? parameters = null);
|
||||
|
||||
/// <summary>
|
||||
/// 异步执行增删改SQL
|
||||
/// </summary>
|
||||
Task<IDbExecuteResult> ExecuteNonQueryAsync(string sql, Dictionary<string, object>? parameters = null);
|
||||
|
||||
/// <summary>
|
||||
/// 执行查询并返回单个值(如Count/Sum)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">返回值类型</typeparam>
|
||||
/// <param name="sql">SQL语句</param>
|
||||
/// <param name="parameters">参数集合</param>
|
||||
(IDbExecuteResult Result, T? Value) ExecuteScalar<T>(string sql, Dictionary<string, object>? parameters = null);
|
||||
|
||||
/// <summary>
|
||||
/// 异步执行查询并返回单个值
|
||||
/// </summary>
|
||||
Task<(IDbExecuteResult Result, T? Value)> ExecuteScalarAsync<T>(string sql, Dictionary<string, object>? parameters = null);
|
||||
|
||||
/// <summary>
|
||||
/// 执行查询并返回DataTable
|
||||
/// </summary>
|
||||
(IDbExecuteResult Result, DataTable? Data) ExecuteDataTable(string sql, Dictionary<string, object>? parameters = null);
|
||||
|
||||
/// <summary>
|
||||
/// 异步执行查询并返回DataTable
|
||||
/// </summary>
|
||||
Task<(IDbExecuteResult Result, DataTable? Data)> ExecuteDataTableAsync(string sql, Dictionary<string, object>? parameters = null);
|
||||
|
||||
/// <summary>
|
||||
/// 执行查询并映射为实体列表
|
||||
/// </summary>
|
||||
/// <typeparam name="T">实体类型(需有无参构造函数)</typeparam>
|
||||
(IDbExecuteResult Result, List<T> Data) QueryList<T>(string sql, Dictionary<string, object>? parameters = null) where T : new();
|
||||
|
||||
/// <summary>
|
||||
/// 异步执行查询并映射为实体列表
|
||||
/// </summary>
|
||||
Task<(IDbExecuteResult Result, List<T> Data)> QueryListAsync<T>(string sql, Dictionary<string, object>? parameters = null) where T : new();
|
||||
|
||||
/// <summary>
|
||||
/// 执行分页查询并返回分页结果
|
||||
/// </summary>
|
||||
/// <typeparam name="T">实体类型</typeparam>
|
||||
(IDbExecuteResult Result, PaginationResponse<T> Data) QueryPaged<T>(string sql, PaginationRequest pagination, Dictionary<string, object>? parameters = null) where T : new();
|
||||
|
||||
/// <summary>
|
||||
/// 异步执行分页查询并返回分页结果
|
||||
/// </summary>
|
||||
Task<(IDbExecuteResult Result, PaginationResponse<T> Data)> QueryPagedAsync<T>(string sql, PaginationRequest pagination, Dictionary<string, object>? parameters = null) where T : new();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace XP.Common.Database.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据库操作通用执行结果
|
||||
/// </summary>
|
||||
public interface IDbExecuteResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否执行成功
|
||||
/// </summary>
|
||||
bool IsSuccess { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 影响行数(增删改)
|
||||
/// </summary>
|
||||
int RowsAffected { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息(成功/失败提示)
|
||||
/// </summary>
|
||||
string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 异常信息(失败时非空)
|
||||
/// </summary>
|
||||
Exception? Exception { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace XP.Common.Database.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据库事务接口
|
||||
/// </summary>
|
||||
public interface IDbTransaction : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 提交事务
|
||||
/// </summary>
|
||||
IDbExecuteResult Commit();
|
||||
|
||||
/// <summary>
|
||||
/// 回滚事务
|
||||
/// </summary>
|
||||
IDbExecuteResult Rollback();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace XP.Common.Database.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// 通用分页请求模型
|
||||
/// </summary>
|
||||
public class PaginationRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 页码(从1开始)
|
||||
/// </summary>
|
||||
public int PageIndex { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 每页条数
|
||||
/// </summary>
|
||||
public int PageSize { get; set; } = 20;
|
||||
|
||||
/// <summary>
|
||||
/// 排序字段(如:CreateTime DESC)
|
||||
/// </summary>
|
||||
public string OrderBy { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace XP.Common.Database.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// 通用分页响应模型
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数据类型</typeparam>
|
||||
public class PaginationResponse<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// 页码
|
||||
/// </summary>
|
||||
public int PageIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 每页条数
|
||||
/// </summary>
|
||||
public int PageSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总条数
|
||||
/// </summary>
|
||||
public int TotalCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总页数
|
||||
/// </summary>
|
||||
public int TotalPages => (int)Math.Ceiling((double)TotalCount / PageSize);
|
||||
|
||||
/// <summary>
|
||||
/// 当前页数据
|
||||
/// </summary>
|
||||
public List<T> Data { get; set; } = new List<T>();
|
||||
|
||||
/// <summary>
|
||||
/// 是否有下一页
|
||||
/// </summary>
|
||||
public bool HasNextPage => PageIndex < TotalPages;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user