C# 各种数据类型的最大值和最小值常数

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TypeMaxMinValue
{
class Program
    {
static void Main(string[] args)
        {
            PrintDataRange();        
        }
static void PrintDataRange() {
            System.Console.WriteLine("int类型的最大值:  {0},最小值:  {1}", int.MaxValue, int.MinValue);
            System.Console.WriteLine("uint类型的最大值:  {0},最小值:  {1}", uint.MaxValue, uint.MinValue);
            System.Console.WriteLine("byte类型的最大值:  {0},最小值:  {1}", byte.MaxValue, byte.MinValue);
            System.Console.WriteLine("sbyte类型的最大值:  {0},最小值:  {1}", sbyte.MaxValue, sbyte.MinValue);
            System.Console.WriteLine("short类型的最大值:  {0},最小值:  {1}", short.MaxValue, short.MinValue);
            System.Console.WriteLine("ushort类型的最大值:  {0},最小值:  {1}",ushort.MaxValue, ushort.MinValue);
            System.Console.WriteLine("long类型的最大值:  {0},最小值:  {1}", long.MaxValue, long.MinValue);
            System.Console.WriteLine("ulong类型的最大值:  {0},最小值:  {1}", ulong.MaxValue, ulong.MinValue);
            System.Console.WriteLine("float类型的最大值:  {0},最小值:  {1}", float.MaxValue, float.MinValue);
            System.Console.WriteLine("double类型的最大值:  {0},最小值:  {1}", double.MaxValue, double.MinValue);
            System.Console.WriteLine("decimal类型的最大值:  {0},最小值:  {1}", decimal.MaxValue, decimal.MinValue);
            System.Console.ReadLine();
        }       
    }
}
运行结果如下:

int类型的最大值:  2147483647,最小值:  -2147483648
uint类型的最大值:  4294967295,最小值:  0
byte类型的最大值:  255,最小值:  0
sbyte类型的最大值:  127,最小值:  -128
short类型的最大值:  32767,最小值:  -32768
ushort类型的最大值:  65535,最小值:  0
long类型的最大值:  9223372036854775807,最小值:  -9223372036854775808
ulong类型的最大值:  18446744073709551615,最小值:  0
float类型的最大值:  3.402823E+38,最小值:  -3.402823E+38
double类型的最大值:  1.79769313486232E+308,最小值:  -1.79769313486232E+308
decimal类型的最大值:  79228162514264337593543950335,最小值:  -792281625142643375 93543950335

发表在 C# | 标签为 | 一条评论

宅男有三宝:科技,女优,人真好。型男有三宝:亲亲,抱抱,再推倒。腐女有三宝:意淫,淘宝,节日少。韩剧有三宝:车祸,癌症,医不好。周董有三宝:哎呦,不错,这个屌。

男同學们,不要买七彩的杰士邦,如果您买了,请把黑色那只直接扔掉。。。你们懂的,黑色显瘦

书记在台上作报告,一不小心放了个屁,扩音器效果很好,台下听了爆笑。书记大怒,严肃说道:“大家都认真点,今天开会是在传达党的声音!”

某年会上,每个同学都出节目。一同学异常有个性表演魔术,上台,坐在凳子上,把书放在裤子拉链部位,然后他凝视我们班一女生,1分钟后,表演魔术,老师同学全场泪奔….那魔术节目名字叫…《会动的书》.

一个小偷半夜溜进了一个戒备森严的地方 经过千辛万苦打开保险库之后 发现里面居然全都是果冻。。。又累又饿的小偷一怒之下吃掉所有果冻离开了,第二天当地报纸新闻头条:《震惊!精子库疯狂被盗》

一对情侣去野营,男的嘘嘘的时候不小心被蜜蜂蛰了小JJ。 晚上在帐篷里忍痛OOXX,完事后女生泪眼婆娑: 感谢小蜜蜂!

亲戚关系解析:韩国的爸爸是美国,妈妈是日本,他和爹比较亲,在外人面前从来不拉他娘的手。朝鲜的爸爸是俄罗斯,妈妈是中国。他爹喜欢抽丫的,但是他总是满脸堆笑,然后板着脸到娘家要吃要喝,还经常在人前不给面子。

一辈子骗你一次的是医院的临终关怀大夫;一年骗你一次的是税务局个人缴税额度;一个季度骗你一次的是统计局的平均收入;一个月骗你一次的是单位发工资的会计财务;一天骗你一次的是每天晚上七点整

医生与院长不和,被调到妇科,窃喜。次日接看女患者数十名,连摸带看,持续勃起八小时。晚上老婆纠缠,阳萎不举,才恍然大悟:CAO他娘个B!又被领导暗算了!

一个人在书店买书,对店员说:我想买本书,里面没有凶杀,却暗藏杀机,没有爱情,却爱恨难舍,没有侦探,却时时警惕。你能给我介绍一本吗?“只有这个”,店员说“中国股市行情”。

胸的符号文化:(oYo) 美丽的胸部; (o)(o) 完美胸部; (+)(+) 隆过的胸部 (^)(^) 躺着的胸部; (Q)(O) 有伤痕的胸部; (o)(O) 不对称的胸部; (p)(p) 不穿内衣时的胸部 ;(.Y.) 波霸 ;|o||o| 伪娘的胸部。

母亲啊,您太了不起了,能在中国把我养大。

地铁上听到一五六岁的女孩对着老爸嚷嚷:"都说女儿是爸爸的前世情人,你说我上辈子怎么就看上你了我,,,"全车厢笑喷。

一个朋友去买了个大蛋糕回来,蛋糕上写着诸葛藏藏,我们都纳闷:诸葛藏藏是谁呀? 朋友说:就是为了多骗点奶油

曾经的梦中情 人:皮肤白皙,知性,善交际,套穿白色长裙,披肩长发,大波浪。10 年后,我找到了梦中情人:皮肤白皙,知性善交,忌套,穿白色长裙,披肩长发,大波,浪。

各国机场迎接国家LD人时都会挂出条幅:欢迎XX来美、欢迎XX来俄之类。但日本一直对此很是冷淡~

今儿一法医朋友给我讲,上学学解剖那会儿,班里一彪悍女生给一男尸解剖. 因为其下体过于庞大,闲碍事儿,于是扯着JJ咣咣咣钉到了实验板上。顿时全体男生下体阴冷阴冷的。。。。听完我也觉得阴冷阴冷的

中国不再禁止国外艾滋病等患者入境,网友评论一语道破:哈哈哈,是有人的儿子回不了国急的。

小时候,“中华”是一管白白的牙膏。我在这头,笑容在那头;上学了,“中华”是一支细细的铅笔。我在这头,考卷在那头;工作了,“中华”是一条红红的香烟。我在这头,领导在那头;结婚了,“中华”是贷款买的轿车。我在这头,而奋斗的路,还在那头……

有个在线好友,签名是:珍惜一切时间,全力以赴心中梦想。状态是:斗地主游戏中..

读书时一直暗恋她,但没勇气表白,更没胆量碰她。毕业后,她已经结婚了,老公跟我是同学。那天下午,她抱着娃娃在喂奶,我找了个很好的借口。我走到她面前,一把两手按住她的MIMI,然后跟她娃娃说,喊叔叔,不喊不给吃!

看到前女友的QQ个性签名上写“我们一年了。”,MD她和那个人都一年了,我们分手还不到一年。

网易评论:“日本的妓女是明星,中国的明星是妓女。”

从前有个姑娘叫小A,后来她有了男人,就变成了小V。而这个过程呢,我们就管它叫AV。

鹦鹉偷吃伟哥,主人怕其上火,将其放进冰柜。半小时打开,见鹦鹉满身大汗,主人不解,问何故?鹦鹉懊恼:真他妈费劲,那只冻母鸡的腿太难掰了!

宅男有三宝:科技,女优,人真好。型男有三宝:亲亲,抱抱,再推倒。腐女有三宝:意淫,淘宝,节日少。韩剧有三宝:车祸,癌症,医不好。周董有三宝:哎呦,不错,这个屌。

从前有座山,山里有个庙,庙里有个老和尚和一个小和尚,然后小和尚喊了老和尚 一声:爸,外面下雪了….

我国有一套独特的话语体系,“恐怖分子”在我们这里叫“反美武装”,“城管打人”叫“肢体冲突”,美军撤退叫“溃败”,我军撤退叫“战略转移”,逃亡时更牛逼,叫“长征”。以前老看到“群体性事件”,我还以为是一群人在大街上做爱。

一群羊在山坡吃草看见一群母牛慌张逃来,问:发生什么事了?母牛们回答:领导来了,光吹牛B。母牛逃窜过后不久一群公牛又慌忙逃来,羊们问:你们公牛跑什么啊?公牛们回答:领导来了,他们不光吹牛B,还扯蛋呢!

上了公共汽车。看见一个抱孩子的妇女,我主动让了座。她激动的说:“谢谢你!”我当时怒吓道:“先谢国家!”

昨天好不容易问同学要了兽兽的种子,兴冲冲的下好,然后一个对话框弹出来“该文件已存在,是否替代原文件?”,顿时石化了…我爸总是走在我前面!

奥斯卡揭晓,网友发来贺电: 阿凡达的鸟再大,也敌不过拆蛋部队……

数字军团和字母军团打了起来

男同學们,不要买七彩的杰士邦,如果您买了,请把黑色那只直接扔掉。。。你们懂的,黑色显瘦

书记在台上作报告,一不小心放了个屁,扩音器效果很好,台下听了爆笑。书记大怒,严肃说道:“大家都认真点,今天开会是在传达党的声音!”

某年会上,每个同学都出节目。一同学异常有个性表演魔术,上台,坐在凳子上,把书放在裤子拉链部位,然后他凝视我们班一女生,1分钟后,表演魔术,老师同学全场泪奔….那魔术节目名字叫…《会动的书》.

一个小偷半夜溜进了一个戒备森严的地方 经过千辛万苦打开保险库之后 发现里面居然全都是果冻。。。又累又饿的小偷一怒之下吃掉所有果冻离开了,第二天当地报纸新闻头条:《震惊!精子库疯狂被盗》

一对情侣去野营,男的嘘嘘的时候不小心被蜜蜂蛰了小JJ。 晚上在帐篷里忍痛OOXX,完事后女生泪眼婆娑: 感谢小蜜蜂!

亲戚关系解析:韩国的爸爸是美国,妈妈是日本,他和爹比较亲,在外人面前从来不拉他娘的手。朝鲜的爸爸是俄罗斯,妈妈是中国。他爹喜欢抽丫的,但是他总是满脸堆笑,然后板着脸到娘家要吃要喝,还经常在人前不给面子。

一辈子骗你一次的是医院的临终关怀大夫;一年骗你一次的是税务局个人缴税额度;一个季度骗你一次的是统计局的平均收入;一个月骗你一次的是单位发工资的会计财务;一天骗你一次的是每天晚上七点整

医生与院长不和,被调到妇科,窃喜。次日接看女患者数十名,连摸带看,持续勃起八小时。晚上老婆纠缠,阳萎不举,才恍然大悟:CAO他娘个B!又被领导暗算了!

一个人在书店买书,对店员说:我想买本书,里面没有凶杀,却暗藏杀机,没有爱情,却爱恨难舍,没有侦探,却时时警惕。你能给我介绍一本吗?“只有这个”,店员说“中国股市行情”。

胸的符号文化:(oYo) 美丽的胸部; (o)(o) 完美胸部; (+)(+) 隆过的胸部 (^)(^) 躺着的胸部; (Q)(O) 有伤痕的胸部; (o)(O) 不对称的胸部; (p)(p) 不穿内衣时的胸部 ;(.Y.) 波霸 ;|o||o| 伪娘的胸部。

母亲啊,您太了不起了,能在中国把我养大。

地铁上听到一五六岁的女孩对着老爸嚷嚷:"都说女儿是爸爸的前世情人,你说我上辈子怎么就看上你了我,,,"全车厢笑喷。

一个朋友去买了个大蛋糕回来,蛋糕上写着诸葛藏藏,我们都纳闷:诸葛藏藏是谁呀? 朋友说:就是为了多骗点奶油

曾经的梦中情 人:皮肤白皙,知性,善交际,套穿白色长裙,披肩长发,大波浪。10 年后,我找到了梦中情人:皮肤白皙,知性善交,忌套,穿白色长裙,披肩长发,大波,浪。

各国机场迎接国家LD人时都会挂出条幅:欢迎XX来美、欢迎XX来俄之类。但日本一直对此很是冷淡~

今儿一法医朋友给我讲,上学学解剖那会儿,班里一彪悍女生给一男尸解剖. 因为其下体过于庞大,闲碍事儿,于是扯着JJ咣咣咣钉到了实验板上。顿时全体男生下体阴冷阴冷的。。。。听完我也觉得阴冷阴冷的

中国不再禁止国外艾滋病等患者入境,网友评论一语道破:哈哈哈,是有人的儿子回不了国急的。

小时候,“中华”是一管白白的牙膏。我在这头,笑容在那头;上学了,“中华”是一支细细的铅笔。我在这头,考卷在那头;工作了,“中华”是一条红红的香烟。我在这头,领导在那头;结婚了,“中华”是贷款买的轿车。我在这头,而奋斗的路,还在那头……

有个在线好友,签名是:珍惜一切时间,全力以赴心中梦想。状态是:斗地主游戏中..

读书时一直暗恋她,但没勇气表白,更没胆量碰她。毕业后,她已经结婚了,老公跟我是同学。那天下午,她抱着娃娃在喂奶,我找了个很好的借口。我走到她面前,一把两手按住她的MIMI,然后跟她娃娃说,喊叔叔,不喊不给吃!

看到前女友的QQ个性签名上写“我们一年了。”,MD她和那个人都一年了,我们分手还不到一年。

网易评论:“日本的妓女是明星,中国的明星是妓女。”

从前有个姑娘叫小A,后来她有了男人,就变成了小V。而这个过程呢,我们就管它叫AV。

鹦鹉偷吃伟哥,主人怕其上火,将其放进冰柜。半小时打开,见鹦鹉满身大汗,主人不解,问何故?鹦鹉懊恼:真他妈费劲,那只冻母鸡的腿太难掰了!

宅男有三宝:科技,女优,人真好。型男有三宝:亲亲,抱抱,再推倒。腐女有三宝:意淫,淘宝,节日少。韩剧有三宝:车祸,癌症,医不好。周董有三宝:哎呦,不错,这个屌。

从前有座山,山里有个庙,庙里有个老和尚和一个小和尚,然后小和尚喊了老和尚 一声:爸,外面下雪了….

我国有一套独特的话语体系,“恐怖分子”在我们这里叫“反美武装”,“城管打人”叫“肢体冲突”,美军撤退叫“溃败”,我军撤退叫“战略转移”,逃亡时更牛逼,叫“长征”。以前老看到“群体性事件”,我还以为是一群人在大街上做爱。

一群羊在山坡吃草看见一群母牛慌张逃来,问:发生什么事了?母牛们回答:领导来了,光吹牛B。母牛逃窜过后不久一群公牛又慌忙逃来,羊们问:你们公牛跑什么啊?公牛们回答:领导来了,他们不光吹牛B,还扯蛋呢!

上了公共汽车。看见一个抱孩子的妇女,我主动让了座。她激动的说:“谢谢你!”我当时怒吓道:“先谢国家!”

昨天好不容易问同学要了兽兽的种子,兴冲冲的下好,然后一个对话框弹出来“该文件已存在,是否替代原文件?”,顿时石化了…我爸总是走在我前面!

奥斯卡揭晓,网友发来贺电: 阿凡达的鸟再大,也敌不过拆蛋部队……

数字军团和字母军团打了起来,数字头领0说:“1、3,你们组成B,潜入字母军团!”过了 一会,只见1、3两人头破血流地回来了,说:“头领!装B被发现了!”

新时代四大靠不住:丰田车的底盘,开发商的楼盘,股市的大盘,前男友的硬盘。

某领导发言:“…雷锋没有死!…”台下听众听的直愣,秘书在旁边悄声提醒:“精神,精神!”——意指“雷锋精神没有死”,不想领导接茬说道:“对!还精神着呢…”台下听众雷到一片…

某国际学校的老师出了一道开放性问题:你对其他国家的食物短缺有什么自己的看法?非洲学生问:什么叫食物?欧洲学生问:什么叫短缺?美国学生问:什么叫其他国家?中国学生问:什么叫自己的看法?

,数字头领0说:“1、3,你们组成B,潜入字母军团!”过了 一会,只见1、3两人头破血流地回来了,说:“头领!装B被发现了!”

新时代四大靠不住:丰田车的底盘,开发商的楼盘,股市的大盘,前男友的硬盘。

某领导发言:“…雷锋没有死!…”台下听众听的直愣,秘书在旁边悄声提醒:“精神,精神!”——意指“雷锋精神没有死”,不想领导接茬说道:“对!还精神着呢…”台下听众雷到一片…

某国际学校的老师出了一道开放性问题:你对其他国家的食物短缺有什么自己的看法?非洲学生问:什么叫食物?欧洲学生问:什么叫短缺?美国学生问:什么叫其他国家?中国学生问:什么叫自己的看法?

发表在 幽默 | 标签为 , | 2 条评论

C#中问号的用法(非表达式)—单问号,双问号

单问号—用于给变量设初值的时候,给变量(int类型)赋值为null,而不是0!

双问号—用于判断并赋值,先判断当前变量是否为null,如果是就可以赋一个新值,否则跳过!

public int? para;
//public int para;如果不注释掉此行,而把上一行注释掉的话,下面会报错!
public int par()
{
return this.para ?? 0;
}

发表在 C# | 标签为 , | 3 条评论

一步一步学Linq to sql

(一):预备知识

什么是Linq to sql

Linq to sql(或者叫DLINQ)是LINQ(.NET语言集成查询)的一部分,全称基于关系数据的 .NET 语言集成查询,用于以对象形式管理关系数据,并提供了丰富的查询功能,它和Linq to xml、Linq to objects、Linq to dataset、Linq to entities等组成了强大的LINQ。

要学好LINQ查询语法,就不得不先理解C# 3.0的一些新特性,下面一一简单介绍。

隐含类型局部变量

var age = 26;

var username = "zhuye";

var userlist = new [] {"a","b","c"};

foreach(var user in userlist)

Console.WriteLine(user);

纯粹给懒人用的var关键字,告诉编译器(对于CLR来说,它是不会知道你是否使用了var,苦力是编译器出的),你自己推断它的类型吧,我不管了。但是既然让编译器推断类型就必须声明的时候赋值,而且不能是null值。注意,这只能用于局部变量,用于字段是不可以的。

匿名类型

var data = new {username = "zhuye",age = 26};

Console.WriteLine("username:{0} age:{1}", data.username, data.age);

匿名类型允许开发人员定义行内类型,无须显式定义类型。常和var配合使用,var用于声明匿名类型。定义一个临时的匿名类型在LINQ查询句法中非常常见,我们可以很方便的实现对象的转换和投影。

扩展方法

public static class helper

    {

public static string MD5Hash(this string s)

        {

Return System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(s,"MD5");

        }

public static bool In(this object o, IEnumerable b)

        {

foreach(object obj in b)

            {

if(obj==o)

return true;

            }

return false;

        }      

}

// 调用扩展方法

Console.WriteLine("123456".MD5Hash());

Console.WriteLine("1".In(new[]{"1","2","3"}));

很多时候我们需要对CLR类型进行一些操作,苦于无法扩展CLR类型的方法,只能创建一些helper方法,或者生成子类。扩展方法使得这些需求得意实现,同时也是实现LINQ的基础。定义扩展方法需要注意,只能在静态类中定义并且是静态方法,如果扩展方法名和原有方法名发生冲突,那么扩展方法将失效。

自动属性

public class Person

    {

public string username { get; protected set; }

public int age { get; set; }

public Person()

        {

this.username = "zhuye";          

        }

}

Person p = new Person();

//p.username = "aa";

Console.WriteLine(p.username);

意义不是很大,纯粹解决机械劳动。编译器自动为你生成get、set操作以及字段,并且你不能使用字段也不能自定义get、set操作,不过你可以分别定义get和set的访问级别。

对象初始化器

public class Person

    {

public string username { get; set; }

public int age { get; set; }

public override string  ToString()

        {

return string.Format("username:{0} age:{1}", this.username, this.age);

        }

}

Person p = new Person() {username = "zhuye", age=26};

Console.WriteLine(p.ToString());

编译器会自动为你做setter操作,使得原本几行的属性赋值操作能在一行中完成。这里需要注意:

· 允许只给一部分属性赋值,包括internal访问级别

· 可以结合构造函数一起使用,并且构造函数初始化先于对象初始化器执行

集合初始化器

public class Person

{

public string username { get; set; }

public int age { get; set; }

public override string  ToString()

        {

return string.Format("username:{0} age:{1}", this.username, this.age);

        }

}

var persons = new List<Person> {

new Person {username = "a", age=1},

new Person {username = "b", age=2}};

foreach(var p in persons)

Console.WriteLine(p.ToString());

编译器会自动为你做集合插入操作。如果你为Hashtable初始化的话就相当于使用了两个对象初始化器。

Lambda表达式

var list = new [] { "aa", "bb", "ac" };

var result = Array.FindAll(list, s => (s.IndexOf("a") > -1));

foreach (var v in result)

Console.WriteLine(v);

其实和2.0中的匿名方法差不多,都是用于产生内联方法,只不过Lambda表达式的语法更为简洁。语法如下:

(参数列表) => 表达式或者语句块

其中:

参数个数:可以有多个参数,一个参数,或者无参数。

表达式或者语句块:这部分就是我们平常写函数的实现部分(函数体)。

前面的示例分别是1个参数的例子,下面结合扩展方法来一个复杂的例子:

public delegate int mydg(int a, int b);

public static class LambdaTest

      {

public static int oper(this int a, int b, mydg dg)

          {

return dg(a, b);

          }

      }

Console.WriteLine(1.oper(2, (a, b) => a + b));

Console.WriteLine(2.oper(1, (a, b) => a – b));

查询句法

var persons = new List<Person> {

new Person {username = "a", age=19},

new Person {username = "b", age=20},

new Person {username = "a", age=21},

    };

var selectperson = from p in persons where p.age >= 20 select p.username.ToUpper();

foreach(var p in selectperson)

Console.WriteLine(p);

查询句法是使用标准的LINQ查询运算符来表达查询时一个方便的声明式简化写法。该句法能在代码里表达查询时增进可读性和简洁性,读起来容易,也容易让人写对。Visual Studio 对查询句法提供了完整的智能感应和编译时检查支持。编译器在底层把查询句法的表达式翻译成明确的方法调用代码,代码通过新的扩展方法和Lambda表达式语言特性来实现。上面的查询句法等价于下面的代码:

var selectperson = persons.Where(p=>p.age>=20).Select(p=>p.username.ToUpper());

LINQ查询句法可以实现90%以上T-SQL的功能(由于T-SQL是基于二维表的,所以LINQ的查询语法会比T-SQL更简单和灵活),但是由于智能感应的原因,select不能放在一开始就输入。

(二):DataContext与实体

DataContext

DataContext类型(数据上下文)是System.Data.Linq命名空间下的重要类型,用于把查询句法翻译成SQL语句,以及把数据从数据库返回给调用方和把实体的修改写入数据库。

DataContext提供了以下一些使用的功能:

· 以日志形式记录DataContext生成的SQL

· 执行SQL(包括查询和更新语句)

· 创建和删除数据库

DataContext是实体和数据库之间的桥梁,那么首先我们需要定义映射到数据表的实体。

定义实体类

using System.Data.Linq.Mapping;

[Table(Name = "Customers")]

public class Customer

{

    [Column(IsPrimaryKey = true)]

public string CustomerID {get; set;}

    [Column(Name = "ContactName")]

public string Name { get; set; }

    [Column]

public string City {get; set;}

}

以Northwind数据库为例,上述Customers类被映射成一个表,对应数据库中的 Customers表。然后在类型中定义了三个属性,对应表中的三个字段。其中,CustomerID字段是主键,如果没有指定Column特性的Name属性,那么系统会把属性名作为数据表的字段名,也就是说实体类的属性名就需要和数据表中的字段名一致。

现在,创建一个ASP.NET页面,然后在页面上加入一个GridView控件,使用下面的代码进行绑定数据:

using System.Data.Linq;

DataContext ctx = newDataContext("server=xxx;database=Northwind;uid=xxx;pwd=xxx");

Table<Customer> Customers = ctx.GetTable<Customer>();

GridView1.DataSource = from c in Customers where c.CustomerID.StartsWith("A")select new {顾客ID=c.CustomerID, 顾客名=c.Name, 城市=c.City};

GridView1.DataBind();

使用DataContext类型把实体类和数据库中的数据进行关联。你可以直接在DataContext的构造方法中定义连接字符串,也可以使用IDbConnection:

using System.Data.SqlClient;

IDbConnection conn = newSqlConnection("server=xxx;database=Northwind;uid=xxx;pwd=xxx");

DataContext ctx = new DataContext(conn);

之后,通过GetTable获取表示底层数据表的Table类型,显然,数据库中的Customers表的实体是Customer类型。随后的查询句法,即使你不懂SQL应该也能看明白。从Customers表中找出CustomerID以“A”开头的记录,并把CustomersID、Name以及City封装成新的匿名类型进行返回。

结果如下图:

clip_image001

强类型DataContext

public partial class NorthwindDataContext : DataContext

{

public Table<Customer> Customers;

public NorthwindDataContext(IDbConnection connection) : base(connection) { }

public NorthwindDataContext(string connection) : base(connection) { }

}

强类型数据上下文使代码更简洁:

NorthwindDataContext ctx = newNorthwindDataContext("server=xxx;database=Northwind;uid=xxx;pwd=xxx");

GridView1.DataSource = from c in ctx.Customers wherec.CustomerID.StartsWith("A") select new { 顾客ID = c.CustomerID, 顾客名 = c.Name,城市 = c.City };

GridView1.DataBind();

DataContext其实封装了很多实用的功能,下面一一介绍。

日志功能

using System.IO;

NorthwindDataContext ctx = newNorthwindDataContext("server=xxx;database=Northwind;uid=xxx;pwd=xxx");

StreamWriter sw = new StreamWriter(Server.MapPath("log.txt"), true); // Append

ctx.Log = sw;

GridView1.DataSource = from c in ctx.Customers wherec.CustomerID.StartsWith("A") select new { 顾客ID = c.CustomerID, 顾客名 = c.Name,城市 = c.City };

GridView1.DataBind();

sw.Close();

运行程序后在网站所在目录生成了log.txt,每次查询都会把诸如下面的日志追加到文本文件中:

SELECT [t0].[CustomerID], [t0].[ContactName], [t0].[City]

FROM [Customers] AS [t0]

WHERE [t0].[CustomerID] LIKE @p0

– @p0: Input String (Size = 2; Prec = 0; Scale = 0) [A%]

– Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.20706.1

应该说这样的日志对于调试程序是非常有帮助的。

探究查询

using System.Data.Common;

using System.Collections.Generic;

NorthwindDataContext ctx = newNorthwindDataContext("server=xxx;database=Northwind;uid=xxx;pwd=xxx");

var select = from c in ctx.Customers where c.CustomerID.StartsWith("A") selectnew { 顾客ID = c.CustomerID, 顾客名 = c.Name, 城市 = c.City };

DbCommand cmd = ctx.GetCommand(select);

Response.Write(cmd.CommandText + "<br/>");

foreach (DbParameter parm in cmd.Parameters)

    Response.Write(string.Format("参数名:{0},参数值:{1}<br/>", parm.ParameterName, parm.Value));

Customer customer = ctx.Customers.First();

customer.Name = "zhuye";

IList<object> queryText = ctx.GetChangeSet().ModifiedEntities;

Response.Write(((Customer)queryText[0]).Name);

在这里,我们通过DataContext的GetCommand方法获取了查询对应的DbCommand,并且输出了CommandText和所有的DbParameter。之后,我们又通过GetChangeSet方法获取了修改后的实体,并输出了修改内容。

执行查询

NorthwindDataContext ctx = newNorthwindDataContext("server=xxx;database=Northwind;uid=xxx;pwd=xxx");

string newcity = "Shanghai";

ctx.ExecuteCommand("update Customers set City={0} where CustomerID like ‘A%’", newcity);

IEnumerable<Customer> customers = ctx.ExecuteQuery<Customer>("select * from Customers where CustomerID like ‘A%’");

GridView1.DataSource = customers;

GridView1.DataBind();

前一篇文章已经说了,虽然Linq to sql能实现90%以上的TSQL功能。但是不可否认,对于复杂的查询,使用TSQL能获得更好的效率。因此,DataContext类型也提供了执行SQL语句的能力。代码的执行结果如下图:

clip_image002

创建数据库

testContext ctx = newtestContext("server=xxx;database=testdb;uid=xxx;pwd=xxx");

ctx.CreateDatabase();

[Table(Name = "test")]

public class test

{

    [Column(IsPrimaryKey = true, IsDbGenerated = true)]

public int ID { get; set; }

    [Column(DbType="varchar(20)")]

public string Name { get; set; }

}

public partial class testContext : DataContext

{

public Table<test> test;

public testContext(string connection) : base(connection) { }

}

这段代码在数据库中创建了名为testdb的数据库,等同于下面的脚本:

CREATE TABLE [dbo].[test](

    [ID] [int] IDENTITY(1,1) NOT NULL,

    [Name] [varchar](20) COLLATE Chinese_PRC_CI_AS NULL,

CONSTRAINT [PK_test] PRIMARY KEY CLUSTERED

(

    [ID] ASC

)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]

) ON [PRIMARY]

同时,DataContext还提供了DeleteDatabase()方法,在这里就不列举了。

使用DbDataReader数据源

using System.Data.SqlClient;

var conn = new SqlConnection("server=xxx;database=Northwind;uid=xxx;pwd=xxx");

var ctx = new DataContext(conn);

var cmd = new SqlCommand("select * from customers where CustomerID like ‘A%’", conn);

conn.Open();

var reader = cmd.ExecuteReader();      

GridView1.DataSource = ctx.Translate<Customer>(reader);

GridView1.DataBind();

conn.Close();

你同样可以选择使用DataReader获取数据,增加了灵活性的同时也增加了性能。

看到这里,你可能会觉得手工定义和数据库中表对应的实体类很麻烦,不用担心,VS2008提供了自动生成实体类以及关系的工具,工具的使用将在以后讲解。今天就讲到这里,和DataContext相关的事务、加载选项、并发选项以及关系实体等高级内容也将在以后讲解。

clip_image002[1]

(三):增删改

示例数据库

字段名

字段类型

允许空

字段说明

ID

uniqueidentifier

 

表主键字段

UserName

varchar(50)

 

留言用户名

PostTime

datetime

 

留言时间

Message

varchar(400)

留言内容

IsReplied

bit

 

留言是否回复

Reply

varchar(400)

留言管理员回复

在数据库中创建一个名为GuestBook的数据库,在里面创建一个tbGuestBook的表,结构如上表。

生成实体类

右键点击网站项目,选择添加新项,然后选择“Linq to sql Classes”,命名为GuestBook。然后打开App_Code里面的GuestBook.dbml。设计视图上的文字提示你可以从服务器资源管理器或者攻击箱拖动项到设计界面上来创建实体类。

那么,我们就在服务器资源管理器中创建一个指向GuestBook数据库的数据连接,然后把tbGuestBook表拖动到GuestBook.dbml的设计视图上,按CTRL+S保存。打开GuestBook.designer.cs可以发现系统自动创建了GuestBook数据库中tbGuestBook表的映射,如下图:

clip_image003

简易留言簿

现在,我们就可以使用Linq to sql完成简易留言簿了。实现以下功能:

· 发表留言(增)

· 查看留言(查)

· 管理员回复留言(改)

· 管理员删除留言(删除)

首先,创建一个Default.aspx,在页面上加入一些控件:

<div>

姓名

<asp:TextBox ID="tb_UserName" runat="server"></asp:TextBox><br />

<br />

留言

<asp:TextBox ID="tb_Message" runat="server" Height="100px"TextMode="MultiLine" Width="300px"></asp:TextBox><br />

<br />

<asp:Button ID="btn_SendMessage" runat="server" Text="发表留言"OnClick="btn_SendMessage_Click" /><br />

<br />

<asp:Repeater ID="rpt_Message" runat="server">

<ItemTemplate>

<table width="600px" style="border:solid 1px #666666; font-size:10pt;background-color:#f0f0f0">

<tr>

<td align="left" width="400px">

<%# Eval("Message")%>

</td>

<td align="right" width="200px">

<%# Eval("PostTime")%> – <%# Eval("UserName")%>

</td>

</tr>

<tr>

<td colspan="2" align="right">

<hr width="300px" />

管理员回复:<%# Eval("IsReplied").ToString() == "False" ? "暂无" : Eval("Reply")%>

</td>

</tr>

</table>

<br/>

</ItemTemplate>

</asp:Repeater>

</div>

你可能很难想象,使用Linq to sql进行数据访问会是这么简单,后台代码:

public partial class _Default : System.Web.UI.Page

{

GuestBookDataContext ctx = newGuestBookDataContext("server=xxx;database=GuestBook;uid=xxx;pwd=xxx");

protected void Page_Load(object sender, EventArgs e)

    {

if (!IsPostBack)

        {

            SetBind();

        }

    }

protected void btn_SendMessage_Click(object sender, EventArgs e)

    {

tbGuestBook gb = new tbGuestBook();

gb.ID = Guid.NewGuid();

gb.UserName = tb_UserName.Text;

        gb.Message = tb_Message.Text;

        gb.IsReplied = false;

        gb.PostTime = DateTime.Now;

        ctx.tbGuestBooks.Add(gb);

        ctx.SubmitChanges();

        SetBind();

    }

private void SetBind()

    {

        rpt_Message.DataSource = from gb in ctx.tbGuestBooks orderbygb.PostTime descending select gb;

rpt_Message.DataBind();

    }

}

前面创建Linq to sql Classes的时候我们输入名字GuestBook,系统就为我们自动创建了GuestBookDataContext(你也可以在GuestBook.Designer.cs中找到类定义)。在绑定的时候我们使用查询句法查询留言表中所有留言,按照发表时间倒序(天哪?这是数据访问吗?好像仅仅定义了一句SQL啊)。在发表留言按钮中,我们为一个tbGuestBook赋值,然后把它加入留言表,再提交更改,就这样完成了记录的插入。

运行效果如下图:

clip_image004

然后,再创建一个Admin.aspx,前台代码如下:

<div>

<asp:Repeater ID="rpt_Message" runat="server"OnItemCommand="rpt_Message_ItemCommand">

<ItemTemplate>

<table width="600px" style="border:solid 1px #666666; font-size:10pt;background-color:#f0f0f0">

<tr>

<td align="left" width="400px">

<%# Eval("Message")%>

</td>

<td align="right" width="200px">

<%# Eval("PostTime")%> – <%# Eval("UserName")%>

</td>

</tr>

<tr>

<td colspan="2" align="right">

<hr width="300px" />

<asp:Button ID="btn_DeleteMessage" runat="server" Text="删除留言"CommandName="DeleteMessage" CommandArgument=’<%# Eval("ID")%>’/>

管理员回复:<asp:TextBox runat="server" ID="tb_Reply"TextMode="MultiLine" Width="300px" Text=’<%# Eval("Reply")%>’/>

<asp:Button ID="btn_SendReply" runat="server" Text="发表回复"CommandName="SendReply" CommandArgument=’<%# Eval("ID")%>’/>

</td>

</tr>

</table>

<br/>

</ItemTemplate>

</asp:Repeater>

</div>

后台代码:

public partial class Admin : System.Web.UI.Page

{

GuestBookDataContext ctx = newGuestBookDataContext("server=xxx;database=GuestBook;uid=xxx;pwd=xxx");

protected void Page_Load(object sender, EventArgs e)

    {

if (!IsPostBack)

        {

            SetBind();

        }

    }

private void SetBind()

    {

        rpt_Message.DataSource = from gb in ctx.tbGuestBooks orderbygb.PostTime descending select gb;

        rpt_Message.DataBind();

    }

protected void rpt_Message_ItemCommand(object source,RepeaterCommandEventArgs e)

    {

if (e.CommandName == "DeleteMessage")

        {

StreamWriter sw = new StreamWriter(Server.MapPath("log.txt"),true);

            ctx.Log = sw;

tbGuestBook gb = ctx.tbGuestBooks.Single(b => b.ID == newGuid(e.CommandArgument.ToString()));

            ctx.tbGuestBooks.Remove(gb);

            ctx.SubmitChanges();

            SetBind();

            sw.Close();

        }

if (e.CommandName == "SendReply")

        {

StreamWriter sw = new StreamWriter(Server.MapPath("log.txt"),true);

            ctx.Log = sw;

tbGuestBook gb = ctx.tbGuestBooks.Single(b => b.ID == newGuid(e.CommandArgument.ToString()));

            gb.Reply = ((TextBox)e.Item.FindControl("tb_Reply")).Text;

            gb.IsReplied = true;

            ctx.SubmitChanges();

            SetBind();

            sw.Close();

        }

    }

}

运行效果如下图:

clip_image005

在这里,我们通过Single方法获取一条记录,也就是一个tbGuestBook实例,更新了一些属性后保存也就完成了改这个操作。删除操作更简单,只需要从表中移除对象。你是不是觉得好像不是在操作数据库,像在操作内存中的对象。

由于写了日志,看看改和删操作会是怎么样的SQL?

UPDATE [dbo].[tbGuestBook]

SET [IsReplied] = @p4, [Reply] = @p5

WHERE ([ID] = @p0) AND ([UserName] = @p1) AND ([PostTime] = @p2) AND ([Message] = @p3) AND (NOT ([IsReplied] = 1)) AND ([Reply] IS NULL)

– @p0: Input Guid (Size = 0; Prec = 0; Scale = 0) [00000000-0000-0000-0000-000000000000]

– @p1: Input String (Size = 4; Prec = 0; Scale = 0) [ghgh]

– @p2: Input DateTime (Size = 0; Prec = 0; Scale = 0) [2007-8-16 10:20:09]

– @p3: Input String (Size = 3; Prec = 0; Scale = 0) [ghj]

– @p4: Input Boolean (Size = 0; Prec = 0; Scale = 0) [True]

– @p5: Input String (Size = 3; Prec = 0; Scale = 0) [qqq]

– Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.20706.1

DELETE FROM [dbo].[tbGuestBook] WHERE ([ID] = @p0) AND ([UserName] = @p1) AND ([PostTime] = @p2) AND ([Message] = @p3) AND (NOT ([IsReplied] = 1)) AND ([Reply] = @p4)

– @p0: Input Guid (Size = 0; Prec = 0; Scale = 0) [158ec941-13ff-4093-bd8b-9fceae152171]

– @p1: Input String (Size = 2; Prec = 0; Scale = 0) [44]

– @p2: Input DateTime (Size = 0; Prec = 0; Scale = 0) [2007-8-16 9:56:19]

– @p3: Input String (Size = 2; Prec = 0; Scale = 0) [44]

– @p4: Input String (Size = 3; Prec = 0; Scale = 0) [222]

– Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.20706.1

(四):查询句法

select

描述:查询顾客的公司名、地址信息

查询句法:

var 构建匿名类型1 = from c in ctx.Customers

select new

                      {

公司名 = c.CompanyName,

地址 = c.Address

                      };

对应SQL:

SELECT [t0].[CompanyName], [t0].[Address]

FROM [dbo].[Customers] AS [t0]     

描述:查询职员的姓名和雇用年份

查询句法:

var 构建匿名类型2 = from emp in ctx.Employees

select new

                      {

姓名 = emp.LastName + emp.FirstName,

雇用年 = emp.HireDate.Value.Year

                      };

对应SQL:

SELECT [t0].[LastName] + [t0].[FirstName] AS [value], DATEPART(Year, [t0].[HireDate]) AS [value2]

FROM [dbo].[Employees] AS [t0]

描述:查询顾客的ID以及联系信息(职位和联系人)

查询句法:

var 构建匿名类型3 = from c in ctx.Customers

select new

                      {

                          ID = c.CustomerID,

联系信息 = new

                          {

职位 = c.ContactTitle,

联系人 = c.ContactName

                          }

                      };

对应SQL:

SELECT [t0].[CustomerID], [t0].[ContactTitle], [t0].[ContactName]

FROM [dbo].[Customers] AS [t0]

描述:查询订单号和订单是否超重的信息

查询句法:

var select带条件 = from o in ctx.Orders

select new

                        {

订单号 = o.OrderID,

是否超重 = o.Freight > 100 ? "是" : "否"

                        };

对应SQL:

SELECT [t0].[OrderID],

    (CASE

        WHEN [t0].[Freight] > @p0 THEN @p1

        ELSE @p2

     END) AS [value]

FROM [dbo].[Orders] AS [t0]

– @p0: Input Currency (Size = 0; Prec = 19; Scale = 4) [100]

– @p1: Input String (Size = 1; Prec = 0; Scale = 0) [是]

– @p2: Input String (Size = 1; Prec = 0; Scale = 0) [否]

where

描述:查询顾客的国家、城市和订单数信息,要求国家是法国并且订单数大于5

查询句法:

var 多条件 = from c in ctx.Customers

where c.Country == "France" && c.Orders.Count > 5

select new

                  {

国家 = c.Country,

城市 = c.City,

订单数 = c.Orders.Count

                  };     

对应SQL:

SELECT [t0].[Country], [t0].[City], (

    SELECT COUNT(*)

    FROM [dbo].[Orders] AS [t2]

    WHERE [t2].[CustomerID] = [t0].[CustomerID]

    ) AS [value]

FROM [dbo].[Customers] AS [t0]

WHERE ([t0].[Country] = @p0) AND (((

    SELECT COUNT(*)

    FROM [dbo].[Orders] AS [t1]

    WHERE [t1].[CustomerID] = [t0].[CustomerID]

    )) > @p1)

– @p0: Input String (Size = 6; Prec = 0; Scale = 0) [France]

– @p1: Input Int32 (Size = 0; Prec = 0; Scale = 0) [5]   

orderby

描述:查询所有没有下属雇员的雇用年和名,按照雇用年倒序,按照名正序

查询句法:

var 排序 = from emp in ctx.Employees

where emp.Employees.Count == 0

orderby emp.HireDate.Value.Year descending, emp.FirstNameascending

select new

                 {

雇用年 = emp.HireDate.Value.Year,

名 = emp.FirstName

                 };    

对应SQL:

SELECT DATEPART(Year, [t0].[HireDate]) AS [value], [t0].[FirstName]

FROM [dbo].[Employees] AS [t0]

WHERE ((

    SELECT COUNT(*)

    FROM [dbo].[Employees] AS [t1]

    WHERE [t1].[ReportsTo] = [t0].[EmployeeID]

    )) = @p0

ORDER BY DATEPART(Year, [t0].[HireDate]) DESC, [t0].[FirstName]

– @p0: Input Int32 (Size = 0; Prec = 0; Scale = 0) [0]

分页

描述:按照每页10条记录,查询第二页的顾客

查询句法:

var 分页 = (from c in ctx.Customers select c).Skip(10).Take(10);

对应SQL:

SELECT TOP 10 [t1].[CustomerID], [t1].[CompanyName], [t1].[ContactName], [t1].[ContactTitle], [t1].[Address], [t1].[City], [t1].[Region], [t1].[PostalCode], [t1].[Country], [t1].[Phone], [t1].[Fax]

FROM (

    SELECT ROW_NUMBER() OVER (ORDER BY [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax]) AS [ROW_NUMBER], [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax]

    FROM [dbo].[Customers] AS [t0]

    ) AS [t1]

WHERE [t1].[ROW_NUMBER] > @p0

– @p0: Input Int32 (Size = 0; Prec = 0; Scale = 0) [10]

分组

描述:根据顾客的国家分组,查询顾客数大于5的国家名和顾客数

查询句法:

var 一般分组 = from c in ctx.Customers

group c by c.Country into g

where g.Count() > 5

orderby g.Count() descending

select new

                   {

国家 = g.Key,

顾客数 = g.Count()

                   };

对应SQL:

SELECT [t1].[Country], [t1].[value3] AS [顾客数]

FROM (

    SELECT COUNT(*) AS [value], COUNT(*) AS [value2], COUNT(*) AS [value3], [t0].[Country]

    FROM [dbo].[Customers] AS [t0]

    GROUP BY [t0].[Country]

    ) AS [t1]

WHERE [t1].[value] > @p0

ORDER BY [t1].[value2] DESC

– @p0: Input Int32 (Size = 0; Prec = 0; Scale = 0) [5]

描述:根据国家和城市分组,查询顾客覆盖的国家和城市

查询句法:

var 匿名类型分组 = from c in ctx.Customers

group c by new { c.City, c.Country } into g

orderby g.Key.Country, g.Key.City

select new

                     {

国家 = g.Key.Country,

城市 = g.Key.City

                     };

对应SQL:

SELECT [t1].[Country], [t1].[City]

FROM (

    SELECT [t0].[City], [t0].[Country]

    FROM [dbo].[Customers] AS [t0]

    GROUP BY [t0].[City], [t0].[Country]

    ) AS [t1]

ORDER BY [t1].[Country], [t1].[City]

描述:按照是否超重条件分组,分别查询订单数量

查询句法:

var 按照条件分组 = from o in ctx.Orders

group o by new { 条件 = o.Freight > 100 } into g

select new

                     {

数量 = g.Count(),

是否超重 = g.Key.条件 ? "是" : "否"

                     };

对应SQL:

SELECT

    (CASE

        WHEN [t2].[value2] = 1 THEN @p1

        ELSE @p2

     END) AS [value], [t2].[value] AS [数量]

FROM (

    SELECT COUNT(*) AS [value], [t1].[value] AS [value2]

    FROM (

        SELECT

            (CASE

                WHEN [t0].[Freight] > @p0 THEN 1

                WHEN NOT ([t0].[Freight] > @p0) THEN 0

                ELSE NULL

             END) AS [value]

        FROM [dbo].[Orders] AS [t0]

        ) AS [t1]

    GROUP BY [t1].[value]

    ) AS [t2]

– @p0: Input Currency (Size = 0; Prec = 19; Scale = 4) [100]

– @p1: Input String (Size = 1; Prec = 0; Scale = 0) [是]

– @p2: Input String (Size = 1; Prec = 0; Scale = 0) [否]

distinct

描述:查询顾客覆盖的国家

查询句法:

var 过滤相同项 = (from c in ctx.Customers orderby c.Country selectc.Country).Distinct();

对应SQL:

SELECT DISTINCT [t0].[Country]

FROM [dbo].[Customers] AS [t0]

union

描述:查询城市是A打头和城市包含A的顾客并按照顾客名字排序

查询句法:

var 连接并且过滤相同项 = (from c in ctx.Customers where c.City.Contains("A")select c).Union

            (from c in ctx.Customers where c.ContactName.StartsWith("A")select c).OrderBy(c => c.ContactName);

对应SQL:

SELECT [t3].[CustomerID], [t3].[CompanyName], [t3].[ContactName], [t3].[ContactTitle], [t3].[Address], [t3].[City], [t3].[Region], [t3].[PostalCode], [t3].[Country], [t3].[Phone], [t3].[Fax]

FROM (

    SELECT [t2].[CustomerID], [t2].[CompanyName], [t2].[ContactName], [t2].[ContactTitle], [t2].[Address], [t2].[City], [t2].[Region], [t2].[PostalCode], [t2].[Country], [t2].[Phone], [t2].[Fax]

    FROM (

        SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax]

        FROM [dbo].[Customers] AS [t0]

        WHERE [t0].[City] LIKE @p0

        UNION

        SELECT [t1].[CustomerID], [t1].[CompanyName], [t1].[ContactName], [t1].[ContactTitle], [t1].[Address], [t1].[City], [t1].[Region], [t1].[PostalCode], [t1].[Country], [t1].[Phone], [t1].[Fax]

        FROM [dbo].[Customers] AS [t1]

        WHERE [t1].[ContactName] LIKE @p1

        ) AS [t2]

    ) AS [t3]

ORDER BY [t3].[ContactName]

– @p0: Input String (Size = 3; Prec = 0; Scale = 0) [%A%]

– @p1: Input String (Size = 2; Prec = 0; Scale = 0) [A%]

concat

描述:查询城市是A打头和城市包含A的顾客并按照顾客名字排序,相同的顾客信息不会过滤

查询句法:

var 连接并且不过滤相同项 = (from c in ctx.Customers where c.City.Contains("A")select c).Concat

            (from c in ctx.Customers where c.ContactName.StartsWith("A")select c).OrderBy(c => c.ContactName);

对应SQL:

SELECT [t3].[CustomerID], [t3].[CompanyName], [t3].[ContactName], [t3].[ContactTitle], [t3].[Address], [t3].[City], [t3].[Region], [t3].[PostalCode], [t3].[Country], [t3].[Phone], [t3].[Fax]

FROM (

    SELECT [t2].[CustomerID], [t2].[CompanyName], [t2].[ContactName], [t2].[ContactTitle], [t2].[Address], [t2].[City], [t2].[Region], [t2].[PostalCode], [t2].[Country], [t2].[Phone], [t2].[Fax]

    FROM (

        SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax]

        FROM [dbo].[Customers] AS [t0]

        WHERE [t0].[City] LIKE @p0

        UNION ALL

        SELECT [t1].[CustomerID], [t1].[CompanyName], [t1].[ContactName], [t1].[ContactTitle], [t1].[Address], [t1].[City], [t1].[Region], [t1].[PostalCode], [t1].[Country], [t1].[Phone], [t1].[Fax]

        FROM [dbo].[Customers] AS [t1]

        WHERE [t1].[ContactName] LIKE @p1

        ) AS [t2]

    ) AS [t3]

ORDER BY [t3].[ContactName]

– @p0: Input String (Size = 3; Prec = 0; Scale = 0) [%A%]

– @p1: Input String (Size = 2; Prec = 0; Scale = 0) [A%]

取相交项

描述:查询城市是A打头的顾客和城市包含A的顾客的交集,并按照顾客名字排序

查询句法:

var 取相交项 = (from c in ctx.Customers where c.City.Contains("A") selectc).Intersect

            (from c in ctx.Customers where c.ContactName.StartsWith("A")select c).OrderBy(c => c.ContactName);

对应SQL:

SELECT [t1].[CustomerID], [t1].[CompanyName], [t1].[ContactName], [t1].[ContactTitle], [t1].[Address], [t1].[City], [t1].[Region], [t1].[PostalCode], [t1].[Country], [t1].[Phone], [t1].[Fax]

FROM (

    SELECT DISTINCT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax]

    FROM [dbo].[Customers] AS [t0]

    ) AS [t1]

WHERE (EXISTS(

    SELECT NULL AS [EMPTY]

    FROM [dbo].[Customers] AS [t2]

    WHERE ([t1].[CustomerID] = [t2].[CustomerID]) AND ([t2].[ContactName] LIKE @p0)

    )) AND ([t1].[City] LIKE @p1)

ORDER BY [t1].[ContactName]

– @p0: Input String (Size = 2; Prec = 0; Scale = 0) [A%]

– @p1: Input String (Size = 3; Prec = 0; Scale = 0) [%A%]

排除相交项

描述:查询城市包含A的顾客并从中删除城市以A开头的顾客,并按照顾客名字排序

查询句法:

var 排除相交项 = (from c in ctx.Customers where c.City.Contains("A") selectc).Except

            (from c in ctx.Customers where c.ContactName.StartsWith("A")select c).OrderBy(c => c.ContactName);

对应SQL:

SELECT [t1].[CustomerID], [t1].[CompanyName], [t1].[ContactName], [t1].[ContactTitle], [t1].[Address], [t1].[City], [t1].[Region], [t1].[PostalCode], [t1].[Country], [t1].[Phone], [t1].[Fax]

FROM (

    SELECT DISTINCT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax]

    FROM [dbo].[Customers] AS [t0]

    ) AS [t1]

WHERE (NOT (EXISTS(

    SELECT NULL AS [EMPTY]

    FROM [dbo].[Customers] AS [t2]

    WHERE ([t1].[CustomerID] = [t2].[CustomerID]) AND ([t2].[ContactName] LIKE @p0)

    ))) AND ([t1].[City] LIKE @p1)

ORDER BY [t1].[ContactName]

– @p0: Input String (Size = 2; Prec = 0; Scale = 0) [A%]

– @p1: Input String (Size = 3; Prec = 0; Scale = 0) [%A%]

子查询

描述:查询订单数超过5的顾客信息

查询句法:

var 子查询 = from c in ctx.Customers

where

                       (from o in ctx.Orders group o by o.CustomerID into owhere o.Count() > 5 select o.Key).Contains(c.CustomerID)

select c;

对应SQL:

SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax]

FROM [dbo].[Customers] AS [t0]

WHERE EXISTS(

    SELECT NULL AS [EMPTY]

    FROM (

        SELECT COUNT(*) AS [value], [t1].[CustomerID]

        FROM [dbo].[Orders] AS [t1]

        GROUP BY [t1].[CustomerID]

        ) AS [t2]

    WHERE ([t2].[CustomerID] = [t0].[CustomerID]) AND ([t2].[value] > @p0)

    )

– @p0: Input Int32 (Size = 0; Prec = 0; Scale = 0) [5]

in操作

描述:查询指定城市中的客户

查询句法:

var in操作 = from c in ctx.Customers

where new string[] { "Brandenburg", "Cowes", "Stavern"}.Contains(c.City)

select c;

对应SQL:

       SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax]

FROM [dbo].[Customers] AS [t0]

WHERE [t0].[City] IN (@p0, @p1, @p2)

– @p0: Input String (Size = 11; Prec = 0; Scale = 0) [Brandenburg]

– @p1: Input String (Size = 5; Prec = 0; Scale = 0) [Cowes]

– @p2: Input String (Size = 7; Prec = 0; Scale = 0) [Stavern]

join

描述:内连接,没有分类的产品查询不到

查询句法:

var innerjoin = from p in ctx.Products

join c in ctx.Categories

on p.CategoryID equals c.CategoryID

select p.ProductName;

对应SQL:

SELECT COUNT(*) AS [value]

FROM [dbo].[Products] AS [t0]

INNER JOIN [dbo].[Categories] AS [t1] ON [t0].[CategoryID] = ([t1].[CategoryID])

描述:外连接,没有分类的产品也能查询到

查询句法:

var leftjoin = from p in ctx.Products

join c in ctx.Categories

on p.CategoryID equals c.CategoryID

into pro

from x in pro.DefaultIfEmpty()

select p.ProductName;

对应SQL:

SELECT COUNT(*) AS [value]

FROM [dbo].[Products] AS [t0]

LEFT OUTER JOIN [dbo].[Categories] AS [t1] ON [t0].[CategoryID] = ([t1].[CategoryID])

你可能会很奇怪,原先很复杂的SQL使用查询句法会很简单(比如按照条件分组)。但是原先觉得很好理解的SQL使用查询句法会觉得很复杂(比如连接查询)。其实,我们还可以通过其它方式进行连接操作,在以后说DataLoadOptions类型的时候会再说。虽然Linq to sql已经非常智能了,但是对于非常复杂的查询还是建议通过存储过程实现,下次讲解如何调用存储过程。

(五):存储过程

普通存储过程

首先在查询分析器运行下面的代码来创建一个存储过程:

create proc sp_singleresultset

as

set nocount on

select * from customers

然后打开IDE的服务器资源管理器,之前我们从表中拖动表到dbml设计视图,这次我们从存储过程中找到刚才创建的存储过程,然后拖动到设计视图。在方法面板中可以看到已经创建了一个sp_singleresultset的方法,如下图:

clip_image006

然后打开Northwind.designer.cs,可以找到下面的代码:

    [Function(Name="dbo.sp_singleresultset")]

public ISingleResult<sp_singleresultsetResult> sp_singleresultset()

    {

IExecuteResult result = this.ExecuteMethodCall(this, ((MethodInfo)(MethodInfo.GetCurrentMethod())));

return((ISingleResult<sp_singleresultsetResult>)(result.ReturnValue));

    }

我们可以发现,IDE为这个存储过程单独生成了返回结果集的实体定义,你可能会觉得很奇怪,IDE怎么知道这个存储过程将会返回哪些数据那?其实,在把存储过程拖拽入dbml设计视图的时候,IDE就执行了类似下面的命令:

SET FMTONLY ON;

exec Northwind.dbo.sp_singleresultset

SET FMTONLY OFF;

这样就可以直接获取存储过程返回的元数据而无须执行存储过程。

其实我们存储过程返回的就是顾客表的数据,如果你觉得为存储过程单独设置结果集实体有些浪费的话可以在存储过程的属性窗口中调整返回类型从“自动生成的类型”到Customer,不过以后你只能通过删除方法面板中的存储过程,然后重新添加来还原到“自动生成的类型”。下面,我们可以写如下的Linq to object代码进行查询:

var 单结果集存储过程 =

from c in ctx.sp_singleresultset()

where c.CustomerID.StartsWith("A")

select c;

在这里确实是Linq to object的,因为查询句法不会被整句翻译成SQL,而是从存储过程的返回对象中再去对对象进行查询。SQL代码如下:

EXEC @RETURN_VALUE = [dbo].[sp_singleresultset]

– @RETURN_VALUE: Output Int32 (Size = 0; Prec = 0; Scale = 0) []

带参数的存储过程

创建如下存储过程:

create proc [dbo].[sp_withparameter]

@customerid nchar(5),

@rowcount int output

as

set nocount on

set @rowcount = (select count(*) from customers where customerid = @customerid)

使用同样的方法生成存储过程方法,然后使用下面的代码进行测试:

int? rowcount = -1;

        ctx.sp_withparameter("", ref rowcount);

        Response.Write(rowcount);

        ctx.sp_withparameter("ALFKI", ref rowcount);

        Response.Write(rowcount);

结果输出了“01”。说明ID为“”的顾客数为0,而ID为“ALFKI”的顾客数为1。存储过程的输出参数被封装成了ref参数,对于C#语法来说非常合情合理。SQL代码如下:

EXEC @RETURN_VALUE = [dbo].[sp_withparameter] @customerid = @p0, @rowcount = @p1 OUTPUT

– @p0: Input StringFixedLength (Size = 5; Prec = 0; Scale = 0) []

– @p1: InputOutput Int32 (Size = 0; Prec = 0; Scale = 0) [-1]

– @RETURN_VALUE: Output Int32 (Size = 0; Prec = 0; Scale = 0) []

带返回值的存储过程

再来创建第三个存储过程:

create proc [dbo].[sp_withreturnvalue]

@customerid nchar(5)

as

set nocount on

if exists (select 1 from customers where customerid = @customerid)

return 101

else

return 100

生成方法后,可以通过下面的代码进行测试:

        Response.Write(ctx.sp_withreturnvalue(""));

        Response.Write(ctx.sp_withreturnvalue("ALFKI"));

运行后程序输出“100101”

多结果集的存储过程

再来创建一个多结果集的存储过程:

create proc [dbo].[sp_multiresultset]

as

set nocount on

select * from customers

select * from employees

找到生成的存储过程方法:

[Function(Name="dbo.sp_multiresultset")]

public ISingleResult<sp_multiresultsetResult> sp_multiresultset()

    {

IExecuteResult result = this.ExecuteMethodCall(this, ((MethodInfo)(MethodInfo.GetCurrentMethod())));

return ((ISingleResult<sp_multiresultsetResult>)(result.ReturnValue));

    }

由于现在的VS2008会把多结果集存储过程识别为单结果集存储过程(只认识第一个结果集),我们只能对存储过程方法多小动手术,修改为:

    [Function(Name="dbo.sp_multiresultset")]

    [ResultType(typeof(Customer))]

    [ResultType(typeof(Employee))]

public IMultipleResults sp_multiresultset()

    {

IExecuteResult result = this.ExecuteMethodCall(this, ((MethodInfo)(MethodInfo.GetCurrentMethod())));

return (IMultipleResults)(result.ReturnValue);

    }

然后使用下面的代码测试:

var 多结果集存储过程 = ctx.sp_multiresultset();

var Customers = 多结果集存储过程.GetResult<Customer>();

var Employees = 多结果集存储过程.GetResult<Employee>();

        GridView1.DataSource = from emp in Employees whereemp.FirstName.Contains("A") select emp;

        GridView1.DataBind();

        GridView2.DataSource = from c in Customers wherec.CustomerID.StartsWith("A") select c;

        GridView2.DataBind();

使用存储过程新增数据

存储过程除了可以直接调用之外,还可以用于实体的增删改操作。还记得在《一步一步学Linq to sql(三):增删改》中创建的留言簿程序吗?下面我们就来改造这个程序,使用存储过程而不是系统生成的SQL实现实体增删改。首先,我们创建下面的存储过程

create proc sendmessage

@username varchar(50),

@message varchar(500)

as

insert into tbguestbook

(id,username,posttime,[message],isreplied,reply)

values

(newid(),@username,getdate(),@message,0,”)

然后,打开留言簿dbml,把存储过程从服务器资源管理器拖拽到设计视图上。右键点击tbGuestBook实体类,选择配置行为。如下图,为插入操作选择刚才创建的存储过程方法,并进行参数匹配:

clip_image007

由于我们的存储过程只接受2个参数,相应修改以下创建留言的按钮处理事件:

protected void btn_SendMessage_Click(object sender, EventArgs e)

    {

tbGuestBook gb = new tbGuestBook();

        gb.UserName = tb_UserName.Text;

        gb.Message = tb_Message.Text;

        ctx.tbGuestBooks.Add(gb);

        ctx.SubmitChanges();

        SetBind();

    }

运行程序后可以发现,在提交修改的时候调用了下面的SQL:

EXEC @RETURN_VALUE = [dbo].[sendmessage] @username = @p0, @message = @p1

– @p0: Input AnsiString (Size = 5; Prec = 0; Scale = 0) [zhuye]

– @p1: Input AnsiString (Size = 11; Prec = 0; Scale = 0) [new message]

– @RETURN_VALUE: Output Int32 (Size = 0; Prec = 0; Scale = 0) []

使用存储过程删除数据

创建如下存储过程:

create proc delmessage

@id uniqueidentifier

as

delete tbguestbook where id=@id

按照前面的步骤生成存储过程方法,并为删除操作执行这个存储过程方法。在选择参数的时候我们可以看到,ID分当前值和原始值,我们选择当前值即可,如下图:

clip_image008

无须改动任何逻辑代码,进行删除留言操作后可以跟踪到下面的SQL:

EXEC @RETURN_VALUE = [dbo].[delmessage] @id = @p0

– @p0: Input Guid (Size = 0; Prec = 0; Scale = 0) [9e3c5ee3-2575-458e-899d-4b0bf73e0849]

– @RETURN_VALUE: Output Int32 (Size = 0; Prec = 0; Scale = 0) []

使用存储过程更改数据

创建如下存储过程:

create proc replymessage

@id uniqueidentifier,

@reply varchar(500)

as

update tbguestbook set reply=@reply,isreplied=1 where id=@id

由于更新的时候并不会更新主键,所以我们可以为两个参数都指定当前值。回复留言后可以跟踪到下面的SQL:

EXEC @RETURN_VALUE = [dbo].[replymessage] @id = @p0, @reply = @p1

– @p0: Input Guid (Size = 0; Prec = 0; Scale = 0) [67a69d0f-a88b-4b22-8939-fed021eb1cb5]

– @p1: Input AnsiString (Size = 6; Prec = 0; Scale = 0) [464456]

– @RETURN_VALUE: Output Int32 (Size = 0; Prec = 0; Scale = 0) []

假设有这样一种应用,我们需要修改留言簿中不合法的用户名:

create proc modiusername

@oldusername varchar(50),

@newusername varchar(50)

as

update tbguestbook set username=@newusername where username = @oldusername

有个网友起名叫“admin”,我们要把所有这个名字修改为“notadmin”。那么,可以如下图设置update操作:

clip_image009

然后运行下面的测试代码:

var messages = from gb in ctx.tbGuestBooks

select gb;

foreach (var gb in messages)

        {

if(gb.UserName == "admin")

                gb.UserName = "notadmin";

        }

运行程序后能跟踪到下面的SQL:

SELECT [t0].[ID], [t0].[UserName], [t0].[PostTime], [t0].[Message], [t0].[IsReplied], [t0].[Reply]

FROM [dbo].[tbGuestBook] AS [t0]

EXEC @RETURN_VALUE = [dbo].[modiusername] @oldusername = @p0, @newusername = @p1

– @p0: Input AnsiString (Size = 5; Prec = 0; Scale = 0) [admin]

– @p1: Input AnsiString (Size = 8; Prec = 0; Scale = 0) [notadmin]

– @RETURN_VALUE: Output Int32 (Size = 0; Prec = 0; Scale = 0) []

(六):探究特性

延迟执行

IQueryable query = from c in ctx.Customers select c;

这样的查询句法不会导致语句立即执行,它仅仅是一个描述,对应一个SQL。仅仅在需要使用的时候才会执行语句,比如:

IQueryable query = from c in ctx.Customers select c;

foreach (Customer c in query)

            Response.Write(c.CustomerID);

如果你执行两次foreach操作,将会捕获到两次SQL语句的执行:

IQueryable query = from c in ctx.Customers select c;

foreach (Customer c in query)

            Response.Write(c.CustomerID);

foreach (Customer c in query)

            Response.Write(c.ContactName);

对应SQL:

SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax]

FROM [dbo].[Customers] AS [t0]

SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax]

FROM [dbo].[Customers] AS [t0]

对于这样的需求,建议你先使用ToList()等方法把查询结果先进行保存,然后再对集合进行查询:

IEnumerable<Customer> customers = (from c in ctx.Customers selectc).ToList();

foreach (Customer c in customers)

            Response.Write(c.CustomerID);

foreach (Customer c in customers)

            Response.Write(c.ContactName);

延迟执行的优点在于我们可以像拼接SQL那样拼接查询句法,然后再执行:

var query = from c in ctx.Customers select c;

var newquery = (from c in query select c).OrderBy(c => c.CustomerID);

DataLoadOptions

var products = from p in ctx.Products select p;

foreach (var p in products)

        {

if (p.UnitPrice > 10)

                ShowDetail(p.Order_Details);

        }

private void ShowDetail(EntitySet<Order_Detail> orderdetails)

    {}

由于ShowDetail方法并没有使用到订单详细信息,所以这个操作只会执行下面的SQL:

SELECT [t0].[ProductID], [t0].[ProductName], [t0].[SupplierID], [t0].[CategoryID], [t0].[QuantityPerUnit], [t0].[UnitPrice], [t0].[UnitsInStock], [t0].[UnitsOnOrder], [t0].[ReorderLevel], [t0].[Discontinued]

FROM [dbo].[Products] AS [t0]

现在修改一下ShowDetail方法:

private void ShowDetail(EntitySet<Order_Detail> orderdetails)

    {

foreach (var o in orderdetails)

       {

           Response.Write(o.Quantity + "<br>");

       }

    }

你会发现Linq to sql对每个价格大于10的产品都根据产品号进行了一次查询:

SELECT [t0].[OrderID], [t0].[ProductID], [t0].[UnitPrice], [t0].[Quantity], [t0].[Discount]

FROM [dbo].[Order Details] AS [t0]

WHERE [t0].[ProductID] = @p0

– @p0: Input Int32 (Size = 0; Prec = 0; Scale = 0) [1]

这样的语句查询了N次。这样的查询不是很合理,我们可以通过设置DataContext的DataLoadOption,来指示 DataContext再加载产品信息的同时把对应的产品订单信息一起加载:

DataLoadOptions options = new DataLoadOptions();

        options.LoadWith<Product>(p => p.Order_Details);

        ctx.LoadOptions = options;

var products = from p in ctx.Products select p;

。。。。。。。。

再执行先前的查询会发现Linq to sql进行了左连接:

SELECT [t0].[ProductID], [t0].[ProductName], [t0].[SupplierID], [t0].[CategoryID], [t0].[QuantityPerUnit], [t0].[UnitPrice], [t0].[UnitsInStock], [t0].[UnitsOnOrder], [t0].[ReorderLevel], [t0].[Discontinued], [t1].[OrderID], [t1].[ProductID] AS [ProductID2], [t1].[UnitPrice] AS [UnitPrice2], [t1].[Quantity], [t1].[Discount], (

SELECT COUNT(*)

    FROM [dbo].[Order Details] AS [t2]

WHERE [t2].[ProductID] = [t0].[ProductID]

) AS [count]

FROM [dbo].[Products] AS [t0]

LEFT OUTER JOIN [dbo].[Order Details] AS [t1] ON [t1].[ProductID] = [t0].[ProductID]

ORDER BY [t0].[ProductID], [t1].[OrderID]

那么,我们怎么限制订单详细表的加载条件那?

DataLoadOptions options = new DataLoadOptions();

options.LoadWith<Product>(p => p.Order_Details);

options.AssociateWith<Product>(p => p.Order_Details.Where(od => od.Quantity > 80));

ctx.LoadOptions = options;

var products = from p in ctx.Products select p;

这样,就只会有数量大于80的订单详细信息会和产品一起加载:

SELECT [t0].[ProductID], [t0].[ProductName], [t0].[SupplierID], [t0].[CategoryID], [t0].[QuantityPerUnit], [t0].[UnitPrice], [t0].[UnitsInStock], [t0].[UnitsOnOrder], [t0].[ReorderLevel], [t0].[Discontinued], [t1].[OrderID], [t1].[ProductID] AS [ProductID2], [t1].[UnitPrice] AS [UnitPrice2], [t1].[Quantity], [t1].[Discount], (

SELECT COUNT(*)

    FROM [dbo].[Order Details] AS [t2]

WHERE ([t2].[Quantity] > @p0) AND ([t2].[ProductID] = [t0].[ProductID])

) AS [count]

FROM [dbo].[Products] AS [t0]

LEFT OUTER JOIN [dbo].[Order Details] AS [t1] ON ([t1].[Quantity] > @p0) AND ([t1].[ProductID] = [t0].[ProductID])

ORDER BY [t0].[ProductID], [t1].[OrderID]

– @p0: Input Int32 (Size = 0; Prec = 0; Scale = 0) [80]

DataLoadOptions限制

Linq to sql对DataLoadOptions的使用是有限制的,它只支持1个1对多的关系。一个顾客可能有多个订单,一个订单可能有多个详细订单:

DataLoadOptions options = new DataLoadOptions();

        options.LoadWith<Customer>(c => c.Orders);

        options.LoadWith<Order>(o => o.Order_Details);

        ctx.LoadOptions = options;

IEnumerable<Customer> customers = ctx.Customers.ToList<Customer>();

这样的语句执行后会导致下面的SQL执行N次(参数不同):

SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [t0].[RequiredDate], [t0].[ShippedDate], [t0].[ShipVia], [t0].[Freight], [t0].[ShipName], [t0].[ShipAddress], [t0].[ShipCity], [t0].[ShipRegion], [t0].[ShipPostalCode], [t0].[ShipCountry], [t1].[OrderID] AS [OrderID2], [t1].[ProductID], [t1].[UnitPrice], [t1].[Quantity], [t1].[Discount], (

    SELECT COUNT(*)

    FROM [dbo].[Order Details] AS [t2]

    WHERE [t2].[OrderID] = [t0].[OrderID]

    ) AS [count]

FROM [dbo].[Orders] AS [t0]

LEFT OUTER JOIN [dbo].[Order Details] AS [t1] ON [t1].[OrderID] = [t0].[OrderID]

WHERE [t0].[CustomerID] = @x1

ORDER BY [t0].[OrderID], [t1].[ProductID]

– @x1: Input StringFixedLength (Size = 5; Prec = 0; Scale = 0) [ALFKI]

而对于多对1的关系,Linq to sql对于DataLoadOptions没有限制:

DataLoadOptions options = new DataLoadOptions();

        options.LoadWith<Product>(c => c.Category);

        options.LoadWith<Product>(c => c.Order_Details);

        options.LoadWith<Order_Detail>(o => o.Order);

        ctx.LoadOptions = options;

IEnumerable<Product> products = ctx.Products.ToList<Product>();

由于多个产品对应1个分类,多个详细订单对应1个订单,只有产品和详细订单才是多对1的关系,所以也只会有1次SQL(不过这样的操作还是少执行为妙,消耗太大了):

SELECT [t0].[ProductID], [t0].[ProductName], [t0].[SupplierID], [t0].[CategoryID], [t0].[QuantityPerUnit], [t0].[UnitPrice], [t0].[UnitsInStock], [t0].[UnitsOnOrder], [t0].[ReorderLevel], [t0].[Discontinued], [t3].[OrderID], [t3].[ProductID] AS [ProductID2], [t3].[UnitPrice] AS [UnitPrice2], [t3].[Quantity], [t3].[Discount], [t4].[OrderID] AS [OrderID2], [t4].[CustomerID], [t4].[EmployeeID], [t4].[OrderDate], [t4].[RequiredDate], [t4].[ShippedDate], [t4].[ShipVia], [t4].[Freight], [t4].[ShipName], [t4].[ShipAddress], [t4].[ShipCity], [t4].[ShipRegion], [t4].[ShipPostalCode], [t4].[ShipCountry], (

    SELECT COUNT(*)

    FROM [dbo].[Order Details] AS [t5]

INNER JOIN [dbo].[Orders] AS [t6] ON [t6].[OrderID] = [t5].[OrderID]

    WHERE [t5].[ProductID] = [t0].[ProductID]

) AS [count], [t2].[test], [t2].[CategoryID] AS [CategoryID2], [t2].[CategoryName], [t2].[Description], [t2].[Picture]

FROM [dbo].[Products] AS [t0]

LEFT OUTER JOIN (

    SELECT 1 AS [test], [t1].[CategoryID], [t1].[CategoryName], [t1].[Description], [t1].[Picture]

    FROM [dbo].[Categories] AS [t1]

    ) AS [t2] ON [t2].[CategoryID] = [t0].[CategoryID]

LEFT OUTER JOIN ([dbo].[Order Details] AS [t3]

INNER JOIN [dbo].[Orders] AS [t4] ON [t4].[OrderID] = [t3].[OrderID]) ON [t3].[ProductID] = [t0].[ProductID]

ORDER BY [t0].[ProductID], [t2].[CategoryID], [t3].[OrderID]

主键缓存

Linq to sql对查询过的对象进行缓存,之后的如果只根据主键查询一条记录的话会直接从缓存中读取。比如下面的代码:

Customer c1 = ctx.Customers.Single(customer => customer.CustomerID =="ANATR");

        c1.ContactName = "zhuye";

Customer c2 = ctx.Customers.Single(customer => customer.CustomerID =="ANATR");

        Response.Write(c2.ContactName);

执行后只会产生一条SQL:

SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax]

FROM [dbo].[Customers] AS [t0]

WHERE [t0].[CustomerID] = @p0

– @p0: Input String (Size = 5; Prec = 0; Scale = 0) [ANATR]

由于没有提交修改,所以数据库中的记录还是没有更新。由于这个特性,我们在使用存储过程作为实体更新方法的时候就要当心了,存储过程书写错误,即使你提交了修改也很可能导致缓存中的数据和数据库中的数据不一致,引起不必要的麻烦。

DataContext隔离

有的时候我们会把对象从外部传入DataContext,要求它更新,由于不同的DataContext是相对独立的。由于新的DataContext中还没有获取实体,我们只能通过附加方式更新数据。

首先把Customer表的主键字段加上IsVersion标识:

    [Column(Storage="_CustomerID", DbType="NChar(5) NOT NULL", CanBeNull=false, IsPrimaryKey=true, IsVersion = true)]

运行下面的测试代码:

Customer c = new Customer { CustomerID = "ALFKI", ContactName ="zhuye", CompanyName = "1111" };

        ctx.Customers.Attach(c, true);

        ctx.SubmitChanges();

会捕捉到下面的SQL语句:

UPDATE [dbo].[Customers]

SET [CompanyName] = @p2, [ContactName] = @p3, [ContactTitle] = @p4, [Address] = @p5, [City] = @p6, [Region] = @p7, [PostalCode] = @p8, [Country] = @p9, [Phone] = @p10, [Fax] = @p11

WHERE ([CustomerID] = @p0) AND ([CustomerID] = @p1)

– @p0: Input StringFixedLength (Size = 5; Prec = 0; Scale = 0) [ALFKI]

– @p1: Input String (Size = 5; Prec = 0; Scale = 0) [ALFKI]

– @p2: Input String (Size = 4; Prec = 0; Scale = 0) [1111]

– @p3: Input String (Size = 5; Prec = 0; Scale = 0) [zhuye]

– @p4: Input String (Size = 0; Prec = 0; Scale = 0) []

– @p5: Input String (Size = 0; Prec = 0; Scale = 0) []

– @p6: Input String (Size = 0; Prec = 0; Scale = 0) []

– @p7: Input String (Size = 0; Prec = 0; Scale = 0) []

– @p8: Input String (Size = 0; Prec = 0; Scale = 0) []

– @p9: Input String (Size = 0; Prec = 0; Scale = 0) []

– @p10: Input String (Size = 0; Prec = 0; Scale = 0) []

– @p11: Input String (Size = 0; Prec = 0; Scale = 0) []

(七):并发与事务

检测并发

首先使用下面的SQL语句查询数据库的产品表:

select * from products where categoryid=1

查询结果如下图:

clip_image010

为了看起来清晰,我已经事先把所有分类为1产品的价格和库存修改为相同值了。然后执行下面的程序:

var query = from p in ctx.Products where p.CategoryID == 1 select p;

foreach (var p in query)

            p.UnitsInStock = Convert.ToInt16(p.UnitsInStock – 1);

        ctx.SubmitChanges(); // 在这里设断点

我们使用调试方式启动,由于设置了断点,程序并没有进行更新操作。此时,我们在数据库中运行下面的语句:

update products

set unitsinstock = unitsinstock -2, unitprice= unitprice + 1

where categoryid = 1

然后在继续程序,会得到修改并发(乐观并发冲突)的异常,提示要修改的行不存在或者已经被改动。当客户端提交的修改对象自读取之后已经在数据库中发生改动,就产生了修改并发。解决并发的包括两步,一是查明哪些对象发生并发,二是解决并发。如果你仅仅是希望更新时不考虑并发的话可以关闭相关列的更新验证,这样在这些列上发生并发就不会出现异常:

[Column(Storage="_UnitsInStock", DbType="SmallInt", UpdateCheck =UpdateCheck.Never)]

[Column(Storage="_UnitPrice", DbType="Money", UpdateCheck =UpdateCheck.Never)]

为这两列标注不需要进行更新检测。假设现在产品价格和库存分别是27和32。那么,我们启动程序(设置端点),然后运行UPDATE语句,把价格+1,库存-2,然后价格和库存分别为28和30了,继续程序可以发现价格和库存分别是28和31。价格+1是之前更新的功劳,库存最终是-1是我们程序之后更新的功劳。当在同一个字段上(库存)发生并发冲突的时候,默认是最后的那次更新获胜。

解决并发

如果你希望自己处理并发的话可以把前面对列的定义修改先改回来,看下面的例子:

var query = from p in ctx.Products where p.CategoryID == 1 select p;

foreach (var p in query)

            p.UnitsInStock = Convert.ToInt16(p.UnitsInStock – 1);

try

        {

            ctx.SubmitChanges(ConflictMode.ContinueOnConflict);

        }

catch (ChangeConflictException)

        {

foreach (ObjectChangeConflict cc in ctx.ChangeConflicts)

            {

Product p = (Product)cc.Object;

                Response.Write(p.ProductID + "<br/>");

                cc.Resolve(RefreshMode.OverwriteCurrentValues); // 放弃当前更新,所有更新以原先更新为准

            }

        }

        ctx.SubmitChanges();

首先可以看到,我们使用try{}catch{}来捕捉并发冲突的异常。在SubmitChanges的时候,我们选择了ConflictMode.ContinueOnConflict选项。也就是说遇到并发了还是继续。在catch{}中,我们从ChangeConflicts中获取了并发的对象,然后经过类型转化后输出了产品ID,然后选择的解决方案是RefreshMode.OverwriteCurrentValues。也就是说,放弃当前的更新,所有更新以原先更新为准。

我们来测试一下,假设现在产品价格和库存分别是27和32。那么,我们启动程序(在ctx.SubmitChanges(ConflictMode.ContinueOnConflict)这里设置端点),然后运行UPDATE语句,把价格+1,库存-2,然后价格和库存分别为28和30了,继续程序可以发现价格和库存分别是28和30。之前SQL语句库存-2生效了,而我们程序的更新(库存-1)被放弃了。在页面上也显示了所有分类为1的产品ID(因为我们之前的SQL语句是对所有分类为1的产品都进行修改的)。

然后,我们来修改一下解决并发的方式:

cc.Resolve(RefreshMode.KeepCurrentValues); // 放弃原先更新,所有更新以当前更新为准

来测试一下,假设现在产品价格和库存分别是27和32。那么,我们启动程序(在ctx.SubmitChanges(ConflictMode.ContinueOnConflict)这里设置端点),然后运行UPDATE语句,把价格+1,库存-2,然后价格和库存分别为28和30了,继续程序可以发现价格和库存分别是27和31。产品价格没有变化,库存-1了,都是我们程序的功劳,SQL语句的更新被放弃了。

然后,我们再来修改一下解决并发的方式:

cc.Resolve(RefreshMode.KeepChanges); // 原先更新有效,冲突字段以当前更新为准

来测试一下,假设现在产品价格和库存分别是27和32。那么,我们启动程序(在ctx.SubmitChanges(ConflictMode.ContinueOnConflict)这里设置端点),然后运行UPDATE语句,把价格+1,库存-2,然后价格和库存分别为28和30了,继续程序可以发现价格和库存分别是28和31。这就是默认方式,在保持原先更新的基础上,对于发生冲突的字段以最后更新为准。

我们甚至还可以针对不同的字段进行不同的处理策略:

foreach (ObjectChangeConflict cc in ctx.ChangeConflicts)

{

Product p = (Product)cc.Object;

foreach (MemberChangeConflict mc in cc.MemberConflicts)

    {

string currVal = mc.CurrentValue.ToString();

string origVal = mc.OriginalValue.ToString();

string databaseVal = mc.DatabaseValue.ToString();

MemberInfo mi = mc.Member;

string memberName = mi.Name;

        Response.Write(p.ProductID + " " + mi.Name + " " + currVal + " " + origVal +" "+ databaseVal + "<br/>");

if (memberName == "UnitsInStock")

            mc.Resolve(RefreshMode.KeepCurrentValues); // 放弃原先更新,所有更新以当前更新为准

else if (memberName == "UnitPrice")

            mc.Resolve(RefreshMode.OverwriteCurrentValues); // 放弃当前更新,所有更新以原先更新为准

else

            mc.Resolve(RefreshMode.KeepChanges); // 原先更新有效,冲突字段以当前更新为准

    }

}

比如上述代码就对库存字段作放弃原先更新处理,对价格字段作放弃当前更新处理。我们来测试一下,假设现在产品价格和库存分别是27和32。那么,我们启动程序(在ctx.SubmitChanges(ConflictMode.ContinueOnConflict)这里设置端点),然后运行UPDATE语句,把价格+1,库存-2,然后价格和库存分别为28和30了,继续程序可以发现价格和库存分别为28和31了。说明对价格的处理确实保留了原先的更新,对库存的处理保留了当前的更新。页面上显示的结果如下图:

clip_image011

最后,我们把提交语句修改为:

ctx.SubmitChanges(ConflictMode.FailOnFirstConflict);

表示第一次发生冲突的时候就不再继续了,然后并且去除最后的ctx.SubmitChanges();语句。来测试一下,在执行了SQL后再继续程序可以发现界面上只输出了数字1,说明在第一条记录失败后,后续的并发冲突就不再处理了。

事务处理

Linq to sql在提交更新的时候默认会创建事务,一部分修改发生错误的话其它修改也不会生效:

        ctx.Customers.Add(new Customer { CustomerID = "abcdf", CompanyName ="zhuye" });

        ctx.Customers.Add(new Customer { CustomerID = "abcde", CompanyName ="zhuye" });

        ctx.SubmitChanges();

假设数据库中已经存在顾客ID为“abcde”的记录,那么第二次插入操作失败将会导致第一次的插入操作失效。执行程序后会得到一个异常,查询数据库发现“abcdf”这个顾客也没有插入到数据库中。

如果每次更新后直接提交修改,那么我们可以使用下面的方式做事务:

if (ctx.Connection != null) ctx.Connection.Open();

DbTransaction tran = ctx.Connection.BeginTransaction();

        ctx.Transaction = tran;

try

        {

            CreateCustomer(new Customer { CustomerID = "abcdf", CompanyName ="zhuye" });

            CreateCustomer(new Customer { CustomerID = "abcde", CompanyName ="zhuye" });

            tran.Commit();

        }

catch

        {

            tran.Rollback();

        }

private void CreateCustomer(Customer c)

    {

        ctx.Customers.Add(c);

        ctx.SubmitChanges();

    }

运行程序后发现增加顾客abcdf的操作并没有成功。或者,我们还可以通过TransactionScope实现事务:

using (TransactionScope scope = new TransactionScope())

        {

            CreateCustomer(new Customer { CustomerID = "abcdf", CompanyName ="zhuye" });

            CreateCustomer(new Customer { CustomerID = "abcde", CompanyName ="zhuye" });

            scope.Complete();

        }

(八):继承与关系

论坛表结构

为了演示继承与关系,我们创建一个论坛数据库,在数据库中创建三个表:

1、 论坛版块分类表 dbo.Categories:

字段名

字段类型

可空

备注

CategoryID

int

not null

identity/主键

CategoryName

varchar(50)

not null

 

2、 论坛版块表 dbo.Boards:

字段名

字段类型

可空

备注

BoardID

int

not null

identity/主键

BoardName

varchar(50)

not null

 

BoardCategory

int

not null

对应论坛版块分类表的CategoryID

3、 论坛主题表 dbo.Topics:

字段名

字段类型

可空

备注

TopicID

int

not null

identity/主键

TopicTitle

varchar(50)

not null

 

TopicContent

varchar(max)

not null

 

ParentTopic

int

null

如果帖子是主题贴这个字段为null,否则就是所属主题id

TopicType

tinyint

not null

0 – 主题贴

1 – 回复帖

实体继承的定义

Linq to sql支持实体的单表继承,也就是基类和派生类都存储在一个表中。对于论坛来说,帖子有两种,一种是主题贴,一种是回复帖。那么,我们就先定义帖子基类:

[Table(Name = "Topics")]

public class Topic

{

    [Column(Name = "TopicID", DbType = "int identity", IsPrimaryKey = true, IsDbGenerated = true, CanBeNull = false)]

public int TopicID { get; set; }

    [Column(Name = "TopicType", DbType = "tinyint", CanBeNull = false)]

public int TopicType { get; set; }

    [Column(Name = "TopicTitle", DbType = "varchar(50)", CanBeNull = false)]

public string TopicTitle { get; set; }

    [Column(Name = "TopicContent", DbType = "varchar(max)", CanBeNull =false)]

public string TopicContent { get; set; }

}

这些实体的定义大家应该很熟悉了。下面,我们再来定义两个实体继承帖子基类,分别是主题贴和回复贴:

public class NewTopic : Topic

{

public NewTopic()

    {

base.TopicType = 0;

    }

}

public class Reply : Topic

{

public Reply()

    {

base.TopicType = 1;

    }

    [Column(Name = "ParentTopic", DbType = "int", CanBeNull = false)]

public int ParentTopic { get; set; }

}

对于主题贴,在数据库中的TopicType就保存为0,而对于回复贴就保存为1。回复贴还有一个相关字段就是回复所属主题贴的TopicID。那么,我们怎么告知Linq to sql在TopicType为0的时候识别为NewTopic,而1则识别为Reply那?只需稍微修改一下前面的Topic实体定义:

[Table(Name = "Topics")]

[InheritanceMapping(Code = 0, Type = typeof(NewTopic), IsDefault = true)]

[InheritanceMapping(Code = 1, Type = typeof(Reply))]

public class Topic

{

    [Column(Name = "TopicID", DbType = "int identity", IsPrimaryKey = true, IsDbGenerated = true, CanBeNull = false)]

public int TopicID { get; set; }

    [Column(Name = "TopicType", DbType = "tinyint", CanBeNull = false, IsDiscriminator = true)]

public int TopicType { get; set; }

    [Column(Name = "TopicTitle", DbType = "varchar(50)", CanBeNull = false)]

public string TopicTitle { get; set; }

    [Column(Name = "TopicContent", DbType = "varchar(max)", CanBeNull =false)]

public string TopicContent { get; set; }

}

为类加了InheritanceMapping特性定义,0的时候类型就是NewTopic,1的时候就是Reply。并且为TopicType字段上的特性中加了IsDiscriminator = true,告知Linq to sql这个字段就是用于分类的字段。

实体继承的使用

定义好继承的实体之后,我们就可以使用了。先是自定义一个DataContext吧:

public partial class BBSContext : DataContext

{

public Table<BoardCategory> BoardCategories;

public Table<Board> Boards;

public Table<Topic> Topics;

public BBSContext(string connection) : base(connection) { }

}

然后,我们来测试一下Linq to sql是否能根据TopicType识别派生类:

BBSContext ctx = newBBSContext("server=xxx;database=BBS;uid=xxx;pwd=xxx");

var query = from t in ctx.Topics select t;

foreach (Topic topic in query)

        {

if (topic is NewTopic)

            {

NewTopic newtopic = topic as NewTopic;

                Response.Write("标题:" + newtopic.TopicTitle + " 类型:" + newtopic.TopicType + "<br/>");

            }

else if (topic is Reply)

            {

Reply reply = topic as Reply;

                Response.Write("标题:" + reply.TopicTitle + " 类型:" + reply.TopicType + " 隶属主题:" + reply.ParentTopic + "<br/>");

            }

        }

然后我们往Topics表中加一些数据,如下图:

clip_image012

启动程序得到如下测试结果:

clip_image013

当然,你也可以在查询句法中直接查询派生实体:

IEnumerable newtopiclist = (from t in ctx.Topics.OfType<NewTopic>()select t).ToList();

        newtopics.DataSource = newtopiclist;

IEnumerable replylist = (from t in ctx.Topics.OfType<Reply>() selectt).ToList();

        replies.DataSource = replylist;

        Page.DataBind();

newtopic和replies是两个GridView控件,执行效果如下图:

clip_image014

再来看看如何进行增删操作:

NewTopic nt = new NewTopic() { TopicTitle = "还是新主题", TopicContent ="还是新主题" };

Reply rpl = new Reply() { TopicTitle = "还是新回复", TopicContent = "还是新回复", ParentTopic = 4 };

        ctx.Topics.Add(nt);

        ctx.Topics.Add(rpl);

        ctx.SubmitChanges();

        rpl = ctx.Topics.OfType<Reply>().Single(reply => reply.TopicID == 8);

        ctx.Topics.Remove(rpl);

        ctx.SubmitChanges();

实体关系的定义

比如我们的论坛分类表和论坛版块表之间就有关系,这种关系是1对多的关系。也就是说一个论坛分类可能有多个论坛版块,这是很常见的。定义实体关系的优势在于,我们无须显式作连接操作就能处理关系表的条件。

首先来看看分类表的定义:

[Table(Name = "Categories")]

public class BoardCategory

{

    [Column(Name = "CategoryID", DbType = "int identity", IsPrimaryKey = true, IsDbGenerated = true, CanBeNull = false)]

    public int CategoryID { get; set; }

    [Column(Name = "CategoryName", DbType = "varchar(50)", CanBeNull = false)]

    public string CategoryName { get; set; }

    private EntitySet<Board> _Boards;

    [Association(OtherKey = "BoardCategory", Storage = "_Boards")]

    public EntitySet<Board> Boards

    {

        get { return this._Boards; }

        set { this._Boards.Assign(value); }

    }

    public BoardCategory()

    {

        this._Boards = new EntitySet<Board>();

    }

}

CategoryID和CategoryName的映射没有什么不同,只是我们还增加了一个Boards属性,它返回的是Board实体集。通过特性,我们定义了关系外键为BoardCategory(Board表的一个字段)。然后来看看1对多,多端版块表的实体:

[Table(Name = "Boards")]

public class Board

{

    [Column(Name = "BoardID", DbType = "int identity", IsPrimaryKey = true, IsDbGenerated = true, CanBeNull = false)]

    public int BoardID { get; set; }

    [Column(Name = "BoardName", DbType = "varchar(50)", CanBeNull = false)]

    public string BoardName { get; set; }

    [Column(Name = "BoardCategory", DbType = "int", CanBeNull = false)]

    public int BoardCategory { get; set; }

    private EntityRef<BoardCategory> _Category;

    [Association(ThisKey = "BoardCategory", Storage = "_Category")]

    public BoardCategory Category

    {

        get { return this._Category.Entity; }

        set

        {

            this._Category.Entity = value;

            value.Boards.Add(this);

        }

}

}

在这里我们需要关联分类,设置了Category属性使用BoardCategory字段和分类表关联。

实体关系的使用

好了,现在我们就可以在查询句法中直接关联表了(数据库中不一定要设置表的外键关系):

        Response.Write("————-查询分类为1的版块————-<br/>");

var query1 = from b in ctx.Boards where b.Category.CategoryID == 1select b;

foreach (Board b in query1)

            Response.Write(b.BoardID + " " + b.BoardName + "<br/>");

        Response.Write("————-查询版块大于2个的分类————-<br/>");

var query2 = from c in ctx.BoardCategories where c.Boards.Count > 2select c;

foreach (BoardCategory c in query2)

            Response.Write(c.CategoryID + " " + c.CategoryName + " " + c.Boards.Count + "<br/>");

在数据库中加一些测试数据,如下图:

clip_image015

运行程序后得到下图的结果:

clip_image016

我想定义实体关系的方便我不需要再用语言形容了吧。执行上述的程序会导致下面SQL的执行:

SELECT [t0].[BoardID], [t0].[BoardName], [t0].[BoardCategory]

FROM [Boards] AS [t0]

INNER JOIN [Categories] AS [t1] ON [t1].[CategoryID] = [t0].[BoardCategory]

WHERE [t1].[CategoryID] = @p0

– @p0: Input Int32 (Size = 0; Prec = 0; Scale = 0) [1]

SELECT [t0].[CategoryID], [t0].[CategoryName]

FROM [Categories] AS [t0]

WHERE ((

    SELECT COUNT(*)

    FROM [Boards] AS [t1]

    WHERE [t1].[BoardCategory] = [t0].[CategoryID]

    )) > @p0

– @p0: Input Int32 (Size = 0; Prec = 0; Scale = 0) [2]

SELECT [t0].[BoardID], [t0].[BoardName], [t0].[BoardCategory]

FROM [Boards] AS [t0]

WHERE [t0].[BoardCategory] = @p0

– @p0: Input Int32 (Size = 0; Prec = 0; Scale = 0) [1]

可以看到,第二个查询并没有做外连接,还记得DataLoadOptions吗?我们可以要求Linq to sql在读取版块分类信息的时候也把版块信息一起加载:

DataLoadOptions options = new DataLoadOptions();

        options.LoadWith<BoardCategory>(c => c.Boards);

        ctx.LoadOptions = options;

        Response.Write("————-查询版块大于2个的分类————-<br/>");

        var query2 = from c in ctx.BoardCategories where c.Boards.Count > 2 select c;

        foreach (BoardCategory c in query2)

            Response.Write(c.CategoryID + " " + c.CategoryName + " " + c.Boards.Count + "<br/>");

查询经过改造后会得到下面的SQL:

SELECT [t0].[CategoryID], [t0].[CategoryName], [t1].[BoardID], [t1].[BoardName], [t1].[BoardCategory], (

    SELECT COUNT(*)

    FROM [Boards] AS [t3]

    WHERE [t3].[BoardCategory] = [t0].[CategoryID]

    ) AS [count]

FROM [Categories] AS [t0]

LEFT OUTER JOIN [Boards] AS [t1] ON [t1].[BoardCategory] = [t0].[CategoryID]

WHERE ((

    SELECT COUNT(*)

    FROM [Boards] AS [t2]

    WHERE [t2].[BoardCategory] = [t0].[CategoryID]

    )) > @p0

ORDER BY [t0].[CategoryID], [t1].[BoardID]

– @p0: Input Int32 (Size = 0; Prec = 0; Scale = 0) [2]

在添加分类的时候,如果这个分类下还有新的版块,那么提交新增分类的时候版块也会新增:

        BoardCategory dbcat = new BoardCategory() { CategoryName = "Database" };

        Board oracle = new Board() { BoardName = "Oracle", Category = dbcat};

        ctx.BoardCategories.Add(dbcat);

        ctx.SubmitChanges();

上述代码导致下面的SQL被执行:

INSERT INTO [Categories]([CategoryName]) VALUES (@p0)

SELECT [t0].[CategoryID]

FROM [Categories] AS [t0]

WHERE [t0].[CategoryID] = (SCOPE_IDENTITY())

– @p0: Input AnsiString (Size = 8; Prec = 0; Scale = 0) [Database]

INSERT INTO [Boards]([BoardName], [BoardCategory]) VALUES (@p0, @p1)

SELECT [t0].[BoardID]

FROM [Boards] AS [t0]

WHERE [t0].[BoardID] = (SCOPE_IDENTITY())

– @p0: Input AnsiString (Size = 6; Prec = 0; Scale = 0) [Oracle]

– @p1: Input Int32 (Size = 0; Prec = 0; Scale = 0) [23]

(九):其它补充

外部映射文件

我们可以使用sqlmetal命令行工具来生成外部映射文件,使用方法如下:

1、开始菜单 -》 VS2008 -》VS工具 -》VS2008命令行提示

2、输入命令:

D:\Program Files\Microsoft Visual Studio 9.0\VC>sqlmetal /conn:server=xxx;

database=Northwind;uid=xxx;pwd=xxx /map:c:\northwind.map /code:c:\northwind.cs

3、这样,我们就可以在C盘下得到一个xml映射文件和C#的实体类代码

4、把.cs文件添加到项目中来(放到App_Code目录),然后使用下面的代码加载映射文件:

String path = @"C:\Northwind.map";

XmlMappingSource xms = XmlMappingSource.FromXml(File.ReadAllText(path));

Northwind ctx = new Northwind("server=xxx;database=Northwind;uid=xxx;pwd=xxx", xms);

5、现在就可以照常进行其它工作了。使用sqlmetal可以很方便的同步数据库与实体和映射文件。每次修改数据库结构,从dbml设计器上删除表、存储过程然后再重新添加也是很麻烦的事情。

处理空值

var count = (from c in ctx.Customers where c.Region == null select c).Count();

Response.Write(count + "<br/>");

var query = from emp in ctx.Employees select emp.ReportsTo;

foreach (Nullable<int> r in query)

{

  Response.Write(r.HasValue ? r.Value.ToString() + "<br/>" : "没有<br/>");

}

代码执行后捕获到下面的SQL被执行:

SELECT COUNT(*) AS [value]

FROM [dbo].[Customers] AS [t0]

WHERE [t0].[Region] IS NULL

SELECT [t0].[ReportsTo]

FROM [dbo].[Employees] AS [t0]

已编译查询

对于一些在项目中经常被用到的查询可以封装成已编译查询,这样就能提高执行效率:

static class Queries

{

public static Func<NorthwindDataContext, string, IQueryable<Customer>>

        CustomersByCity = CompiledQuery.Compile((NorthwindDataContext ctx,string city) => from c in ctx.Customers where c.City == city select c);

}

调用查询方式如下:

GridView1.DataSource = Queries.CustomersByCity(ctx, "London");

GridView1.DataBind();

获取一些信息

  var query = from c in ctx.Customers select c;

  Response.Write("Provider类型:" + ctx.Mapping.ProviderType + "<br/>");

  Response.Write("数据库:" + ctx.Mapping.DatabaseName + "<br/>");

  Response.Write("表:" + ctx.Mapping.GetTable(typeof(Customer)).TableName + "<br/>");

  Response.Write("表达式:" + query.Expression.ToString() + "<br/>");

  Response.Write("sql:" + query.Provider.ToString() + "<br/>");

上面的代码执行结果如下:

Provider类型:System.Data.Linq.SqlClient.SqlProvider
数据库:Northwind
表:dbo.Customers
表达式:Table(Customer).Select(c => c)
sql:SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax] FROM [dbo].[Customers] AS [t0]

撤销提交

var customer = ctx.Customers.Single(c => c.CustomerID == "AROUT");

customer.ContactName = "zhuye";

customer.Country = "Shanghai";

Response.Write(string.Format("Name:{0},Country:{1}<br/>", customer.ContactName, customer.Country));

customer = ctx.Customers.GetOriginalEntityState(customer);

Response.Write(string.Format("Name:{0},Country:{1}<br/>", customer.ContactName, customer.Country));

上面的代码执行效果如下:

Name:zhuye,Country:Shanghai
Name:Thomas Hardy,Country:UK

批量操作

下面的代码会导致提交N次DELETE操作:

var query = from c in ctx.Customers select c;

ctx.Customers.RemoveAll(query);

ctx.SubmitChanges();

应该使用sql语句进行批操作:

string sql = String.Format("delete from {0}", ctx.Mapping.GetTable(typeof(Customer)).TableName);

  ctx.ExecuteCommand(sql);

对于批量更新操作也是同样道理。

(十):分层构架的例子

项目介绍

这节将要把《一步一步学Linq to sql(三):增删改》中留言簿的例子修改为使用WCF的多层构架。我们将会建立以下项目:

· A,网站项目 WebSite:留言簿表现层

· B,类库项目 Contract:定义数据访问服务的契约

· C,类库项目 Service:定义数据访问服务

· D,类库项目Entity:留言簿实体

· E,控制台项目Host:承载数据访问服务

项目之间的引用如下:

· A引用B和D;

· B引用D和System.ServiceModel程序集

· C引用B、D、System.ServiceModel以及System.Data.Linq程序集

· D引用System.Data.Linq程序集

· E引用C和System.ServiceModel程序集

生成映射文件和实体

打开VS2008命令行提示,执行以下命令:

sqlmetal /conn:server=xxx;database=GuestBook;uid=xxx;pwd=xxx /map:c:\guestbook.map /code:c:\guestbook.cs /serialization:Unidirectional

注意到,这里我们使用了serialization开关,告知sqlmetal在生成实体的时候自动把它们标记为WCF数据对象。生成结束后把C:\GUESTBOOK.CS添加到Entity项目中。

编写数据访问服务

首先我们可以定义出留言簿数据访问服务的契约(接口),把如下的代码保存为IDataAccess.cs放在Contract类库项目中:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.ServiceModel;

namespace Contract

{

    [ServiceContract]

public interface IDataAccess

    {

        [OperationContract]

void SendMessage(TbGuestBook gb);

        [OperationContract]

List<TbGuestBook> GetData();

        [OperationContract]

void DeleteMessage(string ID);

        [OperationContract]

void SendReply(TbGuestBook gb);

    }

}

在这里定义了四个方法:

· 创建留言

· 获取所有留言

· 删除留言

· 管理员发表回复

然后,我们来实现这个契约,把如下代码保存为DataAccess.cs放在Service类库项目中:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using Contract;

using System.Data.Linq.Mapping;

using System.IO;

using System.ServiceModel;

namespace Service

{

    [ServiceBehavior(IncludeExceptionDetailInFaults = true)]

public class DataAccess : IDataAccess

    {

GuestBook ctx;

public DataAccess()

        {

XmlMappingSource xms =XmlMappingSource.FromXml(File.ReadAllText("c:\\guestbook.map"));

            ctx = new GuestBook("server=srv-devdbhost;database=GuestBook;uid=sa;pwd=Abcd1234", xms);

            ctx.Log = Console.Out;

        }

public void SendMessage(TbGuestBook gb)

        {

            ctx.TbGuestBook.Add(gb);

            ctx.SubmitChanges();

        }

public List<TbGuestBook> GetData()

        {

var query = from gb in ctx.TbGuestBook orderby gb.PostTimedescending select gb;

return query.ToList();

        }

public void DeleteMessage(string ID)

        {

TbGuestBook gb = ctx.TbGuestBook.Single(message => message.ID ==new Guid(ID));

            ctx.TbGuestBook.Remove(gb);

            ctx.SubmitChanges();

        }

public void SendReply(TbGuestBook gb)

        {

//ctx.ExecuteCommand("update tbGuestBook set reply={0},isreplied=1 where ID={1}", gb.Reply, gb.ID);

TbGuestBook record = ctx.TbGuestBook.Single(message => message.ID == gb.ID);

            record.IsReplied = true;

            record.Reply = gb.Reply;

            ctx.SubmitChanges();

        }

    }

}

这里需要注意几点:

· 我们把DataContext的操作在控制台输出

· 在进行发表回复(更新操作)的时候,注释的代码和没有注释的代码虽然都能完成更新操作,但是前者更合理,因为后者会先进行SELECT再进行UPDATE

WCF服务端与客户端

打开Host项目中的Program.cs,使用下面的代码来实现WCF的服务端:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.ServiceModel;

using Service;

using Contract;

namespace Host

{

class Program

    {

static void Main(string[] args)

        {

Uri uri = new Uri("net.tcp://localhost:8080/DataAccessService");

using (ServiceHost sh = new ServiceHost(typeof(DataAccess), uri))

            {

NetTcpBinding ctb = new NetTcpBinding();

                sh.AddServiceEndpoint(typeof(IDataAccess), ctb, string.Empty);

                sh.Opened += delegate { Console.WriteLine("服务已经启动"); };

                sh.Open();

Console.ReadLine();

            }

        }

    }

}

在WebSite项目中的App_Code文件夹下创建一个用户调用服务的类,GetService.cs:

using System;

using System.Data;

using System.Configuration;

using System.Linq;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Web.UI.HtmlControls;

using System.Xml.Linq;

using Contract;

using System.ServiceModel.Description;

using System.ServiceModel;

public class GetService

{

public static IDataAccess GetDataAccessService()

    {

ServiceEndpoint sep = newServiceEndpoint(ContractDescription.GetContract(typeof(IDataAccess)),

new NetTcpBinding(),

newEndpointAddress("net.tcp://localhost:8080/DataAccessService"));

ChannelFactory<IDataAccess> cf = new ChannelFactory<IDataAccess>(sep);

return cf.CreateChannel();

    }

}

调用服务

最后,就可以调用数据访问服务来进行留言、回复、删除留言等操作了。页面的代码不再贴了,大家可以看第三篇或者下载源代码。我们把Default.cs修改成如下:

public partial class _Default : System.Web.UI.Page

{

protected void Page_Load(object sender, EventArgs e)

    {

if (!IsPostBack)

        {

            SetBind();

        }

    }

protected void btn_SendMessage_Click(object sender, EventArgs e)

    {

TbGuestBook gb = new TbGuestBook();

        gb.ID = Guid.NewGuid();

        gb.IsReplied = false;

        gb.PostTime = DateTime.Now;

        gb.UserName = tb_UserName.Text;

        gb.Message = tb_Message.Text;

GetService.GetDataAccessService().SendMessage(gb);

        SetBind();

    }

private void SetBind()

    {

        rpt_Message.DataSource = GetService.GetDataAccessService().GetData();

        rpt_Message.DataBind();

    }

}

Admin.cs代码修改成如下:

public partial class Admin : System.Web.UI.Page

{

protected void Page_Load(object sender, EventArgs e)

    {

if (!IsPostBack)

        {

            SetBind();

        }

    }

private void SetBind()

    {

        rpt_Message.DataSource = GetService.GetDataAccessService().GetData();

        rpt_Message.DataBind();

    }

protected void rpt_Message_ItemCommand(object source,RepeaterCommandEventArgs e)

    {

if (e.CommandName == "DeleteMessage")

        {

GetService.GetDataAccessService().DeleteMessage(e.CommandArgument.ToString());

            SetBind();

        }

if (e.CommandName == "SendReply")

        {

TbGuestBook gb = new TbGuestBook();

            gb.ID = new Guid(e.CommandArgument.ToString());

            gb.Reply = ((TextBox)e.Item.FindControl("tb_Reply")).Text;

GetService.GetDataAccessService().SendReply(gb);

            SetBind();

        }

    }

}

就这样实现了一个多层构架的留言簿程序。对于WCF的一些内容本文不多作解释了。点击这里下载本篇代码。
如果您觉得这个例子太简单,还可以在这里下载一个Linq/WCF/MVC结合使用更复杂的例子,此例的目的主要演示一个框架,实现不完整。

一步一步学Linq to sql到这里就结束了,看到这里应该已经算师父领进门了,后续的提高还要靠大家自己去琢磨。

发表在 C# | 标签为 , , | 3 条评论

实现IHttpModule接口获取Session来实现页面访问日志功能

我们在开发企业Web应用程序时经常需要对用户的操作记录日志,以便在发生突发事件后有据可查,比如要对用户访问的每一个页面都做日志记录。通常的做法可能是编写一个记录日志的方法(如:AddAccessLog),在每一个页面的Page_Load事件中调用这个AddAccessLog方法,从而达到记录页面访问日志的目的。这样的方法在页面较少的时候可行,但是当项目变得越来越大,需要记录日志的页面越来越多的时候,我们要在每个页面中都调用这样的方法,从而使得系统很难维护。有没有简单一点的办法呢,何不用IHttpModule接口实现一个自定义的LogHttpModule来试试呢?

IHttpModule接口中定义了两个方法:Init和Dispose。Init方法初始化一个模块,并为它做好处理请求的准备。这时,我们同意接受感兴趣的事件通知。Dispose方法处置该模块使用资源。Init方法接受一个服务该请求的HttpApplication对象的引用。使用该引用可以连接到系统事件。

class PageLoggerModule : IHttpModule  //实现IHttpModule接口
{

public void Dispose()
{
}

public void Init(HttpApplication context)
{
    context.BeginRequest += new EventHandler(context_BeginRequest);
}

void context_BeginRequest(object sender, EventArgs e)
{

//在这里实现具体代码

HttpApplication app = (HttpApplication)sender;
HttpContext ctx = app.Context;

//acquire session state
string userId = "Not Registered";
if (ctx.Session != null && ctx.Session["userId"] != null)
{
    userId = ctx.Session["userId"].ToString();
}

//acquire request string
string funcId = "Init funcId";
if (ctx.Request.QueryString["funcId"] != null)
{
    funcId = ctx.Request.QueryString["funcId"].ToString();
}

LogEntry log = new LogEntry(); // 这里是自定义的一个类,属性列表和数据库中的字段一致
log.DateTime = System.DateTime.Now;
log.IpAddress = ctx.Request.UserHostAddress;
log.MachineName = ctx.Request.UserHostName;
log.UserId = userId;
log.FunctionId = funcId;

string logString = log.DateTime.ToString() + "<br/>"
+ log.UserId + "<br/>"
+ log.IpAddress + "<br/>"
+ log.MachineName + "<br/>"
+ log.FunctionId + "<br/>";

//you can add other codes here
ctx.Response.AppendHeader("Author", "Changyu Du");

ctx.Response.Write(logString);

}

}

在Web.config中,System.Web节中增加一个HttpModule:

    <httpModules>
       <add name="PageLoggerHttpModule" type="PageLoggerHttpModule.PageLoggerModule,PageLoggerHttpModule"/>
    </httpModules>

新建一个普通的aspx页面,在页面加载时把用户信息保存到Session中,模拟一下项目应用中的情形:

protected void Page_Load(object sender, EventArgs e)
{
    //Add userName into SESSION
    if (Session["userId"] == null)
    {
        Session["userId"] = "1101";
    }
    else
    {
        Response.Write("SESSION :userId = "+Session["userId"].ToString());
    }
}

为了确保把用户Id信息加入到session中,测试页面上还随便加了个button,button_click什么都不干,就是为了让页面回发一下确保把userId存入到Session中去。F5运行一下,嗯,不错,LogEntry的信息都输出出来了,已经取得了访问时间,IP地址之类的信息,基本成功!下来直接把LogEntry的信息保存到数据库就OK了。

可是,别忙,怎么总取不到Session中的用户信息呢?记录访问日志用户Id这样的信息肯定是需要记录的啊!

后来放狗一搜才发现,还是对aspnet的事件处理流程不理解,Begin_Request时还没有加载Session状态呢,自然就取不到了。

下面是MSDN上提供的事件触发顺序:

在处理该请求时将由 HttpApplication 类执行以下事件。希望扩展 HttpApplication 类的开发人员尤其需要注意这些事件。

1. 对请求进行验证,将检查浏览器发送的信息,并确定其是否包含潜在恶意标记。有关更多信息,请参见ValidateRequest脚本侵入概述

2. 如果已在 Web.config 文件的 UrlMappingsSection 节中配置了任何 URL,则执行 URL 映射。

3. 引发 BeginRequest 事件。

4. 引发 AuthenticateRequest 事件。

5. 引发 PostAuthenticateRequest 事件。

6. 引发 AuthorizeRequest 事件。

7. 引发 PostAuthorizeRequest 事件。

8. 引发 ResolveRequestCache 事件。

9. 引发 PostResolveRequestCache 事件。

10. 根据所请求资源的文件扩展名(在应用程序的配置文件中映射),选择实现 IHttpHandler 的类,对请求进行处理。如果该请求针对从 Page 类派生的对象(页),并且需要对该页进行编译,则 ASP.NET 会在创建该页的实例之前对其进行编译。

11. 引发 PostMapRequestHandler 事件。

12. 引发 AcquireRequestState 事件。

13. 引发 PostAcquireRequestState 事件。

14. 引发 PreRequestHandlerExecute 事件。

15. 为该请求调用合适的 IHttpHandler 类的 ProcessRequest 方法(或异步版 BeginProcessRequest)。例如,如果该请求针对某页,则当前的页实例将处理该请求。

16. 引发 PostRequestHandlerExecute 事件。

17. 引发 ReleaseRequestState 事件。

18. 引发 PostReleaseRequestState 事件。

19. 如果定义了 Filter 属性,则执行响应筛选。

20. 引发 UpdateRequestCache 事件。

21. 引发 PostUpdateRequestCache 事件。

22. 引发 EndRequest 事件。

AcquireRequestState事件,当实际服务请求的处理程序获得与该请求关联的状态信息时发生。在这个事件发生时才能取到Session中是userId信息。BeginRequest事件在AcquireRequestState之前发生,我们把取Session状态的代码放在BeginRequest中肯定是取不到的。

问题找到了,把日志记录代码放在AcquireRequestState中就可以了,于是改成下面的样子:

public void Init(HttpApplication context)
{
    context.AcquireRequestState += new EventHandler(context_AcquireRequestState);
}

void context_AcquireRequestState(object sender, EventArgs e)
{

  //原先context_BeginRequest中的代码,不重复贴占地方了 :)

//把LogEntry中的信息保存到数据库

}

好了,这样我们继承了IHttpModule接口,实现了一个自定义的LogMudule,这样在用户方面每个页面时,都会自动记录用户的信息记如访问日志数据库中,再也不用到每个页面的Page_Load中去写了,维护起来也方便多了! Yeah~

发表在 ASP.NET | 标签为 , , | 2 条评论