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.
PolyglotNotebooksStudy/Docs/多语言笔记.2.1.操作数据库-SQL内核.md

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. 查询(把预览给勾上) image

  • NuGet官网搜索 Microsoft.DotNet.Interactive. image

//引入 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