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

274 阅读35分钟

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

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

译者:飞龙

协议:CC BY-NC-SA 4.0

第十章:使用 C#和 LINQ 以及自定义数据类型

在本章中,我们将讨论如何使用 LINQ 与自定义类型。

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

打开一个项目。转到Default.aspx,并在以<form id=...开头的行下面放置一个按钮。要做到这一点,转到工具箱,获取一个Button控件,并将其拖放到那里。更改按钮上的文本以显示“显示人员”:

<asp:Button ID="Button1" runat="server" Text="Show People" />

设置数据库

我们将有一个数据库,我们将对其进行查询,并且我们将展示那些,例如,名字中有某个字母的人,赚取一定数量的钱,并以某种方式进行排序。

为了实现这一点,转到设计视图,并双击“显示人员”按钮。这将带我们进入Default.aspx.cs。删除Page_Load块。该项目的起始代码的相关部分应该看起来像图 10.5.1

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

在下一阶段,首先转到文件顶部,并在using System之后输入以下内容:

using System.Linq;

接下来,我们将创建一个类。我们将其称为Person。因此,在以public partial class...开头的行上面插入以下内容:

public class Person

使用 LINQ 制作自定义类型

现在,在上一行下面的花括号集之间,您将声明两个自动属性,如下所示:

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

然后,为了创建一个构造函数,请在这些行下面输入以下内容:

public Person(string name, decimal salary)

接下来,您将在构造函数内设置属性的值。因此,请在以下这些行下面的一组花括号之间输入以下内容:

Name = name; Salary = salary;

这是我们简单的自定义类型Person,具有两个自动属性和一个带参数的构造函数。

设置一个人员数组

在下一阶段,您将创建一个人员数组;请在以下以protected void Button1_Click....开头的行下面的一组花括号之间输入以下内容:

Person[] people = new Person[] { new Person("John", 76877), new Person("Bobby", 78988), new Person("Joan", 87656) };

查询数组

现在,要查询这个,请在此行下面输入以下内容:

IEnumerable<Person> peopleWithN = people.Where(per => per.Name.EndsWith("n")).OrderByDescending(per => per.Salary);

当您输入时,注意到IEnumerable不会显示出来,因此您必须再次转到文件顶部,并在using System.Linq之后输入以下内容:

using System.Collections.Generic;

现在让我们在下面使用它;因此,在以Person[] people...开头的行下面,输入前面提到的IEnumerable<Person>...行。

在这里,Person是可以从人员列表中枚举的对象类型。peopleWithN表示我们将搜索名字中有字母n的人。实际上,代码搜索名字以n结尾的人。(请注意,per代表列表中的每个人。)此外,我们按工资降序排序。

因为人们有时会不一致地输入信息,所以您首先必须将所有内容转换为等效的情况,但这是您自己要解决的问题。

请记住,在这一行中,我们有people,这是某种对象的名称,以及Where,一个扩展方法,后跟一个 Lambda。接下来,我们使用OrderByDescending,您可以从方法列表中选择它,以按降序排序值,例如人的工资。

因此,此行的目的是选择每个名字以n结尾的人,然后按工资排序结果。这将产生一个IEnumerable对象,现在您当然可以逐步进行,并在下一行中说以下内容:

foreach(Person p in peopleWithN)

现在,为了打印所有内容,请在此行下面的一组花括号之间输入以下内容:

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

在这里,我们首先放置了Name变量,然后是格式化为货币的Salary变量。

运行程序

这是我们程序的核心。在浏览器中启动它。单击“显示人员”按钮,结果将显示如图 10.5.2所示:

图 10.5.2:运行程序的结果

所以,琼赚了87,656.00,约翰赚了87,656.00,约翰赚了76,877.00。他们被选中是因为他们的名字都以小写字母n结尾,正如您所看到的,然后按工资降序排序。所以,它的运行结果符合预期。正如您所看到的,您还可以使用 LINQ 定义自定义类型,比如在public class Person下面的花括号中。它非常强大并且运行良好。

章节复习

为了复习,包括注释在内的本章的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.Linq;
using System.Collections.Generic;
//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; } //auto implemented properties
    public decimal Salary { get; set; }
    public Person(string name, decimal salary)
    {
        Name = name; Salary = salary;//set values of properties
    }
}
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        //make array of people
        Person[] people = new Person[] { new Person("John", 76877), 
                                         new Person("Bobby",78988), 
                                         new Person("Joan", 87656) };
        //find all people with "n" as the last letter, and then display 
        //the results sorted from high to low salary
        IEnumerable<Person> peopleWithN = 
        people.Where(per => per.Name.EndsWith("n")).OrderByDescending
        (per => per.Salary);
        //display name and salary formatted as currency
        foreach (Person p in peopleWithN)
        {
            sampLabel.Text += $"<br>{p.Name} {p.Salary:C}";
        }
    }
}

摘要

在本章中,我们讨论了如何将 LINQ 与自定义类型一起使用。您设置了一个数据库,使用 LINQ 创建了一个自定义类型,设置了一个人员数组,并对数组进行了查询。

在下一章中,您将学习如何使用查询语法编写查询。

第十一章:使用查询语法构造查询

在本章中,您将学习如何使用查询语法编写查询,例如方法链接,就像我们以前做过的那样。

向 HTML 添加显示按钮

打开一个项目,唯一放入中的是一个按钮,没有其他内容。为此,请转到工具箱,获取一个Button控件,并将其拖放到以<form id=...开头的行下方。将按钮上的文本替换为显示:

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

现在,切换到设计视图,并双击显示按钮。这将带我们进入Default.aspx.cs。删除事件处理存根。此项目的起始代码的相关部分应如图 11.6.1所示:

图 11.6.1:此项目的起始代码部分

接下来,转到文件顶部,在using System下输入以下内容:

using System.Collections.Generic;
using System.Linq;

为了利用这一点,我们将做如下操作。这是例行代码;这是机械的。首先,当有人单击显示按钮时,您希望创建一个标签,以便始终有一个累积输出。为此,请在以protected void Button1_Click...开头的行下的大括号之间输入以下内容:

sampLabel.Text = "";

创建一个 decimal 工资数组

接下来,在上一行下面,您将创建一个名为salariesdecimal数组,自然而然地。因此,输入以下内容:

decimal[] salaries = new decimal[] { 56789, 78888, 35555, 34533, 75000 };

这就是您可以查询decimal数组的方法。这是一个特定的decimal数组,但基本上可以是任何数组。我们加入了一些值,就这样。

使用范围变量

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

IEnumerable<string> salResults = from salary in salaries

请注意,返回或结果集将是string类型,而不是decimal类型。在salResults =之后,您希望定义 LINQ 查询的主体,因此您说from salary in salaries。如果您将鼠标悬停在此处的salary上,您会看到它被称为范围变量,如图 11.6.2所示。因此,您要求它查看salaries。作为范围变量,它是逐个遍历所有条目的数量。

图 11.6.2:范围变量

选择工资范围并按降序排列

现在,您将指定某种逻辑条件。例如,以某种方式过滤结果。因此,在此行下缩进输入以下内容:

where 35000 <= salary && salary <= 75000
select $"<br>{salary:C}";

接下来,您可以对结果集进行orderby,以按降序列出工资,例如;因此,请直接在此行下面输入以下内容:

orderby salary descending

默认情况下是按升序排列的,从小到大,您希望将其反转。当您加入descending关键字时,它就会从大到小排列。

接下来,记住目标是获得一个填充有字符串的IEnumerable构造。因此,最后,请在此代码块的一部分中输入以下内容:

select $"<br>{salary:C}";

您还可以在原地格式化结果,就像我在这一行中所做的那样,例如,以货币格式。

显示结果

有了这个代码块,当然,下一阶段是迭代并显示结果。为此,您可以进行转换为列表并打印,或者您可以接下来做以下操作:

foreach(string formattedSalary in salResults)

我们怎么知道在这一行中应该说string?记住,前面的IEnumerable行填充有字符串,对吗?如果您将鼠标悬停在IEnumerable上,它会说IEnumerable<out T>,而T是字符串。

现在,为了显示结果,请在上一行下面的一对大括号之间输入以下内容:

sampLabel.Text += formattedSalary;

在这里,formattedSalary是要显示的内容。

现在,在浏览器中打开此内容。单击显示按钮。结果将显示如图 11.6.3所示:

图 11.6.3:运行程序的初始结果

这里的工资按降序排列,从高到低,并且在 75000 美元到 35000 美元的范围内。所以,这正如预期的那样工作。

观察延迟执行

现在,你应该知道的一件事是所谓的延迟执行的概念。所以,为了了解这意味着什么,看看接下来的内容。

想象一下,我在foreach(string formattedSalary in salResults)行右边放了一个断点。然后,从调试菜单中选择单步执行,并点击显示按钮。注意每一行是如何连续运行的(每行后面都会显示毫秒数)。你应该看到它是如何进入的;它是如何运行的。

你应该注意的一件事是 LINQ 的延迟执行的概念,这意味着salResults实际上是运行的,正如你所看到的,所以它本质上是一个查询变量。它在你使用foreach循环迭代时运行,就像下面的代码块中所示的那样,而不是在你在前面的IEnumerable块中编写时运行。它不会在那时运行。它会在你迭代它时运行。否则,你的程序可能会在这些查询结果中携带大量信息。所以,这就是延迟执行的内容:

IEnumerable<string> salResults = from salary in salaries
                                 where 35000 <= salary && salary <= 75000
                                 orderby salary descending
                                 select $"<br>{salary:C}";
        foreach(string formattedSalary in salResults)
        {
            //display formatted salaries one at a time
            sampLabel.Text += formattedSalary;
        }

在下一阶段,我们将看一个可能能做的另一个实际例子。我们还想显示水平线,所以在sampLabel.Text += formattedSalary行下面的闭合大括号之后,输入以下内容:

sampLabel.Text += "<br><hr/>";

<hr/>标签将在输出中添加一条水平线。

创建一个字典

接下来,我们将创建一个Dictionary;为此,请在下面输入以下行:

Dictionary<string, decimal> nameSalaries = new Dictionary<string, decimal>();

在这里,<string,decimal>代表键值对。

处理键值对

现在,让我们添加一些键值对。所以,从输入以下内容开始:

nameSalaries.Add("John Jones", 45355);

在这一行中,John Jones是键,值是他的薪水,或者$45,355。

然后,你可以重复这个几次,所以直接在它下面再粘贴这一行三次。比如,约翰·史密斯,76900;约翰·詹金斯,89000;史蒂夫·乔布斯,98000:

nameSalaries.Add("John Smith", 76900);
nameSalaries.Add("John Jenkins", 89000);
nameSalaries.Add("Steve Jobs", 98000);

请注意,我在这里重复了名字约翰几次,因为我想简要说明一个概念。最后一个列出的是史蒂夫·乔布斯,当然他的薪水远远超过了 98000!

查询键值对中的数据

现在,我们将再次查询这个。这是我们在键值对中拥有的数据,我们将对其进行查询。所以,在这些行下面输入以下内容:

var dictResults = from nameSalary in nameSalaries

在这里,nameSalary是一个范围变量,它指的是约翰·琼斯、约翰·史密斯、约翰·詹金斯、史蒂夫·乔布斯等等,而nameSalaries是字典本身。nameSalary是键和值的特定组合。

然后,在这一行下面,缩进以下代码:

where nameSalary.Key.Contains("John") && nameSalary.Value >= 65000

在这里,我们说键包含名字约翰,薪水大于或等于$65,000。如果你愿意,你可以添加OrderBy等等,但对于我们的目的,直接在这一行下面输入以下内容:

select $"<br>{nameSalary.Key} earns {nameSalary.Value:C} per year.";

在这一行中,我们将选择那些符合这两个条件的记录:姓名是约翰,薪水超过$65,000。所以,在我们的情况下,肯定是约翰·史密斯和约翰·詹金斯。为了使输出看起来好看,我们说nameSalary.Value:C以货币格式化,然后添加per year

现在,将鼠标悬停在dictResults上。你看到弹出提示中说IEnumerable吗?现在,var被称为隐式类型。我们之前见过var。有时很难从像我们创建的这样的查询中判断输出会是什么,因为它足够复杂。所以,如果你使用隐式类型,它会告诉你输出应该是什么,所以是string类型的IEnumerable。现在,如果你愿意,你可以将这一行改为:

IEnumerable<string> dictResults = from nameSalary in nameSalaries

现在我们也知道这一点,因为在这个查询的末尾,你看到了字符串,对吧?这些是包含格式化信息的字符串,当然,你可以像往常一样迭代它们;所以,接下来输入以下内容:

foreach(string nameSal in dictResults)

然后,在这一行下面的一对大括号之间,以以下内容结束:

sampLabel.Text += nameSal;

运行程序

在浏览器中运行这个,确保它按预期工作。点击显示按钮。结果显示在图 11.6.4中:

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

现在你已经得到了我之前描述的第一组结果,第二组结果显示在它们下面,显示两个名字都包含 John,金额为$65,000 或更多。

章节回顾

作为回顾,包括注释的本章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;
using System.Linq;
//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)
    {
        //clear label on button click
        sampLabel.Text = "";
        //make array of salaries
        decimal[] salaries = 
        new decimal[] { 56789, 78888, 35555, 34533, 75000 };
        //construct Linq query, which produces a collection of 
        //formatted strings
        IEnumerable<string> salResults = from salary in salaries
                              where 35000 <= salary && salary <= 75000
                              orderby salary descending
                              select $"<br>{salary:C}";
        foreach(string formattedSalary in salResults)
        {
            //display formatted salaries one at a time
            sampLabel.Text += formattedSalary;
        }
        //show horizontal rule on screen
        sampLabel.Text += "<br><hr/>";
        //make dictionary to hold names and salaries as key/value pairs
        Dictionary<string, decimal> nameSalaries = 
        new Dictionary<string, decimal>();
        nameSalaries.Add("John Jones", 45355);
        nameSalaries.Add("John Smith", 76900);
        nameSalaries.Add("John Jenkins", 89000);
        nameSalaries.Add("Steve Jobs", 98000);
        //query below represents all people named John who make 65000 
        //and more
        //this query gives back a formatted string for each key/value 
        //pair that 
        //satisfies the condition
        IEnumerable<string> dictResults = from nameSalary in nameSalaries
                            where nameSalary.Key.Contains("John") && 
                            nameSalary.Value >= 65000
                            select $"<br>{nameSalary.Key} earns 
                            {nameSalary.Value:C} per year.";
        foreach(string nameSal in dictResults)
        {
            sampLabel.Text += nameSal;//display named and salaries
        }
    }
}

总结

在本章中,你学会了如何使用查询语法编写查询。你创建了一个十进制薪水数组,使用了范围变量,观察了延迟执行,创建了一个字典,使用了键值对,查询了键值对中的数据,并学习了隐式类型。

在下一章中,我们将进一步探讨 LINQ。具体来说,我们将研究 LINQ 的一些强大功能,如平均、求和和计数等聚合函数。此外,我们还将讨论列表的列表,这是非常实用的东西。

第十二章:执行聚合函数的查询

在本章中,我们将进一步探讨 LINQ。具体来说,我们将看看 LINQ 执行聚合函数的能力,比如平均值、求和、计数等。此外,我们还将讨论列表的列表,这是一个非常实用的东西。

在 HTML 中添加显示按钮

打开项目,并且为了保持简洁,我们将在以<form id=....开头的行下面放一个按钮。要做到这一点,去工具箱,拖动一个Button控件,并把它拖到那里。将按钮上的文本更改为Show

现在,切换到设计视图,并双击显示按钮。这将带我们进入Default.aspx.cs。删除Page_Load块。我们不需要那个。这个项目的起始代码的相关部分应该看起来像图 12.7.1

图 12.7.1:这个项目的起始代码部分

在下一阶段,转到文件顶部,在using System下面输入以下内容:

using System.Collections.Generic;
using System.Linq;

创建一个数组

在本章中有很多要输入的代码,但这是机械的。首先,我们将创建一个数组,所以在以protected void Button1_Click...开头的行的大括号之间输入以下内容:

IEnumerable<int> scores = new int[] { 45, 98, 99, 78, 89, 87, 77, 67, 71, 81 };

在这里,IEnumerable是数据类型,scores是数组的名称。你放入数组的值并不重要。

对列表中的值求平均值

现在,首先我们将找到这个列表的平均值。所以,输入以下内容:

var goodStudentAverage = (from score in scores where score >= 90 select score).Average();

我们将选择得分为90或以上的学生。想象一下,这些是几个学生的学期成绩。因此,在前一行中,我们说得分是>=90,选择那个分数。这是一个你可以在一行中写的查询。在这个上下文中,score是范围变量,scores是数组,选择的条件是where score=>90。然后,你输入.(点)Average()来平均整个东西。换句话说,这样写的方式是,括号中的查询将运行,然后平均数组中的值。如果你将鼠标悬停在这一行的var上,你会看到它说double,因为,如果你将鼠标悬停在Average上,你也会看到它返回一个double。因此,这个Average()函数作用于IEnumerable类型的列表,但它返回一个double数据类型给我们。

显示结果

现在,你当然可以显示结果,因为记住它只是一个单一的数值,一个聚合数量。你现在可以在这一行下面说以下内容:

sampLabel.Text = $"<br>The average for great students is {goodStudentAverage}";

使用 Count 函数

现在,如果你愿意,你也可以使用Count函数,所以你可以说下面的内容:

var averageStudentCount = scores.Where(grade =>70 <= grade && grade <80).Count();

在以var开头的前一行中,我们在单行中使用了查询语法,或者内联查询语法,因为我们使用了fromwhere。现在,我们可以使用方法链接和其中的 Lambda 表达式来表达相同的事情。所以,这里我们说scores.Where,然后我们说grade是这样的,即70 <=grade,但grade <80。因此,我们正在定义那些得分在7080之间的人,不包括80的分数,并且我们将它们标记为平均学生。然后我们将Count它们。这将告诉我们有多少这样的人,然后我们可以显示那个数字。例如,你可以输入以下内容:

sampLabel.Text += $"<br>There are {averageStudentCount} average students.";

记住,averageStudentCount产生一个数字,所以,例如,结果可能是,有 25 个平均学生

使用列表的列表

现在,这个概念的一个非常现实的应用可能是有一个列表的列表。首先输入以下内容:

List<int> firstStudent = new List<int> { 90, 89, 92 };

想象一下,您有一个学生firstStudent。然后,他或她有一些成绩,所以您创建了整数的new List,然后在一对大括号中初始化了这个列表。因此,按照所示的方式添加一些值。(请注意,我输入的值在90 +/-范围内。)这是您可以以前未见过的方式初始化列表。

现在,让我们再为另一个学生做一个整数的列表。为此,输入以下内容以为secondStudent创建new List。同样,使用另一组值初始化此列表。(请注意,在这一行中,我将输入的值在80 +/-范围内。)现在,当您有一个完整的班级时,您将有这样的列表,对吧?这是因为您有多个学生在一个班级中:

List<int> secondStudent = new List<int> { 78, 81, 79};

因此,现在您可以创建构造函数。接下来输入以下内容:

List<List<int>> classList = new List<List<int>>();

向 classList 添加学生

在这里,我们有一个整数的列表列表——您可以在其他列表中嵌入列表。然后,我们会说,例如,classList列表等于一个新的列表列表。要初始化此列表,您可以使用Add。在下一行,输入以下内容:

classList.Add(firstStudent);
classList.Add(secondStudent);

这是如何将第一个学生、第二个学生等添加到班级列表中的方法。

总结 classList 中的信息

在下一阶段,您希望能够获得一些有用的信息。例如,想象一下您有这样一个列表的列表,您希望进行总结。因此,接下来输入以下内容:

var avgPerStudent = classList.Select(student => student.Average());

现在,以avgPerStudent为例,表示平均学生分数。现在,在输入classList.Select()之后,要选择的数量是代表每个学生的列表,由(student => student.Average())捕获。现在,请确保您理解student参数是什么。在这里,您选择一个学生并平均他们的成绩。将鼠标悬停在student上,您会看到该数量代表与第一个学生对应的整数列表。然后,student.Average表示对该学生进行平均,然后对下一个学生重复此过程。如果将鼠标悬停在var上,您会看到在这种情况下返回的是IEnumerable类型。您可以迭代这些值。要做到这一点,您将输入以下内容:

foreach(var studentAvg in avgPerStudent)

现在,在此行下方,输入以下内容以在一对大括号中显示结果:

sampLabel.Text += $"<br>Average grade={studentAvg}";

运行程序

现在,构建此程序并在浏览器中运行它。单击“显示”按钮:

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

现在这些是一些专业的结果。优秀学生的平均分是 98.5。有三个平均学生。两个列表的扩展平均成绩显示在最后。

因此,您学到了更多关于 LINQ 的知识——Average函数和Count函数,还学会了如何制作一个列表的列表。您可以使用Select等语句对这些列表进行操作,然后可以嵌入 Lambda 表达式以单独处理列表中的每个列表。

章节回顾

回顾一下,包括注释在内的本章的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;
using System.Linq;
//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)
    {
        IEnumerable<int> scores = 
        new int[] { 45, 98, 99, 78, 89, 87, 77, 67, 71, 81 }; 
        //array of integers
        //line 17 below selects all scores 90 or above, and averages them,
        //giving back a double value
        var goodStudentAverage = (from score in scores where score >= 90 select score).Average();
        //line 19 below displays the average
        sampLabel.Text = $"<br>The average for great students is {goodStudentAverage}";
        //line 21 below selects all students below 70 and 80,
        //and counts them
        var averageStudentCount = scores.Where(grade => 70 <= grade && grade < 80).Count();
        //line 23 below displays the student count
        sampLabel.Text += $"<br>There are {averageStudentCount} average students.";
        //lines 25 and 26 create two new lists with initializer lists
        List<int> firstStudent = new List<int> {90,89,92};
        List<int> secondStudent = new List<int> { 78, 81, 79 };
        //line 28 creates a list of lists
        List<List<int>> classList = new List<List<int>>();
        classList.Add(firstStudent);
        classList.Add(secondStudent);
        //line 32 below find the average for each list, and 
        //stores the averages
        //so avgPerStudent is of type IEnumerable
        var avgPerStudent = classList.Select(student => student.Average());
        //lines 35-38 display the averages
        foreach(var studentAvg in avgPerStudent)
        {
            sampLabel.Text += $"<br>Average grade={studentAvg}";
        }
    }
}

摘要

在本章中,我们进一步探讨了 LINQ。具体来说,我们研究了 LINQ 执行聚合函数(如平均、求和和计数)的能力。此外,我们还讨论了列表的列表。您对列表中的值进行了平均,使用了Count函数,处理了列表的列表,向classList添加了学生,并总结了classList中的信息。

在下一章中,您将学习关于元组的知识,它们基本上是包含多个值的集合。

第十三章:使用 LINQ 对元组进行总结

在本章中,您将了解元组。这些基本上是几个值的集合。

在 HTML 中添加显示元组摘要值按钮

打开一个项目,并在以<form id=....开头的行下放置一个按钮。将按钮文本替换为显示元组摘要值

现在,切换到设计视图,并双击显示元组摘要值按钮。这将带我们进入Default.aspx.cs。删除Page_Load块。该项目的起始代码的相关部分应该看起来像图 13.8.1

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

介绍元组

现在,首先我们将创建一个返回元组值的函数。那么,什么是元组?让我们定义它们。正如我之前所说,它基本上是几个值的集合。现在,在 C#中,这意味着您将在以public partial class...开头的行下的封闭大括号下方输入以下内容:

private static Tuple<double, double, double, double> SummarizeList(List<double> listDoubles)

在前一行中,Tuple是一个类。然后,要定义元组存储的值的数量,请记住我们与向量的工作。我们向向量添加了两个或三个值。这是一个类似的概念。如果您将鼠标悬停在Tuple上,它会说Tuple 表示 n 元组,其中n是八或更多,因此 T1、T2、T3,一直到 TRest。哇,所以您可以创建八个或更多的元组!

添加命名空间

在我们的案例中,我们放置了<double, double, double, double>。所以,这是一个可以容纳四个值的元组。请注意,当您输入时,List<double>没有显示,因此您需要添加一些命名空间。在文件顶部的using System下,输入以下内容:

using System.Collections.Generic;
using System.Linq;

在这里,我们使用了通用集合和 LINQ,现在List<double>显示为高亮显示,应该称为listDoubles

使用元组制作列表

在过程的下一个阶段,您将创建此列表。因此,在以下行的大括号之间输入以下内容:

Tuple<double, double, double, double> summary = Tuple.Create(listDoubles.Sum(),listDoubles.Average(),listDoubles.Max(), listDoubles.Min());

要形成元组,您说Tuple.Create(listDoubles.Sum()Tuple是类的名称,该类内的成员之一是Create函数,因此选择它。现在,我们可以创建一个具有四个条目的元组。接下来,我们说listDoubles.Sum()。请注意,当您输入Sum时,它是一个扩展方法。如果您删除Sum,您会注意到Linq变为灰色。这再次确认了为什么需要Linq——用于Sum函数。

这个元组中的第一个条目是列表的总和。记住,我们正在调用summary。所以,这就像是列表中条目的统计摘要。除了listDoubles.Sum(),您当然也可以有其他一些。您可以有一个平均值,listDoubles.Average(),您还可以添加listDoubles.Max()listDoubles.Min()

返回元组

最后,您可以返回元组。为此,请在以下行下输入以下内容:

return summary;

在您之前写的第一行中,记住private表示只有在那里可访问,static表示它在类级别上运行,这意味着您可以直接使用名称调用SummarizeList——您不需要将其放在对象上。

现在,在这种特殊情况下,它将返回这个结构,Tuple<double, double, double, double>,称为元组,在这里只是一种存储四个双精度值的方式。然后,要为第一个条目创建一个元组,您使用 LINQ。然后您使用 LINQ 来获取第二个条目,LINQ 来获取第三个条目,最后,LINQ 来获取第四个条目。因此,SumMinMaxAverage都是扩展方法,然后您将其return

创建一个双精度列表

现在,对于下一阶段,看一下按钮单击事件。这里的代码非常简单。首先,在以protected void Button1_Click...开头的行下的大括号内输入以下内容。您将创建一个名为lst的双精度列表,如下所示:

List<double> lst = new List<double> { 1, 2, 5, 68, 899, 1, -989, 0.1143, 98, 2553 };

new List of double值之后,通过在大括号中添加一些数字来指定初始化程序——不管它们是什么,对吧?放一些负数、一些小数、一些整数等等。

总结列表

接下来,我们将调用SummarizeList。因此,请在此行下面输入以下内容:

var results = SummarizeList(lst);

在这种情况下,老实说,var很容易,对吧?如果你不使用它,你将不得不输入Tuple<double, double, double, double>,这将是数据类型。换句话说,那真的很啰嗦,而且占用了很多空间。所以,请记住,var表示隐式数据类型,但它足够聪明,知道数据类型是什么。

显示结果

然后一旦你返回它,你可以去那里的项目商店。因此,你可以输入以下内容:

sampLabel.Text = $"Sum={results.Item1}";

在下一阶段,复制这行并直接粘贴到下面。编辑Average函数的文本如下:

sampLabel.Text += $"<br>Average={results.Item2}";

确保你调用这些行的方式与函数对应;所以,SumAverageMaxMin。再次复制上一行并直接粘贴到下面,这样你就不必追加了。由于下一个是为Max,编辑文本如下:

sampLabel.Text += $"<br>Max={results.Item3}";

这将是Item3和你可以提取的元组。

最后,让我们再做一次。因此,再次复制上一行并直接粘贴到下面。由于最后一个是为Min,编辑文本如下:

sampLabel.Text += $"<br>Min={results.Item4}";

当然,这是Item4和你可以提取的元组。

运行程序

现在,让我们在浏览器中加速。点击显示元组摘要值按钮。结果显示在图 13.8.2中:

图 13.8.2:运行本章程序的结果

你看到了 Sum、Average、Max 和 Min,所以它的工作符合预期。

现在,作为对此更现实的扩展,想象一个元组列表。你肯定可以做到,所以你可以在最后一行下面添加类似以下内容:

List<Tuple<string, double, double, decimal>>;

你可以有一个元组列表。每个元组代表,例如,关于一个人的信息,然后你会有一个人的列表。这是让你思考的事情:如何构建它并为自己做一个项目。然而,这些都是基础。

章节回顾

回顾一下,包括注释的本章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;
using System.Linq;
//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
{
    private static Tuple<double, double, double , double> SummarizeList(List<double> listDoubles)
    {
        Tuple<double, double, double, double> summary = 
        Tuple.Create(listDoubles.Sum(),
        listDoubles.Average(), listDoubles.Max(), listDoubles.Min());
    return summary;
    }
    protected void Button1_Click(object sender, EventArgs e)
    {
        List<double> lst = 
        new List<double> { 1, 2, 5, 68, 899, 1, -989, 0.1143, 98, 2553 };
        var results = SummarizeList(lst);
        sampLabel.Text = $"Sum={results.Item1}";
        sampLabel.Text += $"<br>Average={results.Item2}";
        sampLabel.Text += $"<br>Max={results.Item3}";
        sampLabel.Text += $"<br>Min={results.Item4}";
    }
}

总结

在本章中,你学习了关于元组的知识,它基本上是几个值的集合。你创建了一个带有元组的列表,返回了元组,并对列表进行了总结。

在下一章中,我们将讨论使用 LINQ 来对相关结果进行分组。分组是在数据库中对结果进行分类的基本操作。

第十四章:用分组总结结果

在本章中,我们将讨论使用 LINQ 对相关结果进行分组。分组是在数据库中对结果进行分类的基本操作。

在 HTML 中添加一个显示结果按钮

打开一个项目。首先,我们将在 HTML 中放置一个按钮,上面写着“显示结果”;要做到这一点,在以<form id=....开头的行下面放置一个按钮。

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

接下来,切换到设计视图,并双击“显示结果”按钮。这将带我们进入Default.aspx.cs。删除Page_Load块。这个项目的起始代码的相关部分应该看起来像图 14.9.1

图 14.9.1:这个项目的起始代码部分

添加命名空间

首先,我们需要添加一些命名空间。要做到这一点,在文件顶部的using System下面输入以下内容:

using System.Linq;
using System.Collections.Generic;

创建学生类并定义字段

接下来,我们将创建一个名为Student的类。在以public partial class _Default...开头的行之上输入以下内容:

public class Student

接下来,要定义字段,在这一行下面的一对大括号之间输入以下内容:

public string Name { get; set; }

这里有一些属性,然后让我们再添加一个。在这一行下面输入以下内容:

public List<int> Grades;

在这里,List<int>是学生的成绩,让我们称之为Grades

制作学生名单

现在,在下一个阶段,我们将制作一个学生名单。要做到这一点,首先在以protected void Button1_Click...开头的行之后的一对大括号之间输入以下内容:

List<Student> students = new List<Student>

在这里,students是列表的名称。然后我们有一个新的学生列表。接下来,为了初始化列表,我们将把所有新学生放在这一行下面的一对大括号之间,开始如下:

new Student {Name="Smith, John", Grades=new List<int> {78,98,67,87 } },

在前一行的new Student之后,你需要在一对大括号中分别放入每个学生的所有信息。首先,你需要定义Name的值,所以你将其设置为Smith,例如,John,插入一个逗号,然后在一个新的整数列表中放入 John 的Grades,将这些值设置为78986787

接下来,我们需要为其他学生重复几次这个过程;所以,复制这一行,然后粘贴到下面。编辑这一行,将Name变量的值更改为AdamsAmy,然后将成绩更改为91998993

new Student {Name="Adams, Amy", Grades=new List<int> {91,99,89,93 } },

这种编码水平非常实用和现实。在编码了五年之后,我可以告诉你,事情总是更有趣和更具挑战性。

现在,再重复一次这个过程。复制前面的行,然后粘贴到下面。编辑这一行,将Name变量的值更改为SmithMary,然后将成绩更改为89878488。确保在最后一个new Student类的下一行插入一个闭合的大括号和一个分号:

new Student {Name="Smith, Mary", Grades=new List<int> {89,87,84,88 }};

分组名称

同样,因为我们想要按姓和名分组,所以我使用了两个相同的姓。我们将以姓的首字母和名字的顺序进行分组显示结果。

接下来,我们将编写 LINQ 查询来完成分组。同样,这可以以更复杂的方式完成,但这是一个相对简单的例子。所以,在列表中最后一个new Student类的下一行输入以下内容:

var groupsByFirstLetters = from student in students group student by student.Name[0];

记住,groupsByFirstLetters表示姓的第一个字母。所以,要编写查询,你说fromstudentstudents中,然后在下一行你group学生按student.Name分组。因为Name是一个字符串,你可以通过使用方括号提取第一个字符,然后在字符串中获取索引为0的值。这就是你可以写的原因。否则,它会显得有点神秘。

显示分组结果

现在,要以分组的方式显示结果,你必须使用嵌套的foreach循环。所以,接下来输入以下内容:

foreach(var studentGroup in groupsByFirstLetters)

在这里,情况变得更有趣。如果你将鼠标悬停在var上,它会告诉你var代表什么。它说,它是一个字符和学生的分组。它代表具有共同键的对象集合

现在,我们可以按以下方式使用它。在前一行下面的一对大括号之间输入以下内容:

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

首先,我们想显示键,也就是每个姓氏的第一个字母,然后所有内容将在该姓氏的第一个字母下进行总结。所以,我们说studentGroup.Key。这里有一个叫做Key的属性,它是每个组的分组键。请记住,这里我们是按姓氏的第一个字母进行分组。所以,键就是那个数量。

接下来,一旦你修复了该组内的第一个字母,通常会有几个学生或几个项目,对吧?所以,现在你需要逐个显示这些项目。在下面输入以下内容:

foreach(var st in studentGroup)

注意一下关于foreach循环嵌套的情况。你看到了吗,在foreach (var studentGroup in groupsByFirstLetters)这一行中,外部的for循环获取了studentGroup变量,然后该组的键通过sampLabel.Text += $"<br>{studentGroup.Key}"这一行显示出来了?接下来,你将遍历每个组内的学生。这就是为什么在下一阶段,如果你将鼠标悬停在前一行的var上,你会看到它显示student ststudentGroup中。这就是细节。

接下来,为了显示它,输入以下内容在前面foreach行的大括号中:

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

这就是核心。现在记住,我们从一个叫做Student的类开始。然后我们有一个学生列表。请注意,在学生列表中,你也可以使用一种语法,即属性的名称,然后是属性的值,不需要括号。你可以直接使用大括号在学生列表的定义中创建对象。

var groupsByFirstLetters...开头的代码块为我们分组。然后我们需要外部循环foreach (var studentGroup...来显示每个组的键。然后内部的foreach循环foreach (var st in studentGroup)显示该组内的学生。所以,这两个循环是必需的,它们有不同的作用。

现在在浏览器中运行一下,看看结果。点击“显示结果”按钮。如你所见,在图 14.9.2中,你有字母 S,这是第一组的键,组内有 Smith, John 和 Smith, Mary。接下来,你有字母 A,这是第二组的键,组内有 Adams, Amy:

图 14.9.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.Linq;
using System.Collections.Generic;
//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 Student //define student class
{
    public string Name { get; set; }
    public List<int> Grades;
}
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        //create list of students
        List<Student> students = new List<Student>
        {
            new Student {Name="Smith, John", 
            Grades=new List<int> {78,98,67,87}},
            new Student {Name="Adams, Amy", 
            Grades=new List<int> {91,99,89,93}},
            new Student {Name="Smith, Mary", 
            Grades=new List<int> {89,87,84,88}}
        };
        //create query that groups students by first letter of last name
        //Name is a string, so student.Name[0] means grab the 
        //first character for grouping
        var groupsByFirstLetters = 
        from student in students group student by student.Name[0];
        //the outer loop is needed to display the "Key", 
        //which is the first letter for each group
        foreach(var studentGroup in groupsByFirstLetters)
        {
            sampLabel.Text += $"<br>{studentGroup.Key}";
            //the inner loop is needed to display the students 
            //within each group
            foreach(var st in studentGroup)
            {
                sampLabel.Text += $"<br>{st.Name}";
            }
        }
    }
}

总结

在本章中,我们讨论了使用 LINQ 对相关结果进行分组。你创建了一个学生类并定义了字段,创建了一个学生列表,对名字进行了分组,最后显示了分组结果。

在下一章中,你将学习如何使用 LINQ 编写查询,以连接不同的结果集或不同的数据集。

第十五章:使用内连接加入数据集

在本章中,您将学习如何使用 LINQ 编写查询,以连接不同的结果集或不同的数据集。本章的代码并不是很复杂,只有一点点。

在 HTML 中添加一个连接类的按钮

打开一个项目。在 HTML 页面中放置一个按钮,上面写着连接类,放在以<form id=....开头的行下面。因此,我们将有两个不同的类,然后将它们连接在一起,产生一些结果,然后显示它们。这是目标:

<asp:Button ID="Button1" runat="server" Text="Join Classes" />

接下来,切换到设计视图,并双击连接类按钮。这将带我们进入Default.aspx.cs。删除Page_Load块。该项目的起始代码的相关部分应如图 15.10.1所示:

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

添加命名空间

现在,我们将编写以下代码。我们需要 LINQ 和泛型集合命名空间;因此,在文件顶部的using System下面输入以下内容:

using System.Linq;
using System.Collections.Generic;

创建人员和汽车类

我们将创建两个类。一个是person,另一个是car类。为此,请在以public partial class _Default...开头的行的正上方直接输入以下内容:

public class Person

现在,我们只需要一个名字;因此,在此行下面的一对大括号之间输入以下内容:

public string Name { get; set; }

然后,我们还需要创建一个名为Car的类。因此,在前一行下面的封闭大括号下面输入以下内容:

public class Car

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

public Person Owner { get; set; }

如您现在所见,public Person 被定义为类内字段的数据类型。例如,一辆车有一个所有者。

现在,在前一行下面添加另一个数据类型,如下所示:

public string Maker { get; set; }

显然,您可以看到Car类内有Person字段。这些类之间存在连接。我们很快就会用到这个。现在,让我们来构建。

创建人员对象

首先,我们必须创建一些Person对象,否则我们将没有任何东西可以连接。因此,在以protected void Button1_Click...开头的行下面的一对大括号之间输入以下内容:

Person per1 = new Person() { Name = "Mark Owens" };

现在,复制此行并将其直接粘贴到下面。编辑该行,将其更改为Person per2,并将Name变量的值更改为Jenny Smith

Person per2 = new Person() { Name = "Jenny Smith" };

最后,复制前面的行并将其粘贴到下面。编辑该行,将其更改为Person per3,并将Name变量的值更改为John Jenkins

Person per3 = new Person() { Name = "John Jenkins" };

所以,现在我们有一些人将成为汽车的所有者。

创建汽车对象

现在,让我们创建一些car对象。跳过一行,然后输入以下内容:

Car car1 = new Car() { Owner = per1, Maker = "Honda" };

要初始化car1,您可以从Owner = per1开始。这建立了两个类之间的连接;也就是说,car1的所有者是per1,即Mark Owens。然后,您添加制造商,我们将说car1的制造商是Honda

再次复制此行,并将其直接粘贴到前一行下面。编辑该行,将其更改为Car car2,所有者更改为per2,但制造商保持为Honda

Car car2 = new Car() { Owner = per2, Maker = "Honda" };

有时,不幸的是,为了阐明一个概念,我必须写相当多的代码,否则很难阐明这个概念。

再次复制前面的行并将其粘贴到下面。编辑该行,将其更改为Car car3Owner更改为per1,但这次将Maker更改为Toyota

Car car3 = new Car() { Owner = per1, Maker = "Toyota" };

最后,复制前面的行并将其粘贴到下面。编辑该行,将其更改为Car car4Owner更改为per4,并将Maker更改为Tesla

Car car4 = new Car() { Owner = per2, Maker = "Tesla" }; 

当然,要注意的是,per3变量并未被用作汽车的所有者,对吧?因此,当我们进行连接时,连接这两个数据集的查询将返回共享的记录。这意味着,例如,没有一辆车是由per3拥有的。

创建所有者及其汽车的列表

接下来,跳过一行,输入以下内容:

List<Person> people = new List<Person> { per1, per2, per3 };

在这里,我们说一个人的列表,people,等于一个新的人的列表,然后,我们把这些个体——per1per2per3放进去。接下来,你要对汽车做同样的事情,所以输入以下内容:

List<Car> cars = new List<Car> { car1, car2, car3, car4 };

再次,要初始化汽车列表,你说car1car2car3car4

连接所有者和汽车列表

现在,你可以连接这些列表。要做到这一点,略过一行,然后输入以下内容:

var carsWithOwners = from person in people

对于有所有者的汽车,你可以编写查询:from person in people。接下来,继续输入以下内容:

join car in cars on person equals car.Owner

在这里,我们正在连接这两个列表。我们使用personjoin people,并将其设置为car.Owner。然后,一旦它们连接起来,拥有汽车的人基本上,你可以接着说以下内容:

select new{ OwnerName = person.Name, CarMake = car.Maker };

在这一行中创建了一个匿名类型。因此,如果你将鼠标悬停在var上,它会说 T 是'a。这是一个匿名数据类型。因此,carsWithOwners基本上是一个匿名类型的列表,但因为它是一个列表,而且是IEnumerable,你可以使用foreach循环逐步进行遍历。

获取并显示结果

现在我们需要获取结果。所以,略过一行,说以下内容:

foreach(var ownedCar in carsWithOwners)

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

sampLabel.Text += $"<br>Owner={ownedCar.OwnerName} Car Make={ownedCar.CarMake}";

这将为我们显示结果。

运行程序

现在在浏览器中打开这个,然后点击连接类按钮。看一下结果,也显示在图 15.10.2中:

图 15.10.2:这个项目的结果

所以,马克·欧文斯有两辆车。接下来,珍妮·史密斯有一辆本田和一辆特斯拉。对吗?

现在,因为约翰·詹金斯是per3,他不会出现在汽车列表中作为所有者。这意味着per3Car列表之间没有连接。换句话说,在 LINQ 中进行连接时,会使用per1,因为它是按所有者Car.Owner进行的。因此,将使用per1per2,但不会使用per3。然后,显示结果。

章节回顾

回顾一下,包括注释在内的本章的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.Linq;
using System.Collections.Generic;
//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
{
    //define Person class
    public string Name { get; set; }
}
public class Car
{
    //define Car class, using field of type Person
    public Person Owner { get; set; }
    public string Maker { get; set; }
}
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        //make three new people
        Person per1 = new Person() { Name = "Mark Owens" };
        Person per2 = new Person() { Name = "Jenny Smith" };
        Person per3 = new Person() { Name = "John Jenkins" };
        //make four new cars
        Car car1 = new Car() { Owner = per1, Maker = "Honda" };
        Car car2 = new Car() { Owner = per2, Maker = "Honda" };
        Car car3 = new Car() { Owner = per1, Maker = "Toyota" };
        Car car4 = new Car() { Owner = per2, Maker = "Tesla" };
        //make lists of people and cars
        List<Person> people = new List<Person> { per1, per2, per3 };
        List<Car> cars = new List<Car> { car1, car2, car3, car4 };
        //use linq to write a query that joins the two lists by car Owner
        //here, the type of var is an enumerable list of anonymous 
        //data types
        var carsWithOwners = from person in people join car in cars on person equals car.Owner
        select new { OwnerName = person.Name, CarMake = car.Maker };
        //foreach loops iterates over carsWithOwners
        foreach(var ownedCar in carsWithOwners)
        {
            sampLabel.Text += $"<br>Owner={ownedCar.OwnerName} Car Make= {ownedCar.CarMake}";
        }
    }
}

总结

在本章中,你学会了如何使用 LINQ 编写查询,连接不同的结果集或不同的数据集。你创建了PersonCar类,制作了PersonCar对象,制作了所有者及其汽车的列表,并连接了所有者和汽车列表。

在下一章中,你将使用 SQL Server 2017 Express。

第十六章:下载、安装和运行 SQL Server 2017

在本章中,您将下载、安装和运行 SQL Server 2017 Express。

下载 SQL Server 2017 Express

单击以下链接,将您带到可以下载 SQL Server 2017 Express 的网站,如图 16.1.1所示:

www.microsoft.com/en-us/sql-server/sql-server-editions-express

图 16.1.1:SQL Server 2017 Express 版下载屏幕

接下来,单击“立即下载”按钮。下载完成后,双击SQL Server2017-SSEI-Expr.exe。在用户账户控制屏幕上选择“是”。

选择安装类型

接下来,您需要选择安装类型,如图 16.1.2所示。选择基本安装并接受许可条款协议:

图 16.1.2:从安装类型屏幕中选择基本安装

安装程序

接下来,要么接受默认的安装位置,要么选择自己的位置。然后安装程序将下载并安装。在安装过程中要耐心等待,因为这是一个很大的程序,可能需要一点时间。

安装完成后,您将看到一个类似于图 16.1.3的屏幕:

图 16.1.3:安装已成功完成

在 Visual Studio 中使用 SQL Server

一旦我们下载并安装了 SQL Server,让我们在 Visual Studio 中查看它。转到“视图”,然后选择“SQL Server 对象资源管理器”;它会在左侧打开一个小窗格,如图 16.1.4所示:

图 16.1.4:Visual Studio 中的 SQL Server 对象资源管理器窗格

接下来,单击“添加 SQL Server”按钮,如图 16.1.5所示:

图 16.1.5:添加 SQL Server 按钮

现在,出现了图 16.1.6中显示的对话框。注意其中写着 Windows 身份验证。这被称为集成安全性。您不必指定不同的用户名和密码。只需填写服务器名称字段,单击“连接”,然后您就可以登录:

图 16.1.6:连接对话框

您拥有的具体版本将与图 16.1.7中显示的版本不同,但这些是适用于许多不同版本的基本内容:

图 16.1.7:特定于 SQL Server 2017 Express 版本的数据库文件夹

创建 SQL Server 数据库

现在,我们将创建一个数据库。要做到这一点,展开“数据库”文件夹,右键单击它。选择“添加新数据库”,如图 16.1.8所示,并将数据库命名为People

图 16.1.8:添加新数据库

添加和定义表

现在,展开 People 节点,然后在其中,您将看到一个名为 Tables 的文件夹。再次展开 Tables 节点,因为您需要添加自己的表。您的 SQL Server 对象资源管理器窗格应该看起来像图 16.1.9中显示的那样:

图 16.1.9:SQL Server 对象资源管理器窗格,其中 People 节点和 Tables 节点已展开

现在,右键单击“Tables”文件夹,然后选择“添加新表”。这将打开表定义阶段,如图 16.1.10所示:

图 16.1.10:表定义屏幕

这是您定义表的地方。看一下截图中左上角附近的第一个带有小键的字段。此键将用于标识表的记录或行,以及当您想要启用自动生成时。换句话说,您希望为每条记录分配的编号自动生成,这样您就不必跟踪它。

因此,如果右键单击键并选择属性,它会在屏幕右侧显示图 16.1.11中显示的面板:

图 16.1.11:表属性面板

现在,看标识规范的位置。展开该节点,然后在标识处,从下拉菜单中选择 True。如图 16.1.12所示,标识增量为 1,标识种子为 1,这很好。因此,它从 1 开始,每次添加新记录时,记录编号只增加 1。请注意,它还会自动更改代码视图。

图 16.1.12:设置标识规范

现在,在屏幕底部的 T-SQL 窗口中,它说CREATE TABLE [dbo].[Table]。如果您将[Table]更改为[People],那么现在就是表名,如图 16.1.13所示:

图 16.1.13:更改表的名称

在以下行中,[Id]是列或字段的名称;INT是数据类型,NOT NULL表示必须指定条目,PRIMARY KEY用于标识记录;您现在知道IDENTITY是什么了,因为您在之前的步骤中已经看到了它。

向表中添加字段

现在,您将添加自己的字段和列。因此,接下来,输入以下内容:

[NAME] VARCHAR(100) NOT NULL

这是NAME字段,数据类型是varchar,代表可变字符。这基本上是一个文本字段,因此将长度指定为100,并且因为条目应该被指定,我们将使其为NOT NULL

让我们再添加一个字段。要做到这一点,请输入以下内容:

[DATEADDED] DATE NULL

在这里,DATE ADDED是记录添加的日期,DATE是数据类型。您的屏幕应该看起来像图 16.1.14中显示的那样。

同样,如果您不想要空值,可以在设计窗口中取消选中。因此,设计选项卡中的表视图和代码视图是相互交互的:

图 16.1.14:已向表中添加了两个字段,NAME 和 DATEADDED

这里需要注意的一点是选项卡上标有 T-SQL。嗯,SQL 是结构化查询语言,微软版本是 T-SQL 或Transact 结构化查询语言。核心基本相同,但在微软版本中有一些附加功能。

更新数据库的结构

现在,您必须更新事物的结构,因此点击屏幕右上角的更新按钮。等待一会儿更新所有内容,然后点击更新数据库按钮。

图 16.1.15所示,更新已成功完成:

图 16.1.15:数据库更新已完成并成功!

现在,在左侧的 SQL Server 对象资源管理器窗格中,如果展开 dbo.People,然后展开列,您可以看到默认字段或列以及您创建的新字段,如图 16.1.16所示:

图 16.1.16:我们在 dbo.People 中创建的列

章节回顾

在上面的截图中,从顶部开始,第二项表示服务器(SQL Server)。然后,在其中,您有数据库,如 People 图标所示。当您展开数据库时,会有一个表的图标,一个列的图标,以及表示主键的关键图标。因此,使用的小图标代表不同级别的嵌套在这个数据库结构中。

这些是基础知识。

因此,请确保您可以重新创建所有这些。退出此窗口,然后单击“查看”,转到“起始页”。然后,执行以下操作:

  1. 打开 SQL Server 对象资源管理器窗格。

  2. 右键单击 SQL Server。

  3. 从下拉菜单中选择“断开连接”。

  4. 右键单击,然后从下拉菜单中选择“添加 SQL Server”。

  5. 浏览您的服务器。

  6. 单击连接屏幕底部的“连接”按钮。

  7. 展开服务器以显示“数据库”文件夹。

  8. 展开“数据库”文件夹,您可以看到People数据库。

  9. 打开“表”文件夹,那里有dbo.People表。

摘要

在本章中,您下载、安装并运行了 SQL Server 2017 Express。您在 Visual Studio 中使用 SQL Server,连接了两者,创建了一个 SQL Server 数据库,添加并定义了一个表,向表中添加了字段,并更新了数据库的结构。

在下一章中,您将学习如何连接到 SQL Server,然后在网页中显示数据库表中的记录。

第十七章:编写手动连接到表并检索记录的代码

在本章中,您将学习如何连接到 SQL Server,然后在网页中显示数据库表dbo.People中的记录。

在 HTML 中添加显示记录按钮

打开一个项目,并在页面中,在以<form id=....开头的行下放置一个按钮。为此,转到工具箱,获取一个Button控件,并将其拖放到那里。将按钮上的文本更改为显示记录。记住,记录只是一行信息,而一行当然是表中的一行,例如,从左到右横跨的一行。

现在,切换到设计视图,并左键双击显示记录按钮。这将带我们进入带有事件处理程序的Default.aspx.cs。删除Page_Load块。该项目的起始代码的相关部分应该如图 17.2.1所示:

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

添加一个命名空间

要使其与 SQL Server 一起工作,您必须添加一个命名空间。因此,转到文件顶部,并在using System下输入以下内容:

using System.Data.SqlClient;

创建连接字符串

现在,除此之外,我们将逐行构建代码。您需要的第一件事是连接字符串。因此,让我们做以下事情:

  1. 打开 SQL Server 对象资源管理器。

  2. 右键单击数据库的名称,例如 People,在此处查看其属性。

  3. 然后,要获取连接字符串,请确保展开属性窗格中的 General 节点,然后转到称为 Connection string 的节点,并双击它以选择它及其长描述。

  4. 接下来,右键单击长描述并复制它。(手工构造很难准确,最好直接从那里复制)。此过程显示在图 17.2.2中:

图 17.2.2:复制连接字符串

  1. 现在,在以protected void Button1_Click...开头的行下的大括号集合中输入以下内容:
string connString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=People;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";

在这里,输入string connString =后,放置@符号以指示它是一个文字字符串或逐字字符串,应该准确解释。然后,在""符号中粘贴长字符串。因此,在这一行中,您当然有Data Source,计算机的名称,Initial Catalog作为数据库,Integrated SecurityTrue,因为我们是这样设置的,以及一些其他现在并不是很重要的信息。

连接到 SQL Server

要通过页面连接到 SQL Server,我们将尝试以下操作。首先,您必须创建一个要发给 SQL Server 的命令。为此,请输入以下内容:

string commandText = "Select * from dbo.People";

在这里,Select *表示从dbo.People中选择所有内容。请记住,我们称我们的数据库为People;因此,这意味着从People数据库中的表中选择所有内容。这就是它的意思:从该表中选择所有内容。

现在,还有一件事。当您处理低级资源时,特别是读取硬盘时,例如,您必须建立与硬盘的通信通道。因此,因为这是这种情况,接下来键入以下内容:

using (SqlConnection conn = new SqlConnection(connString))

在这里,using是一个很好的构造,因为它允许您获取资源,使用资源,然后为您处理资源-非常好地和非常干净地。例如,SqlConnection就是这样一种东西。

现在,如果右键单击SqlConnection并从菜单中选择转到定义,并滚动到底部,您将看到有一行说 Dispose-protected override void Dispose。现在,如果展开protected override void Open()行,它说,使用由 system.Data.SqlClient.SqlConnection.ConnectionString 指定的属性设置打开数据库连接,如图 17.2.3所示:

图 17.2.3:protected override void Open 的扩展定义

如果你想知道可能会抛出哪些异常,所有的都列在protected override void Open()的定义中,同样也是protected override void Close()

构造函数是定义中列出的第一个函数。所以,现在让我们关闭它。

捕获异常

在下一阶段,因为可能会抛出错误,我们将使用trycatch,这样我们就可以捕获它们并显示给用户。首先在以using (SqlConnection conn...开头的行下面的开花括号下面的一行中输入try

try

接下来,在try下面插入一对花括号,然后在那里的闭合花括号下面输入以下内容:

catch (Exception ex)

显示错误

现在,如果生成错误,我们将显示它;因此在此行下面的一对花括号中输入以下内容:

sampLabel.Text = $"{ex.Message}";

如果数据库连接出现问题,将显示一个有用的消息。

打开连接

现在让我们继续连接。首先,让我们尝试打开它。在try下面的一对花括号中输入以下内容:

conn.Open();

这打开了一个连接。然后你将制作一个 SQL 命令,所以接下来输入以下内容:

SqlCommand sqlComm = new SqlCommand(commandText, conn);

这需要命令的文本。所以,我们将从前一行写的Select * from dbo.People中选择它;选择所有人,然后你说(command,conn),这是连接的名称。

请记住,在以string commandText...开头的行中,参数是command,在下面的行中是connection。这是两件不同的事情。

使用 SQL Server 数据读取器

现在,在下一阶段,输入以下内容:

using (SqlDataReader reader = sqlComm.ExecuteReader())

在这里,SqlDataReader是一个类。如果你将鼠标悬停在它上面,弹出的工具提示会告诉你这个东西究竟能做什么。现在,如果你右键单击SqlDataReader并选择“转到定义”,它特别实现了一个叫做IDisposable的接口,以及你可以在下拉时看到的所有函数。此外,如果你右键单击IDisposable并选择“转到定义”,那么就会有void Dispose(),展开后会说,执行与释放、释放或重置非托管资源相关的应用程序定义的任务。这特指低级磁盘写入和读取等操作。

接下来,你会看到前一行中的reader变量和sqlComm.ExecuteReader(),它返回一个SqlDataReader类,正如你在工具提示中所看到的。

现在在此行下面的一对花括号中输入以下内容:

while(reader.Read())

现在,为什么这是合法的?将鼠标悬停在Read上,你会看到它返回一个布尔值,并且它说,将 SqlDataReader 推进到下一条记录。它返回truefalse,无论是否还有记录可以读取。因此,在此行下面的一对花括号中输入以下内容:

sampLabel.Text += $"<br>{reader[0]}, {reader[1]}, {reader[2]}";

一定要放入<br>标签,因为可能会返回多个项目,所以你希望它们垂直堆叠。

在前一行中,012是索引;reader[0]reader[1]reader[2]表示列 1列 2列 3。这与索引为0的数组相同。

运行程序

现在在浏览器中启动这个程序。点击“显示记录”按钮,然后你会看到记录——Id、名称和日期,如图 17.2.4所示:

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

如果你右键单击屏幕并选择查看源代码,如图 17.2.5中所示的高亮区域,它会生成一个 span。退出这个屏幕并关闭你不再需要的窗口。

图 17.2.5:如果查看源代码,你会看到它生成了一个 span

章节回顾

为了复习,本章的 HTML 文件的完整版本如下所示:

<!DOCTYPE html>
<html >
  <head runat="server">
    <title>Our First Page</title>
  </head>
  <body>
    <form id="form1" runat="server">
        <asp:Button ID="Button1" runat="server" Text="Show Records"
        OnClick="Button1_Click" />
      <div style="text-align:center;">
        <asp:Label ID="sampLabel" runat="server"></asp:Label>
      </div>
    </form>
  </body>
</html> 

本章的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;//needed for SQL 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 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;Mu
ltiSubnetFailover=False";
        //this is the SQL that runs against the table
        string commandText = "Select * from dbo.People";
        //using statement here helps to ensure connection is properly
        //disposed of here
        using (SqlConnection conn = new SqlConnection(connString))
        {
            try
            {
                conn.Open(); //open connection
                //make command object
                SqlCommand sqlComm = new SqlCommand(commandText, conn);
                //using here helps to ensure data reader is properly 
                //disposed of also
                using (SqlDataReader reader = sqlComm.ExecuteReader())
                {
                    //Read returns true while there are records to read
                    while(reader.Read())
                    {
                        //reader[0] is column 1, and so on for the 
                        //other two
                        sampLabel.Text += $"<br>{reader[0]}, {reader[1]}, {reader[2]}";
                    }
                }
            }
            //a common exception occurs when the server is down and cannot 
            //be reached
            catch(Exception ex)
            {
                sampLabel.Text = $"{ex.Message}";
            }
        }
    }
}

您可以查看代码并注意以下内容,这是您在本章中学到的:

  1. 首先是连接字符串connString

  2. 然后是CommandText

  3. 获取SqlConnection

  4. 使用conn.Open()打开它。

  5. 创建一个命令:SqlCommand(commandText, conn)

  6. 使用SqlDataReader数据读取器。

  7. 读取值:sampLabel.Text += $"<br>{reader[0]}, {reader[1]}, {reader[2]}";

  8. 如果有任何异常,您可以使用catch (Exception ex)捕获它们。

总结

在本章中,您学会了如何连接到 SQL Server,然后在网页中显示来自数据库表的记录。您创建了一个连接字符串,连接到 SQL Server,编写了捕获异常和显示错误的代码,打开了连接,并与 SQL Server 的DataReader一起工作。

在下一章中,您将制作一个表,编写一个过程,并使用该过程将记录插入到表中。

第十八章:使用存储过程将记录插入表中

在本章中,您将学习如何使用存储在 SQL Server 的Programmability文件夹中的存储过程直接将记录插入表中。我们将通过 HTML 页面中的文本框进行操作。

在 HTML 中添加文本框和按钮

启动一个项目。首先,在页面中放入一对框。为此,请在以<form id= ....开头的行下输入以下内容:

Enter Name:<asp:TextBoxID="TextBox1" runat="server"></asp:TextBox><br />
Enter Date:<asp:TextBoxID="TextBox2" runat="server"></asp:TextBox><br />

对于Name字段,它只是一个文本框。因此,对于文本,换句话说,我们将使用一个字符串。转到工具箱,获取一个TextBox控件,并将其拖放到那里。对于日期,我们将尝试从框中解析为日期时间。

您的Default.aspx屏幕现在应该看起来像图 18.3.1中显示的屏幕。

图 18.3.1:本章的 Default.aspx 屏幕中的屏幕

请记住,我们有两个框,我们输入值,并将它们保存到表中。这是这里的目标。

接下来,让我们也在那里放一个按钮。因此,再次转到工具箱,获取一个按钮,并将其拖放到这些行的正下方。更改按钮上的文本,以使其更有帮助,例如,说插入并显示

因此,当您单击按钮时,您将插入新记录,并且还将显示记录以确认它与现有记录一起保存。

回顾在 SQL Server 中已经创建的内容

接下来,打开 SQL Server 对象资源管理器屏幕。现在,请记住,您创建了一个名为People的数据库,然后在其中还有一个名为People的表。此外,在其中,您有一个名为Id的列。这是主键。请记住,它是自动递增的,因此您不必指定 ID。也就是说,它会自动为您完成。

接下来,有两个字段:一个是NAME,另一个是DATEADDEDNAMEvarchar(100)DATEADDED的类型是date。两个值都必须提供,这就是为什么它说not null。到目前为止,SQL Server 对象资源管理器屏幕显示在图 18.3.2中:

图 18.3.2:数据库 People 的 SQL Server 对象资源管理器屏幕

创建新的存储过程

现在,展开Programmability文件夹。有一个名为存储过程的文件夹。右键单击它,然后选择添加新存储过程...。这是基本的存储过程代码:

图 18.3.3:默认存储过程屏幕

要使用存储过程,您首先需要将其重命名。为此,请将顶行中的[Procedure]更改为[AddName],如下所示:

CREATE PROCEDURE[dbo].[AddName]

正如您所看到的,它只是驻留在 SQL Server 中的一些代码。然后,您可以,例如,执行该代码以在数据库表中执行某些操作。

在我们的情况下,我们将使用此过程将记录插入表中。我们需要参数,因为我们将输入两个值。因此,按照以下方式编辑存储过程的下两行:

首先,将param1更改为Name,将默认值更改为int = 0,并将数据类型分配为varchar(100)

下一行,将param2更改为DateAdded,类型为date。因此,这是两个参数:

@Name varchar(100), 
@DateAdded date

现在,因为您不会选择记录,而是会插入记录,所以,我们将输入一个insert语句,然后在SELECT行的位置键入以下内容:

insert into People (NAME,DATEADDED) values (@Name,@DateAdded)

在这里,您insert into People数据库,然后列出应接收新信息的字段,即NAMEDATEADDED。然后,您输入values,然后是参数列表—@Name@DateAdded

记住,这行中的参数类似于之前创建的函数。通过它们,在你在 C#中编写函数时,将值传递给函数。同样的原理也适用于这里。通过参数,值被传递到存储过程的主体中,在这种特殊情况下,直接将字段值插入到表中。在这里,@Name@DateAdded的值被传递到NAMEDATEADDED中。这就是目标。

完整的存储过程显示在图 18.3.4中:

图 18.3.4:存储过程,dbo.AddName

更新数据库结构

现在,让我们更新一下结构;所以,单击“更新”按钮,然后在弹出的对话框中单击“更新数据库”,如图 18.3.5所示。

图 18.3.5:预览数据库更新对话框

更新后,展开“可编程性”文件夹,然后展开“存储过程”文件夹。在那里,你会看到dbo.AddName。现在,如果你展开dbo.AddName,会看到一系列参数:@Name@DateAdded

现在,让我们利用到目前为止所做的事情。单击Default.aspx选项卡,然后进入设计视图,双击“插入并显示”按钮。这将带我们进入Default.aspx.cs。删除Page_Load存根。我们将从图 18.3.6中显示的代码开始这个项目:

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

添加一个命名空间

再次,要使这个与 SQL Server 一起工作,你必须添加一个命名空间。所以,转到文件顶部,在using System下输入以下内容:

using System.Data.SqlClient;//commands and connections 

当然,这将用于诸如命令和连接之类的东西,你可以将其填写为注释。我们将在这一行下面再做一个,所以输入以下内容:

using System.Data;

这行也将为我们服务。会有相当多的代码,但它是高度顺序的——它从上到下自然而然地进行,它会为你完成工作。

现在,每次单击按钮时,你都希望清除标签,以便输出不会继续累积;所以,在以protected void Button1_Click...开头的行下的一对大括号之间,输入以下内容:

sampLabel.Text = "";

构建连接字符串

在下一阶段,你想要获取连接字符串;所以,在下一行开始时输入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))

这就是如何初始化连接。如果右键单击SqlConnection并选择“转到定义”,它会说它是DbConnection类型,并且继承自SqlConnection。现在,如果你右键单击DbConnection并选择“转到定义”,它会说它实现了IDisposable。然后,如果你右键单击IDisposable并选择“转到定义”,它会说,执行与释放、释放或重置非托管资源相关的应用程序定义的任务。因此,例如,对于从硬盘获取信息的低级通道,你必须确保它们被正确清理。现在可以关闭这个窗口。

捕获异常

接下来,因为在与数据库一起工作时可能会出现各种问题,您需要先try,然后捕获任何异常。为此,在前一行下的开放大括号下面,输入以下内容:

try
{

}
catch (Exception ex) 

在这里,我真的只是为了能够显示一些诊断信息而添加了catch (Exception ex)。接下来,在此下面的一对大括号之间,输入以下内容:

sampLabel.Text = $"{ex.Message}";

我们使用这行只是为了显示诊断信息。

尝试命令

现在,让我们进入try部分。这是一切都可能发生的地方。首先,让我们创建一个命令。在try下面的大括号之间输入以下内容:

SqlCommand cmd = new SqlCommand();

接下来,您将设置命令的类型,因此请输入以下内容:

cmd.CommandType = CommandType.StoredProcedure;

这行说明了它自己。

现在,为了实际获取文本以选择要调用的特定存储过程,您需要输入以下内容:

cmd.CommandText = "AddName";

记住,AddName是我们在 SQL Server 中称之为的存储过程。

添加参数

现在,对于下一个阶段,我们将添加所谓的参数。换句话说,您必须确保实际传递值到存储过程中,以便您可以将它们保存在表中。因此,请接下来输入以下内容:

cmd.Parameters.AddWithValue("@Name", TextBox1.Text);

在这里,我们从参数的名称开始:@Name,然后它的值将来自第一个框:TextBox1.Text

接下来,您将重复此逻辑,因此请输入以下内容:

cmd.Parameters.AddWithValue("@DateAdded", DateTime.Parse(TextBox2.Text));

在这里,@DateAdded是参数的名称,下一个阶段来自第二个框:TextBox2.Text。这行将转换框中的值,假设它可以转换为DateTime对象,以便它与数据库中的@DateAdded类型匹配。这就是为什么我们要采取这一步骤。

当然,在更现实的情况下,您可能想尝试DateTime.TryParse。为了避免过多的复杂性,我们将只使用DateTime.Parse

接下来输入以下内容:

cmd.Connection = conn;

您必须设置conn属性。我们在文件顶部创建了这个属性,以using(SqlConnection conn...开头的行。

对于下一行,请输入以下内容以打开连接:

conn.Open();

保存信息以便以后检索

在下一个阶段,我们将执行NonQuery。为此,请输入以下内容:

cmd.ExecuteNonQuery();

这行将保存信息。现在,从那里开始,当您想要检索信息时,请确保它按预期工作。我们只需将命令类型切换为Text类型的CommandType,因此请接下来输入以下内容:

cmd.CommandType = CommandType.Text;

接下来,我们将指定文本,因此请输入以下内容:

cmd.CommandText = "select * from dbo.People";

在这里,select *表示从People数据库中选择所有内容。

之后,输入以下内容:

using (SqlDataReader reader = cmd.ExecuteReader())

认识索引器的作用

现在,我将向您展示一些以前没有向您展示的东西。将鼠标悬停在ExecuteReader上。这将返回一个SqlDataReader类。现在,在前一行中右键单击SqlDataReader,然后选择“转到定义”。您还记得我们之前学到的索引器吗?看看它说的 public override object this[string name]。如果您展开它,它说它获取指定列的值及其本机格式,给定列名。如果您返回,下一个定义是 public override object this[int i]。如果您展开这个,它说,获取指定列的值及其本机格式,给定列的顺序,这里是列的编号。因此,public override object...行是指当前的SqlDataReader对象。这基本上是一个索引器。现在您可以看到索引器确实发挥了作用。现在您可以关闭这个。

为了利用这些信息,请在前面using行下的一对大括号之间输入以下内容:

while(reader.Read())

然后,在这行下面的一对大括号之间,输入以下内容:

sampLabel.Text += $"<br>{reader[0]}, {reader[1]}, {reader[2]}";

在这里,在sampLabel.Text...之后,您指定reader[0]{reader[1]}{reader2},这是通过索引访问的三列。

您现在已经输入了程序的核心。

运行程序

现在,让我们来看看结果。在浏览器中打开这个。首先,输入一些值:Berry Gibbs作为Name,一个日期,然后点击“插入并显示”按钮。结果显示在图 18.3.7中:

![图 18.3.7:运行我们的程序后的初始结果所以,就是这样——它按预期工作。现在,让我们再试一个。输入Mark Owens作为Name,添加一个日期,然后再次点击“插入并显示”按钮。正如您在图 18.3.8中所看到的,它已经被自动添加了。这证实它已保存到表中,然后我们可以检索它:

图 18.3.8:运行程序后修改的结果

所以,这些是建立连接的基础知识。

现在考虑这一点。想象一下,在前一行中,我写成了cmd.CommandText = "AddNames"而不是AddName。换句话说,我拼错了存储过程的名称。如果我在浏览器中打开这个,就像图 18.3.9中所看到的那样,它会显示“字符串无法识别为有效的日期时间”。这很有用,对吧?我没有填写NameDate。所以,它无法转换为DateTime

图 18.3.9:未输入值运行程序的结果

现在,即使我为NameDate输入值,它也会显示“找不到存储过程'AddNames'”,如图 18.3.10所示,因为我拼错了存储过程的名称:

图 18.3.10:拼错存储过程名称后运行程序的结果

所以,使用try行,因为之后的所有命令都可能产生某种错误,至少您可以捕获它并显示错误消息,这样您就能知道发生了什么。所以,这非常有用。

章节回顾

为了回顾,包括注释的本章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;//commands and connections
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)
    {
        sampLabel.Text = "";
        string connString = @"Data Source=DESKTOP-4L6NSGO\SQLEXPRESS;Initial Catalog=People;Integrated Security=True;Connect Timeout=15;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
        //put conn in a using so it can be properly closed and disposed of
        using (SqlConnection conn = new SqlConnection(connString))
        {
            try
            {
                //make sql command
                SqlCommand cmd = new SqlCommand();
                //specify type
                cmd.CommandType = CommandType.StoredProcedure;
                //write name of stored procedure inside SQL Server as  
                //the name here
                cmd.CommandText = "AddName";
                //read the field box 1, and pass in through @Name
                cmd.Parameters.AddWithValue("@Name", TextBox1.Text);
                //pass in date through @DateAdded
                cmd.Parameters.AddWithValue("@DateAdded", 
                DateTime.Parse(TextBox2.Text));
                //set connection property of command object
                cmd.Connection = conn;
                //open connection
                conn.Open();
                //execute the stored procedure
                cmd.ExecuteNonQuery();
                //change command type to just plain text
                cmd.CommandType = CommandType.Text;
                //write a simple SQL select statement
                cmd.CommandText = "select * from dbo.People";
                //execute reader
                using (SqlDataReader reader = cmd.ExecuteReader())
                {
                    //Read() returns true while it can read
                    while(reader.Read())
                    {
                        //reader[0] means get first column, 
                        //reader uses an indexer to do this
                        sampLabel.Text += $"<br>{reader[0]}, {reader[1]}, {reader[2]}";
                    }
                }
            }
            catch(Exception ex)
            {
                sampLabel.Text = $"{ex.Message}";
            }
        }
    }
}

总结

在本章中,您学习了如何使用存储过程直接将记录插入到表中,并存储在 SQL Server 的可编程文件夹中。您创建了一个新的存储过程,更新了数据库结构,构建了连接字符串,初始化了连接,尝试了命令并捕获了异常,添加了参数,保存了信息以供以后检索,并认识到了索引器的作用。

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