下一版本的ASP.NET

 
技术是不断进步且永无止尽的,就在我们在使用着ASP.NET 2.0开发我们的网站的时候,microsoft已在悄然开发着它的下一个版本了。目前,代号“Orcas”的新一个版本的Visual Studio已经Beta1,并即将在2007年底推出,伴随而来的.NET 3.5将会带来一系列新的开发特性。
在C# 3.0中,新增加了大量的特性,使得C# 3.0更加接近于动态语言。目前已知的,在C#的下一个版本中将具有如下新特性。
隐式局部变量的概念是指允许开发人员在声明变量的时候不需要再指定变量的类型,编译器将自动从初始值中推断出来,它的写法是使用关键字var。例如下面的代码:
void SomeMethod()
{
 var i = 5; // i is compiled as an int
 var s = “Hello”; //s is compiled as a string
 var a = new [] {0 ,1, 2}; //a is compiled as int[]
 var expr =      //expr is compiled as IEnumerable<Customer>
      from c in Customers
      where c.City == “London”
      select c;
 var anon = new {Name = "Terry", Age = 34}; //anon is compiled as
                                           //an anonymous type
 var list = new List<int>(); //list is compiled as List<int>
}
代码 20-1
 
上面的代码中使用了一些初始值来初始化符变量。注意这里变量被强类型化,比如墙类型化到整型或字符串类型,它们不是一个对象或者VB6的变量,也不带有其他对象或者变量的负载。为了保证使用var关键字进行声明的变量的强类型特性,C#3.0要求将赋值(初始化符)放到和声明(声明符)的同一行。同样,初始化符必须是一个表达式,不能是一个对象或者collection初始化符,也不能为null。如果多个声明符对同一个变量存在,那么它们必须在编译时被视作相同类型。
var关键字可以被用在如下的上下文中:
像前面例子那样声明的局部变量中。
l 在for循环结构中,例如:
for(var x = 1; x < 10; x++)
代码 20-2
 
l 在foreach循环结构中,例如:
foreach(var item in list){...}
代码 20-3
 
l 在using结构中,例如;
using (var file = new StreamReader("C://myfile.txt")) {...}
代码 20-4
 
之所以引入隐式局部变量,其目的是为了适应C#的另外一个新特性——匿名类型。var关键字允许使用匿名类型的实例,因而这些实例就是静态类型的。所以,当创建一个包含一组数据的对象的实例的时候,不必要预先定义一个类可以同时支持这个结构和在一个静态类型变量里的数据。
我们知道,在C# 2.0中引入了匿名方法,其目的是为了不需要声明方法而能够使用委托,这样可以减少代码量,而在C# 3.0中,这种思想被进一步发扬光大,匿名类型的引入,使得开发人员能够不需要进行声明就可以构造出一个新类型出来,这个时候由于没有类型名,因此也就在实例化它的时候无法声明其类型,这时就使用var关键字进行声明。例如下面的例子:
var v = new {hair="black", skin="green", teethCount=64};
代码 20-5
 
上面的代码中,通过new关键字的帮助,编译器创建了有三个属性的类型:hair,skin和teethCount。这样C#编译器就会创建一个如下的类:
class __Anonymous1
{
 private string _hair = "black";
 private string _skin = "green";
 private int _teeth = 64;
 public string hair {get { return _hair; } set { _hair = value; }}
 public string skin {get { return _skin; } set { _skin = value; }}
 public int teeth {get { return _teeth; } set { _teeth = value; }}
}
代码 20-6
 
事实上,如果另外一个满足了相同的名称和类型顺序的匿名类型也被创建了,编译器也会聪明的只创建一个匿名类型来支持两个实例来使用。同样,因为实例都是一个类的简单实例,它们可以进行互换因为类型实际上是一样的。
匿名类型提供了一种方便的将一组只读的属性封装为一个独立对象的方式,而不需要事先去明确的定义一个类型。该类型的名称将在编译器编译时生成,因此不能够在源代码中使用,因此这个时候要实例化它就只能使用隐式局部变量的关键字var。
和一般的类所不同的是,匿名类型只包括了几个只读的属性,而不能包括其他任何的成员。
匿名类型最常见的一个场景是从另外的一个或几个类型中抽取几个属性来初始化成一个新的匿名类型。之所以要使用匿名类型,则是为了要为C#提供另外一个非常有用的特性:对象查询表达式。
我们知道,对数据而言,查询功能非常重要,所以数据库一个非常重要的功能就是查询,而在编程过程当中,我们常常要将面向对象的软件思想与面向关系的软件思想结合起来用,其中一个很重要的原因是面向关系的数据很容易查询和排序,而面向对象的数据则很难查询和排序。我们在.NET开发的过程中也大量使用数据集而不是去定义业务对象也是因为这个原因。在C# 3.0中,对象查询表达式被很好的引入,从而使开发人员能够使用一种语言和思想来处理各种任务。例如使用下面的代码,我们就能够很方便的从一个整型集合中查询到我们所需要的数字:
// Data source.
int[] Scores = { 90, 71, 82, 93, 75, 82 };
 
// Query Expression.
IEnumerable<int> scoreQuery = //query variable
    from score in Scores //from clause
    where score > 80 //query body begins here
    select score;
 
// Query Execution.
foreach(int i in scoreQuery)
{
    Console.WriteLine(i);
}
// Outputs: 90 82 93 82
代码 20-7
 
不仅仅是对于基本数据类型的集合,实际上对于诸如数据集、自定义类型集合等等,开发人员也能够使用相同的知识和方法来进行查询和排序,例如下面的代码便是对PurchaseRecord的集合进行查询:
IEnumerable<PurchaseRecord> purchaseQuery =
    from customer in Customers
        from purchase in customer.Purchases
        where purchase.Amount > 100
        select purchase;
代码 20-8
 
此外,C#的对象查询表达式还支持聚合,例如下面的代码:
int scoreQuery4 =
    (from score in Scores
    where score > 80
    select score).Count();
代码 20-9
 
以上三种新的特性结合起来使用,便可以使用C#方便的对集合进行查询和操作,例如下面的代码是对一个字符串集合中的对象进行筛选后组成新的对象输出:
string[] words = { "aPPLE", "BlUeBeRrY", "cHeRry" };
 
var upperLowerWords =
     from w in words
     select new {Upper = w.ToUpper(), Lower = w.ToLower()};
 
foreach (var ul in upperLowerWords) {
   Console.WriteLine("Uppercase: {0}, Lowercase: {1}", ul.Upper, ul.Lower);
代码 20-10
 
C# 3.0的另一项新特性是对象和集合的初始化,该初始化方式不需要调用对象的构造函数,而是采用对对象赋值的方式,例如下面的代码:
public class Test
{
    public int num;
    public string s;
}
///...
// object initializer
Test t = new Test {num = 10, s = "Hello"};
代码 20-11
 
实际上,C#引入该项特性是为了实现匿名类型,因为匿名类型没有除了属性之外的其他成员,因此也就不可能通过调用构造函数的方式来实例化了。
与之相匹配的,C# 3.0的另一新特性是自动实现属性,该项特性仅对于简单的get/set属性适用,其好处是,不需要再声明一个成员变量,例如下面的代码:
public class Person
{
    public string Name {get; set;};
    public int Age{ get; set;}; //Auto-implemented property
}
Person p = new Person {Name = "Chris Smith", Age = 31};
代码 20-12
 
在C# 3.0中,另一项新特性是“外部方法”,它允许开发人员向已有的类添加新方法而不需要编写一个新的派生类、重新编译或者修改原来的类。外部方法是一种特殊类型的静态方法,在 CLR 元数据中以 [System.Runtime.CompilerServices.Extension] 属性标记,但是它们被调用的时候其行为就和已有的内部方法一样。实际上开发人员感觉不出来它们和原有的内部方法有什么不同。引入这项功能的目的是为了能够让现有的大量的类能够支持对象查询表达式。因此最常用的外部方法便是LINQ标准的查询操作符,因为只有这样才能够向现有的IEnumerable和IEnumerable<(Of T>)<T>类型加入查询功能。例如下面的代码之所以能够实现时因为向int数组加入了OrderBy外部方法:
int[] ints = { 10, 45, 15, 39, 21, 26 };
            var result = ints.OrderBy(g => g);
            foreach (var i in result)
            {
                System.Console.Write(i + " ");
            }
代码 20-13
 
添加外部方法其实很简单,首先添加一个静态类,然后再在类中写一个静态方法,方法的传入参数是希望添加外部方法的那个类型,同时要使用关键字this,例如下面的代码变为String类型添加了一个外部方法:
namespace ExtensionMethods
{
    public static class MyExtensions
    {
        public static int WordCount(this System.String str)
        {
            return str.Split(null).Length;
        }
    }
}
代码 20-14
 
 
开发人员可以为类或者接口扩展外部方法,但是要注意的是,外部方法永远不会该写内部方法,因此,如果外部方法的方法签名和该类型的某个内部方法一致,那么该外部方法就会被忽略,从下面的代码中我们可以看出这一点:
using System;
namespace N1
{
 using ExtensionMethodsDemo1;
 
 // Define extension methods for any type that implements IMyInterface.
     public static class Extensions
     {
        public static void MethodA(this IMyInterface myInterface, int i)
        {
            Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, int i)");
        }
            public static void MethodA(this IMyInterface myInterface, string s)
        {
            Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, string s)");
        }
        // This method is never called, because the three classes
        public static void MethodB(this IMyInterface myInterface)
        {
            Console.WriteLine("Extension.MethodB(this IMyInterface myInterface, int i)");
        }
    }
}
namespace ExtensionMethodsDemo1
{
    using N1;
 
    public interface IMyInterface
    {
        void MethodB();
    }
 
    class A : IMyInterface
    {
        public void MethodB(){Console.WriteLine("A.MethodB()");}
    }
    class B : IMyInterface
    {
        public void MethodB(){Console.WriteLine("B.MethodB()");}
        public void MethodA(int i) {Console.WriteLine("B.MethodA(int i)");}
    }
    class C : IMyInterface
        {
            public void MethodB(){Console.WriteLine("C.MethodB()");}
            public void MethodA(object obj) {Console.WriteLine("C.MethodA(object obj)");}
        }
 
 
    class Program
    {
        static void Main(string[] args)
        {
            A a = new A();
            B b = new B();
            C c = new C();
            TestMethodBinding(a,b,c);
        }
       
        static void TestMethodBinding(A a, B b, C c)
        {
            // A has no methods, so each call resolves to
            // the extension methods whose signatures match.
            a.MethodA(1); // Extension.MethodA(object, int)
            a.MethodA("hello"); // Extension.MethodA(object, string)
            a.MethodB();
 
            // B itself has a method with this signature.
            b.MethodA(1); // B.MethodA(int)
            b.MethodB();
 
            // B has no matching method, but E does.
            b.MethodA("hello"); // Extension.MethodA(object, string)
 
            // In each case C has a matching instance method.
            c.MethodA(1); // C.MethodA(object)
            c.MethodA("hello"); // C.MethodA(object)
            c.MethodB();
        }
 
    }
}
代码 20-15
 
1.1.6 Lambda表达式
C# 3.0的Lambda表达式为缩短代码提供了另外一种方法,Lambda表达式是一种内联的简单的用语委托中的语法。想想匿名方法,我们就可以知道,Lambda表达式其实也就是为了实现类似的目的。举个例子讲,可以就像调用一个方法那样使用它来在一个代理或者事件处理程序中赋值。
Lambda表达式的特征是使用运算符=>,该运算符读作“goes to”,该运算符的左边是输入参数而右边则是运算表达式或者描述块。例如在下面的代码中,我们利用Lambda表达式来声明一个代理;
delegate int D1(int i);
D1 myDelegate1 = x => x + 1;
int j = myDelegate1(5); //j = 6
代码 20-16
 
一个基本的Lambda表达式应该是如下面的样子:
(input parameters) => expression
代码 20-17
 
在前面的例子当中已经演示了只有一个参数地情形,而如果有两个参数的话,那么就应当将两个参数使用括号括起来,例如:
(x, y) => x == y
代码 20-18
 
上面的表达式返回的是一个bool值,它指示x和y是否相等。
有些时候让编译器推断参数的类型会比较困难,这是我们可以声明参数的类型,例如下面的代码:
(int x, string s) => s.Length > x
代码 20-19
 
上面提到的都是运算符右边是一个表达式的情况,而如果不仅仅是一个表达式,而是一个描述块的话,那么Lambda表达式应当是如下所示:
(input parameters) => {statement;}
代码 20-20
 
举一个例子:
delegate void D1(string s);
D1 myDel = n => { string s = n + " " + "World"; Console.WriteLine(s); };
myDel("Hello");
代码 20-21
 
实际上,Lambda表达式还可以和标准查询操作符一起使用。很多标准查询操作符都是用代理来确定其查询条件,该代理的标准格式一般是:
public delegate TResult Func<TArg0, TResult>(TArg0 arg0)
代码 20-22
 
这个代理的一个实例Func<int,bool> myFunc中,int是输入参数,而bool则是返回值,返回值永远都是在类型参数的最后一个。比如Func<int, string, bool>则说明该代理有两个输入参数,int和string,而返回参数是bool。在下面的例子中,代理将返回true或false来指示输入值是否等于5:
Func<int, bool> myFunc = x => x == 5;
bool result = myFunc(4); // returns false of course
代码 20-23
 
一个标准的查询操作System.Linq.Enumerable.Count方法的使用如下面的代码所示:
            int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
            int oddNumbers = numbers.Count(n => n % 2 == 1);
代码 20-24
 
最后返回的值oddNumber的值是numbers奇数的数量。
而下面的代码则是返回了一个所有小于6的数字的数组:
var firstNumbersLessThan6 = numbers.TakeWhile(n => n < 6);
代码 20-25
 
在新版本的.NET 中,比较激动人心的一个新特性就是LINQ,实际上在前一节C# 3.0的新特性中已经介绍过。LINQ的全称是Language-Integrated Query,语言集成查询。它是一组强大的扩展现有编程语言的查询功能,通过它可以方便的使用现有的面向对象的编程语言例如C#和VB.NET对数据进行查询,而无论数据是什么形式存在。实际上,.NET FRAMEWORK中提供了一组类库以实现LINQ对对象集合、数据集、SQL SERVER数据库和XML文档的查询。它是适用于所有信息源(而不只是关系数据或 XML 数据)的通用查询工具,而不是在编程语言和运行库中添加相关功能或特定于 XML 的功能。
之所以使用语言集成查询这一术语表明,该查询是开发人员主要编程语言(例如,C#、Visual Basic)的集成功能。语言集成查询使得查询表达式能够得益于丰富的元数据、编译时语法检查、静态输入和智能感知(以前只能用于命令代码)。语言集成查询还允许将单个通用的声明查询工具应用于所有内存中信息,而不只是来自外部源的信息。LINQ的体系结构如下图所示:
图 20-1 LINQ的体系结构
 
.NET 语言集成查询定义了一组通用的标准查询操作符,允许在任何基于 .NET 的编程语言中通过直接的声明方式进行遍历、筛选和投影操作。标准查询操作符允许将查询应用于任何基于 IEnumerable 的信息源。LINQ 允许第三方使用特定于域的新操作符(适用于目标域或技术)来补充标准查询操作符集。更重要的是,第三方还可以使用自己提供附加服务(例如,远程计算、查询转换、优化等)的实现来自由替换标准查询操作符。通过符合 LINQ 样式的约定,此类实现可以享受与标准查询操作符相同的语言集成和工具支持。
查询体系结构的可扩展性在 LINQ 项目本身中用于提供可同时处理 XML 和 SQL 数据的实现。处理 XML 的查询操作符 (XLinq) 使用一个高效、易于使用的内存中 XML 工具来提供宿主编程语言中的 XPath/XQuery 功能。处理关系数据的查询操作符 (DLinq) 将基于 SQL 的架构定义集成构建到 CLR 类型系统中。该集成通过关系数据提供强类型化,同时直接在底层存储中保留关系模型的表达功能和查询计算的性能。
1.2.1 LINQ查询对象集合
下面的代码是一个简单的使用LINQ的例子:
using System;
using System.Query;
using System.Collections.Generic;
 
class app {
 static void Main() {
    string[] names = { "Burke", "Connor", "Frank",
                       "Everett", "Albert", "George",
                       "Harris", "David" };
 
    IEnumerable expr = from s in names
                               where s.Length == 5
                               orderby s
                               select s.ToUpper();
 
    foreach (string item in expr)
      Console.WriteLine(item);
 }
}
代码 20-26
 
运行这段代码,可以得到下面的输出:
BURKE
DAVID
FRANK
代码 20-27
 
分析前面的代码,可以看出其关键实际上在于下面这行代码:
IEnumerable expr = from s in names
                               where s.Length == 5
                               orderby s
                               select s.ToUpper();
代码 20-28
 
它使用了一个查询表达式初始化局部变量 expr。并通过应用一个或多个标准查询操作符或特定于域的操作符,查询表达式可以操作一个或多个信息源。该表达式使用了三个标准查询操作符:Where、OrderBy 和 Select。from 表示声明一个局部变量s,后面的查询操作是围绕s进行的,in则是表明局部变量s将代表集合names中的每一个个体,where是筛选条件,这里Length等于5的个体将被筛选出来,orderby是排序标准,而最后的select则表示如何将筛选出来的结果返回。
在上一节,我们实际上已经提到了和LINQ相关的一些特性,这些特性包括外部方法、Lambda表达式等等,本节不再赘述。
除此之外,和LINQ相关的还有另外一个特性,那就是表达式树,实际上在上一节的最后已经稍微提到了一些。
LINQ 定义了一个特殊类型 Expression(在 System.Expressions 命名空间中),该类型用于指示给定 λ 表达式需要表达式树,而不是基于 IL 的传统方法体。表达式树是 λ 表达式的有效内存中数据表示形式,它使表达式的结构透明且显式。
编译器是发出可执行 IL 还是表达式树取决于 λ 表达式的用法。如果将 λ 表达式指定给委托类型的变量、字段或参数,则编译器将发出与匿名方法等效的 IL。如果将 λ 表达式指定给 Expression 类型的变量、字段或参数,则编译器将发出表达式树。
例如以下两个变量声明:
Func              f = n => n < 5;
Expression<FUNC> e = n => n < 5;
代码 20-29
 
在上面的变量声明中,变量 f 是对委托的引用,可以直接执行:
bool isSmall = f(2); // isSmall is now true
代码 20-30
 
而变量 e 则是对表达式树的引用,不可直接执行:
bool isSmall = e(2); // compile error, expressions == data
代码 20-31
 
与委托(有效的不透明代码)不同,我们可以像与程序中的任何其他数据结构交互那样与表达式树进行交互。例如,以下代码:
Expression<FUNC> filter = n => n < 5;
 
BinaryExpression     body = (BinaryExpression)filter.Body;
ParameterExpression left = (ParameterExpression)body.Left;
ConstantExpression right = (ConstantExpression)body.Right;
 
Console.WriteLine("{0} {1} {2}",
                  left.Name, body.NodeType, right.Value);
代码 20-32
 
在运行时会分解表达式树,并显示以下字符串:
n LT 5
代码 20-33
 
对于启用第三方库(利用属于平台一部分的基本查询抽象)的环境,这种在运行时将表达式视为数据的功能很重要。DLinq 数据访问实现利用该功能将表达式树转换为适用于在存储中计算的 T-SQL 语句。
LINQ还有的一个特性是延迟查询计算。实际上,标准的Where操作符是使用 C# 2.0 中引入的 yield 结构实现的。该实现技术常用于返回值序列的所有标准操作符。使用 yield 的一个有趣的优点是,查询实际上是在迭代完毕后计算的(通过使用 foreach 语句,或者手动使用基础的 GetEnumerator 和 MoveNext 方法)。该延迟计算允许将查询保留为基于 IEnumerable 的值,这些值可以计算多次,每次都可能生成不同的值。
对于许多应用程序而言,这正是所需的行为。对于希望缓存查询计算结果的应用程序而言,提供的两个操作符(ToList 和 ToArray)会强制立即计算查询,并返回包含查询计算结果的 List 或数组。
要了解延迟查询计算如何工作,请考虑以下程序,该程序对数组运行了一个简单的查询:
// declare a variable containing some strings
string[] names = { "Allen", "Arthur", "Bennett" };
 
// declare a variable that represents a query
IEnumerable ayes = names.Where(s => s[0] == 'A');
 
// evaluate the query
foreach (string item in ayes)
 Console.WriteLine(item);
 
// modify the original information source
names[0] = "Bob";
 
// evaluate the query again, this time no "Allen"
foreach (string item in ayes)
    Console.WriteLine(item);
代码 20-34
 
每次迭代变量 ayes 时,都会计算查询。要指示所需结果的缓存副本,我们只需在查询中追加一个 ToList 或 ToArray 操作符,如下所示:
 
// declare a variable containing some strings
string[] names = { "Allen", "Arthur", "Bennett" };
 
// declare a variable that represents the result
// of an immediate query evaluation
string[] ayes = names.Where(s => s[0] == 'A').ToArray();
 
// iterate over the cached query results
foreach (string item in ayes)
    Console.WriteLine(item);
 
// modifying the original source has no effect on ayes
names[0] = "Bob";
 
// iterate over result again, which still contains "Allen"
foreach (string item in ayes)
    Console.WriteLine(item);
代码 20-35
 
ToArray 和 ToList 都可以强制立即执行查询计算,这与返回单个值的标准查询操作符(例如,First、ElementAt、Sum、Average、All 和 Any)一样。
1.2.2 LINQ查询关系型数据存储
除了能够查询对象数组之外,.NET 语言集成查询还可用于查询关系数据存储,而不必离开本地编程语言的语法或编译时环境。它利用 SQL 架构信息到 CLR 元数据的集成。该集成将 SQL 表和视图定义编译为可以从任何语言访问的 CLR 类型。
DLinq 定义了两个核心属性([Table] 和 [Column]),它们指示哪些 CLR 类型和属性对应于外部 SQL 数据。[Table] 属性可以应用于类,并将 CLR 类型与命名的 SQL 表或视图相关联。[Column] 属性则可以应用于任何字段或属性,并将成员与命名的 SQL 列相关联。这两个属性均被参数化,以允许保留特定于 SQL 的元数据。例如下面这个简单的 SQL 架构定义:
 
create table People (
    Name nvarchar(32) primary key not null,
    Age int not null,
    CanCode bit not null
)
 
create table Orders (
    OrderID nvarchar(32) primary key not null,
    Customer nvarchar(32) not null,
    Amount int
)
代码 20-36
 
其CLR 等效形式如下所示:
 
[Table(Name="People")]
public class Person {
 [Column(DbType="nvarchar(32) not null", Id=true)]
 public string Name;
 
 [Column]
 public int Age;
 
 [Column]
 public bool CanCode;
}
 
[Table(Name="Orders")]
public class Order {
 [Column(DbType="nvarchar(32) not null", Id=true)]
 public string OrderID;
 
 [Column(DbType="nvarchar(32) not null")]        
 public string Customer;
 
 [Column]
 public int? Amount;
}
代码 20-37
 
在上例中,我们可以看到,可以为空的列映射到 CLR 中的可空类型,并且对于无法 1:1 对应于 CLR 类型的 SQL 类型(例如,nvarchar、char、text),原始 SQL 类型会保留在 CLR 元数据中。
要针对关系型存储发出查询,LINQ 样式的 DLinq 实现会将查询从表达式树形式转换为 SQL 表达式以及适用于远程计算的 ADO.NET DbCommand 对象。例如,下面这个简单查询:
 
// establish a query context over ADO.NET sql connection
DataContext context = new DataContext(
     "Initial Catalog=petdb;Integrated Security=sspi");
 
// grab variables that represent the remote tables that
// correspond to the Person and Order CLR types
Table custs = context.GetTable();
Table orders    = context.GetTable();
 
// build the query
var query = from c in custs, o in orders
            where o.Customer == c.Name
            select new {
                       c.Name,
                       o.OrderID,
                       o.Amount,
                       c.Age
            };
 
// execute the query
foreach (var item in query)
    Console.WriteLine("{0} {1} {2} {3}",
                      item.Name, item.OrderID,
                      item.Amount, item.Age);
代码 20-38
 
DataContext 类型提供一个轻量转换器,它的工作是将标准查询操作符转换为 SQL居于。DataContext 使用现有的 ADO.NET IDbConnection 来访问存储,并且可以使用已建立的 ADO.NET 连接对象或者可用于创建连接对象的连接字符串来进行初始化。
GetTable 方法提供与 IEnumerable 兼容的变量,这些变量可用于查询表达式,以表示远程表或视图。调用 GetTable方法不会导致与数据库进行交互,虽然它们表示使用查询表达式与远程表或视图进行交互的潜在可能。在面的代码中,直到程序迭代完查询表达式,才会将查询传送到存储,在这种情况下,使用的是 C# 中的 foreach 语句。当程序首次迭代完查询后,DataContext 机制会将表达式树转换为以下将发送给存储的 SQL 语句:
 
SELECT [t0].[Age], [t1].[Amount],
       [t0].[Name], [t1].[OrderID]
FROM [Customers] AS [t0], [Orders] AS [t1]
WHERE [t1].[Customer] = [t0].[Name]
代码 20-39
 
需要注意的是,通过将查询功能直接构建到本地编程语言中,开发人员可以完全控制关系模型,而不必将关系静态转换为 CLR 类型。完整的对象/关系映射还可以利用这个核心查询功能,以方便需要该功能的用户。
1.2.3 LINQ查询XML文档
用于 XML的.NET 语言集成查询 (XLinq) 允许使用标准查询操作符以及特定于树的操作符(提供类似于 XPath 的子代、父代和同辈导航)来查询 XML 数据。它提供了有效的 XML 内存中表示形式,该表示形式与现有的 System.Xml 读取器/写入器基础结构集成在一起,比 W3C 文档更易于使用。将 XML 与查询相集成的大部分工作由以下三个类型执行:XName、XElement 和 XAttribute。
XName 提供一种易于使用的方法来处理命名空间限定的标识符 (QName)(既用作元素又用作属性名)。XName 可以透明地处理高效的标识符原子化,并在需要 QName 时允许使用符号或纯字符串。
XML 元素和属性分别通过 XElement 和 XAttribute 来表示。XElement 和 XAttribute 支持普通的结构语法,以允许开发人员使用自然语法编写 XML 表达式,例如:
 
var e = new XElement("Person",
                     new XAttribute("CanCode", true),
                     new XElement("Name", "Loren David"),
                     new XElement("Age", 31));
 
var s = e.ToString();
代码 20-40
 
这对应于以下 XML:
 
<Person CanCode="true">
<Name>Loren David</Name>
<Age>31</Age>
</Person>
代码 20-41
 
请注意,创建 XML 表达式不需要基于 DOM 的工厂模式,并且 ToString 实现会生成 XML 文本。XML 元素还可以从现有的 XmlReader 或字符串文字生成:
 
var e2 = XElement.Load(xmlReader);
      var e1 = XElement.Parse(
       @"<Person CanCode='true'>
       <Name>Loren David</Name>
       <Age>31</Age>
       </Person>");
代码 20-42
 
XElement 还支持使用现有的 XmlWriter 类型发出 XML。
XElement 与查询操作符相结合,从而允许开发人员针对非 XML 信息编写查询,以及通过将 XElements 构建到 select 子句的正文中来生成 XML 结果:
 
var query = from p in people
            where p.CanCode
            select new XElement("Person",
                                  new XAttribute("Age", p.Age),
                                  p.Name);
代码 20-43
 
该查询将返回一个 XElement 序列。要允许 XElement 的生成超出这种类型的查询结果,XElement 构造函数应允许将元素序列直接作为参数进行传递:
 
var x = new XElement("People",
                  from p in people
                  where p.CanCode
                  select
                    new XElement("Person",
                                   new XAttribute("Age", p.Age),
                                   p.Name));
代码 20-44
 
该 XML 表达式将生成以下 XML:
 
 <People>
        <Person Age="11">Allen Frances</Person>
        <Person Age="59">Connor Morgan</Person>
 </People>
代码 20-45
 
到目前为止,这些示例已经展示了如何使用语言集成的查询生成 新的 XML 值。XElement 和 XAttribute 类型还简化了从 XML 结构提取 信息的过程。XElement 还提供了访问器方法,以便允许将查询表达式应用于传统的 XPath 轴。例如,以下查询仅从上述 XElement 中提取名称:
IEnumerable justNames =
    from e in x.Descendants("Person")
    select e.Value;
 
//justNames = ["Allen Frances", "Connor Morgan"]
代码 20-46
 
要从 XML 中提取结构化值,我们只需在 select 子句中使用对象初始值设定项表达式:
 
IEnumerable persons =
    from e in x.Descendants("Person")
    select new Person {
        Name = e.Value,
        Age = (int)e.Attribute("Age")
    };
代码 20-47
 
请注意,XAttribute 和 XElement 都支持显式转换操作符将文本值作为基元类型来提取。要处理缺失数据,我们只需强制转换为可以为空的类型:
IEnumerable persons = from e in x.Descendants("Person") select new Person { Name = e.Value, Age = (int?)e.Attribute("Age") ?? 21 };
代码 20-48
 
在本例中,当 Age 属性缺失时,我们使用默认值 21。
在ADO.NET 3.0中,新特性主要围绕对象化来展开,包括Entity Framework实体框架和前面已经提过的LINQ to ADO.NET (包括LINQ to DataSet和 LINQ to SQL.) 。
ADO.NET 3.0的实体框架提供了一组将面向关系的数据和面向对象的实体之间相互转换的功能,它包括实体数据模型、实体数据模型工具、映射、对象服务、实体客户和实体SQL等一系列的功能。
ADO.NET实体框架允许开发者通过对象模型来替代关系模型访问数据,以便减少对象数据的应用程序的代码量和维护工作量。
一般通用的数据模型设计模式是将数据模型派生为三个部分:
l 一个在系统中定义实体和关系的概念模型。
l 一个使实体和关系实体化为带外键约束的关系性数据库表的逻辑模型。
l 一个描述例如分区和索引等特定引擎信息的物理模型。
而ADO.NET则通过允许开发人员编写代码来操作使用实体数据模型(EDM)生成的对象来取代直接使用逻辑模型的方式扩展了概念模型的能力。然后实体框架将这些操作映射为特定于存储的关系性命令。实体框架提供了如下特性:
l 应用程序将从硬编码实现的特定数据引擎或逻辑模型中获益。
l 在概念模型和特定于存储的逻辑模型之间映射且当它们修改时不需要修改应用程序代码。
l 开发人员能够将一个概念模型应用到多个存储引擎上。
l 多个可编程对象能够在一个单一的概念模型上分层。
l 依靠概念模型,语言集成查询能够提供编译时的校验。
实体数据模型(EDM)是应用程序中用于设计数据的规范。建立于实体框架上的应用程序定义了在设计架构中应用程序域的实体和关系。设计架构用于建立应用程序代码中使用的可编程类,用于持久化数据的存储结构则在另外一个架构中被描绘出来,而映射描述则将涉及架构和存储架构连接起来。
因为可编程对象模型是建立在设计架构上的,而存储架构被映射到存储架构,因此映射描述实际上是连接了可编程类和存储结构。使用EDM定义的实体能够以序列化的方式使用DataReader读取或者被实例化为对象。不需要使用SQL语句或者其它的数据库语法就可以更新和存储实例化了的对象。在实体数据模型架构和映射信息中实体数据模型支持简单的实体类型,此外,开发人员还能够扩展这些实体类型以支持应用程序的设计。
Visual Studio提供了一组用于帮助开发基于实体数据模型的应用程序的工具,这些工具包括一个项模板和用于生成模型映射架构的向导:
在C#和VB.NET项目类型中提供了一个项模板以启动实体数据模型向导。
实体数据模型向导会生成.csdl、.ssdl和msl文件。向导能够从现有的数据库中生成上述三个文件或者生成空文件,稍后你可以手动编辑它们。该向导还能够配置成单一文件生成器以仅仅基于.csdl文件运行,该单一文件生成器将从.csdl文件中定义的实体模型生成C#或VB代码。
此外,还有一个命令行工具EdmGen.exe同样能够从实体数据模型元文件中生成模型,验证现有模型和执行其他功能。
实体框架还包括了一个强有力的在表现关系型数据库中逻辑架构的实体数据模型和表现概念应用程序架构的模型之间进行映射的引擎。该引擎能够实现诸如重命名列、垂直分区数据,以及将一组数据变为类一级的实体实例等等的转换。该映射支持获取和更新数据。
实体框架还提供了将托管对象和数据一同工作的支持。该工具自动生成用于表现在概念实体对象模型中所定义各种元素的.NET类,这些类用于以对象实例的方式从查询中表示结果。系统通过自动生成SQL语句或指定地存储过程的方式提供诸如更改跟踪和顺序更新数据等功能。
实体客户端是一个新的.NET Framework数据提供程序,它允许开发人员使用相同的编码模式来操作概念实体数据模型和数据库。实体客户端通过使用EntityConnection对象来暴露实体对象模型的概念架构,通过使用EntityConnection对象和实体SQL查询语言来执行查询。当它和对象服务共同使用时,实体客户端提供了查询和更新数据方面的基础。
为了支持实体对象模型,实体框架提供了一种类似于SQL的语言——实体SQL。实体SQL是一种支持实体对象模型构造,允许用户有效的查询使用实体模型表现的数据的查询语言。实体SQL被设计为类似于用于查询的Transact-SQL,它的结构和操作都和Transact-SQL相似。例如下面的一段实体SQL代码:
SELECT ITEM d FROM Departments AS d WHERE d.location = "Seattle"
代码 20-49
 
它表示从Departments中查询出location属性等于Seattle的对象。
实体框架使用元数据来通过应用程序的如下三个方面建立实体和关系模型:对象模型、概念模型和存储模型。附加的元数据在两层之间映射实体和关系。
前面已经提到过LINQ,在ADO.NET中,支持使用两种扩展的方式来使用LINQ查询数据:LINQ to DataSet和LINQ to SQL。LINQ to DataSet用于查询以数据表和数据集的方式缓存的数据,而LINQ to SQL用于查询诸如数据库之类的外部数据存储中的关系型数据。LINQ to SQL依靠紧密连接着关系型数据库的对象模型来执行查询操作。
LINQ to DataSet提供了查询存储于数据集中的断开式数据的能力,它使用标准的LINQ语法并提供了编译时的语法检查,静态键入和自动完成功能。
LINQ to DataSet能够对数据集中的单表进行查询,例如:
DataTable orders = ds.Tables["SalesOrderHeader"];
 
var query = from o in orders.AsEnumerable()
            where o.Field<bool>("OnlineOrderFlag") == true
            select new { SalesOrderID = o.Field<int>("SalesOrderID"),
                         OrderDate = o.Field<DateTime>("OrderDate"),
                         SalesOrderNumber =
                           o.Field<string>("SalesOrderNumber")};
foreach(var onlineOrder in query)
{
 Console.WriteLine("Order ID: {0} Order date: {1:d} Order number: {2}",
                   onlineOrder.SalesOrderID,
                   onlineOrder.OrderDate,
                   onlineOrder.SalesOrderNumber);
}
代码 20-50
 
此外,通过使用Join 或 GroupJoin操作符,LINQ to DataSet还能够对数据集中的多表进行查询,如以下的代码:
DataTable orders      = ds.Tables["SalesOrderHeader"];
DataTable details = ds.Tables["SalesOrderDetail"];
           
var query = from o in orders.AsEnumerable()
            join d in details.AsEnumerable()
            on o.Field<int>("SalesOrderID") equals
              d.Field<int>("SalesOrderID")
            where o.Field<bool>("OnlineOrderFlag") == true
                && o.Field<DateTime>("OrderDate").Month == 8
            select new { SalesOrderID = o.Field<int>("SalesOrderID"),
                         SalesOrderDetailID =
                           d.Field<int>("SalesOrderDetailID"),
                         OrderDate = o.Field<DateTime>("OrderDate"),
                         ProductID = d.Field<int>("ProductID") };
 
foreach(var o in query)
{
Console.WriteLine("{0}/t{1}/t{2:d}/t{3}",
              o.SalesOrderID,
o.SalesOrderDetailID,
o.OrderDate,
o.ProductID);
}
代码 20-51
 
如果使用的是类型化数据集,那么查询起来就更为方便,甚至不再需要使用Join和GroupJoin操作符:
var query = from o in orders
            where o.OnlineOrderFlag == true
            select new { o.SalesOrderID,
                         o.OrderDate,
                         o.SalesOrderNumber };
 
foreach(var order in query)
{
    Console.WriteLine("{0}/t{1:d}/t{2}",
order.SalesOrderID,
order.OrderDate,
order.SalesOrderNumber);
}
代码 20-52
 
此外,LINQ还能够依靠映射到关系型数据库的对象模型来直接对数据库进行查询,这就是LINQ to SQL。在该对象模型中,每一张数据库表将被抽象成为一个独立的类,LINQ to SQL将对该对象模型中这些类的查询转换为T-SQL语句,并将其发送到数据库中执行,当数据库返回查询结果时,LINQ to SQL将这些结果重新转换为对象。下图说明了LINQ to SQL的结构:
图 20-2 LINQ to SQL的结构
 
而下面的示意图则说明了LINQ to SQL的执行流程;
图 20-3 LINQ to SQL的执行流程
 
建立LINQ to SQL应用程序的对象模型可以使用多种方法,Visual Studio提供了三种方法:
l O/R设计器:该设计器为从现有的数据库中创建数据对象模型提供了一个简单易用的用户界面。
l SQL元数据工具:这是一个命令行工具,为创建数据对象模型提供了一组不同于O/R设计器的操作方式。
l 代码编辑器:除了以上两种方式之外,还可以使用代码编辑器来手动创建用户描述数据对象模性的XML文件。
在生成数据对象模型之后,便可以使用LINQ对该数据对象模型进行查询操作了,查询的方法和前面已经讲到的LINQ查询对象集合没有任何差别,开发人员基本上感觉不到自己是在和数据库打交道。
.NET Framework 3.5包含了一系列在ASP.NET方面的增强特性,它提供了在Web开发方面的一系列的新的功能。其中最有意义的增强是支持启用AJAX技术的网站开发,这包括新的服务器空间和类型,一个新的面向对象的客户端类型库以及完整的ECMA脚本的自动完成支持。从而使开发人员能够非常方便的向现有页面添加AJAX体验和开发新的AJAX页面。还使开发人员能够只使用服务器端控件和代码就能够开发具有AJAX特性的页面。
.NET Framework 3.5包含了如下一系列的在ASP.NET方面的增强:
l 新的服务器控件、类型和客户端脚本库,这些东西协同工作能够允许开发人员开发AJAX样式的Web应用程序。这些Web应用程序独立于浏览器,而且这些AJAX功能能够重用和扩展。开发人员能够仅仅使用服务器端的控件和代码来开发AJAX页面——当然也可以仅仅使用客户端代码来开发或者两者都使用。在ASP.NET中使用的AJAX客户端脚本库被称为Microsoft AJAX Library,支持面向对象的机遇客户端脚本的AJAX应用程序开发。
l 对ASP.NET 2.0中的服务器端认证、角色管理以及个性化配置服务进行扩展,使之能够支持Web Services,这样使这些服务能够在任意的基于Web的应用程序中使用。这些服务能够在基于Microsoft AJAX Library的客户端脚本、Windows窗口应用程序,或其他任何能够读取和使用Windows Communications Foundation(WCF)的应用程序中使用。
l 一个新的ListView数据控件,该数据控件能够显示数据并提供高度可自定义、提供了丰富的客户端特性的UI。该控件在显示重复数据方面非常有用,它很类似于DataList和Repeater控件,但是,和DataList以及Repeater控件不同的是,ListView控件支持编辑、插入、编辑以及排序和分页等等功能。
l 对于新的LINQ功能,ASP.NET数据控件都进行了更新,以支持采用LINQ技术对数据对象集合进行排序和筛选。
l 对于AJAX应用程序,现在可以通过使用客户端脚本访问ASP.NET Web 服务 (.asmx 文件) 和 WCF Web 服务(.svc files)。
l 一个新的合并工具(Aspnet_merge.exe)以合并预编译的程序集,这样可以支持灵活的部署和发布管理。
.NET Framework 3.5同时还提供了IIS 7.0的紧密集成支持。如果服务器运行IIS7.0,则现在可以ASP.NET服务比如认证和缓存所有内容类型,而不仅仅是ASP.NET页面(.aspx文件),因为ASP.NET运行时现在是Web服务器的核心了。一致的请求处理管线意味着你能够在ASP.NET中以托管代码的方式来开发HTTP管线,它将处理IIS上所有的请求。此外,IIS和ASP.NET 模块和处理器现在支持统一的配置。
Silverlight 是一种新的 Web 呈现技术,能在各种平台上运行。借助该技术,您将拥有内容丰富、视觉效果绚丽的交互式体验,而且,无论是在浏览器内、在多个设备上还是在桌面操作系统(如 Apple Macintosh)中,都可以获得这种体验。Microsoft .NET Framework 3.0(Windows 编程基础结构)中的呈现技术 XAML(可扩展应用程序标记语言)遵循 WPF (Windows Presentation Foundation),它是 Silverlight 呈现功能的基础。
Silverlight 是应用程序开发人员和设计人员可以向其客户呈现潜在用户体验丰富性的下一个发展阶段。为了实现此目的,它允许设计人员展现其创造力并以能够直接对 Web 产生影响的格式保存其工作。过去,设计人员会使用提供了丰富输出功能的工具来设计网站和用户体验,但在这些设计的实现能力方面,开发人员会受到 Web 平台的限制。在 Silverlight 模型中,设计人员可以构建所需的用户体验,并将其表示为 XAML。随后,开发人员可以使用 Silverlight 运行时直接将该 XAML 合并到网页中。因此,两者的合作可以比以往任何时候都更加紧密,从而提供丰富的客户端用户体验。
由于 XAML 属于 XML,因此它是基于文本的,能够为这些丰富的内容提供与防火墙兼容的、易于检查的说明。尽管其他技术(如 Java 小程序、ActiveX 和 Flash)可用来部署比 DHTML/CSS/JavaScript 更丰富的内容,但它们都会向浏览器发送二进制内容。这就导致难以进行安全性审核,更不用说还有更新上的困难,因为进行任何更改后都必须重新安装整个应用程序,而这并不是友好的用户体验,并且可能导致页面停滞。如果使用 Silverlight,则需要更改丰富的内容时,服务器端会生成新的 XAML 文件。用户下次浏览到该页面时,会下载该 XAML 并更新体验,而不需要进行任何重新安装。
Silverlight 的核心是浏览器增强模块,其作用是呈现 XAML 并在浏览器界面上绘制生成的图形。它的下载体积较小(不到 2 MB),可以在用户点击包含 Silverlight 内容的站点时进行安装。该模块向 JavaScript 开发人员公开 XAML 页面的底层框架,以便实现页面级的内容交互,这样,开发人员就可以进行自己的工作,例如编写事件处理程序或使用 JavaScript 代码来处理 XAML 页面内容。最后,在浏览器中运行,我们可以看到如下的效果:
图 20-4
 
由于基于XAML的WPF已经内置于.NET Framework 3.0和Windows Vista中并且将成为下一代Windows应用程序界面开发的首选技术,因为我们有理由认为,C/S和B/S之间的界限将越来越模糊,我们可以采用同一种技术来开发基于桌面和基于浏览器的应用程序,而以HTML为基础的ASP.NET技术也将走向消亡,未来的若干年中将是基于XAML的应用程序的天下。
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值