操作数据库 ========= 原理:使用#!connect命令,连接子内核进行操作。可以连接MSSQL、SQLite、PostgreSQL等; ## 初始化 运行各单元格之前,必须先执行一次。 ```csharp //全局初始化 #!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](./assets/images/NuGet1.jpg) + 在[NuGet官网](https://www.nuget.org/)搜索 `Microsoft.DotNet.Interactive.` ![image](./assets/images/NuGet2.jpg) ```csharp //引入 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`,再发送给内核执行 ```csharp // 连接魔术命令: // #!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语法,进行数据库操作 ```sql-SqlServerKernelDemo --语句选择 #!connect 命令设置的SQL内核名 #!sql-SqlServerKernelDemo --原始SQL查询语句 select top 10 * from student ``` 使用SQL语法,进行数据库操作 ```sql-SqlServerKernelDemo --右下方:选择的SQL内核 --原始SQL查询语句 select top 10 * from student ``` ## 连接数据库:使用 `--create-dbcontext` 参数,自动创建 EFCore 上下文 `DbContext` 说明:目前默认情况下,Microsoft.DotNet.Interactive.SqlServer里引用的Microsoft.Identity.Client包,与环境中不一样,故需要单独引用特定版本的Microsoft.Identity.Client包,不知道后续官方是否会改正。目前单独包引用放在初始化单元格。 ### 带 `--create-dbcontext` 参数的连接 ```csharp /* 连接魔术命令 给 #!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-SqlServerKernelWithEF --原始SQL查询语句 select top 5 * from student ``` ### C#语句中,直接使用 DbContext 上下文 ```csharp //连接建立后,执行环境中就有了相关的类: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包(引入一次就好,为明确步骤再次引入) ```csharp #r "nuget:Microsoft.DotNet.Interactive.SqlServer,*-*" ``` ### 2、连接内核命令:生成新SQL内核 ```csharp //优化变通方式 { //内核名:魔法命令中的内核名,执行后会自动加 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 #!sql-SqlServerKernelShared --name sqlServerKernelQueryShared SELECT top 5 Id,Name,Age from Student; SELECT 5 as Count; ``` ### 4、多种方式使用查询共享数据 ```csharp //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>(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(); } ``` ```javascript //js中使用:查询共享数据 #!set --name fromMsSqlQueryShared --value @sql-SqlServerKernelShared:sqlServerKernelQueryShared console.log(fromMsSqlQueryShared[0].data) ``` ## 各数据库示例 ### SQL Server 数据库 ```csharp #r "nuget:Microsoft.DotNet.Interactive.SqlServer,*-*" ``` ```csharp 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-SqlServerKernelStudy --直接执行SQL语句,不共享结果 #!sql-SqlServerKernelStudy SELECT top 5 * from Student; ``` ```sql-SqlServerKernelStudy --共享操作结果(可多个操作语句) #!sql-SqlServerKernelStudy --name sqlServerQuerySharedDemo SELECT top 5 Id,Name,Age from Student; SELECT 5 as Count; ``` ```csharp #!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>(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(); } ``` ```javascript #!set --name fromMsSqlQuery --value @sql-SqlServerKernelStudy:sqlServerQuerySharedDemo console.log(fromMsSqlQuery[0].data) ``` ### PostgreSQL 数据库 ```csharp #r "nuget:Microsoft.DotNet.Interactive.PostgreSQL,*-*" ``` ```csharp //魔术命令 //#!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 #!sql-PostgreSQLStudy SELECT * FROM "Student" LIMIT 2; SELECT COUNT(*) as Count FROM "Student"; ``` ### SQLite 数据库 ```csharp #r "nuget:Microsoft.DotNet.Interactive.SQLite,*-*" ``` ```csharp /* 各种连接参数 相对位置:当前目录 #!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 #!sql-SQLiteSharedKernel SELECT * FROM Student LIMIT 2; SELECT COUNT(*) AS Count FROM Student; ``` ### DuckDB 数据库 ```csharp #r "nuget:Microsoft.DotNet.Interactive.DuckDB,*-*" ``` ```csharp //下面这种被注释的方式:不可重复执行 // #!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 #!DuckDBSharedKernel SELECT * FROM Student ORDER BY Id ```