C-7-入门实用指南-三-

33 阅读49分钟

C#7 入门实用指南(三)

原文:zh.annas-archive.org/md5/0D2F44FACA4630D8785DF55498F3E611

译者:飞龙

协议:CC BY-NC-SA 4.0

第十九章:使用可空特性使应用程序更稳定

在本章中,您将学习使用nullable关键字来确保具有缺失值的记录仍然可以被引入应用程序中。

在 HTML 中添加一个显示人员按钮

打开 Visual Studio,创建一个项目。我们首先要做的是将一个简单的按钮放入 HTML 页面中。为此,请转到工具箱,获取一个Button控件,并将其放在以<form id=...开头的行下面。将按钮上的文本更改为“显示人员”。

您将创建一个名为Person的类,并且将从数据库中创建该类。要做到这一点,转到“视图”菜单并打开 SQL Server 对象资源管理器。请记住,我们创建了一个名为People的数据库,它由这些字段组成:IdNAMEDATEADDED

向 people 数据库添加一个字段

现在,让我们再添加一个字段。右键单击 dbo.People 表图标,然后选择“查看代码”。要添加一个额外的字段,在DATEADDED之后输入以下内容:

SALARY decimal(18,2)

这是一个新的字段类型,decimal (18,2)表示一个宽度为 18 个单位且有 2 位小数的字段;也就是说,它总共有 18 个单位宽,右边有 2 个单位,左边有 16 个单位,总共有 18 个单位。接下来,点击“更新”,然后点击出现的对话框中的“更新数据库”按钮。现在,您可以在 SQL Server 对象资源管理器窗格中看到,该字段已添加,如图 19.4.1所示:

图 19.4.1:工资字段已添加到 dbo.People

修改 dbo.People 表

现在,有了这个,您可以修改表。右键单击 dbo.People 表图标,然后转到“查看数据”。为了说明这个概念,在一些行中输入一些工资金额,将其他行留空。因此,数据库的组合将获得 NULL 信息。dbo.People 现在看起来像图 19.4.2

图 19.4.2:工资已输入到表中

如果点击刷新按钮()重新加载它,它会确认已保存。

如果双击工资列标题,它会展开列以适应。

在这里,如果您为薪水输入诸如77777777777777777777之类的内容,将显示一个错误消息,指示单元格(行,列)的值无效。因此,请记住,如果您尝试输入诸如788777.988888之类的内容,它将自动四舍五入为两位小数,即788777.99。这基本上就是decimal (18,2)的工作原理:它对可以输入的数据施加限制。

为该项目编写代码

在下一个阶段,转到设计视图,并双击“显示人员”按钮。这将带我们进入Default.aspx.cs。删除Page_Load块。该项目的起始代码的相关部分应如图 19.4.3所示:

图 19.4.3:该项目的起始代码部分

现在,我们将编写代码。让我们逐步进行代码的创建。首先,在using System下面的文件顶部输入以下内容:

using System.Collections.Generic;

我们将使用这行来制作一个人员列表。然后,在它的下面也输入以下内容:

using System.Data.SqlClient;

创建 person 类

现在是下一个阶段;我们将创建一个名为Person的类;因此,请在以public partial class _Default...开头的行的上方输入以下内容:

public class Person

创建属性

接下来,我们将创建两个属性。因此,请在一对大括号之间输入以下行:

public string Name { get; set; }
public decimal? Salary { get; set; }

因为由 public decimal 引用的信息可能丢失,所以您要输入一个?符号。这是一个nullable数量,我们将其称为Salary。这就是这个类。

现在,要使用这个,你必须采取以下典型的步骤。首先,你希望在有人点击按钮时清除标签的输出,所以在以protected void Button1_Click...开头的行下面的花括号之间输入以下内容:

sampLabel.Text = "";

制作人员名单

在下一个阶段,我们将制作一个人员名单,所以在这一行下面输入以下内容:

List<Person> peopleList = new List<Person>();

在这里,我们称之为peopleList,并将其设置为新的人员列表。

构建连接字符串

在下一个阶段,你需要获取连接字符串,所以,在下一行开始,输入string connString =,然后跟上@符号使其成为逐字字符串,然后放上""符号。现在,要获取连接字符串,做以下操作:

  1. 点击菜单栏中的“查看”,选择“SQL Server 对象资源管理器”。

  2. 右键单击 People 数据库,选择属性。

  3. 在属性窗格中,双击连接字符串以选择它及其长描述。

  4. 然后右键单击长描述并复制它。

  5. 在一对""符号之间粘贴描述。

连接字符串行应该如下所示:

string connString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=People;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";

现在可以关闭 SQL Server 对象资源管理器和属性窗格了。

输入与 SQL 相关的代码

现在,让我们转到与 SQL 相关的代码。首先,在连接字符串下面输入以下内容:

using (SqlConnection conn = new SqlConnection(connString))

我们将调用 SQL 连接,conn,并使用连接字符串初始化新的 SQL 连接。

现在,让我们创建一个命令;在这一行下面的花括号之间输入以下内容:

SqlCommand comm = new SqlCommand("select * from dbo.People", conn);

接下来,通过在下面输入以下内容打开一个连接:

conn.Open();

接下来,在这条线下面输入以下内容:

using (SqlDataReader reader = comm.ExecuteReader())

从表中添加人员到列表

在过程的下一个阶段,从下面的花括号之间输入以下内容:

while(reader.Read())

当这个条件返回True时,我们将使用数据库中表的信息来创建对象。为了做到这一点,在这一行下面的花括号之间输入以下内容:

peopleList.Add(new Person() { Name = (string)reader[1], Salary = reader[3] as decimal? });

这里,这行的第一部分获取索引为 1 的列,将其转换为字符串,然后将其分配给每个对象的名称属性。然后,我们说Salary = reader[3],因为这是可能缺少值的部分,我们说decimal?—即可空的十进制。

显示记录

在这一点上我们已经接近了;最后阶段,当然是显示记录以查看可空的效果。在peopleList.Add...行下面的所有花括号之外(如下所示),输入以下foreach语句:

peopleList.Add(new Person()... 
    }}}foreach (Person p in peopleList)

接下来,在这一行下面的花括号之间输入以下内容:

sampLabel.Text += $"<br>{p.Name}, {p.Salary:C}";

这是我们应用程序的核心。

在运行此应用程序之前,再次注意...Salary = reader[3] as decimal? })Salary属性的一个有趣的地方。as decimal后面的问号表示它是一个可空的十进制数。十进制值可能丢失,这是一个不同的情况。如果你只是写as decimal,工具提示会说这是一个错误。

运行程序

现在,打开你的浏览器。点击“显示人员”按钮。让我们检查结果,如图 19.4.4所示:

图 19.4.4:运行程序的结果

注意,当没有工资时,它只显示姓名,不会提供其他任何信息,也不会崩溃。所以,这很好。

这是那个小符号的实际应用,问号,在我们的数据类型和可空之后。

章节回顾

为了回顾,包括注释的本章Default.aspx.cs文件的完整版本如下所示:

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;
using System.Collections.Generic; //needed for lists
using System.Data.SqlClient;//needed for commands and connections
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
public class Person
{
    public string Name { get; set; }
    public decimal? Salary { get; set;}
}
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        //clear label text every button click
        sampLabel.Text = "";
        //make list of people
        List<Person> peopleList = new List<Person>();
        //get connection string form SQL Server
        string connString = @"Data Source=DESKTOP-4L6NSGO\SQLEXPRESS;Initial
        Catalog=People;Integrated Security=True;Connect Timeout=15;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
        //make connection, be sure it's in a using so it's properly
        //disposed of
        using (SqlConnection conn = new SqlConnection(connString))
        {
            //make sql command
            SqlCommand comm = new SqlCommand("select * from dbo.People", conn);
            //open connection
            conn.Open();
            //make reader, be sure it's inside a using so it's properly 
            //disposed of
            using (SqlDataReader reader = comm.ExecuteReader())
            {
                while (reader.Read())
                {
                    //add new people to list, noting that reader[3] 
                    //could be null, so do it as "as decimal?" 
                    //nullable decimal
                    peopleList.Add(new Person() { Name = (string)reader[1], 
                    Salary = reader[3] as decimal? });
                }
            }
        }
        //display list of people, formatting salary as currency
        foreach(Person p in peopleList)
        {
            sampLabel.Text += $"<br>{p.Name}, {p.Salary:C}";
        }
    }
}

总结

在本章中,您学习了使用nullable关键字来确保具有缺失值的记录仍然可以被引入应用程序。您向People数据库添加了一个字段,修改了dbo.people表,创建了一个Person类,制作了一个人员列表,构建了连接字符串,输入了与 SQL 相关的代码,并从dbo.people表中添加了人员到列表中。

在下一章中,您将学习如何将图表拖入页面,然后通过 C#作为连接页面和数据库的语言,使它们与 SQL Server 内的一些简单表格配合使用。

第二十章:将图表控件连接到 SQL Server

在本章中,你将学习如何将图表拖放到页面中,然后通过 C#作为连接页面和数据库的语言,使其与 SQL Server 中的一些简单表一起工作。

将图表放入 HTML 页面

启动一个项目,我们首先要做的是将一个图表放在页面中。转到工具箱(Ctrl + Alt + X),在搜索栏中输入char...,然后将其拖放到以<form id=...开头的行下面。

如你在屏幕上看到的,这生成了所有以下标记。你可以保持不变。对我们的目的来说已经足够了:

<asp:Chart ID="Chart1"runat="server">
  <Series>
    <asp:SeriesName="Series1" ChartType="Point"></asp:Series>
  </Series>
  <ChartAreas>
    <asp:ChartArea Name="ChartArea1"></asp:ChartArea>
  </ChartAreas>
</asp:Chart>

你可以删除两个<div...行和<asp:Label ID...行。我们不需要它们。

在 HTML 页面中添加一个按钮

接下来,在</asp:Chart>行下面放置一个按钮。再次,转到工具箱,抓取一个Button控件,拖放到那里。将按钮上的文本更改为“加载数据”。这里,“加载数据”意味着加载并在图表中显示它。

注意,当你拖入一个图表时,页面会在System.Web.UI.DataVisualization.Charting的顶部添加整个块,如下所示:

<%@Register Assembly="System.Web.DataVisualization, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Namespace="System.Web.UI.DataVisualization.Charting" TagPrefix="asp" %>

People数据库中添加一个新表

接下来,在下一个阶段,点击菜单栏中的“查看”,选择“SQL Server 对象资源管理器”。你需要添加一个新表,所以在People数据库中,右键点击“表”文件夹,选择“添加新表...”。你的屏幕应该看起来像图 20.5.1中所示的样子:

图 20.5.1:一个空白的新表

接下来,在Id字段中输入XValues,然后点击数据类型字段。开始输入decimal,注意到decimal(18,0)会自动显示出来。现在将其改为(18,3)。这意味着一个宽度为 18 且有 3 位小数的字段;也就是说,总共有 18 位,右边 3 位,左边 15 位。对于这个字段,应该勾选“允许空值”。对于YValues也是一样。假设我们做了一个实验,测量了一些数量。所以,在Id字段中输入YValues,在数据类型字段中输入decimal(18,3),并且对于这个字段勾选“允许空值”。

接下来,右键点击“Id”,选择“设置为主键”。

启用自动递增

接下来,你想要启用自动递增,具体来说就是以下内容:

  1. 首先,将表重命名为ExperimentValues,如下所示:
    CREATE TABLE [dbo].[ExperimentValues]
  1. 在“主键”后面,输入identity(1,1),如下所示:
    [Id] INT NOT NULL PRIMARY KEY identity(1,1),

在这里,identity(1,1),正如你之前学到的,意味着这个字段将从 1 开始每次添加新记录时增加 1。所以,这是我们表的结构,如图 20.5.2所示:

图 20.5.2:本章表的结构

向新表中添加值

接下来,点击“更新”按钮。在弹出的对话框中点击“更新数据库”。

现在,你有了ExperimentValues。右键点击它,选择“查看数据”,然后让我们添加一些数值,如图 20.5.3所示:

图 20.5.3:添加到 ExperimentValues 表中的值

现在,我们在表中有了一些数值。再次注意到,“Id”字段是自动递增的——它从 1 开始,每添加一条新记录就增加 1。关闭表窗口,回到Default.aspx.cs

现在,双击“设计”按钮,会出现一个小图表,如图 20.5.4所示:

图 20.5.4:实验值表中数据的理论预览

编码项目

这个图表还不代表真实的数据。这只是一个理论预览。所以,双击“加载数据”按钮,这会打开Default.aspx.cs中的事件处理程序。删除Page_Load存根。我们将从本项目的代码开始,如图 20.5.5所示:

图 20.5.5:此项目的起始代码

添加命名空间

首先,您必须添加一个命名空间。因此,转到文件顶部,在using System下输入以下内容:

using System.Data.SqlClient;

此行用于连接和命令。

构建连接字符串

在下一阶段,您需要连接字符串。因此,在下一行开始时,输入string connString =,然后输入@符号使其成为逐字字符串,然后放置""符号。现在,要获取连接字符串,请执行以下操作:

  1. 单击菜单栏中的“查看”,然后选择“SQL Server 对象资源管理器”。

  2. 右键单击 People 数据库,然后选择属性。

  3. 在属性窗格中,双击连接字符串以选择其长描述。

  4. 然后,右键单击长描述并将其复制。

  5. ""符号集之间粘贴描述。

然后,连接字符串行应如下所示:

string connString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=People;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";

这是特定于您计算机的连接字符串。您现在可以关闭 SQL Server 对象资源管理器和属性窗格。

现在,在此行下方输入以下内容:

using (SqlConnection conn = new SqlConnection(connString)) 

编写 SQL 查询

接下来,您将创建commandText变量。因此,在一对大括号之间,输入以下内容:

string commandText = "select XValues, YValues from dbo.ExperimentValues";

要定义文本,您必须编写实际的 SQL 查询,因此输入select XValues, YValues from dbo.ExperimentValues。这将从ExperimentValues表中的这两个列名中选择XValuesYValues

创建命令对象

现在,您需要创建命令对象,因此接下来输入以下内容:

SqlCommand command = new SqlCommand(commandText, conn);

在这里,您传入两个相关数量,两个参数,具体来说是(commandText, conn)

打开连接并创建 SQL 数据读取器

在下一阶段,您将打开一个连接,因此在上一行下方输入以下内容:

conn.Open();

然后,您将创建一个 SQL 数据读取器,因此接下来输入以下内容:

SqlDataReader reader = command.ExecuteReader(); 

此行将获取我们需要的数据。

现在您已经完成了所有这些,接下来在上一行下方输入以下内容:

Chart1.DataBindTable(reader, "XValues");

请注意,我们包括列名XValues,它将用作x轴的标签。因此,x轴是水平轴。

运行程序

这是应用程序的核心。在浏览器中启动它,并单击“加载数据”按钮。

图 20.5.6:来自 ExperimentValues 表的实际数据的显示

这是数据,如图 20.5.6所示。它具有沿水平和垂直轴的值。

修改程序以显示 Y 值

如果您愿意,只是为了向您展示它有多容易,您可以将以下行更改为 Y 值。换句话说,您可以将它们颠倒过来:

Chart1.DataBindTable(reader, "YValues");

现在,在浏览器中启动它,并再次单击“加载数据”按钮。结果显示在图 20.5.7中:

图 20.5.7:来自 ExperimentValues 表的值的图表

现在您看到它看起来非常不同。这就是您如何制作简单的图表。现在保存这个。这就是整个应用程序。

章节回顾

让我们回顾一下我们做了什么:您构建了连接字符串并完成了在using (SqlConnection conn...行内建立连接,以便可以正确处理连接。然后,您编写了查询字符串,创建了命令对象,打开了连接并执行了读取器。最后,您使用DataBind将数据库表绑定到图表,以便可以显示结果。

本章的Default.aspx.cs文件的完整版本,包括注释,显示在以下代码块中:

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;
using System.Data.SqlClient;
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        //make connection string
        string connString = @"Data Source=DESKTOP-4L6NSGO\SQLEXPRESS;Initial Catalog=People;Integrated Security=True;Connect Timeout=15;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
        //enclose connection making inside a using so connection is
        //properly disposed of
        using (SqlConnection conn = new SqlConnection(connString))
        {
            //make command text
            string commandText = "select XValues, YValues from dbo.ExperimentValues";
            //make command object
            SqlCommand command = new SqlCommand(commandText, conn);
            //open connection
            conn.Open();
            //execute reader to read values from table
            SqlDataReader reader = command.ExecuteReader();
            //bind chart to table do display the results
            Chart1.DataBindTable(reader, "XValues");
        }
    }
}

摘要

在本章中,您学习了如何将图表拖入页面,然后通过 C#作为连接页面和数据库的语言,使它们与 SQL Server 中的一些简单表一起工作。您将图表放入 HTML 页面,向People数据库添加了一个新表,启用了自动递增,向新表添加了值,添加了命名空间,构建了连接字符串,编写了 SQL 查询,打开了连接并创建了一个 SQL 数据读取器,运行了程序,最后修改它以显示 Y 值。

在下一章中,您将学习如何将 LINQ 与 SQL 和 SQL Server 一起使用。

第二十一章:使用 LINQ 操作来自 SQL Server 的表

在本章中,您将学习如何将 LINQ 与 SQL 和 SQL Server 一起使用。

更改 ExperimentValues 表中的数据

我们将使用在上一章中创建的数据库表ExperimentValues,如图 21.6.1所示:

图 21.6.1:第二十章的 ExperimentValues 表

请记住,表中有一个Id字段(PK,主键整数,非空),然后是XValues(十进制,(18, 3),表示宽 18 个单位,小数点后 3 位,然后左边 15 个单位,总共 18 个单位。如果你愿意,你可以将其设置为null。同样,YValues(十进制,(18, 3);所以,小数点后 3 位,然后左边 15 个单位,总共 18 个单位。

现在确保你在里面有数据。所以,右键单击dbo.ExperimentValues并选择查看数据。你应该看到我们在上一章中输入的数据。当然,你可以随时更改它。为了使事情变得更容易,让我们将值更改为图 21.6.2中显示的值:

图 21.6.2:ExperimentValues 表的新数据

如果你愿意,你可以重新加载它以查看它是否已保存。这就是我们简单的数据库表。

总结字段

现在我们将进入并总结字段。您将使用 LINQ 找到X值的总和和Y值的总和。首先,进入,并在以<form id= ....开头的行下方放置一个按钮。转到工具箱(Ctrl + Alt + X),获取一个Button控件,并将其拖放到那里。更改按钮上的文本以显示 Sum Fields。当然,还可以执行其他几个操作。这只是一个操作:求和。

关闭工具箱并切换到设计视图。双击 Sum Fields 按钮。这将带我们进入Default.aspx.cs。删除Page_Load块。该项目的起始代码的相关部分应如图 21.6.3所示:

图 21.6.3:该项目的起始代码部分

添加命名空间

首先,在文件顶部的using System下,输入以下所有必要的行:

using System.Data.SqlClient;
using System.Linq;
using System.Data;

构建连接字符串

下一阶段将是建立连接字符串,所以在以下行下的一对大括号中,首先输入string connString =,然后跟上@符号使其成为逐字字符串,然后放入""符号。现在要获取连接字符串,执行以下操作:

  1. 单击菜单栏中的“查看”,然后选择“SQL Server 对象资源管理器”。

  2. 右键单击People数据库,然后选择属性。

  3. 在属性窗格中,双击连接字符串以选择带有其长描述的内容。

  4. 然后右键单击长描述并复制它。

  5. 将描述粘贴在一对""符号之间。

然后连接字符串行应如下所示:

string connString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=People;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";

现在可以关闭 SQL Server 对象资源管理器和属性窗格。

建立 SQL 连接

在下一阶段,我们将像往常一样进行。所以,输入以下行:

using (SqlConnection conn = new SqlConnection(connString))

注意,当你输入这个时,你会看到文件顶部的using System.Data.SqlClient;行中的SqlClient变为活动状态。它变成了黑色。这意味着 SQL 连接存储在那里,如果你将鼠标悬停在上面,它还会告诉你这一点:class System.Data.SqlClient.SqlConnection

在下一阶段,在此行下的一对大括号之间输入以下内容:

SqlCommand command = new SqlCommand("select * from dbo.ExperimentValues", conn);

SqlCommand()后面的括号之间,将定义命令的文本直接放入构造函数作为参数。记住,你已经有了ExperimentValues*符号表示选择所有列。所以,你需要命令文本和连接。

制作适配器

接下来,你将创建一个适配器。所以,输入以下内容:

SqlDataAdapter adapter = new SqlDataAdapter(command);

在这里,SqlDataAdapter是存在于实际数据库和我们之间的东西。这是一种将信息从这里适应到那里的方式。要初始化它,可以传入特定的 SQL 命令。因此,在我们的情况下,我们将传入(command)。您可以在此行后面添加注释//make adapter

制作数据表

接下来,您将制作一个数据表,如下所示:

DataTable dt = new DataTable();

再次注意,一旦输入DataTable,文件顶部的using System.Data;命名空间就会激活。因此,如果将鼠标悬停在DataTable上,它会说 System.Data.DataTable 类。这就是它存储的地方。因此,它存储在Data命名空间中。

用数据填充表

现在我们需要用一些信息填充这个表。因此,接下来输入以下内容:

adapter.Fill(dt);

在这里,您输入适配器的名称,然后填充数据集。因此,通过这三行,首先制作一个适配器并使用 SQL 命令获取信息,然后制作一个数据表。然后使用适配器填充该表。现在我们可以如下使用它:

var summedXValues = dt.AsEnumerable().Sum(row => row.Field<decimal>(1));

在这里,我们可以将数据表变成可枚举的,以便我们可以浏览它。请注意,我们在其中使用=>添加了一个 Lambda 表达式;<decimal>是数据类型,然后,如果将鼠标悬停在<decimal>()括号后面,工具提示会说(DataColumn column): 提供对指定行中每个列值的强类型访问。因此,在括号之间插入 1。

接下来,为summedYValues变量输入以下内容,并注意我们在括号之间放了一个 2:

var summedYValues = dt.AsEnumerable().Sum(row => row.Field<decimal>(2));

一旦您输入了所有这些,然后您可以显示xy值的总和,因此接下来输入以下行:

sampLabel.Text = $"Sum of y values={summedYValues}";
sampLabel.Text += $"<br>Sum of x values={summedXValues}";

显示总和值

在前面的行中,请注意第一行不需要<br>标签,但下一行需要。此外,第一行只需要=, 而下一行需要+=来追加。

运行程序

请记住,目的是对字段求和,因此打开浏览器,然后单击 Sum Fields 按钮:

图 21.6.4:运行我们程序的初始结果

您可以看到 Y 值的总和为 50.000,X 值的总和为 10.000。您可以通过打开 SQL Server 对象资源管理器窗格,右键单击ExperimentValues表,并将值相加来确认这是否按预期工作,如图 21.6.5所示:

图 21.6.5:添加 X 和 Y 列中的值

XValues 列加起来是 10.000,YValues 列加起来是 50.000。这两个总和与程序运行的结果一致。

关闭ExperimentValues表窗口和 SQL 对象资源管理器窗格。这次又按预期工作了。

添加注释

现在在连接字符串行上面添加此注释:

//make connection string

每当处理低级资源时,应用using块。在以using (SqlConnection conn...开头的行上面添加以下评论:

//make connection object

请记住,目的是正确地制作、使用和处理它,以便不会留下任何内存泄漏。每当处理硬盘访问时,例如,都要这样做。

在以SqlCommand command =...开头的行上面添加以下评论:

//make SQL command

然后,在以sqlDataAdapter adapter...开头的行上面添加以下评论以强调适配器的目的:

//make adapter object and pass in the command

同时,在该行的末尾添加此评论:

//make adapter

接下来,对于DataTable dt...,添加此注释:

//make table

适配器是允许我们填充表的机制,因此在adapter.Fill(dt);行的末尾添加以下评论:

//fill table with adapter

接下来,在第 30 行上面添加以下评论:

//lines 30 - 31 use LINQ to sum each column

最后,在第 33 行上面添加以下评论:

//lines 33-34 display the results in the web page

在下一行中,请注意这里的字段是decimal,因为这是我们在 SQL Server 中创建的,1 只是表示第一个字段,索引为 1。然而,请记住,这实际上意味着第二列,因为有三列:

var summedXValues = dt.AsEnumerable().Sum(row => row.Field<decimal>(1));

图 21.6.6所示,Id 实际上是索引 0,XValues 是索引 1,YValues 是索引 2。这就是为什么我们在这里使用 1 和 2,因为有三列,其中第二列位于索引 1:

图 21.6.6:Id 是索引 0,XValues 是索引 1,YValues 是索引 2

章节回顾

本章的Default.aspx.cs文件的完整版本,包括注释,如下面的代码块所示。

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;
using System.Data.SqlClient;
using System.Linq;
using System.Data;
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        //make connection string
        string connString = @"Data Source=DESKTOP-4L6NSGO\SQLEXPRESS;Initial Catalog=People;Integrated Security=True;Connect Timeout=15;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
        //make connection object
        using (SqlConnection conn = new SqlConnection(connString))
        {
            //make sql command
            SqlCommand command = new SqlCommand("select * from dbo.ExperimentValues", conn);
            //make adapter object and pass in the command
            //make adapter
            SqlDataAdapter adapter = new SqlDataAdapter(command);
            //make table
            DataTable dt = new DataTable();
            adapter.Fill(dt); //fill table with adapter
            //lines 30 - 31 use linq to sum each column
            var summedXValues = dt.AsEnumerable().Sum(row => row.Field< 
            decimal>(1));
            var summedYValues = dt.AsEnumerable().Sum(row => row.Field< 
            decimal>(2));
            //lines 33-34 display the results in the web page
            sampLabel.Text = $"Sum of y values={summedYValues}";
            sampLabel.Text += $"<br>Sum of x values={summedXValues}";
        }
    }
} 

总结

在本章中,您学习了如何将 LINQ 与 SQL 和 SQL Server 一起使用。您更改了ExperimentValues表中的数据,编写了使用 LINQ 对字段进行汇总的代码,添加了命名空间,构建了连接字符串,建立了 SQL 连接,创建了适配器,创建了数据表,填充了数据表,显示了汇总值,运行了程序,并最终添加了注释。

在下一章中,您将学习如何制作一个页面,将页面上的内容保存到硬盘上,然后再读取它。

第二十二章:创建一个保存文本到磁盘的页面

在本章中,您将学习如何制作一个页面,将页面上的内容保存到硬盘上,然后再读取它。

创建一个保存文本的应用程序

在本章结束时,您将制作一个类似于图 22.1.1所示的小应用程序。对于保存位置,您可以输入类似于c:\data\samp.txt的内容,以保存一个文本文件:

图 22.1.1:与本章中要构建的应用程序类似的用户界面

然后,您可以输入一些文本,例如这是要保存的一些示例文本。

图 22.1.2:在应用程序中输入一些示例文本的保存位置

现在单击“保存文本”按钮。这将弹出记事本,以确认已保存,如图 22.1.3所示:

图 22.1.3:示例文本已保存,并弹出记事本

如果您愿意,您也可以在页面中打开文本。因此,单击“打开”,然后它保存在页面中,如图 22.1.4所示:

图 22.1.4:示例文本保存在页面中

此外,如果您没有指定路径,显然会导致错误,如图 22.1.5所示。在这种情况下,它显示“空路径名不合法。”消息:

图 22.1.5:未输入保存位置时显示的错误消息

所以,这是这里的目标。记住这个例子。

现在让我们创建一个项目。转到“文件”|“新建”|“网站...”然后,从“视图”菜单中,转到“解决方案资源管理器”,并单击“Default.aspx”。

为您的项目创建用户界面

首先,您必须构建您的用户界面,因此您需要在 HTML 页面中有一个文本框,您可以在其中输入路径。为此,请转到工具箱,获取一个TextBox控件,并将其放在以<form id=...开头的行下面。在此行的开头输入“保存路径”,如下所示:

Save Path:<asp:TextBoxID="TextBox1"runat="server"></asp:TextBox><br/>

接下来,您将有一个按钮,基本上是用来在网页中打开保存的文件,因此将按钮中的文本更改为“在页面中打开”,如下所示:

<asp:Button ID="Button1"runat="server"Text="Open In Page" /><br />

在这种情况下,它只是意味着将简单的文本读回页面。现在进入设计视图,查看到目前为止的用户界面,如图 22.1.6所示:

图 22.1.6:到目前为止的用户界面

接下来,您还需要一个地方来输入要保存的文本。因此,获取另一个TextBox控件,并在此行的开头输入Text To Be Saved,如下所示:

Text To Be Saved:<asp:TextBoxID="TextBox2"runat="server"Width="1069px"></asp:TextBox><br/>

删除两个<div>行 - 您不需要它们。

现在让我们添加一个“保存”按钮。因此,我们将有一个“在页面中打开”按钮和一个“保存”按钮。现在,从工具箱中拖动一个按钮,并将其放在Button1上方。(当然,布局取决于您。)更改文本如下:

<asp:Button ID="Button2"runat="server"Text="Save" /><br />

该项目的完整 HTML 文件如下代码块所示。

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<!DOCTYPE html>
<html >
  <head runat="server">
    <title>Our First Page</title>
  </head>
  <body>
    <form id="form1" runat="server">
      Save Path:<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
      <br />
      <asp:Button ID="Button2" runat="server" Text="Save" 
      OnClick="Button2_Click" />
      <br />
      <asp:Button ID="Button1" runat="server" Text="Open In Page" 
      OnClick="Button1_Click" />
      <br />
      Text To Be Saved:<asp:TextBox ID="TextBox2" runat="server" 
      Width="1069px"></asp:TextBox>
      <br />
      <asp:Label ID="sampLabel" runat="server"></asp:Label>
    </form>
  </body>
</html>

现在,如果您进入设计视图,将显示一个简单的界面,如图 22.1.7所示:

图 22.1.7:完整的简单用户界面

如果您愿意,您可以拖动Text To Be Saved框的一个角,将其放大,以便有更大的地方保存您的文本。现在您有一个保存的地方,一个保存按钮,打开页面和sampLabel。这对我们的目的已经足够了。

开始编写项目

现在双击“保存”按钮。这将带我们进入Default.aspx.cs。删除Page_Load块。该项目的起始代码的相关部分应如图 22.1.8所示:

图 22.1.8:该项目的起始代码

因此,对于“保存”按钮代码,您必须添加一个命名空间。首先,在文件顶部的using System下,输入以下内容:

using System.IO;

捕获异常

让我们利用一下这个。现在,因为有可能有人没有在框中输入任何内容,可能会生成错误消息,你想要捕获它。所以,在以protected void Button2_Click...开头的行的大括号下面,输入以下内容:

try
{

}
catch(Exception ex)
{
   sampLabel.Text = ex.Message;
}

前面的sampLabel.Text行用于显示生成并捕获的异常的消息。

创建一个 StreamWriter 类

接下来,我们将使用一个StreamWriter类。这个类可以获得对硬盘等的低级访问,所以你必须确保它在一个using语句内。你需要能够创建它,使用它,并完全处理掉它。所以,在try下面的一对大括号之间输入以下内容:

using (StreamWriter sw = new StreamWriter(TextBox1.Text))

初始化这个类时,要传入的参数是TextBox1.Text。所以这个将写入文件。确认一下,你可以在Default.aspx的源视图中,验证Save PathTextBox1

现在,为了实际写入文件,在上一条语句下的一对大括号之间输入以下内容:

sw.Write(TextBox2.Text);

在这里,sw是一个流写入器,sw.write是它的一个方法,一个函数,然后你将取出TextBox2的东西并写入。所以,从TextBox1中获取路径,从TextBox2中取出文本。

现在,如果你右键点击StreamWriter并选择Go To Definition,结果看起来像图 22.1.9所示:

图 22.1.9:StreamWriter 的定义

在最底部,你可以看到它有Dispose,你可以在顶部附近看到StreamWriter继承自TextWriter。接下来,如果你选择TextWriterGo To Definition,你会看到有IDisposable,如图 22.1.10所示:

图 22.1.10:TextWriter 的定义

如果你右键点击IDisposable并选择Go To Definition,就会出现Dispose,如图 22.1.11所示:

图 22.1.11:IDisposable 的定义

如果你展开public interface IDisposable,它会显示注释,执行与释放、释放或重置非托管资源相关的应用程序定义的任务;换句话说,这些是低级资源,所以最好不要使用它。

另外,你想要确认它保存到文件中,所以在文件顶部的using System.IO;下面输入以下内容:

using System.Diagnostics;

这一行将在一切保存之后打开记事本。

现在,在sampLabel.Text = ex.Message;下面的闭合大括号下面输入以下内容:

Process.Start("notepad.exe", TextBox1.Text);

在这里,TextBox1.Text只是将你在框中输入的文本反馈出来。

接下来,回到Default.aspx。在设计视图中,双击打开页面按钮。这将再次带你进入Default.aspx.cs。你接下来编写的代码将在Open按钮上执行。所以逻辑上非常相似。

现在,在以protected void Button1_Click...开头的行的大括号下面,输入以下内容:

try
{
}
catch(Exception ex)
{
   sampLabel.Text = ex.Message;
}

再次,你使用trycatch,因为在尝试打开时可能会产生错误。在标签上显示相同的文本。基本上,从上面复制try/catch块,然后粘贴到下面。它完全相同。

创建一个 StreamReader 类

现在,然而,你将在这个try语句下的大括号之间输入以下内容:

using (StreamReader sr = new StreamReader(TextBox1.Text))

再次,StreamReader是一个类——它需要一个流。就像是两个地方之间的通信渠道。

接下来,为了显示文本,在这行下面的一对大括号之间输入以下内容:

sampLabel.Text = sr.ReadToEnd();

在这里,ReadToEndStreamReader类内部可用的函数,它从当前位置读取到流的末尾的所有字符。这对我们的目的已经足够了。所以这就是代码。

你已经创建了在设计视图中看到的简单界面,如图 22.1.7所示。

运行程序

现在,在浏览器中打开它。在顶部,您有保存路径。首先,想象一下在框中未输入路径,然后单击保存按钮。如图 22.1.12所示,它会打开记事本;所以,那部分是有效的。但是,它显示了消息“空路径名称不合法”。但这是一个有用的东西,对吧?

图 22.1.12:未指定路径时,显示错误消息并打开空白记事本

现在,让我们指定一个合法的路径,比如c:\data\temp.txt。然后,在“要保存的文本”框中输入“大项目”。单击保存按钮。大项目被打开,文件名为 temp,如图 22.1.13所示。所以,它已经保存了:

图 22.1.13:指定合法路径后,记事本打开,显示“要保存的文本”框中的文本

如果您愿意,您可以确认它将在页面中打开,因此单击“在页面中打开”,页面上也会显示“大项目”,如图 22.1.14所示:

图 22.1.14:文本也在页面中打开

所以,它正在按预期工作。

章节回顾

回顾一下,回到Default.aspx.cs。因为您正在处理输入/输出资源,所以必须确保您有 I/O(using System.IO;);另外,因为您正在处理低级磁盘写入和读取,所以确保您将StreamWriterStreamReader封装在using中,这样您就可以获取它们,使用它们,并正确地处理掉它们。最后,因为通常会生成异常,例如当找不到路径或类似情况时,还要使用trycatch,并向用户显示消息,使应用程序看起来专业。请记住,这将运行,因为我们正在从本地计算机运行页面。

本章的Default.aspx.cs文件的完整版本,包括注释,如下代码块所示:

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;
using System.IO;//needed for files
using System.Diagnostics;//needed for Process.Start
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
public partial class _Default : System.Web.UI.Page
{
    protected void Button2_Click(object sender, EventArgs e)
    {
        //this is needed so that errors can be caught
        try
        {
            //enclose Streams inside usings because streams deal with 
            //low level access
            using (StreamWriter sw = new StreamWriter(TextBox1.Text))
            {
                //this writes the text to a file
                sw.Write(TextBox2.Text);
            }
        }
        catch(Exception ex)
        {
            sampLabel.Text = ex.Message;
        }
        Process.Start("notepad.exe", TextBox1.Text);
    }
    protected void Button1_Click(object sender, EventArgs e)
    {
        //same as try above
        try
        {
            //save as StreamWriter above
            using (StreamReader sr = new StreamReader(TextBox1.Text))
            {
                //read file contents into label text property
                sampLabel.Text = sr.ReadToEnd();
            }
        }
        catch (Exception ex)
        {
            sampLabel.Text = ex.Message;
        }
    }
}

总结

在本章中,您学会了如何创建页面,然后将页面上的内容保存到硬盘并读取回来。您创建了一个简单的用户界面,编写了捕获异常的代码,并创建了StreamWriterStreamReader类。

在下一章中,您将学习如何在 ASP.NET 中使用上传功能。

第二十三章:创建使用文件上传控件的页面

在本章中,您将学习如何在 ASP.NET 中使用上传功能。为此,我们将在页面上创建一个带有以下控件的界面:

图 23.2.1:我们用户界面的控件

当您点击浏览按钮时,您应该会得到一些示例文件,如图 23.2.2所示。选择其中一个文件,例如samp.txt

图 23.2.2:C:\data 目录文件列表

现在,当您点击上传按钮时,一旦文件上传完成,浏览器将显示一个消息,类似于图 23.2.3中显示的消息,显示文件已上传的位置,目录中有多少文件,以及它们的名称。这是我们的目标:

图 23.2.3:单击上传按钮时显示的消息

确保您的硬盘的根目录中有一个名为data的文件夹,并且在该文件夹中,您有另一个名为uploads的文件夹。要在命令行级别执行此操作,请转到命令提示符(C:\)并按照以下步骤操作:

  1. 输入cd..以切换到根目录。

  2. 然后,输入cd data并按Enter

  3. C:\data目录下,输入dir,如下所示:

 C:\data\dir
  1. C:\data目录下,输入cd uploads,如下所示:
 C:\data\cd uploads
  1. C:\data\uploads目录下,再次输入dir
 C:\data\cd uploads\dir

您的屏幕将类似于图 23.2.4所示的屏幕:

图 23.2.4:C:\data\uploads 的命令行目录列表

现在让我们实现这一点。

从头开始启动我们的项目

让我们从头开始创建一个新项目。转到文件 | 新建 | 网站...;然后,转到解决方案资源管理器,单击Default.aspx

现在我们可以看到一个基本的 HTML。让我们把一个FileUpload控件放进去。要做到这一点,去工具箱,抓取一个FileUpload控件,并将其拖放到以<form id=...开头的行下方,并在其后添加一个<br/>标签,如下所示:

<asp:FileUploadID ="FileUpload1" runat="server" /><br/>

接下来,在这一行下面放入一个按钮,如下所示:

<asp:Button ID="Button1" runat="server" Text="Upload" /><br /> 

更改按钮上的文本,使其显示更有意义的内容,例如上传

删除两个<div>行——您不需要它们。

当您进入设计视图时,您会看到这个简单的界面,如图 23.2.5所示。您有一个浏览按钮,它是上传控件的一部分,因此不需要单独放在那里,还有一个上传按钮:

图 23.2.5:我们项目的简单界面

现在,双击上传按钮。这将带您进入Default.aspx.cs。删除Page_Load块。该项目的起始代码的相关部分应该如图 23.2.6所示:

图 23.2.6:该项目的起始代码

添加一个命名空间

要读取文件,首先在文件顶部的using System之后插入以下内容:

using System.IO;

将文件保存到特定位置

您需要做的第一件事是指定文件应该保存的位置。因此,在以protected void Button1_Click...开头的行下方的一对大括号中输入以下内容:

string savePath = @"c:\data\uploads\";

这里,savePath是文件将被保存的路径的名称。您输入@符号来创建一个逐字字符串,c:\data\uploads就是文件将被保存的地方。请记住,如果您删除@符号,会导致错误,因为它意味着按照原样读取字符串。

接下来,输入以下内容:

if(FileUpload1.HasFile)

这里,HasFile是一个简单的属性。然后,您可以这样说(在一对大括号之间):

{
    string fileName = FileUpload1.FileName;
}

此行获取文件名,这里再次,FileName是一个属性。

现在,输入以下内容:

savePath += fileName;

因此,savePath首先是文件夹结构,然后您还将文件名附加到其中。

保存文件

现在,要实际保存文件,请输入以下内容:

FileUpload1.SaveAs(savePath);

请记住,每当您想了解这些术语中的任何一个更多信息时,都可以这样做。只需右键单击它们,然后选择转到定义。例如,如图 23.2.7所示,如果展开public void SaveAs行,它会说将上传文件的内容保存到 Web 服务器上指定的路径。它还会引发异常,因此存在错误的可能性。请记住这一点。

图 23.2.7:Go To Definition 中 SaveAs 的解释

向用户显示消息

接下来,让我们向用户显示一些有用的诊断消息。为此,请输入以下内容:

sampLabel.Text = "<br>Your file was saved as " + fileName;

另一种可能性是没有文件。换句话说,FileUpload1.HasFile为 false。如果是这种情况——没有文件,您可以将前一行复制到下面,并更改文本以使其有意义。从上一个闭合大括号下面开始输入else,然后输入以下内容:

{
   sampLabel.Text = "<br>You did not specify a file to upload.";
} 

确定目录中存储了哪些文件

接下来,让我们去看看目录中有哪些文件。因此,在上一行下面的闭合大括号下面输入以下内容:

string sourceDirectory = @"C:\data\uploads";

同样,您将从与之前以“String savePath…”开头的相同位置获取它,并将c:\data\uploads\粘贴到这一行中。

接下来,您可以在以下行上键入try,并在其下的一对大括号之间输入以下内容:

{
   var txtFiles = Directory.EnumerateFiles(sourceDirectory, "*.txt");
}

输入EnumerateFiles时出现的工具提示显示有几种重载——string pathstring searchPattern。因此,这里的路径将是sourceDirectory,而searchPattern将用于搜索以.txt结尾的所有内容。因此我们在末尾放置*.txt。这就是如何枚举所有文件。

确定返回类型

如果您将鼠标悬停在上一行中的var上,弹出的工具提示会告诉您返回类型是什么。它说IEnumerable。现在将鼠标悬停在EnumerateFiles上,右键单击它,然后选择转到定义:

图 23.2.8:在定义中,它显示返回类型为 IEnumerable

图 23.2.8所示,返回类型是IEnumerable,这意味着您可以遍历结果,或者使用foreach语句显示它们。

接下来,在上一行下面输入以下内容:

foreach(string currentFile in txtFiles)

然后就在这下面输入以下内容(缩进):

sampLabel.Text += $"<br>{currentFile}";

探索 EnumerateFiles 的异常

现在,再次将鼠标悬停在EnumerateFiles上,右键单击它,然后选择转到定义。展开定义并查看它可能引发的异常。有很多异常,其中一些在图 23.2.9中显示:

图 23.2.9:EnumerateFiles 可能引发的异常的部分列表

例如,DirectoryNotFound可能是一个常见的异常;path是一个文件名,PathTooLongSecurityException也是常见的异常。因此,EnumerateFiles有相当多的异常。

捕获异常

换句话说,您需要插入某种catch来处理这些情况。因此,在最后一个闭合大括号之后输入以下内容:

catch(Exception ex)

现在,在一对大括号之间,输入以下内容:

{
    sampLabel.Text += ex.Message;
}

在这里,ex.Message表示要在屏幕上显示的异常对象的消息。

运行程序

现在让我们确认这将起作用,所以在浏览器中启动它。单击“浏览”,并从C:\data目录中获取temp.txt文件。单击“上传”。正如您在图 23.2.10中所看到的,您的文件已保存,并且在同一目录中还有其他文件。完美!

图 23.2.10:运行我们的程序的结果

现在,想象一下您犯了以下错误(输入upload而不是“uploads”):

string sourceDirectory = @"C:\data\upload";

如果您再次运行它,通过单击“浏览”并选择samplefile.txt文件,您可以从图 23.2.11中显示的错误消息中看到,它找不到路径的一部分...:

图 23.2.11:当路径输入错误时显示的错误消息

所以这些是使它工作的基础知识。再次确保输入并运行此代码几次,然后您将完全了解发生了什么。请记住,我们可以这样做是因为网页只能在我们的本地计算机上访问。在更现实的情况下,您需要更加关注安全性,并防范恶意上传。

章节回顾

本章的Default.aspx.cs文件的完整版本,包括注释,如下所示:

using System;
using System.IO;
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        string savePath = @"c:\data\uploads\"; //make upload directory
        if (FileUpload1.HasFile)
        {
            string fileName = FileUpload1.FileName;//get file name
            savePath += fileName;//attach file name to save path
            FileUpload1.SaveAs(savePath);//save file
            sampLabel.Text = "<br>Your file was saved as " + fileName;
        }
        else
        {
            sampLabel.Text = "<br>You did not specify a file to upload.";
        }
        //could also use savePath here
        string sourceDirectory = @"C:\data\uploads";
        try
        {
            //list files using EnumerateFiles
            var txtFiles = Directory.EnumerateFiles(sourceDirectory,
            "*.txt");
            foreach (string currentFile in txtFiles) //display files
            sampLabel.Text += $"<br>{currentFile}";
        }
        //display any error messages
        catch (Exception ex)
        {
            sampLabel.Text += ex.Message;
        }
    }
} 

总结

在本章中,您学会了如何在 ASP.NET 中使用上传功能。您将文件保存到特定位置,向用户显示消息,确定存储在目录中的文件,探索EnumerateFiles的异常,并编写捕获异常的代码。

在下一章中,您将学习使用序列化将对象保存到硬盘的另一种方法。然后,您将了解从硬盘重建对象的过程,这被称为反序列化

第二十四章:序列化和反序列化对象

在本章中,您将学习另一种将对象保存到硬盘的方法—使用序列化。您还将学习从硬盘重建对象的过程,这称为反序列化。

将两个按钮添加到 HTML 中

启动一个项目,在这个项目中,您将在页面中插入两个按钮。您将在以<form id=...开头的行下方放置第一个按钮。要做到这一点,转到工具箱,获取一个Button控件,并将其拖放到那里。将第一个按钮上的文本更改为Save。现在获取另一个按钮,并将其拖放到该行下方。将第二个按钮上的文本更改为Open。因此,您在页面中放置了两个按钮,如下所示:

<asp:ButtonID="Button1" runat="server" Text="Save" /><br/>
<asp:ButtonID="Button2" runat="server" Text="Open" /><br/>

删除两个<div>行—您不需要它们。当然,在最后您还有一个标签:

<asp:LabelID="sampLabel" runat="server"></asp:Label>

在设计视图中,如图 24.3.1所示,您有两个按钮—Save 和 Open—然后是一个标签,打开的对象可以在其中显示:

图 24.3.1:我们在设计视图中的简单界面

开始编写项目

首先,我们将创建Save按钮,双击它,这将显示Button1_click的事件处理程序。删除Page_Load块。该项目的起始代码的相关部分应如图 24.3.2所示:

图 24.3.2:该项目的起始代码

添加命名空间

接下来,您需要添加新的命名空间,因此在文件顶部的using System下面,输入以下内容:

using System.IO;

显然,这一行用于输入和输出。接下来,输入以下内容:

using System.Runtime.Serialization.Formatters.Binary;

这一行允许您编写代码。当我们一起编写代码时,您将更好地理解这些命名空间的目的。接下来,让我们再做一个,如下所示:

using System.Diagnostics;

这一行只是为了让您能够打开记事本。保存为二进制格式后,您将使用记事本查看文件。现在您可以折叠这些命名空间,如果您愿意的话。

创建可序列化类

因此,首先您需要可以序列化的东西—一个可序列化的类。您将在前面的using语句下方放置它。输入以下内容:

[Serializable()]

您可以以这种方式装饰一个类。接下来,要序列化的内容如下输入:

public class Person

为可序列化类添加功能

这是你的可序列化类。接下来,你将为其添加功能。因此,在此行下面的一对大括号之间,输入以下内容:

public string Name { get; set; }
public decimal Salary { get; set; }

接下来,我们将重写一个方法,以便我们可以显示一个人并实际格式化它。因此,输入以下内容:

public override string ToString()

现在,如果您将鼠标悬停在ToString上,您将看到它是一个对象类。请记住,对象类是整个层次结构的父类。这是ToString被定义的地方。工具提示显示为 string object.ToString()。现在我们将覆盖它并编写我们自己的定义。

接下来,在override行下面的一对大括号中,输入以下内容:

return $"{Name} makes {Salary:C} per year.";

这将是我们对ToString的特定实现;也就是说,Name每年赚取一定金额的钱—无论每个Person类的实例的名称和薪水是什么。

定义保存文件的路径

接下来,在以protected void Button1_Click...开头的行下面的一对大括号中,输入以下内容:

string file = @"c:\data\person.bin"; 

在这里,您正在定义文件将被保存的路径。请注意,这次我们使用了不同的扩展名—.bin代表二进制,而不是.txt代表文本。

创建一个 Person 对象

接下来,输入以下内容以创建一个新的Person对象:

Person per = new Person() { Name = "John Smith", Salary = 78999 };

请记住,创建对象的另一种方法是您可以在大括号内设置属性的值。因此,我们有John Smith和他的Salary属性值。因此,我们创建了一个new Person对象。

处理非托管资源

现在,输入以下内容:

using (FileStream str = File.Create(file))

将鼠标悬停在前一行的FileStream上,以查看其位置;它在System.IO内。请注意,using System.IO;不再是灰色的,因为FileStream现在在那里。

接下来,右键单击FileStream,然后选择“转到定义”。您会看到它是从Stream派生的。现在,如果您向下滚动到底部,看到Dispose并展开它,您会看到它说释放 System.IO.FileStream 使用的未托管资源...,如图 24.3.3所示:

图 24.3.3:FileStream 的扩展定义

这就是为什么我们将其放在using语句中的原因,因为它涉及到未托管资源,比如低级磁盘访问。所以,我们将创建一个文件。

创建一个二进制格式化程序

接下来,您将创建一个二进制格式化程序,所以在以下行之间输入以下内容:

BinaryFormatter binFormatter = new BinaryFormatter();

同样,BinaryFormatter在这里是一个类,所以如果您将鼠标悬停在它上面,工具提示会说以二进制格式序列化和反序列化对象,或连接对象的整个图形。

序列化对象

接下来,要序列化我们的对象,您说binFormatter.Serialize,这是一个在那里定义的函数,然后您需要一个流和一个要通过流序列化的对象(per):

binFormatter.Serialize(str, per);

为了确认这个工作,输入以下内容在闭合大括号下面:

Process.Start("notepad.exe", file);

这将只是启动文件,以确认它已经被保存。

测试程序

在编写其余代码之前,我们可以进行一次测试。所以让我们在浏览器中启动它,然后点击保存*:*。

图 24.3.4:程序的测试运行,以确保它正常工作

现在您可以看到,当您检查它时,保存的内容看起来与普通文本非常不同。请记住,当您学习属性时,我们谈到了后备字段。字段的实际值显示在图 24.3.5中。您可以看到工资、姓名值,然后是字段。这就是我们所说的二进制。它看起来与普通文本非常不同:

图 24.3.5:后备字段显示字段的实际值

从硬盘重建对象

在下一阶段,我们希望能够从硬盘重新构建这个对象。为此,在设计视图中双击“打开”按钮。这将带您回到Default.aspx.cs文件。

现在,在以protected void Button2_Click...开头的行下面的一组大括号内,创建一个新的Person对象,如下所示:

Person personRebuilt;

我们从硬盘构建这个。在这行下面输入以下内容:

string file = @"c:\data\person.bin";

通过这行,我们将从该文件中读取回来。

接下来,您必须确认文件实际存在,所以输入以下内容:

if(File.Exists(file))

如果文件存在,您将采取一些操作,这些操作将重建对象。

现在在以下行下面的一组大括号之间输入以下内容:

using (FileStream personStream = File.OpenRead(file))

在这里,我们打开文件进行读取。将鼠标悬停在OpenRead上。注意它返回一个FileStream类,因此表达式的右侧和左侧是一致的。

接下来,在这行下面的另一组大括号之间,输入以下内容:

BinaryFormatter binReader = new BinaryFormatter();

现在,我们将重建Person对象,所以接下来输入以下内容:

personRebuilt = (Person)binReader.Deserialize(personStream);

这将是对Person类型的转换。然后,将personStream传递到二进制读取器上定义的Deserialize函数中,然后将其转换回Person对象。

显示结果

现在,有了这个,我们可以显示东西。例如,接下来输入以下内容:

sampLabel.Text = personRebuilt.ToString();

请记住,此行中的ToString是在Person内部定义的。它是覆盖对象内部定义的基本ToString方法的方法。如果您将鼠标悬停在此处的ToString上,它会显示字符串Person.ToString()

运行程序

现在让我们在浏览器中打开这个新代码。单击“保存”按钮,它会打开记事本,如图 24.3.6所示:

图 24.3.6:单击“保存”按钮时运行程序的结果

现在单击“打开”按钮,它看起来像图 24.3.7中显示的屏幕:

图 24.3.7:单击“打开”按钮时运行程序的结果

因此,这证明了对象已被构建,并且还证实了在这个重建的对象personRebuilt上,您可以调用在类的定义中详细说明的通常函数、方法等,即return $"{Name} makes {Salary:C} per year.";行。

章节回顾

回顾一下,要记住的重点是,您可以从一个对象开始,并添加相当多的命名空间,特别是BinaryFormatterIO。接下来,您定义一个类,并在下面添加可序列化属性。然后,您编写代码以二进制格式保存,还编写了代码以从二进制格式重建为您的应用程序中可以使用的格式。

本章的Default.aspx.cs文件的完整版本,包括注释,如下所示:

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Diagnostics; //for notepad
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
[Serializable()]
public class Person //make class serializable
{
    public string Name { get; set; } //define name property
    public decimal Salary { get; set; } //define Salary property
    public override string ToString() 
    //override ToString() from object class
    {
        //return pretty string to describe each person
        return $"{Name} makes {Salary:C} per year.";
    }
}
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        //define path where file will be saved
        string file = @"c:\data\person.bin";
        //build an object
        Person per = new Person() { Name = "John Smith", Salary = 78999 };
        //enclose FileStream in a using because of low level access
        using (FileStream str = File.Create(file))
        {
            //make a formatter
            BinaryFormatter binFormatter = new BinaryFormatter();
            //this is the step that saves the information
            binFormatter.Serialize(str, per);
        }
        //start notepad and display file
        Process.Start("notepad.exe", file);
    }
    protected void Button2_Click(object sender, EventArgs e)
    {
        //person object to hold the rebuild person from disk
        Person personRebuilt;
        string file = @"c:\data\person.bin"; //path
        if(File.Exists(file)) //first confirm file exists
        {
            //enclose FileStream in a using
            using (FileStream personStream = File.OpenRead(file))
            {
                //make a formatter
                BinaryFormatter binReader = new BinaryFormatter();
                //reconstruct person using a cast
                personRebuilt = 
                (Person)binReader.Deserialize(personStream);
                //invoke to string on the person
                sampLabel.Text = personRebuilt.ToString();
            }
        }
    }
}

总结

在本章中,您学习了另一种将对象保存到硬盘的方法——使用序列化。然后,您学习了从硬盘重建对象的过程——反序列化。您创建了一个serializable类,为该类添加了特性,定义了保存文件的路径,创建了一个Person对象,编写了处理非托管资源的代码,创建了一个二进制格式化程序,对对象进行了序列化,并测试了您的程序。

在下一章中,您将学习如何在像素级别处理图像。我们将反转颜色并进行更改。

第二十五章:通过像素操作图像来玩一点

在本章中,你将学习如何在像素级别处理图像。你将反转颜色,改变它们。

操作图像

首先,在我的c:\data文件夹中,我有一个名为lessonimage的文件。正如你在图 25.4.1中所看到的,可乐罐上的文字是红色的,背景似乎是红棕色的:

图 25.4.1:本章中用于在像素级别反转颜色的图像

我们要做的是交换颜色,这样可乐罐上的文字,例如,将变成绿色,你将学习如何在单个像素级别操作图像。

向 HTML 添加一个按钮和一个图像控件

打开一个新项目。删除以<div...开头的两行;也删除这次的Label行。你不需要它们中的任何一个。

接下来,你需要在页面中插入一个Button控件。要做到这一点,去工具箱,拖动一个Button控件,并将其拖放到以<form id=...开头的行下面。将第一个按钮上的文本更改为Load

现在,你需要在页面中插入一个图像控件。所以,回到工具箱,拖动一个Image控件,并将其拖放到前一行下面,两者之间留一行空白。你的Default.aspx文件应该看起来像图 25.4.2中显示的那样:

图 25.4.2:本章的完整 HTML

所以,你为这个项目有一个非常简单的界面:一个按钮用于加载图像,另一个按钮是一个图像控件,用于显示图像:

图 25.4.3:我们项目的简单界面

现在,双击加载按钮。这会带你进入Default.aspx.cs。删除Page_Load事件;我们不关心那个。这个项目起始代码的相关部分应该看起来像图 25.4.4

图 25.4.4:这个项目的起始代码

添加一个命名空间

自然而然的,首先要做的是添加一个相关的新命名空间。为此,在文件顶部的using System下面输入以下行:

using System.Drawing;

为了使事情变得清晰干净,如果你愿意的话,你可以折叠文件顶部的所有代码组,这样基本上第一行清晰可见的是public partial class...

制作一个位图

当然,下一步是放入代码来实现你想要的功能。首先,你将制作一个位图。在以protected void Button1_Click...开头的行下面的大括号之间输入以下内容:

Bitmap image = new Bitmap(@"c:\data\lessonimage.bmp");

在这里,Bitmap是一个我们将称之为image的类。基本上,你有一个可以操作的位图。然后,为了初始化它,你传入一个路径。在这种情况下,它是(@"c:\data\lessonimage.bmp");

将图像保存为位图图片

接下来,打开画图并加载本章要操作的图像,如图 25.4.5所示:

图 25.4.5:在画图中要操作的图像

现在,要将其保存为位图,转到文件 | 另存为,然后选择 BMP 图片,如图 25.4.6所示:

图 25.4.6:画图中的另存为选项

BMP 图片的描述说保存任何类型的高质量图片并在计算机上使用。当你要保存文件时,另存为对话框中的文件类型字段显示为 24 位位图(.bmp;.dib)。你可以将任何图像保存为位图。

访问像素的位置

接下来,在Bitmap image = new Bitmap...行后输入以下内容:

int x, y;

你需要这行来获取图像中每个像素的位置。

现在,要访问每个像素的位置,你需要嵌套的for循环。首先输入下一行:

for(x = 0; x < image.Width; x++)

这里的外部for循环用于控制x坐标的水平移动。

现在,在一对花括号之间,输入以下内容:

for(y = 0; y < image.Height; y++)

这个内部的for循环是为了控制每个像素的y坐标,或者它的垂直位置。

操纵像素

一旦你完成了所有这些,下一阶段就是操纵像素。所以,从另一组花括号开始,并在它们之间输入以下内容,缩进:

Color pixelColor = image.GetPixel(x, y);

这一行首先读取每个像素的颜色。如果你将鼠标悬停在这一行的GetPixel上,你会看到它返回的是颜色,而不是位置。工具提示说它获取了位图中指定像素的颜色。

现在你将创建一个新的颜色。输入以下内容,也缩进,接下来:

Color newColor = Color.FromArgb(pixelColor.B, pixelColor.R, pixelColor.G);

在这里,=符号后面的Color是一个struct值类型。除了FromArgb,你还可以使用FromKnownColorFromName。这意味着字符串名称是已知的。在FromArgb后面,你说pixelColor.B来获取这个颜色结构的蓝色分量,pixelColor.R来获取红色分量,然后pixelColor.G来获取绿色分量。因此,你用这一行创建了一个新的Color对象。

接下来,输入以下内容:

image.SetPixel(x, y, newColor);

如果你将鼠标悬停在SetPixel上,工具提示说在这个位图中设置指定像素的颜色。然后,(x, y, newColor)表示要用来着色该像素的新颜色。

将图片转换为字节数组

现在你需要能够显示图片。你需要写一些代码来完成转换。所以,从外部for循环的闭合花括号下面输入以下内容:

byte[] picBytes = (byte[])new ImageConverter().ConvertTo(image, typeof(byte[]));

在这里,你创建了一个名为picBytes的字节数组,然后使用(byte[])进行转换。有一个图像转换器类,所以你创建了一个新的ImageConverter()类,然后你转换为目标类型,typeof,然后是byte。所以,在这里你将图片转换为字节数组。

现在,如果你去掉(byte[])的转换,工具提示会说你的字节数组不能隐式转换为'object'。这是因为ConvertTo返回一个对象。因此,你需要在它的前面有(byte[])转换。

现在你有了这个,接下来可以说以下内容:

string baseString = Convert.ToBase64String(picBytes);

Convert内部,你现在可以输入Convert.ToBase64String,并且picBytes可以转换为base64字符串。

发送图片 URL

现在你可以发送图片 URL,所以输入以下内容:

Image1.ImageUrl = "data:image/png;base64," + baseString;

在这一行的末尾的baseString变量是在一个图片字节数组上运行两个base64字符串的结果。

运行程序

有了这个,现在让我们来看一下结果;打开你的浏览器,点击加载按钮。修改后的图片显示在图 25.4.7中:

图 25.4.7:运行程序时产生的操纵后的图片

现在你会看到,承诺的图片已经被反转:颜色是绿色的。原来的背景有点红棕色,现在是绿色的。男人的头发原来有点棕色,现在有点深色,桌子也是一样。然而,有些东西似乎没有受到太大影响,比如钱的颜色,对吧?那仍然有点灰色。图片中的黑色物体也是一样。

正如你所看到的,你可以操纵图片,改变它们的位置,并使它们看起来非常不同,所以没有什么是真正固定的。这就是重点。此外,你可以单独访问每个像素,改变颜色,然后程序中最后三行代码负责让你写入Image1.ImageUrl。要设置这个图片的 URL,你需要通过这三行中的前两行。可能有一些更简单的方法,但这是一个可行的方法。

章节回顾

本章的Default.aspx.cs文件的完整版本,包括注释,显示在以下代码块中:

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;
using System.Drawing;
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        Bitmap image = new Bitmap(@"c:\data\lessonimage.bmp");
        //to get each pixel's location
        int x, y;
        //controls moving horizontally
        for(x=0;x<image.Width;x++)
        {
            //controls moving vertically
            for(y=0;y<image.Height;y++)
            {
                //get each pixel's color
                Color pixelColor = image.GetPixel(x, y);
                //make a new color
                Color newColor = Color.FromArgb(pixelColor.B, pixelColor.R, 
                pixelColor.G);
                image.SetPixel(x, y, newColor);//set new color
            }
        }
        //converts picture to array of bytes
        byte[] picBytes =(byte[])new ImageConverter().ConvertTo(image, 
        typeof(byte[]));
        //converts array of bytes to a certain kind of string
        string baseString = Convert.ToBase64String(picBytes);
        //sets the image URL in a format that allows the image to be
        //displayed in a web page
        Image1.ImageUrl = "data:image/png;base64," + baseString;
    }
}

总结

在本章中,您学会了如何在像素级别处理图像。您反转了颜色,对其进行了更改。您将按钮和图像控件插入到 HTML 中,制作了位图,将图像保存为位图图片,编写了访问每个像素位置以操纵像素的代码,将图片转换为字节数组,并发送了图像 URL。

在下一章中,您将学习如何读取文件,然后将它们保存到 SQL Server 作为图像。

第二十六章:将图像保存到 SQL Server

在本章中,您将学习如何读取文件,然后将它们保存到 SQL Server 中作为图像。

在 HTML 中添加按钮和列表框

打开一个包含基本 HTML 的项目。这里需要做的第一件事是插入一个按钮。要做到这一点,转到工具箱,然后将Button控件拖放到以<form id=...开头的行下方。请记住,我们将在此项目中构建的简单界面将涉及单击按钮并从硬盘中读取文件到列表框中。更改Button控件上的文本为扫描文件夹。您将在此项目中扫描图像文件夹。

之后,您将插入一个ListBox控件。因此,再次转到工具箱,在搜索字段中键入list,然后将ListBox控件拖放到上一行下方。单击按钮后,您将填充ListBox控件。

在最后阶段,您将把所有文件保存到 SQL Server。这是我们的目标。为此,在上一行下方再拖入一个按钮。更改Button控件上的文本为保存到 SQL Server

删除以<div...开头的两行,还有Label行也要删除。这些都不需要。

你的Default.aspx文件应该看起来像图 26.5.1中显示的那样:

图 26.5.1:本章的完整 HTML

转到设计视图,如图 26.5.2所示,您将得到此项目的非常简单的界面:一个扫描文件夹按钮,用于获取文件名,然后一个保存文件到 SQL Server 的按钮:

图 26.5.2:我们项目的简单界面

创建用于存储文件的数据库表

您需要有一个数据库表,可以在其中保存文件。首先打开 SQL Server 对象资源管理器。您会记得您有一个名为People的数据库。转到表文件夹,在其上右键单击,并选择添加新表...,如图 26.5.3所示:

图 26.5.3:在 SQL Server 对象资源管理器中添加新表

您可以保留顶部的默认内容基本上不变,但要进行以下更改:

  1. 更改底部的 T-SQL 选项卡中的第一行,如下所示:
    [Id] INT NOT NULL PRIMARY KEY identity(1.1),
  1. 接下来添加这一行:
    IMAGE image not null
  1. 更改表的名称为Images,如下所示:
    CREATE TABLE[dbo].Images

这是我们的表,如图 26.5.4所示:

图 26.5.4:SQL Server 中的 dbo.Images 表

然后更新此内容,然后单击出现的对话框中的“更新数据库”按钮。等待更改生效。因此,如果展开表节点,您应该看到一个带有 IMAGE 列的 dbo.Images 表,如图 26.5.5所示:

图 26.5.5:表节点包含带有 IMAGE 列的 dbo.Images 表

将图像文件存储在硬盘上

在下一个阶段的过程中,您必须确保您有要读取的图像。因此,请在C:\驱动器的某个位置放置一些图像。例如,图 26.5.6显示了针对此特定计算机上的C:\data目录运行dir *.jpeg命令时获得的列表:

图 26.5.6:列出存储在 C:\data 目录中的三个图像文件

列表显示这些图像:face1.jpegface2.jpegface3.jpeg。因此,在这种特殊情况下,有三个文件需要从硬盘中读取。

现在在设计视图中双击扫描文件夹按钮。这将带您进入Default.aspx.cs。删除Page_Load存根。我们将处理与此相关的事件。这涉及到相当多的代码,因此这更像是一个项目。此项目的起始代码的相关部分应如图 26.5.7所示:

图 26.5.7:此项目的起始代码

添加命名空间

首先,您需要添加相关的命名空间。因此,在文件顶部附近的using System下面,输入以下内容:

using System.Data.SqlClient;

请记住,我们在连接和命令中使用这个。

在这一行下面输入以下内容:

using System.IO;

同样,这一行是为了能够读取硬盘。这是两个新的命名空间。您现在可以折叠掉以public partial class...开头的所有内容。

编写应用程序

现在让我们逐行通过代码的创建。因此,从以protected void Button1_Click...开头的行开始,在一组花括号中输入以下内容:

var imgFiles = Directory.GetFiles(@"c:\data\", "*.jpg");

在这里,您有一个Directory类和一个名为GetFiles的文件读取方法,它返回一个字符串数组,这些字符串是文件的路径。然后指定它们搜索的目录的路径,所以(@"c:\data\"...),然后您只想搜索图像文件,所以您可以指定一个过滤器,或者在这种情况下是*.jpg。如果您将鼠标悬停在var上,您会看到它是一个字符串数组。

现在您可以加载到ListBox控件中。接下来输入以下内容:

foreach(var imgFile in imgFiles)

接下来,对于文件数组中的每个文件,输入以下内容:

ListBox1.Items.Add(imgFile);

因此,您获取了所有文件路径,然后使用foreach循环将它们添加到ListBox控件中,以便它们可以在页面中显示。这是我们的目标。

测试扫描文件夹功能

进入设计视图,在这一点上,扫描文件夹应该可以工作。为此,点击“扫描文件夹”按钮。

正如您在图 26.5.8中所看到的,文件已加载:

图 26.5.8:文件已正确加载到 ListBox 中

现在这一步完成了,您可以再次使用foreach循环获取每个文件,并将其保存到 SQL Server。接下来我们来做这个。

构建连接字符串

现在在设计视图中双击“保存到 SQL Server”按钮。这将带您回到Default.aspx.cs。正如您可以想象的那样,下一个阶段将是获取连接字符串。您以前已经做过这个。因此,在以protected void Button2_Click...开头的行下面的一组花括号中,首先输入string connString =,然后输入@符号使其成为逐字字符串,然后放入""符号。现在要获取连接字符串,请执行以下操作:

  1. 单击菜单栏中的“查看”,然后选择“SQL Server 对象资源管理器”。

  2. 右键单击“People”数据库,然后选择“属性”。

  3. 在属性窗格中,双击“连接字符串”以选择它及其长描述。

  4. 然后,右键单击长描述并将其复制。

  5. 在“”符号的一组之间粘贴描述。

连接字符串行应该如下所示:

string connString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=People;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";

您可以将此内容分成多行,这样会更整洁一些,如果您愿意的话。现在可以关闭 SQL Server 对象资源管理器和属性窗格。

使用连接字符串

现在我们将使用连接字符串,当然。因此,对于下一个阶段,在另一组花括号中输入以下内容:

using (SqlConnection conn = new SqlConnection(connString))

我们将调用连接字符串conn,并使用连接字符串初始化SqlConnection

接下来,我们需要打开一个连接。在上一行下面的一组花括号中输入以下内容:

conn.Open();

然后,输入以下foreach循环:

foreach(var item in ListBox1.Items)

这里,ItemsListBox控件的一个属性。这是它包含的项目的列表,您可以逐个检查它们,以便对它们采取离散的操作。在另一组花括号中输入以下内容:

using (SqlCommand cmd = new SqlCommand("insert into dbo.Images (image) values(@image)", conn))

请注意,我们将SqlCommand放在using语句中。如果您右键单击SqlCommand并选择“转到定义”,您会看到它说,DbCommand 继承自它,如果您向下滚动到底部,您会看到它有一个Dispose行。要完成这里的代码,您有(image)作为字段,其参数是@image

对于下一个阶段,在另一组花括号中输入以下内容:

byte[] picAsBytes = File.ReadAllBytes(item.ToString());

如果您将前一行仅留在(item),它会在红色下划线下出现错误。因此,我们将其转换为ToString。在这里,我们将每个项目作为一系列字节读取,并将其存储在数组中,因为然后,它可以在 SQL Server 中转换为图像。

输入以下内容:

cmd.Parameters.AddWithValue("@image", picAsBytes);

再次,@image在这里是参数。因此,我们将把图片保存到image参数作为一系列字节。现在输入以下内容:

cmd.ExecuteNonQuery();

这一行执行实际的保存。信不信由你,这就是整个应用程序。

运行程序

现在让我们在浏览器中查看结果。首先,点击“扫描文件夹”。您可以看到图像列表。然后,点击“保存到 SQL Server”按钮。页面上没有显示任何内容,因为我们还没有编写任何代码在保存后显示任何内容。所以现在我们必须检查 SQL Server。

让我们转到“查看”|“SQL Server 对象资源管理器”。右键单击 dbo.Images 表图标,然后选择“查看数据”。正如您在图 26.5.9中所看到的,这些是以低级形式存储的图像。这证实它们已经被保存了:

图 26.5.9:dbo.Images 表中以低级形式存储的图像

也许,作为自己的任务,您可以从 SQL Server 中提取文件并将它们显示为图像。这将是一个有趣的练习。

章节回顾

回顾一下,Default.aspx是扫描文件夹按钮、ListBox和保存到 SQL Server 按钮的源代码。Button1_Click...块内的代码实际上扫描文件夹,然后显示可用的图像文件;也就是说,至少以.jpg结尾的文件。然后,当您想要从ListBox控件保存文件到 SQL Server 时,以连接字符串开头的代码运行。

本章的Default.aspx.cs文件的完整版本,包括注释,如下所示:

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;
using System.Data.SqlClient;
using System.IO;
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        //scan folder for all files ending in jpg
        var imgFiles = Directory.GetFiles(@"c:\data\", "*.jpg");
        foreach(var imgFile in imgFiles)
        {
            //add files to list box in page
            ListBox1.Items.Add(imgFile);
        }
    }
    protected void Button2_Click(object sender, EventArgs e)
    {
        //make a connection string
        string connString = @"Data Source=DESKTOP-4L6NSGO\SQLEXPRESS; Initial Catalog=People;Integrated Security=True;Connect Timeout=15;Encrypt=False;TrustServerCertificate=False; ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
        //make connection
        using (SqlConnection conn = new SqlConnection(connString))
        {
            //open connection
            conn.Open();
            foreach(var item in ListBox1.Items)
            {
                using (SqlCommand cmd = 
                new SqlCommand
                ("insert into dbo.Images (image) values (@image)", conn))
                {
                    //read picture as bytes
                    byte[] picAsBytes = File.ReadAllBytes(item.ToString());
                    //add pictures to SQL server as bytes
                    cmd.Parameters.AddWithValue("@image", picAsBytes);
                    //perform the actual saving
                    cmd.ExecuteNonQuery();
                }
            }
        }
    }
}

摘要

在本章中,您学会了如何读取文件,然后将它们保存在 SQL Server 中作为图像。您创建了一个数据库表来存储文件,在硬盘上存储图像文件,添加了命名空间,测试了扫描文件夹功能,并建立并利用了连接字符串。

在下一章中,我们将介绍 XML 的基础知识,XML 代表可扩展标记语言。