这个算是一个简单应用,前面文章我写过ai插件开发,结合着看最好了,可能为你提供更多想法。在工业自动化领域,SCADA系统每天产生海量的设备数据、报警信息和历史记录。作为开发者,你是否遇到过这样的痛点:业务人员总是找你写SQL查询各种数据报表,而你又要花大量时间理解他们的需求?
今天我要分享一个革命性的解决方案:结合SQLite数据库和AI大模型,打造一个能"听懂人话"的智能数据助手。用户只需用自然语言描述需求,系统就能自动生成SQL并返回结果!
让我们一起看看如何用C#实现这个酷炫的功能,彻底解放你的双手!
业务人员不懂SQL语法,每次查询都要找开发人员,效率低下。
SCADA系统表结构复杂,外键关系多,即使是开发人员也容易写错查询语句。
类似的查询需求反复出现,开发人员疲于应付各种"临时需求"。
我们的解决方案包含三个核心组件:
Markdown用户自然语言输入 → AI理解需求 → 自动生成SQL → 执行查询 → 格式化输出
我们首先创建一个完整的SCADA数据库,包含设备、测点、实时数据、历史数据和报警等核心表:
C#public static class DatabaseInitializer
{
/// <summary>
/// 创建SCADA相关数据表
/// </summary>
private static void CreateTables(SQLiteConnection connection)
{
// 设备表 - 存储所有监控设备信息
string createDevicesTable = @"
CREATE TABLE Devices (
DeviceId INTEGER PRIMARY KEY AUTOINCREMENT,
DeviceName TEXT NOT NULL,
DeviceType TEXT NOT NULL, -- PLC、HMI、传感器等
Location TEXT, -- 设备位置
Status TEXT, -- 运行、停机、维护、故障
InstallDate TEXT,
LastMaintenance TEXT
);";
// 测点表 - 存储监控点位信息
string createTagsTable = @"
CREATE TABLE Tags (
TagId INTEGER PRIMARY KEY AUTOINCREMENT,
TagName TEXT NOT NULL UNIQUE,
DeviceId INTEGER,
TagType TEXT, -- AI/AO/DI/DO
Unit TEXT, -- 单位:℃、MPa等
MinValue REAL,
MaxValue REAL,
AlarmHigh REAL, -- 高报警阈值
AlarmLow REAL, -- 低报警阈值
Description TEXT,
FOREIGN KEY (DeviceId) REFERENCES Devices(DeviceId)
);";
// 实时数据表 - 当前最新数据
string createRealTimeDataTable = @"
CREATE TABLE RealTimeData (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
TagId INTEGER,
TagName TEXT,
Value REAL, -- 当前值
Quality INTEGER, -- 数据质量 0-100
Timestamp TEXT,
AlarmStatus TEXT, -- 正常、预警、报警、严重
FOREIGN KEY (TagId) REFERENCES Tags(TagId)
);";
// 更多表结构...
}
}
💡 设计亮点:
这是整个系统的灵魂部分 - 将数据库操作包装成AI可调用的函数:
C#static void RegisterDatabaseQueryPlugin(Kernel kernel)
{
// 🔍 函数1:获取数据库结构信息
var getSchemaFunction = kernel.CreateFunctionFromMethod(
method: () =>
{
using var connection = new SQLiteConnection($"Data Source=test.db;Version=3;");
connection.Open();
var schema = GetDatabaseSchema(connection);
return schema;
},
functionName: "GetDatabaseSchema",
description: "获取数据库的完整结构信息,包括所有表、字段、数据类型等"
);
// 🧠 函数2:智能分析查询需求
var smartQueryFunction = kernel.CreateFunctionFromMethod(
method: (string userQuestion) =>
{
using var connection = new SQLiteConnection($"Data Source=test.db;Version=3;");
connection.Open();
// 获取数据库结构供AI参考
var schema = GetDatabaseSchema(connection);
return $"数据库结构信息:\n{schema}\n\n用户问题:{userQuestion}\n\n请根据上述数据库结构生成合适的SQL查询语句。";
},
functionName: "AnalyzeQueryRequest",
description: "分析用户查询需求并提供数据库结构信息,帮助生成正确的SQL语句"
);
// ⚡ 函数3:安全执行SQL查询
var executeQueryFunction = kernel.CreateFunctionFromMethod(
method: (string sql) =>
{
// 安全检查:只允许SELECT语句
if (!sql.Trim().ToUpper().StartsWith("SELECT"))
{
return "⚠️ 出于安全考虑,只允许执行SELECT查询语句。";
}
using var connection = new SQLiteConnection($"Data Source=test.db;Version=3;");
connection.Open();
using var command = new SQLiteCommand(sql, connection);
using var reader = command.ExecuteReader();
var dt = new DataTable();
dt.Load(reader);
return FormatQueryResult(dt);
},
functionName: "ExecuteSQL",
description: "执行SQL查询语句并返回格式化的结果"
);
// 注册为AI插件
kernel.ImportPluginFromFunctions("DatabasePlugin",
[getSchemaFunction, smartQueryFunction, executeQueryFunction]);
}
🔥 关键技术点:
为了提供更好的用户体验,我们实现了类似ChatGPT的流式输出:
C#static async Task ProcessAIResponse(Kernel kernel, ChatHistory chatHistory)
{
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
// 🎯 配置自动函数调用
var executionSettings = new OpenAIPromptExecutionSettings
{
ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions,
MaxTokens = 2000,
Temperature = 0.7 // 适中的创造性,确保SQL准确性
};
// 🌊 流式响应处理
var streamResponse = chatCompletionService.GetStreamingChatMessageContentsAsync(
chatHistory: chatHistory,
executionSettings: executionSettings,
kernel: kernel
);
string fullResponse = "";
bool isFirstToken = true;
await foreach (var content in streamResponse)
{
if (!string.IsNullOrEmpty(content.Content))
{
// ⏱️ 模拟思考延迟,提升用户体验
if (isFirstToken)
{
await Task.Delay(100);
isFirstToken = false;
}
Console.Write(content.Content);
fullResponse += content.Content;
await Task.Delay(20); // 自然的打字效果
}
}
// 保存完整对话历史
if (!string.IsNullOrWhiteSpace(fullResponse))
{
chatHistory.AddAssistantMessage(fullResponse);
}
}
C#using System.Text;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel;
using System.Data;
using System.Data.SQLite;
namespace AppDbAiAgent
{
internal class Program
{
static async Task Main(string[] args)
{
Console.OutputEncoding = Encoding.UTF8;
// 检查是否需要初始化数据库
if (!File.Exists("test.db"))
{
Console.WriteLine("🔧 正在初始化SCADA数据库...");
DatabaseInitializer.InitializeScadaDatabase();
Console.WriteLine();
}
// 显示数据库统计信息
DatabaseInitializer.ShowDatabaseStatistics();
Console.WriteLine();
// Kernel 初始化及AI服务连接
var kernel = InitializeKernel();
// 插件注册
RegisterDatabaseQueryPlugin(kernel);
// 启动聊天循环
await StartChatLoop(kernel);
}
/// <summary>
/// 初始化Kernel
/// </summary>
static Kernel InitializeKernel()
{
var kernelBuilder = Kernel.CreateBuilder();
kernelBuilder.AddOpenAIChatCompletion(
modelId: "deepseek-chat",
apiKey: Environment.GetEnvironmentVariable("DEEPSEEK_API_KEY") ?? "sk-xxxx",
endpoint: new Uri("https://api.deepseek.com/v1")
);
return kernelBuilder.Build();
}
/// <summary>
/// 启动聊天循环
/// </summary>
static async Task StartChatLoop(Kernel kernel)
{
// 聊天历史管理
var chatHistory = new ChatHistory();
chatHistory.AddSystemMessage(@"你是一个智能学习助手,具备数据查询与分析能力,
请根据用户需求选择合适的工具来协助用户。");
Console.WriteLine("🤖 欢迎使用智能AI数据助手!");
Console.WriteLine("📝 输入 'exit' 可随时退出\n");
while (true)
{
Console.Write("👤 您: ");
string userInput = Console.ReadLine();
if (string.IsNullOrWhiteSpace(userInput)) continue;
if (userInput.Trim().ToLower() == "exit") break;
// 添加用户消息到历史
chatHistory.AddUserMessage(userInput);
try
{
Console.Write("🤖 AI助手: ");
// 处理AI响应
await ProcessAIResponse(kernel, chatHistory);
}
catch (Exception ex)
{
Console.WriteLine($"❌ 发生错误: {ex.Message}\n");
}
}
Console.WriteLine("👋 感谢使用,再见!");
}
/// <summary>
/// 处理AI响应
/// </summary>
static async Task ProcessAIResponse(Kernel kernel, ChatHistory chatHistory)
{
// 获取聊天完成服务
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
// 配置执行设置 - 启用自动函数调用
var executionSettings = new OpenAIPromptExecutionSettings
{
ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions,
MaxTokens = 2000,
Temperature = 0.7
};
// 流式响应输出
var streamResponse = chatCompletionService.GetStreamingChatMessageContentsAsync(
chatHistory: chatHistory,
executionSettings: executionSettings,
kernel: kernel
);
string fullResponse = "";
bool isFirstToken = true;
await foreach (var content in streamResponse)
{
if (!string.IsNullOrEmpty(content.Content))
{
// 第一个token前添加一个小延迟,模拟思考
if (isFirstToken)
{
await Task.Delay(100);
isFirstToken = false;
}
Console.Write(content.Content);
fullResponse += content.Content;
// 添加微小延迟,让输出更自然
await Task.Delay(20);
}
}
Console.WriteLine("\n");
// 将完整响应添加到聊天历史
if (!string.IsNullOrWhiteSpace(fullResponse))
{
chatHistory.AddAssistantMessage(fullResponse);
}
}
static void RegisterDatabaseQueryPlugin(Kernel kernel)
{
// 1. 获取数据库结构的函数
var getSchemaFunction = kernel.CreateFunctionFromMethod(
method: () =>
{
try
{
var dbFilePath = "test.db";
if (!File.Exists(dbFilePath))
return "数据库文件不存在。";
using var connection = new SQLiteConnection($"Data Source={dbFilePath};Version=3;");
connection.Open();
var schema = GetDatabaseSchema(connection);
return schema;
}
catch (Exception ex)
{
return $"获取数据库结构失败: {ex.Message}";
}
},
functionName: "GetDatabaseSchema",
description: "获取数据库的完整结构信息,包括所有表、字段、数据类型等"
);
// 2. 智能SQL查询函数(带结构感知)
var smartQueryFunction = kernel.CreateFunctionFromMethod(
method: (string userQuestion) =>
{
try
{
var dbFilePath = "test.db";
if (!File.Exists(dbFilePath))
return "数据库文件不存在。";
using var connection = new SQLiteConnection($"Data Source={dbFilePath};Version=3;");
connection.Open();
// 获取数据库结构
var schema = GetDatabaseSchema(connection);
// 返回结构信息供AI参考
return $"数据库结构信息:\n{schema}\n\n用户问题:{userQuestion}\n\n请根据上述数据库结构生成合适的SQL查询语句。";
}
catch (Exception ex)
{
return $"查询失败: {ex.Message}";
}
},
functionName: "AnalyzeQueryRequest",
description: "分析用户查询需求并提供数据库结构信息,帮助生成正确的SQL语句"
);
// 3. 执行SQL的函数
var executeQueryFunction = kernel.CreateFunctionFromMethod(
method: (string sql) =>
{
try
{
var dbFilePath = "test.db";
if (!File.Exists(dbFilePath))
return "数据库文件不存在。";
using var connection = new SQLiteConnection($"Data Source={dbFilePath};Version=3;");
connection.Open();
// 安全检查:只允许SELECT语句
if (!sql.Trim().ToUpper().StartsWith("SELECT"))
{
return "⚠️ 出于安全考虑,只允许执行SELECT查询语句。";
}
using var command = new SQLiteCommand(sql, connection);
using var reader = command.ExecuteReader();
var dt = new DataTable();
dt.Load(reader);
if (dt.Rows.Count == 0)
return "查询结果为空。";
return FormatQueryResult(dt);
}
catch (Exception ex)
{
return $"SQL执行出错: {ex.Message}";
}
},
functionName: "ExecuteSQL",
description: "执行SQL查询语句并返回格式化的结果"
);
kernel.ImportPluginFromFunctions("DatabasePlugin", [getSchemaFunction, smartQueryFunction, executeQueryFunction]);
}
/// <summary>
/// 获取数据库结构信息
/// </summary>
private static string GetDatabaseSchema(SQLiteConnection connection)
{
var schema = new StringBuilder();
schema.AppendLine("📊 SCADA数据库结构信息");
schema.AppendLine("=" + new string('=', 50));
// 获取所有表名
var tables = GetTableNames(connection);
foreach (var tableName in tables)
{
schema.AppendLine($"\n🔹 表名: {tableName}");
schema.AppendLine($" 字段信息:");
// 获取表结构
var tableInfo = GetTableStructure(connection, tableName);
foreach (var column in tableInfo)
{
schema.AppendLine($" - {column.Name} ({column.Type}) {(column.IsPrimaryKey ? "[主键]" : "")} {(column.IsNotNull ? "[非空]" : "")}");
}
// 获取记录数
var count = GetTableRecordCount(connection, tableName);
schema.AppendLine($" 📋 记录数: {count}");
// 获取外键信息
var foreignKeys = GetForeignKeys(connection, tableName);
if (foreignKeys.Any())
{
schema.AppendLine($" 🔗 外键关系:");
foreach (var fk in foreignKeys)
{
schema.AppendLine($" - {fk.Column} -> {fk.RefTable}.{fk.RefColumn}");
}
}
}
// 这里有用处,如果是sqlserver,我们可以用自动功能提取常用sql脚本,效果会更好
schema.AppendLine($"\n💡 使用示例:");
schema.AppendLine($" - 查询所有设备: SELECT * FROM Devices");
schema.AppendLine($" - 查询运行状态的设备: SELECT * FROM Devices WHERE Status = '运行'");
schema.AppendLine($" - 查询最近的报警: SELECT * FROM Alarms ORDER BY AlarmTime DESC LIMIT 10");
schema.AppendLine($" - 统计设备类型: SELECT DeviceType, COUNT(*) FROM Devices GROUP BY DeviceType");
return schema.ToString();
}
/// <summary>
/// 获取所有表名
/// </summary>
private static List<string> GetTableNames(SQLiteConnection connection)
{
var tables = new List<string>();
string sql = "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'";
using var command = new SQLiteCommand(sql, connection);
using var reader = command.ExecuteReader();
while (reader.Read())
{
tables.Add(reader.GetString(0));
}
return tables;
}
/// <summary>
/// 获取表结构信息
/// </summary>
private static List<ColumnInfo> GetTableStructure(SQLiteConnection connection, string tableName)
{
var columns = new List<ColumnInfo>();
string sql = $"PRAGMA table_info({tableName})";
using var command = new SQLiteCommand(sql, connection);
using var reader = command.ExecuteReader();
while (reader.Read())
{
columns.Add(new ColumnInfo
{
Name = reader.GetString("name"),
Type = reader.GetString("type"),
IsNotNull = reader.GetBoolean("notnull"),
IsPrimaryKey = reader.GetBoolean("pk")
});
}
return columns;
}
/// <summary>
/// 获取表记录数
/// </summary>
private static int GetTableRecordCount(SQLiteConnection connection, string tableName)
{
string sql = $"SELECT COUNT(*) FROM {tableName}";
using var command = new SQLiteCommand(sql, connection);
return Convert.ToInt32(command.ExecuteScalar());
}
/// <summary>
/// 获取外键信息
/// </summary>
private static List<ForeignKeyInfo> GetForeignKeys(SQLiteConnection connection, string tableName)
{
var foreignKeys = new List<ForeignKeyInfo>();
string sql = $"PRAGMA foreign_key_list({tableName})";
using var command = new SQLiteCommand(sql, connection);
using var reader = command.ExecuteReader();
while (reader.Read())
{
foreignKeys.Add(new ForeignKeyInfo
{
Column = reader.GetString("from"),
RefTable = reader.GetString("table"),
RefColumn = reader.GetString("to")
});
}
return foreignKeys;
}
/// <summary>
/// 格式化查询结果
/// </summary>
private static string FormatQueryResult(DataTable dt)
{
var result = new StringBuilder();
result.AppendLine($"📋 查询结果 (共 {dt.Rows.Count} 条记录):");
result.AppendLine(new string('=', 60));
// 列标题
foreach (DataColumn col in dt.Columns)
{
result.Append($"{col.ColumnName,-15} | ");
}
result.AppendLine();
result.AppendLine(new string('-', 60));
// 数据行(限制显示前20行)
int maxRows = Math.Min(20, dt.Rows.Count);
for (int i = 0; i < maxRows; i++)
{
foreach (var item in dt.Rows[i].ItemArray)
{
string value = item?.ToString() ?? "NULL";
if (value.Length > 12) value = value.Substring(0, 12) + "...";
result.Append($"{value,-15} | ");
}
result.AppendLine();
}
if (dt.Rows.Count > 20)
{
result.AppendLine($"... 还有 {dt.Rows.Count - 20} 条记录未显示");
}
return result.ToString();
}
/// <summary>
/// 列信息类
/// </summary>
public class ColumnInfo
{
public string Name { get; set; }
public string Type { get; set; }
public bool IsNotNull { get; set; }
public bool IsPrimaryKey { get; set; }
}
/// <summary>
/// 外键信息类
/// </summary>
public class ForeignKeyInfo
{
public string Column { get; set; }
public string RefTable { get; set; }
public string RefColumn { get; set; }
}
}
}
C#using System.Text;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel;
using System.Data;
using System.Data.SQLite;
namespace AppDbAiAgent
{
internal class Program
{
static async Task Main(string[] args)
{
Console.OutputEncoding = Encoding.UTF8;
// 检查是否需要初始化数据库
if (!File.Exists("test.db"))
{
Console.WriteLine("🔧 正在初始化SCADA数据库...");
DatabaseInitializer.InitializeScadaDatabase();
Console.WriteLine();
}
// 显示数据库统计信息
DatabaseInitializer.ShowDatabaseStatistics();
Console.WriteLine();
// Kernel 初始化及AI服务连接
var kernel = InitializeKernel();
// 插件注册
RegisterDatabaseQueryPlugin(kernel);
// 启动聊天循环
await StartChatLoop(kernel);
}
/// <summary>
/// 初始化Kernel
/// </summary>
static Kernel InitializeKernel()
{
var kernelBuilder = Kernel.CreateBuilder();
kernelBuilder.AddOpenAIChatCompletion(
modelId: "deepseek-chat",
apiKey: Environment.GetEnvironmentVariable("DEEPSEEK_API_KEY") ?? "sk-xxxx",
endpoint: new Uri("https://api.deepseek.com/v1")
);
return kernelBuilder.Build();
}
/// <summary>
/// 启动聊天循环
/// </summary>
static async Task StartChatLoop(Kernel kernel)
{
// 聊天历史管理
var chatHistory = new ChatHistory();
chatHistory.AddSystemMessage(@"你是一个智能学习助手,具备数据查询与分析能力,
请根据用户需求选择合适的工具来协助用户。");
Console.WriteLine("🤖 欢迎使用智能AI数据助手!");
Console.WriteLine("📝 输入 'exit' 可随时退出\n");
while (true)
{
Console.Write("👤 您: ");
string userInput = Console.ReadLine();
if (string.IsNullOrWhiteSpace(userInput)) continue;
if (userInput.Trim().ToLower() == "exit") break;
// 添加用户消息到历史
chatHistory.AddUserMessage(userInput);
try
{
Console.Write("🤖 AI助手: ");
// 处理AI响应
await ProcessAIResponse(kernel, chatHistory);
}
catch (Exception ex)
{
Console.WriteLine($"❌ 发生错误: {ex.Message}\n");
}
}
Console.WriteLine("👋 感谢使用,再见!");
}
/// <summary>
/// 处理AI响应
/// </summary>
static async Task ProcessAIResponse(Kernel kernel, ChatHistory chatHistory)
{
// 获取聊天完成服务
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
// 配置执行设置 - 启用自动函数调用
var executionSettings = new OpenAIPromptExecutionSettings
{
ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions,
MaxTokens = 2000,
Temperature = 0.7
};
// 流式响应输出
var streamResponse = chatCompletionService.GetStreamingChatMessageContentsAsync(
chatHistory: chatHistory,
executionSettings: executionSettings,
kernel: kernel
);
string fullResponse = "";
bool isFirstToken = true;
await foreach (var content in streamResponse)
{
if (!string.IsNullOrEmpty(content.Content))
{
// 第一个token前添加一个小延迟,模拟思考
if (isFirstToken)
{
await Task.Delay(100);
isFirstToken = false;
}
Console.Write(content.Content);
fullResponse += content.Content;
// 添加微小延迟,让输出更自然
await Task.Delay(20);
}
}
Console.WriteLine("\n");
// 将完整响应添加到聊天历史
if (!string.IsNullOrWhiteSpace(fullResponse))
{
chatHistory.AddAssistantMessage(fullResponse);
}
}
static void RegisterDatabaseQueryPlugin(Kernel kernel)
{
// 1. 获取数据库结构的函数
var getSchemaFunction = kernel.CreateFunctionFromMethod(
method: () =>
{
try
{
var dbFilePath = "test.db";
if (!File.Exists(dbFilePath))
return "数据库文件不存在。";
using var connection = new SQLiteConnection($"Data Source={dbFilePath};Version=3;");
connection.Open();
var schema = GetDatabaseSchema(connection);
return schema;
}
catch (Exception ex)
{
return $"获取数据库结构失败: {ex.Message}";
}
},
functionName: "GetDatabaseSchema",
description: "获取数据库的完整结构信息,包括所有表、字段、数据类型等"
);
// 2. 智能SQL查询函数(带结构感知)
var smartQueryFunction = kernel.CreateFunctionFromMethod(
method: (string userQuestion) =>
{
try
{
var dbFilePath = "test.db";
if (!File.Exists(dbFilePath))
return "数据库文件不存在。";
using var connection = new SQLiteConnection($"Data Source={dbFilePath};Version=3;");
connection.Open();
// 获取数据库结构
var schema = GetDatabaseSchema(connection);
// 返回结构信息供AI参考
return $"数据库结构信息:\n{schema}\n\n用户问题:{userQuestion}\n\n请根据上述数据库结构生成合适的SQL查询语句。";
}
catch (Exception ex)
{
return $"查询失败: {ex.Message}";
}
},
functionName: "AnalyzeQueryRequest",
description: "分析用户查询需求并提供数据库结构信息,帮助生成正确的SQL语句"
);
// 3. 执行SQL的函数
var executeQueryFunction = kernel.CreateFunctionFromMethod(
method: (string sql) =>
{
try
{
var dbFilePath = "test.db";
if (!File.Exists(dbFilePath))
return "数据库文件不存在。";
using var connection = new SQLiteConnection($"Data Source={dbFilePath};Version=3;");
connection.Open();
// 安全检查:只允许SELECT语句
if (!sql.Trim().ToUpper().StartsWith("SELECT"))
{
return "⚠️ 出于安全考虑,只允许执行SELECT查询语句。";
}
using var command = new SQLiteCommand(sql, connection);
using var reader = command.ExecuteReader();
var dt = new DataTable();
dt.Load(reader);
if (dt.Rows.Count == 0)
return "查询结果为空。";
return FormatQueryResult(dt);
}
catch (Exception ex)
{
return $"SQL执行出错: {ex.Message}";
}
},
functionName: "ExecuteSQL",
description: "执行SQL查询语句并返回格式化的结果"
);
kernel.ImportPluginFromFunctions("DatabasePlugin", [getSchemaFunction, smartQueryFunction, executeQueryFunction]);
}
/// <summary>
/// 获取数据库结构信息
/// </summary>
private static string GetDatabaseSchema(SQLiteConnection connection)
{
var schema = new StringBuilder();
schema.AppendLine("📊 SCADA数据库结构信息");
schema.AppendLine("=" + new string('=', 50));
// 获取所有表名
var tables = GetTableNames(connection);
foreach (var tableName in tables)
{
schema.AppendLine($"\n🔹 表名: {tableName}");
schema.AppendLine($" 字段信息:");
// 获取表结构
var tableInfo = GetTableStructure(connection, tableName);
foreach (var column in tableInfo)
{
schema.AppendLine($" - {column.Name} ({column.Type}) {(column.IsPrimaryKey ? "[主键]" : "")} {(column.IsNotNull ? "[非空]" : "")}");
}
// 获取记录数
var count = GetTableRecordCount(connection, tableName);
schema.AppendLine($" 📋 记录数: {count}");
// 获取外键信息
var foreignKeys = GetForeignKeys(connection, tableName);
if (foreignKeys.Any())
{
schema.AppendLine($" 🔗 外键关系:");
foreach (var fk in foreignKeys)
{
schema.AppendLine($" - {fk.Column} -> {fk.RefTable}.{fk.RefColumn}");
}
}
}
// 这里有用处,如果是sqlserver,我们可以用自动功能提取常用sql脚本,效果会更好
schema.AppendLine($"\n💡 使用示例:");
schema.AppendLine($" - 查询所有设备: SELECT * FROM Devices");
schema.AppendLine($" - 查询运行状态的设备: SELECT * FROM Devices WHERE Status = '运行'");
schema.AppendLine($" - 查询最近的报警: SELECT * FROM Alarms ORDER BY AlarmTime DESC LIMIT 10");
schema.AppendLine($" - 统计设备类型: SELECT DeviceType, COUNT(*) FROM Devices GROUP BY DeviceType");
return schema.ToString();
}
/// <summary>
/// 获取所有表名
/// </summary>
private static List<string> GetTableNames(SQLiteConnection connection)
{
var tables = new List<string>();
string sql = "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'";
using var command = new SQLiteCommand(sql, connection);
using var reader = command.ExecuteReader();
while (reader.Read())
{
tables.Add(reader.GetString(0));
}
return tables;
}
/// <summary>
/// 获取表结构信息
/// </summary>
private static List<ColumnInfo> GetTableStructure(SQLiteConnection connection, string tableName)
{
var columns = new List<ColumnInfo>();
string sql = $"PRAGMA table_info({tableName})";
using var command = new SQLiteCommand(sql, connection);
using var reader = command.ExecuteReader();
while (reader.Read())
{
columns.Add(new ColumnInfo
{
Name = reader.GetString("name"),
Type = reader.GetString("type"),
IsNotNull = reader.GetBoolean("notnull"),
IsPrimaryKey = reader.GetBoolean("pk")
});
}
return columns;
}
/// <summary>
/// 获取表记录数
/// </summary>
private static int GetTableRecordCount(SQLiteConnection connection, string tableName)
{
string sql = $"SELECT COUNT(*) FROM {tableName}";
using var command = new SQLiteCommand(sql, connection);
return Convert.ToInt32(command.ExecuteScalar());
}
/// <summary>
/// 获取外键信息
/// </summary>
private static List<ForeignKeyInfo> GetForeignKeys(SQLiteConnection connection, string tableName)
{
var foreignKeys = new List<ForeignKeyInfo>();
string sql = $"PRAGMA foreign_key_list({tableName})";
using var command = new SQLiteCommand(sql, connection);
using var reader = command.ExecuteReader();
while (reader.Read())
{
foreignKeys.Add(new ForeignKeyInfo
{
Column = reader.GetString("from"),
RefTable = reader.GetString("table"),
RefColumn = reader.GetString("to")
});
}
return foreignKeys;
}
/// <summary>
/// 格式化查询结果
/// </summary>
private static string FormatQueryResult(DataTable dt)
{
var result = new StringBuilder();
result.AppendLine($"📋 查询结果 (共 {dt.Rows.Count} 条记录):");
result.AppendLine(new string('=', 60));
// 列标题
foreach (DataColumn col in dt.Columns)
{
result.Append($"{col.ColumnName,-15} | ");
}
result.AppendLine();
result.AppendLine(new string('-', 60));
// 数据行(限制显示前20行)
int maxRows = Math.Min(20, dt.Rows.Count);
for (int i = 0; i < maxRows; i++)
{
foreach (var item in dt.Rows[i].ItemArray)
{
string value = item?.ToString() ?? "NULL";
if (value.Length > 12) value = value.Substring(0, 12) + "...";
result.Append($"{value,-15} | ");
}
result.AppendLine();
}
if (dt.Rows.Count > 20)
{
result.AppendLine($"... 还有 {dt.Rows.Count - 20} 条记录未显示");
}
return result.ToString();
}
/// <summary>
/// 列信息类
/// </summary>
public class ColumnInfo
{
public string Name { get; set; }
public string Type { get; set; }
public bool IsNotNull { get; set; }
public bool IsPrimaryKey { get; set; }
}
/// <summary>
/// 外键信息类
/// </summary>
public class ForeignKeyInfo
{
public string Column { get; set; }
public string RefTable { get; set; }
public string RefColumn { get; set; }
}
}
}
让我们看看这个系统在实际使用中的表现:



问题: 直接拼接用户输入到SQL语句中
解决: 使用参数化查询 + 白名单验证
问题: AI可能生成不存在的表名或字段
解决: 提供完整的数据库结构信息给AI参考
问题: 大表查询可能导致超时
解决: 限制返回行数 + 添加合适的索引
C#// 🛡️ 安全的查询限制
if (dt.Rows.Count > 1000)
{
result.AppendLine("⚠️ 结果集过大,仅显示前1000条记录");
}
C#private static readonly MemoryCache _schemaCache = new MemoryCache(new MemoryCacheOptions());
public static string GetCachedSchema()
{
return _schemaCache.GetOrCreate("db_schema", factory =>
{
factory.SetAbsoluteExpiration(TimeSpan.FromHours(1));
return GetDatabaseSchema();
});
}
C#// 使用连接字符串配置连接池
string connectionString = "Data Source=test.db;Version=3;Pooling=true;Max Pool Size=10;";
通过本文的实战演示,我们成功打造了一个能听懂人话的智能数据助手。这个方案的三大核心优势:
金句总结:
你在项目中是否遇到过类似的数据查询痛点?或者你有更好的AI+数据库结合方案?欢迎在评论区分享你的经验和想法!
如果这篇文章对你有帮助,别忘了转发给更多需要的同行朋友!让我们一起推动C#技术社区的发展!
相关信息
通过网盘分享的文件:AppDbAiAgent.zip 链接: https://pan.baidu.com/s/1_qZtb72ZGKF_ECKm5zI_fg?pwd=wwgs 提取码: wwgs --来自百度网盘超级会员v9的分享
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!