Files
IOT_SIM/runtime/Sim.Data/SqlClient/SqlConnection.cs
T
xuyueandClaude Opus 5.5 670abe5921
CI/CD / test (push) Canceled after 0s
CI/CD / deploy (push) Canceled after 0s
项目 6 数据存储:仿冒 SqlClient(T-SQL 转写成 SQLite)、数据库页面、DataGridView、DateTimePicker
- 每个学生一套数据库,长期保存(data/databases/<用户编号>/库名.db);学生程序运行时挂进沙箱可读写
- Sim.Data:SqlConnection / SqlCommand / SqlDataReader / SqlDataAdapter / SqlCommandBuilder / SqlParameter;
  连接字符串只按库名打开学生自己的库;ScriptDom 解析 T-SQL 后按白名单转写成 SQLite 语句,
  STRICT 表 + 元数据表还原 SQL Server 的列类型,报错编号和中文信息与 SQL Server 一致;
  INSERT 不写列名时跳过自增列,日期统一格式(书上 DateTime.ToString() 拼接的日期能正确比较)
- 常驻 SQL 服务(Sim.Runner sql-server):数据库页面的查询、对象资源管理器,评分前按实验 database.sql 建库
- 数据库页面(代替 SSMS):对象资源管理器、查询窗口、编辑前 200 行、表设计器、恢复实验初始数据
- DataGridView(绑定 DataView/DataTable/DataSet,单元格修改写回 DataTable)、DateTimePicker;设计器工具箱加入
- 评分:每个检查项一份新建的实验库;新增点 / 修改表格单元格、选日期、表格里有 / 没有某行的检查
- 示例:项目 6 任务 2、3;时间格式改成和中文 Windows 一致(9:05:03);异常堆栈不暴露服务器路径
- 测试:数据库端到端、接口(每人只看到自己的库、恢复实验数据)、评分出错情况、T-SQL 生成

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 09:18:56 +08:00

170 lines
6.5 KiB
C#

using System.Collections.Generic;
using System.Data.Common;
using Sim.Data.Engine;
namespace System.Data.SqlClient;
/// <summary>
/// 数据库连接。连接字符串里的服务器名、账号、密码都不检查(书上的长服务器名、"." 都能连上),
/// 只按 Initial Catalog(或 Database)打开这个学生自己的同名数据库;不写时连到 master。
/// </summary>
public sealed class SqlConnection : DbConnection, ICloneable
{
private static readonly HashSet<string> KnownKeywords = new(StringComparer.OrdinalIgnoreCase)
{
"data source", "server", "address", "addr", "network address", "initial catalog", "database", "user id", "uid", "user",
"password", "pwd", "integrated security", "trusted_connection", "persist security info", "persistsecurityinfo",
"connect timeout", "connection timeout", "timeout", "pooling", "max pool size", "min pool size", "multipleactiveresultsets",
"encrypt", "trustservercertificate", "application name", "app", "workstation id", "wsid", "packet size",
"asynchronous processing", "async", "user instance", "attachdbfilename", "connection lifetime", "load balance timeout", "current language", "language",
};
private string _connectionString = "";
private ConnectionState _state = ConnectionState.Closed;
private Session? _session;
public SqlConnection() { }
public SqlConnection(string connectionString) => ConnectionString = connectionString;
[Diagnostics.CodeAnalysis.AllowNull]
public override string ConnectionString
{
get => _connectionString;
set
{
if (_state != ConnectionState.Closed) throw new InvalidOperationException("不允许更改“ConnectionString”属性。连接的当前状态为打开。");
_connectionString = value ?? "";
}
}
public override string Database => _session?.Database ?? Setting("initial catalog", "database") ?? DatabaseStore.Master;
public override string DataSource => Setting("data source", "server", "address", "addr", "network address") ?? "";
public override string ServerVersion => "10.50.1600";
public override ConnectionState State => _state;
public override int ConnectionTimeout => 15;
public string WorkstationId => Environment.MachineName;
/// <summary>连接打开时的数据库会话(平台内部使用)</summary>
internal Session Session(string operation) =>
_state == ConnectionState.Open && _session != null
? _session
: throw new InvalidOperationException($"{operation} 要求已打开且可用的连接。连接的当前状态为已关闭。");
public override void Open()
{
if (_state == ConnectionState.Open) throw new InvalidOperationException("连接未关闭。连接的当前状态为打开。");
CheckKeywords();
var store = DatabaseStore.FromEnvironment()
?? throw new SqlException(-1, "在与 SQL Server 建立连接时出现与网络相关的或特定于实例的错误:这次运行没有准备数据库。");
try
{
_session = new Session(store, Database);
}
catch (SqlException ex)
{
throw ex.Rethrow();
}
SetState(ConnectionState.Open);
}
public override void Close()
{
if (_session == null) return;
_session.Dispose();
_session = null;
SetState(ConnectionState.Closed);
}
public override void ChangeDatabase(string databaseName) => Session("ChangeDatabase").ChangeDatabase(databaseName);
public new SqlCommand CreateCommand() => new() { Connection = this };
protected override DbCommand CreateDbCommand() => CreateCommand();
public new SqlTransaction BeginTransaction() => BeginTransaction(IsolationLevel.ReadCommitted);
public new SqlTransaction BeginTransaction(IsolationLevel iso)
{
Session("BeginTransaction").BeginTransaction();
return new SqlTransaction(this, iso);
}
protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) => BeginTransaction(isolationLevel);
public object Clone() => new SqlConnection(_connectionString);
public static void ClearAllPools() { }
public static void ClearPool(SqlConnection connection) { }
protected override void Dispose(bool disposing)
{
if (disposing) Close();
base.Dispose(disposing);
}
private void SetState(ConnectionState state)
{
var original = _state;
_state = state;
OnStateChange(new StateChangeEventArgs(original, state));
}
private string? Setting(params string[] keys)
{
var builder = Parse();
foreach (var key in keys)
if (builder.TryGetValue(key, out var value) && value?.ToString() is { Length: > 0 } text) return text.Trim();
return null;
}
private DbConnectionStringBuilder Parse()
{
try
{
return new DbConnectionStringBuilder { ConnectionString = _connectionString };
}
catch (ArgumentException)
{
throw new ArgumentException("初始化字符串的格式不符合从索引 0 处开始的规范。");
}
}
private void CheckKeywords()
{
foreach (string key in Parse().Keys)
if (!KnownKeywords.Contains(key)) throw new ArgumentException($"不支持关键字:“{key}”。");
if (Setting("attachdbfilename") != null) throw new SqlException(-1, "平台不支持 AttachDbFilename:请用 Initial Catalog 指定数据库名。");
}
}
public sealed class SqlTransaction : DbTransaction
{
private readonly SqlConnection _connection;
private bool _done;
internal SqlTransaction(SqlConnection connection, IsolationLevel isolationLevel)
{
_connection = connection;
IsolationLevel = isolationLevel;
}
public new SqlConnection Connection => _connection;
protected override DbConnection DbConnection => _connection;
public override IsolationLevel IsolationLevel { get; }
public override void Commit()
{
if (_done) throw new InvalidOperationException("此 SqlTransaction 已完成;它再也无法使用。");
_done = true;
_connection.Session("Commit").Commit();
}
public override void Rollback()
{
if (_done) throw new InvalidOperationException("此 SqlTransaction 已完成;它再也无法使用。");
_done = true;
_connection.Session("Rollback").Rollback();
}
}