You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
18 KiB
18 KiB
操作数据库
原理:使用#!connect命令,连接子内核进行操作。可以连接MSSQL、SQLite、PostgreSQL等;
初始化
运行各单元格之前,必须先执行一次。
//全局初始化
#!import "./Base.ipynb"
#r "nuget:Microsoft.Identity.Client,4.66.2"
//共享
using Microsoft.DotNet.Interactive;
using Microsoft.DotNet.Interactive.Commands;
连接数据库(SQL Server 2019为例)
第一步:C#内核单元格中,引入相关的 nuget 包
每种数据库都有自己的包,形如 Microsoft.DotNet.Interactive.DbName, SQL Server的包为 Microsoft.DotNet.Interactive.SqlServer
-
在
VS Code 终端的 NuGet 包管理
里输入Microsoft.DotNet.Interactive.
查询(把预览给勾上) -
在NuGet官网搜索
Microsoft.DotNet.Interactive.
//引入 SqlServer 的 NuGet 包
#r "nuget:Microsoft.DotNet.Interactive.SqlServer,*-*"
第二步:使用 #!connect 命令的语法,连接数据库子内核
使用魔术命令 #!connect mssql --kernel-name SqlServerKernelDemo "Server=.\SQL2019;Database=study;User Id=sa;Password=gly-bicijinlian;TrustServerCertificate=true;"
实测有缺点:
- 不能重复执行:重复执行会报错
- 连接字符串必须是真实字符串:不能是变量,不灵活;明文"不安全",比如演示环境
变通用法:在C#程序中,拼接好魔术命令#!connect
,再发送给内核执行
// 连接魔术命令:
// #!connect mssql --kernel-name SqlServerKernelDemo "Server=.\SQL2019;Database=study;User Id=sa;Password=密码;TrustServerCertificate=true;"
//优化用法
{
//内核名:魔法命令中的内核名,执行后会自动加 sql- 前缀,做为内核名被使用
string magicCommandKernelName = "SqlServerKernelDemo";
string completeKernelName = "sql-" + magicCommandKernelName;
//引入内核:可重复执行
if(Microsoft.DotNet.Interactive.Kernel.Root.FindKernelByName(completeKernelName) == null)
{
var connectKernelCode = $"#!connect mssql --kernel-name {magicCommandKernelName} \"{SharedDbConnect.MsSqlConnectionString}\"";
await Kernel.Root.SendAsync(new SubmitCode( connectKernelCode, "csharp"));
}
else
{
Console.WriteLine($"名为 {completeKernelName} 的内核已存在。需要新内核时,请为--kernel-name参数使用不同的值, 本次执行不做任何更改!");
}
}
查询数据库(SQL Server 2019为例)
使用SQL语法,进行数据库操作
--语句选择 #!connect 命令设置的SQL内核名
#!sql-SqlServerKernelDemo
--原始SQL查询语句
select top 10 * from student
使用SQL语法,进行数据库操作
--右下方:选择的SQL内核
--原始SQL查询语句
select top 10 * from student
连接数据库:使用 --create-dbcontext
参数,自动创建 EFCore 上下文 DbContext
说明:目前默认情况下,Microsoft.DotNet.Interactive.SqlServer里引用的Microsoft.Identity.Client包,与环境中不一样,故需要单独引用特定版本的Microsoft.Identity.Client包,不知道后续官方是否会改正。目前单独包引用放在初始化单元格。
带 --create-dbcontext
参数的连接
/* 连接魔术命令
给 #!connect mssql 加 --create-dbcontext 参数:连接操作,会执行下面的任务:
1、搭建EFCore基架,并初始化 DBContext 的实例: xxxx
2、安装包相关Nuget包,详情见输出
3、添加新的子内核 #!sql-xxx
**/
//注意:由于引用的Microsoft.Identity.Client包,版本冲突,目前把 Microsoft.Identity.Client的引用,放在 Microsoft.DotNet.Interactive.SqlServer之前
// #r "nuget:Microsoft.Identity.Client,4.66.2" 放入初始化
//优化用法
{
//内核名:魔法命令中的内核名,执行后会自动加 sql- 前缀,做为内核名被使用
string magicCommandKernelName = "SqlServerKernelWithEF";
string completeKernelName = "sql-" + magicCommandKernelName;
//引入内核:可重复执行
if(Microsoft.DotNet.Interactive.Kernel.Root.FindKernelByName(completeKernelName) == null)
{
var connectKernelCode = $"#!connect mssql --kernel-name {magicCommandKernelName} \"{SharedDbConnect.MsSqlConnectionString}\" --create-dbcontext";
await Kernel.Root.SendAsync(new SubmitCode( connectKernelCode, "csharp"));
}
else
{
Console.WriteLine($"名为 {completeKernelName} 的内核已存在。需要新内核时,请为--kernel-name参数使用不同的值, 本次执行不做任何更改!");
}
}
SQL内核中使用(与一般连接一样使用)
--原始SQL查询语句
select top 5 * from student
C#语句中,直接使用 DbContext 上下文
//连接建立后,执行环境中就有了相关的类:DBContext等
/* 因为类型版本问题,不能实际执行;后续看
{
//直接查询
var t = SqlServerKernelWithEF.Students.Take(5).ToList();
display(t);
//EF内执行SQL语句
FormattableString fs = $"select top 3 * from Student;";
var c = SqlServerKernelWithEF.Database.ExecuteSql(fs);
display(c);
}
*/
共享数据库操作结果数据(仅支持SQL Server数据库,以SQL Server 2019为例)
1、引入NuGet包(引入一次就好,为明确步骤再次引入)
#r "nuget:Microsoft.DotNet.Interactive.SqlServer,*-*"
2、连接内核命令:生成新SQL内核
//优化变通方式
{
//内核名:魔法命令中的内核名,执行后会自动加 sql- 前缀,做为内核名被使用
string magicCommandKernelName = "SqlServerKernelShared";
string completeKernelName = "sql-" + magicCommandKernelName;
//引入内核:可重复执行
if(Microsoft.DotNet.Interactive.Kernel.Root.FindKernelByName(completeKernelName) == null)
{
var connectKernelCode = $"#!connect mssql --kernel-name {magicCommandKernelName} \"{SharedDbConnect.MsSqlConnectionString}\"";
await Kernel.Root.SendAsync(new SubmitCode( connectKernelCode, "csharp"));
}
else
{
Console.WriteLine($"名为 {completeKernelName} 的内核已存在。需要新内核时,请为--kernel-name参数使用不同的值, 本次执行不做任何更改!");
}
}
3、共享方式使用第2步生成的新SQL内核,操作数据库(使用了 --name 命令参数)
#!sql-SqlServerKernelShared --name sqlServerKernelQueryShared
SELECT top 5 Id,Name,Age from Student;
SELECT 5 as Count;
4、多种方式使用查询共享数据
//C#语言中使用:查询共享数据
#!set --name fromMsSqlQueryShared --value @sql-SqlServerKernelShared:sqlServerKernelQueryShared
using Microsoft.DotNet.Interactive.Formatting.TabularData;
using Microsoft.DotNet.Interactive.Formatting;
/**
说明:这里获取到的共享SQL结果,实质是SQL操作结果的Json序列化字符串;
在C#单元格里,默认被反序列化为了 System.Text.Json.JsonElement,其根元素RootElement,是结果集合(几条SQL语句,结果集就有几项)
.dot interactive程序集中,序列化时,实际是使用 System.Text.Json 序列化的 TabularDataResource 类(在Microsoft.DotNet.Interactive.Formatting.TabularData命名空间)的集合
所以C#中使用共享数据,有两种方式:直接操作System.Text.Json.JsonElement对象和反序列化为TabularDataResource数组
*/
//使用共享数据:直接操作JsonElement对象
{
//有几个操作结果,fromMsSqlQuery.RootElement中就有几个结果集
var dataSet = fromMsSqlQueryShared.RootElement;
//第一个查询结果集
var dataTable1 = dataSet[0];
//列结构
var fieldNames = dataTable1.GetProperty("schema").GetProperty("fields").EnumerateArray().Select(s=>s.GetProperty("name"));
Console.WriteLine(string.Join(" ",fieldNames));
//数据
var data = dataTable1.GetProperty("data").EnumerateArray().AsEnumerable();
foreach(var row in data)
{
Console.WriteLine($"{row.GetProperty("Id")} {row.GetProperty("Name")} {row.GetProperty("Age")}");
}
//第2个结果集
var count = fromMsSqlQueryShared.RootElement[1].GetProperty("data")[0].GetProperty("Count");
//优化:使用Html展示
}
//使用共享数据:反序列化为TabularDataResource数组
{
var dataSetJsonText = fromMsSqlQueryShared.RootElement.GetRawText();
var dataTables = System.Text.Json.JsonSerializer.Deserialize<List<TabularDataResource>>(dataSetJsonText);
//结果1
dataTables[0].Display();
//结果2
dataTables[1].Display();
}
//使用共享数据:直接转换为TabularDataResource数组
{
var resource = fromMsSqlQueryShared.RootElement.ToTabularDataResource().Data.ToList();
resource[0].First(s => s.Key=="data").Value.Display();
}
//js中使用:查询共享数据
#!set --name fromMsSqlQueryShared --value @sql-SqlServerKernelShared:sqlServerKernelQueryShared
console.log(fromMsSqlQueryShared[0].data)
各数据库示例
SQL Server 数据库
#r "nuget:Microsoft.DotNet.Interactive.SqlServer,*-*"
using Microsoft.DotNet.Interactive;
using Microsoft.DotNet.Interactive.Commands;
//优化方式
{
//内核名:魔法命令中的内核名,执行后会自动加 sql- 前缀,做为内核名被使用
string magicCommandKernelName = "SqlServerKernelStudy";
string completeKernelName = "sql-" + magicCommandKernelName;
//引入内核:可重复执行
if(Microsoft.DotNet.Interactive.Kernel.Root.FindKernelByName(completeKernelName) == null)
{
var connectKernelCode = $"#!connect mssql --kernel-name {magicCommandKernelName} \"{SharedDbConnect.MsSqlConnectionString}\"";
await Kernel.Root.SendAsync(new SubmitCode( connectKernelCode, "csharp"));
}
else
{
Console.WriteLine($"名为 {completeKernelName} 的内核已存在。需要新内核时,请为--kernel-name参数使用不同的值, 本次执行不做任何更改!");
}
}
--直接执行SQL语句,不共享结果
#!sql-SqlServerKernelStudy
SELECT top 5 * from Student;
--共享操作结果(可多个操作语句)
#!sql-SqlServerKernelStudy --name sqlServerQuerySharedDemo
SELECT top 5 Id,Name,Age from Student;
SELECT 5 as Count;
#!set --name fromMsSqlQuery --value @sql-SqlServerKernelStudy:sqlServerQuerySharedDemo
using Microsoft.DotNet.Interactive.Formatting.TabularData;
using Microsoft.DotNet.Interactive.Formatting;
/**
说明:这里获取到的共享SQL结果,实质是SQL操作结果的Json序列化字符串;
在C#单元格里,默认被反序列化为了 System.Text.Json.JsonElement,其根元素RootElement,是结果集合(几条SQL语句,结果集就有几项)
.dot interactive程序集中,序列化时,实际是使用 System.Text.Json 序列化的 TabularDataResource 类(在Microsoft.DotNet.Interactive.Formatting.TabularData命名空间)的集合
所以C#中使用共享数据,有两种方式:直接操作System.Text.Json.JsonElement对象和反序列化为TabularDataResource数组
*/
//使用共享数据:直接操作JsonElement对象
{
//有几个操作结果,fromMsSqlQuery.RootElement中就有几个结果集
var dataSet = fromMsSqlQuery.RootElement;
//第一个查询结果集
var dataTable1 = dataSet[0];
//列结构
var fieldNames = dataTable1.GetProperty("schema").GetProperty("fields").EnumerateArray().Select(s=>s.GetProperty("name"));
Console.WriteLine(string.Join(" ",fieldNames));
//数据
var data = dataTable1.GetProperty("data").EnumerateArray().AsEnumerable();
foreach(var row in data)
{
Console.WriteLine($"{row.GetProperty("Id")} {row.GetProperty("Name")} {row.GetProperty("Age")}");
}
//第2个结果集
var count = fromMsSqlQuery.RootElement[1].GetProperty("data")[0].GetProperty("Count");
//优化:使用Html展示
}
//使用共享数据:反序列化为TabularDataResource数组
{
var dataSetJsonText = fromMsSqlQuery.RootElement.GetRawText();
var dataTables = System.Text.Json.JsonSerializer.Deserialize<List<TabularDataResource>>(dataSetJsonText);
//结果1
dataTables[0].Display();
//结果2
dataTables[1].Display();
}
//使用共享数据:直接转换为TabularDataResource数组
{
var resource = fromMsSqlQuery.RootElement.ToTabularDataResource().Data.ToList();
resource[0].First(s => s.Key=="data").Value.Display();
}
#!set --name fromMsSqlQuery --value @sql-SqlServerKernelStudy:sqlServerQuerySharedDemo
console.log(fromMsSqlQuery[0].data)
PostgreSQL 数据库
#r "nuget:Microsoft.DotNet.Interactive.PostgreSQL,*-*"
//魔术命令
//#!connect postgres --kernel-name PostgreSQLStudy "Host=localhost;Port=5432;Username=postgres;Password=替换成真实密码;Database=Study;"
using Microsoft.DotNet.Interactive;
using Microsoft.DotNet.Interactive.Commands;
//优化方法
{
//内核名:魔法命令中的内核名,执行后会自动加 sql- 前缀,做为内核名被使用
string magicCommandKernelName = "PostgreSQLStudy";
string completeKernelName = "sql-" + magicCommandKernelName;
//引入内核:可重复执行
if(Microsoft.DotNet.Interactive.Kernel.Root.FindKernelByName(completeKernelName) == null)
{
var connectKernelCode = $"#!connect postgres --kernel-name {magicCommandKernelName} \"{SharedDbConnect.PSQLConnectionString}\"";
await Kernel.Root.SendAsync(new SubmitCode( connectKernelCode, "csharp"));
}
else
{
Console.WriteLine($"名为 {completeKernelName} 的内核已存在。需要新内核时,请为--kernel-name参数使用不同的值, 本次执行不做任何更改!");
}
}
#!sql-PostgreSQLStudy
SELECT * FROM "Student" LIMIT 2;
SELECT COUNT(*) as Count FROM "Student";
SQLite 数据库
#r "nuget:Microsoft.DotNet.Interactive.SQLite,*-*"
/* 各种连接参数
相对位置:当前目录
#!connect sqlite --kernel-name SQLiteSharedKernel --connection-string "Data Source=.\assets\database\study.db;"
绝对目录位置
#!connect sqlite --kernel-name MySQLiteDemo "Data Source=C:\Database\study.db;"
缓存共享
#!connect sqlite --kernel-name MySQLiteDemo "Data Source=.\assets\database\study.db;Cache=Shared;"
使用带密码
#!connect sqlite --kernel-name MySQLiteDemo "Data Source=.\assets\database\study.db;Cache=Shared;Password=MyEncryptionKey;"
只读模式
#!connect sqlite --kernel-name MySQLiteDemo "Data Source=.\assets\database\study.db;Mode=ReadOnly"
读写创建模式
#!connect sqlite --kernel-name MySQLiteDemo "Data Source=.\assets\database\study.db;Mode=ReadWriteCreate"
读写模式
#!connect sqlite --kernel-name MySQLiteDemo "Data Source=.\assets\database\study.db;Mode=ReadWrite"
私有内存模式
#!connect sqlite --kernel-name MySQLiteDemo "Data Source=:memory:"
共享内存模式
#!connect sqlite --kernel-name MySQLiteDemo "Data Source=Sharable;Mode=Memory;Cache=Shared"
*/
using Microsoft.DotNet.Interactive;
using Microsoft.DotNet.Interactive.Commands;
//优化方法
{
//内核名:魔法命令中的内核名,执行后会自动加 sql- 前缀,做为内核名被使用
string magicCommandKernelName = "SQLiteSharedKernel";
string completeKernelName = "sql-" + magicCommandKernelName;
//引入内核:可重复执行
if(Microsoft.DotNet.Interactive.Kernel.Root.FindKernelByName(completeKernelName) == null)
{
var connectKernelCode = $"#!connect sqlite --kernel-name {magicCommandKernelName} --connection-string \"{SharedDbConnect.SQLiteConnectionString}\"";
await Kernel.Root.SendAsync(new SubmitCode( connectKernelCode, "csharp"));
}
else
{
Console.WriteLine($"名为 {completeKernelName} 的内核已存在。需要新内核时,请为--kernel-name参数使用不同的值, 本次执行不做任何更改!");
}
}
#!sql-SQLiteSharedKernel
SELECT * FROM Student LIMIT 2;
SELECT COUNT(*) AS Count FROM Student;
DuckDB 数据库
#r "nuget:Microsoft.DotNet.Interactive.DuckDB,*-*"
//下面这种被注释的方式:不可重复执行
// #!connect duckdb --kernel-name DuckDBSharedKernel --connection-string "Data Source=:memory:?cache=shared"
// #!connect duckdb --kernel-name DuckDBSharedKernel --connection-string SharedDbConnect.DuckDBConnectionString
using Microsoft.DotNet.Interactive;
using Microsoft.DotNet.Interactive.Commands;
//优化方法
{
//内核名:魔法命令中的内核名,执行后会不加 sql- 前缀
string magicCommandKernelName = "DuckDBSharedKernel";
//引入内核:可重复执行
if(Kernel.Root.FindKernelByName(magicCommandKernelName) == null)
{
var connectKernelCode = $"#!connect duckdb --kernel-name {magicCommandKernelName} --connection-string \"{SharedDbConnect.DuckDBConnectionString}\"";
await Kernel.Root.SendAsync(new SubmitCode( connectKernelCode, "csharp"));
//初始化数据表及填充数据
var connectKernelCode2 =
"""
#!DuckDBSharedKernel
CREATE TABLE Student (
Id INTEGER,
Name TEXT,
Age INTEGER
);
INSERT INTO Student VALUES (1, '张三', 10), (2, '李四', 33), (3, '王五', 66);
""";
await Kernel.Root.SendAsync(new SubmitCode( connectKernelCode2, "csharp"));
}
else
{
Console.WriteLine($"名为 {magicCommandKernelName} 的内核已存在。需要新内核时,请为--kernel-name参数使用不同的值, 本次执行不做任何更改!");
}
}
#!DuckDBSharedKernel
SELECT * FROM Student ORDER BY Id