LINQtoSQL语法及实例大全Hao0

LINQtoSQL语句(2)之Select/Distinct.3

1.简单用法:...4

2.匿名类型形式:...4

3.条件形式:...5

4.指定类型形式:...6

5.筛选形式:...6

6.shaped形式(整形类型):...6

7.嵌套类型形式:...7

8.本地方法调用形式(LocalMethodCall):...7

9.Distinct形式:...8

LINQtoSQL语句(3)之Count/Sum/Min/Max/Avg.9

1.简单形式:...9

2.带条件形式:...9

1.简单形式:...10

2.映射形式:...10

3.元素:...11

1.简单形式:...11

2.映射形式:...11

1.简单形式:...12

2.映射形式:...12

3.元素:...12

LINQtoSQL语句(4)之Join.13

Join操作符...13

1.一对多关系(1toMany):...13

2.多对多关系(ManytoMany):...14

3.自联接关系:...15

1.双向联接(Twowayjoin):...15

2.三向联接(Therewayjoin):...16

3.左外部联接(LeftOuterJoin):...17

4.投影的Let赋值(Projectedletassignment):...17

5.组合键(CompositeKey):...18

6.可为null/不可为null的键关系(Nullable/NonnullableKeyRelationship):...19

LINQtoSQL语句(5)之OrderBy.19

OrderBy操作...19

1.简单形式...19

2.带条件形式...20

3.降序排序...20

4.ThenBy.20

5.ThenByDescending.22

6.带GroupBy形式...22

LINQtoSQL语句(6)之GroupBy/Having.23

GroupBy/Having操作符...23

1.简单形式:...23

2.Select匿名类:...24

3.最大值...25

4.最小值...26

5.平均值...26

6.求和...26

7.计数...27

8.带条件计数...27

9.Where限制...28

10.多列(MultipleColumns)28

11.表达式(Expression)29

LINQtoSQL语句(7)之Exists/In/Any/All/Contains.29

Exists/In/Any/All/Contains操作符...29

Any.29

1.简单形式:...29

2.带条件形式:...30

All30

Contains.31

1.包含一个对象:...31

2.包含多个值:...32

LINQtoSQL语句(8)之Concat/Union/Intersect/Except.32

Concat/Union/Intersect/Except操作...32

Concat(连接)...32

1.简单形式:...33

2.复合形式:...33

Union(合并)...33

Intersect(相交)...34

Except(与非)...34

LINQtoSQL语句(9)之Top/Bottom和Paging和SqlMethods.35

Top/Bottom操作...35

Take.35

Skip.35

TakeWhile.36

SkipWhile.36

Paging(分页)操作...36

1.索引...36

2.按唯一键排序...36

SqlMethods操作...37

Like.37

已编译查询操作(CompiledQuery)38

LINQtoSQL语句(10)之Insert.38

插入(Insert)1.简单形式...38

2.一对多关系...39

3.多对多关系...39

4.使用动态CUD重写(OverrideusingDynamicCUD)40

LINQtoSQL语句(11)之Update.41

更新(Update)41

1.简单形式...41

2.多项更改...41

LINQtoSQL语句(12)之Delete和使用Attach.42

删除(Delete)1.简单形式...42

2.一对多关系...42

3.推理删除(InferredDelete)43

使用Attach更新(UpdatewithAttach)43

LINQtoSQL语句(13)之开放式并发控制和事务...46

SimultaneousChanges开放式并发控制...46

开放式并发(OptimisticConcurrency)46

1.Implicit(隐式)...48

2.Explicit(显式)...48

LINQtoSQL语句(14)之Null语义和DateTime.49

Null语义...49

1.Null49

2.Nullable.HasValue.50

日期函数...50

1.DateTime.Year.51

2.DateTime.Month.51

3.DateTime.Day.51

LINQtoSQL语句(15)之String.51

字符串(String)...51

1.字符串串联(StringConcatenation)52

2.String.Length.52

3.String.Contains(substring)52

4.String.IndexOf(substring)52

5.String.StartsWith(prefix)53

6.String.EndsWith(suffix)53

7.String.Substring(start)53

8.String.Substring(start,length)53

9.String.ToUpper()54

10.String.ToLower()54

11.String.Trim()54

12.String.Insert(pos,str)54

13.String.Remove(start)55

14.String.Remove(start,length)55

15.String.Replace(find,replace)55

LINQtoSQL语句(16)之对象标识...56

对象标识...56

对象缓存...56

LINQtoSQL语句(17)之对象加载...57

对象加载延迟加载...57

预先加载:LoadWith方法...58

LINQtoSQL语句(18)之运算符转换...59

1.AsEnumerable:将类型转换为泛型IEnumerable.59

2.ToArray:将序列转换为数组...59

3.ToList:将序列转换为泛型列表...59

4.ToDictionary:将序列转化为字典...60

LINQtoSQL语句(19)之ADO.NET与LINQtoSQL.60

1.连接...61

2.事务...61

LINQtoSQL语句(20)之存储过程...63

1.标量返回...63

2.单一结果集...64

3.多个可能形状的单一结果集...65

4.多个结果集...70

5.带输出参数...79

LINQtoSQL语句(21)之用户定义函数...80

1.在Select中使用用户定义的标量函数...80

2.在Where从句中使用用户定义的标量函数...81

3.使用用户定义的表值函数...83

4.以联接方式使用用户定义的表值函数...84

LINQtoSQL语句(22)之DataContext.85

创建和删除数据库...85

数据库验证...88

数据库更改...88

动态查询...89

日志...90

LINQtoSQL语句(23)之动态查询...90

1.Select.91

2.Where.92

LINQtoSQL语句(24)之视图...94

LINQtoSQL语句(25)之继承...96

1.一般形式...97

2.OfType形式...98

3.IS形式...98

4.AS形式...99

5.Cast形式...99

6.UseAsDefault形式...100

7.插入新的记录...101

适用场景:实现过滤,查询等功能。

说明:与SQL命令中的Where作用相似,都是起到范围限定也就是过滤作用的,而判断条件就是它后面所接的子句。

Where操作包括3种形式,分别为简单形式、关系条件形式、First()形式。下面分别用实例举例下:

例如:使用where筛选在伦敦的客户

varq=

fromcindb.Customers

wherec.City=="London"

selectc;

再如:筛选1994年或之后雇用的雇员:

fromeindb.Employees

wheree.HireDate>=newDateTime(1994,1,1)

selecte;

筛选库存量在订货点水平之下但未断货的产品:

frompindb.Products

wherep.UnitsInStock<=p.ReorderLevel&&!p.Discontinued

selectp;

筛选出UnitPrice大于10或已停产的产品:

wherep.UnitPrice>10m||p.Discontinued

下面这个例子是调用两次where以筛选出UnitPrice大于10且已停产的产品。

db.Products.Where(p=>p.UnitPrice>10m).Where(p=>p.Discontinued);

返回集合中的一个元素,其实质就是在SQL语句中加TOP(1)。

简单用法:选择表中的第一个发货方。

Shippershipper=db.Shippers.First();

元素:选择CustomerID为“BONAP”的单个客户

Customercust=db.Customers.First(c=>c.CustomerID=="BONAP");

条件:选择运费大于10.00的订单:

Orderord=db.Orders.First(o=>o.Freight>10.00M);

[1]Select介绍1

[2]Select介绍2

[3]Select介绍3和Distinct介绍

Select/Distinct操作符

适用场景:o(∩_∩)o…查询呗。

说明:和SQL命令中的select作用相似但位置不同,查询表达式中的select及所接子句是放在表达式最后并把子句中的变量也就是结果返回回来;延迟。

Select/Distinct操作包括9种形式,分别为简单用法、匿名类型形式、条件形式、指定类型形式、筛选形式、整形类型形式、嵌套类型形式、本地方法调用形式、Distinct形式。

这个示例返回仅含客户联系人姓名的序列。

selectc.ContactName;

说明:匿名类型是C#3.0中新特性。其实质是编译器根据我们自定义自动产生一个匿名的类来帮助我们实现临时变量的储存。匿名类型还依赖于另外一个特性:支持根据property来创建对象。比如,vard=new{Name="s"};编译器自动产生一个有property叫做Name的匿名类,然后按这个类型分配内存,并初始化对象。但是vard=new{"s"};是编译不通过的。因为,编译器不知道匿名类中的property的名字。例如stringc="d";vard=new{c};则是可以通过编译的。编译器会创建一个叫做匿名类带有叫c的property。

例如下例:new{c,ContactName,c.Phone};ContactName和Phone都是在映射文件中定义与表中字段相对应的property。编译器读取数据并创建对象时,会创建一个匿名类,这个类有两个属性,为ContactName和Phone,然后根据数据初始化对象。另外编译器还可以重命名property的名字。

selectnew{c.ContactName,c.Phone};

selectnew

{

Name=e.FirstName+""+e.LastName,

Phone=e.HomePhone

};

p.ProductID,

HalfPrice=p.UnitPrice/2

上面语句描述:使用SELECT和匿名类型返回所有产品的ID以及HalfPrice(设置为产品单价除以2所得的值)的序列。

说明:生成SQL语句为:casewhenconditionthenelse。

p.ProductName,

Availability=

p.UnitsInStock-p.UnitsOnOrder<0

"OutOfStock":"InStock"

上面语句描述:使用SELECT和条件语句返回产品名称和产品供货状态的序列。

说明:该形式返回你自定义类型的对象集。

selectnewName

FirstName=e.FirstName,

LastName=e.LastName

上面语句描述:使用SELECT和已知类型返回雇员姓名的序列。

说明:结合where使用,起到过滤作用。

上面语句描述:使用SELECT和WHERE返回仅含伦敦客户联系人姓名的序列。

说明:其select操作使用了匿名对象,而这个匿名对象中,其属性也是个匿名对象。

selectnew{

c.CustomerID,

CompanyInfo=new{c.CompanyName,c.City,c.Country},

ContactInfo=new{c.ContactName,c.ContactTitle}

语句描述:使用SELECT和匿名类型返回有关客户的数据的整形子集。查询顾客的ID和公司信息(公司名称,城市,国家)以及联系信息(联系人和职位)。

说明:返回的对象集中的每个对象DiscountedProducts属性中,又包含一个集合。也就是每个对象也是一个集合类。

fromoindb.Orders

o.OrderID,

DiscountedProducts=

fromodino.OrderDetails

whereod.Discount>0.0

selectod,

FreeShippingDiscount=o.Freight

语句描述:使用嵌套查询返回所有订单及其OrderID的序列、打折订单中项目的子序列以及免送货所省下的金额。

varq=fromcindb.Customers

wherec.Country=="UK"||c.Country=="USA"

c.CompanyName,

Phone=c.Phone,

InternationalPhone=

PhoneNumberConverter(c.Country,c.Phone)

PhoneNumberConverter方法如下:

publicstringPhoneNumberConverter(stringCountry,stringPhone)

Phone=Phone.Replace("","").Replace(")",")-");

switch(Country)

case"USA":

return"1-"+Phone;

case"UK":

return"44-"+Phone;

default:

returnPhone;

}

XDocumentdoc=newXDocument(

newXElement("Customers",fromcindb.Customers

select(newXElement("Customer",

newXAttribute("CustomerID",c.CustomerID),

newXAttribute("CompanyName",c.CompanyName),

newXAttribute("InterationalPhone",

PhoneNumberConverter(c.Country,c.Phone))

))));

说明:筛选字段中不相同的值。用于查询不重复的结果集。生成SQL语句为:SELECTDISTINCT[City]FROM[Customers]

varq=(

selectc.City)

.Distinct();

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

[1]Count/Sum讲解

[2]Min讲解

[3]Max讲解

[4]Average和Aggregate讲解

Count/Sum/Min/Max/Avg操作符

适用场景:统计数据吧,比如统计一些数据的个数,求和,最小值,最大值,平均数。

Count

说明:返回集合中的元素个数,返回INT类型;不延迟。生成SQL语句为:SELECTCOUNT(*)FROM

得到数据库中客户的数量:

varq=db.Customers.Count();

得到数据库中未断货产品的数量:

varq=db.Products.Count(p=>!p.Discontinued);

LongCount

说明:返回集合中的元素个数,返回LONG类型;不延迟。对于元素个数较多的集合可视情况可以选用LongCount来统计元素个数,它返回long类型,比较精确。生成SQL语句为:SELECTCOUNT_BIG(*)FROM

varq=db.Customers.LongCount();

Sum

说明:返回集合中数值类型元素之和,集合应为INT类型集合;不延迟。生成SQL语句为:SELECTSUM(…)FROM

得到所有订单的总运费:

varq=db.Orders.Select(o=>o.Freight).Sum();

得到所有产品的订货总数:

varq=db.Products.Sum(p=>p.UnitsOnOrder);

Min

说明:返回集合中元素的最小值;不延迟。生成SQL语句为:SELECTMIN(…)FROM

1.简单形式:

查找任意产品的最低单价:

varq=db.Products.Select(p=>p.UnitPrice).Min();

2.映射形式:

查找任意订单的最低运费:

varq=db.Orders.Min(o=>o.Freight);

查找每个类别中单价最低的产品:

varcategories=

grouppbyp.CategoryIDintog

CategoryID=g.Key,

CheapestProducts=

fromp2ing

wherep2.UnitPrice==g.Min(p3=>p3.UnitPrice)

selectp2

Max

说明:返回集合中元素的最大值;不延迟。生成SQL语句为:SELECTMAX(…)FROM

查找任意雇员的最近雇用日期:

varq=db.Employees.Select(e=>e.HireDate).Max();

查找任意产品的最大库存量:

varq=db.Products.Max(p=>p.UnitsInStock);

查找每个类别中单价最高的产品:

g.Key,

MostExpensiveProducts=

wherep2.UnitPrice==g.Max(p3=>p3.UnitPrice)

Average

说明:返回集合中的数值类型元素的平均值。集合应为数字类型集合,其返回值类型为double;不延迟。生成SQL语句为:SELECTAVG(…)FROM

得到所有订单的平均运费:

varq=db.Orders.Select(o=>o.Freight).Average();

得到所有产品的平均单价:

varq=db.Products.Average(p=>p.UnitPrice);

查找每个类别中单价高于该类别平均单价的产品:

ExpensiveProducts=

wherep2.UnitPrice>g.Average(p3=>p3.UnitPrice)

Aggregate

说明:根据输入的表达式获取聚合值;不延迟。即是说:用一个种子值与当前元素通过指定的函数来进行对比来遍历集合中的元素,符合条件的元素保留下来。如果没有指定种子值的话,种子值默认为集合的第一个元素。

适用场景:在我们表关系中有一对一关系,一对多关系,多对多关系等。对各个表之间的关系,就用这些实现对多个表的操作。

说明:在Join操作中,分别为Join(Join查询),SelectMany(Select一对多选择)和GroupJoin(分组Join查询)。

该扩展方法对两个序列中键匹配的元素进行innerjoin操作

SelectMany

说明:我们在写查询语句时,如果被翻译成SelectMany需要满足2个条件。1:查询语句中没有join和into,2:必须出现EntitySet。在我们表关系中有一对一关系,一对多关系,多对多关系等,下面分别介绍一下。

fromoinc.Orders

selecto;

语句描述:Customers与Orders是一对多关系。即Orders在Customers类中以EntitySet形式出现。所以第二个from是从c.Orders而不是db.Orders里进行筛选。这个例子在From子句中使用外键导航选择伦敦客户的所有订单。

wherep.Supplier.Country=="USA"&&p.UnitsInStock==0

语句描述:这一句使用了p.Supplier.Country条件,间接关联了Supplier表。这个例子在Where子句中使用外键导航筛选其供应商在美国且缺货的产品。生成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]

LEFTOUTERJOIN[dbo].[Suppliers]AS[t1]ON

[t1].[SupplierID]=[t0].[SupplierID]

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

--@p0:InputNVarChar(Size=3;Prec=0;Scale=0)[USA]

--@p1:InputInt(Size=0;Prec=0;Scale=0)[0]

frometine.EmployeeTerritories

wheree.City=="Seattle"

e.FirstName,

e.LastName,

et.Territory.TerritoryDescription

说明:多对多关系一般会涉及三个表(如果有一个表是自关联的,那有可能只有2个表)。这一句语句涉及Employees,EmployeeTerritories,Territories三个表。它们的关系是1:M:1。Employees和Territories没有很明确的关系。

语句描述:这个例子在From子句中使用外键导航筛选在西雅图的雇员,同时列出其所在地区。这条生成SQL语句为:

SELECT[t0].[FirstName],[t0].[LastName],[t2].[TerritoryDescription]

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

AS[t1]INNERJOIN[dbo].[Territories]AS[t2]ON

[t2].[TerritoryID]=[t1].[TerritoryID]

WHERE([t0].[City]=@p0)AND([t1].[EmployeeID]=[t0].[EmployeeID])

--@p0:InputNVarChar(Size=7;Prec=0;Scale=0)[Seattle]

frome1indb.Employees

frome2ine1.Employees

wheree1.City==e2.City

FirstName1=e1.FirstName,LastName1=e1.LastName,

FirstName2=e2.FirstName,LastName2=e2.LastName,

e1.City

语句描述:这个例子在select子句中使用外键导航筛选成对的雇员,每对中一个雇员隶属于另一个雇员,且两个雇员都来自相同城市。生成SQL语句为:

SELECT[t0].[FirstName]AS[FirstName1],[t0].[LastName]AS

[LastName1],[t1].[FirstName]AS[FirstName2],[t1].[LastName]AS

[LastName2],[t0].[City]FROM[dbo].[Employees]AS[t0],

[dbo].[Employees]AS[t1]WHERE([t0].[City]=[t1].[City])AND

([t1].[ReportsTo]=[t0].[EmployeeID])

GroupJoin

像上面所说的,没有join和into,被翻译成SelectMany,同时有join和into时,那么就被翻译为GroupJoin。在这里into的概念是对其结果进行重新命名。

此示例显式联接两个表并从这两个表投影出结果:

joinoindb.Ordersonc.CustomerID

equalso.CustomerIDintoorders

c.ContactName,

OrderCount=orders.Count()

说明:在一对多关系中,左边是1,它每条记录为c(fromcindb.Customers),右边是Many,其每条记录叫做o(joinoindb.Orders),每对应左边的一个c,就会有一组o,那这一组o,就叫做orders,也就是说,我们把一组o命名为orders,这就是into用途。这也就是为什么在select语句中,orders可以调用聚合函数Count。在T-SQL中,使用其内嵌的T-SQL返回值作为字段值。如图所示:

生成SQL语句为:

SELECT[t0].[ContactName],(

SELECTCOUNT(*)

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

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

)AS[OrderCount]

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

此示例显式联接三个表并分别从每个表投影出结果:

equalso.CustomerIDintoords

joineindb.Employeesonc.City

equalse.Cityintoemps

ords=ords.Count(),

emps=emps.Count()

)AS[ords],(

FROM[dbo].[Employees]AS[t2]

WHERE[t0].[City]=[t2].[City]

)AS[emps]

此示例说明如何通过使用此示例说明如何通过使用DefaultIfEmpty()获取左外部联接。在雇员没有订单时,DefaultIfEmpty()方法返回null:

joinoindb.Ordersoneequalso.Employeeintoords

fromoinords.DefaultIfEmpty()

Order=o

说明:以Employees左表,Orders右表,Orders表中为空时,用null值填充。Join的结果重命名ords,使用DefaultIfEmpty()函数对其再次查询。其最后的结果中有个Order,因为fromoinords.DefaultIfEmpty()是对ords组再一次遍历,所以,最后结果中的Order并不是一个集合。但是,如果没有fromoinords.DefaultIfEmpty()这句,最后的select语句写成selectnew{e.FirstName,e.LastName,Order=ords}的话,那么Order就是一个集合。

说明:let语句是重命名。let位于第一个from和select语句之间。

这个例子从联接投影出最终“Let”表达式:

letz=c.City+c.Country

fromoinords

z

这个例子显示带有组合键的联接:

joindindb.OrderDetails

onnew

p.ProductID

}equals

new

d.OrderID,

d.ProductID

intodetails

fromdindetails

d.UnitPrice

说明:使用三个表,并且用匿名类来说明:使用三个表,并且用匿名类来表示它们之间的关系。它们之间的关系不能用一个键描述清楚,所以用匿名类,来表示组合键。还有一种是两个表之间是用组合键表示关系的,不需要使用匿名类。

这个实例显示如何构造一侧可为null而另一侧不可为null的联接:

joineindb.Employees

ono.EmployeeIDequals

(int)e.EmployeeIDintoemps

fromeinemps

e.FirstName

说明:按指定表达式对集合排序;延迟,:按指定表达式对集合排序;延迟,默认是升序,加上descending表示降序,对应的扩展方法是OrderBy和OrderByDescending

这个例子使用orderby按雇用日期对雇员进行排序:

orderbye.HireDate

说明:默认为升序

注意:Where和OrderBy的顺序并不重要。而在T-SQL中,Where和OrderBy有严格的位置限制。

whereo.ShipCity=="London"

orderbyo.Freight

语句描述:使用where和orderby按运费进行排序。

orderbyp.UnitPricedescending

语句描述:使用复合的orderby对客户进行排序,进行排序:

orderbyc.City,c.ContactName

说明:按多个表达式进行排序,例如先按City排序,当City相同时,按ContactName排序。这一句用Lambda表达式像这样写:

.OrderBy(c=>c.City)

.ThenBy(c=>c.ContactName).ToList();

在T-SQL中没有ThenBy语句,其依然翻译为OrderBy,所以也可以用下面语句来表达:

db.Customers

.OrderBy(c=>c.ContactName)

.OrderBy(c=>c.City).ToList();

所要注意的是,多个OrderBy操作时,级连方式是按逆序。对于降序的,用相应的降序操作符替换即可。

.OrderByDescending(c=>c.City)

.ThenByDescending(c=>c.ContactName).ToList();

需要说明的是,OrderBy操作,不支持按type排序,也不支持匿名类。比如

.OrderBy(c=>new

c.City,

c.ContactName

}).ToList();

会被抛出异常。错误是前面的操作有匿名类,再跟OrderBy时,比较的是类别。比如

.Select(c=>new

c.Address

})

.OrderBy(c=>c).ToList();

如果你想使用OrderBy(c=>c),其前提条件是,前面步骤中,所产生的对象的类别必须为C#语言的基本类型。比如下句,这里City为string类型。

.Select(c=>c.City)

这两个扩展方式都是用在OrderBy/OrderByDescending之后的,第一个ThenBy/ThenByDescending扩展方法作为第二位排序依据,第二个ThenBy/ThenByDescending则作为第三位排序依据,以此类推

whereo.EmployeeID==1

orderbyo.ShipCountry,o.Freightdescending

语句描述:使用orderby先按发往国家再按运费从高到低的顺序对EmployeeID1的订单进行排序。

orderbyg.Key

语句描述:使用orderby、Max和GroupBy得出每种类别中单价最高的产品,并按CategoryID对这组产品进行排序。

适用场景:分组数据,为我们查找数据缩小范围。

说明:分配并返回对传入参数进行分组操作后的可枚举对象。分组;延迟

selectg;

语句描述:使用GroupBy按CategoryID划分产品。

说明:frompindb.Products表示从表中将产品对象取出来。grouppbyp.CategoryIDintog表示对p按CategoryID字段归类。其结果命名为g,一旦重新命名,p的作用域就结束了,所以,最后select时,只能selectg。当然,也不必重新命名可以这样写:

grouppbyp.CategoryID;

我们用示意图表示:

如果想遍历某类别中所有记录,这样:

foreach(vargpinq)

if(gp.Key==2)

foreach(varitemingp)

//dosomething

selectnew{CategoryID=g.Key,g};

说明:在这句LINQ语句中,有2个property:CategoryID和g。这个匿名类,其实质是对返回结果集重新进行了包装。把g的property封装成一个完整的分组。如下图所示:

如果想遍历某匿名类中所有记录,要这么做:

if(gp.CategoryID==2)

foreach(varitemingp.g)

MaxPrice=g.Max(p=>p.UnitPrice)

语句描述:使用GroupBy和Max查找每个CategoryID的最高单价。

说明:先按CategoryID归类,判断各个分类产品中单价最大的Products。取出CategoryID值,并把UnitPrice值赋给MaxPrice。

MinPrice=g.Min(p=>p.UnitPrice)

语句描述:使用GroupBy和Min查找每个CategoryID的最低单价。

说明:先按CategoryID归类,判断各个分类产品中单价最小的Products。取出CategoryID值,并把UnitPrice值赋给MinPrice。

AveragePrice=g.Average(p=>p.UnitPrice)

语句描述:使用GroupBy和Average得到每个CategoryID的平均单价。

说明:先按CategoryID归类,取出CategoryID值和各个分类产品中单价的平均值。

TotalPrice=g.Sum(p=>p.UnitPrice)

语句描述:使用GroupBy和Sum得到每个CategoryID的单价总计。

说明:先按CategoryID归类,取出CategoryID值和各个分类产品中单价的总和。

NumProducts=g.Count()

语句描述:使用GroupBy和Count得到每个CategoryID中产品的数量。

说明:先按CategoryID归类,取出CategoryID值和各个分类产品的数量。

NumProducts=g.Count(p=>p.Discontinued)

语句描述:使用GroupBy和Count得到每个CategoryID中断货产品的数量。

说明:先按CategoryID归类,取出CategoryID值和各个分类产品的断货数量。Count函数里,使用了Lambda表达式,Lambda表达式中的p,代表这个组里的一个元素或对象,即某一个产品。

whereg.Count()>=10

ProductCount=g.Count()

语句描述:根据产品的―ID分组,查询产品数量大于10的ID和产品数量。这个示例在GroupBy子句后使用Where子句查找所有至少有10种产品的类别。

说明:在翻译成SQL语句时,在最外层嵌套了Where条件。

grouppbynew

p.CategoryID,

p.SupplierID

intog

g

语句描述:使用GroupBy按CategoryID和SupplierID将产品分组。

说明:既按产品的分类,又按供应商分类。在by后面,new出来一个匿名类。这里,Key其实质是一个类的对象,Key包含两个Property:CategoryID、SupplierID。用g.Key.CategoryID可以遍历CategoryID的值。

grouppbynew{Criterion=p.UnitPrice>10}intog

语句描述:使用GroupBy返回两个产品序列。第一个序列包含单价大于10的产品。第二个序列包含单价小于或等于10的产品。

说明:按产品单价是否大于10分类。其结果分为两类,大于的是一类,小于及等于为另一类。

适用场景:用于判断集合中元素,进一步缩小范围。

说明:用于判断集合中是否有元素满足某一条件;不延迟。(若条件为空,则集合只要不为空就返回True,否则为False)。有2种形式,分别为简单形式和带条件形式。

仅返回没有订单的客户:

where!c.Orders.Any()

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

[t0].[ContactTitle],[t0].[Address],[t0].[City],[t0].[Region],

[t0].[PostalCode],[t0].[Country],[t0].[Phone],[t0].[Fax]

WHERENOT(EXISTS(

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

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

))

仅返回至少有一种产品断货的类别:

fromcindb.Categories

wherec.Products.Any(p=>p.Discontinued)

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

[t0].[Picture]FROM[dbo].[Categories]AS[t0]

WHEREEXISTS(

SELECTNULLAS[EMPTY]FROM[dbo].[Products]AS[t1]

WHERE([t1].[Discontinued]=1)AND

([t1].[CategoryID]=[t0].[CategoryID])

)

说明:用于判断集合中所有元素是否都满足某一条件;不延迟1.带条件形式

wherec.Orders.All(o=>o.ShipCity==c.City)

语句描述:这个例子返回所有订单都运往其所在城市的客户或未下订单的客户。

说明:用于判断集合中是否包含有某一元素;不延迟。它是对两个序列进行连接操作的。

string[]customerID_Set=

newstring[]{"AROUT","BOLID","FISSA"};

wherecustomerID_Set.Contains(o.CustomerID)

selecto).ToList();

语句描述:查找"AROUT","BOLID"和"FISSA"这三个客户的订单。先定义了一个数组,在LINQtoSQL中使用Contains,数组中包含了所有的CustomerID,即返回结果中,所有的CustomerID都在这个集合内。也就是in。你也可以把数组的定义放在LINQtoSQL语句里。比如:

where(

newstring[]{"AROUT","BOLID","FISSA"})

.Contains(o.CustomerID)

NotContains则取反:

where!(

varorder=(fromoindb.Orders

whereo.OrderID==10248

selecto).First();

varq=db.Customers.Where(p=>p.Orders.Contains(order)).ToList();

foreach(varcustinq)

foreach(varordincust.Orders)

语句描述:这个例子使用Contain查找哪个客户包含OrderID为10248的订单。

string[]cities=

newstring[]{"Seattle","London","Vancouver","Paris"};

varq=db.Customers.Where(p=>cities.Contains(p.City)).ToList();

语句描述:这个例子使用Contains查找其所在城市为西雅图、伦敦、巴黎或温哥华的客户。

适用场景:对两个集合的处理,例如追加、合并、取相同项、相交项等等。

说明:连接不同的集合,不会自动过滤相同项;延迟。

selectc.Phone

).Concat(

selectc.Fax

selecte.HomePhone

);

Name=c.CompanyName,

c.Phone

说明:连接不同的集合,自动过滤相同项;延迟。即是将两个集合进行合并操作,过滤相同的项。

selectc.Country

).Union(

selecte.Country

语句描述:查询顾客和职员所在的国家。

说明:取相交项;延迟。即是获取不同集合的相同项(交集)。即先遍历第一个集合,找出所有唯一的元素,然后遍历第二个集合,并将每个元素与前面找出的元素作对比,返回所有在两个集合内都出现的元素。

).Intersect(

语句描述:查询顾客和职员同在的国家。

说明:排除相交项;延迟。即是从某集合中删除与另一个集合中相同的项。先遍历第一个集合,找出所有唯一的元素,然后再遍历第二个集合,返回第二个集合中所有未出现在前面所得元素集合中的元素。

).Except(

语句描述:查询顾客和职员不同的国家。

适用场景:适量的取出自己想要的数据,不是全部取出,这样性能有所加强。

说明:获取集合的前n个元素;延迟。即只返回限定数量的结果集。

selecte)

.Take(5);

语句描述:选择所雇用的前5个雇员。

说明:跳过集合的前n个元素;延迟。即我们跳过给定的数目返回后面的结果集。

selectp)

.Skip(10);

语句描述:选择10种最贵产品之外的所有产品。

说明:直到某一条件成立就停止获取;延迟。即用其条件去依次判断源序列中的元素,返回符合判断条件的元素,该判断操作将在返回false或源序列的末尾结束。

说明:直到某一条件成立就停止跳过;延迟。即用其条件去判断源序列中的元素并且跳过第一个符合判断条件的元素,一旦判断返回false,接下来将不再进行判断并返回剩下的所有元素。

适用场景:结合Skip和Take就可实现对数据分页操作。

orderbyc.ContactName

selectc)

.Skip(50)

.Take(10);

语句描述:使用Skip和Take运算符进行分页,跳过前50条记录,然后返回接下来10条记录,因此提供显示Products表第6页的数据。

wherep.ProductID>50

orderbyp.ProductID

语句描述:使用Where子句和Take运算符进行分页,首先筛选得到仅50(第5页最后一个ProductID)以上的ProductID,然后按ProductID排序,最后取前10个结果,因此提供Products表第6页的数据。请注意,此方法仅适用于按唯一键排序的情况。

在LINQtoSQL语句中,为我们提供了SqlMethods操作,进一步为我们提供了方便,例如Like方法用于自定义通配表达式,Equals用于相比较是否相等。

自定义的通配表达式。%表示零长度或任意长度的字符串;_表示一个字符;[]表示在某范围区间的一个字符;[^]表示不在某范围区间的一个字符。比如查询消费者ID以“C”开头的消费者。

whereSqlMethods.Like(c.CustomerID,"C%")

比如查询消费者ID没有“AXOXT”形式的消费者:

where!SqlMethods.Like(c.CustomerID,"A_O_T")

DateDiffDay

说明:在两个变量之间比较。分别有:DateDiffDay、DateDiffHour、DateDiffMillisecond、DateDiffMinute、DateDiffMonth、DateDiffSecond、DateDiffYear

varq=fromoindb.Orders

whereSqlMethods

.DateDiffDay(o.OrderDate,o.ShippedDate)<10

语句描述:查询在创建订单后的10天内已发货的所有订单。

说明:在之前我们没有好的方法对写出的SQL语句进行编辑重新查询,现在我们可以这样做,看下面一个例子:

//1.创建compiledquery

NorthwindDataContextdb=newNorthwindDataContext();

varfn=CompiledQuery.Compile(

(NorthwindDataContextdb2,stringcity)=>

fromcindb2.Customers

wherec.City==city

selectc);

//2.查询城市为London的消费者,用LonCusts集合表示,这时可以用数据控件绑定

varLonCusts=fn(db,"London");

//3.查询城市为Seattle的消费者

varSeaCusts=fn(db,"Seattle");

语句描述:这个例子创建一个已编译查询,然后使用它检索输入城市的客户。

说明:new一个对象,使用InsertOnSubmit方法将其加入到对应的集合中,使用SubmitChanges()提交到数据库。

varnewCustomer=newCustomer

CustomerID="MCSFT",

CompanyName="Microsoft",

ContactName="JohnDoe",

ContactTitle="SalesManager",

Address="1MicrosoftWay",

City="Redmond",

Region="WA",

PostalCode="98052",

Country="USA",

Phone="(425)555-1234",

Fax=null

db.Customers.InsertOnSubmit(newCustomer);

db.SubmitChanges();

语句描述:使用InsertOnSubmit方法将新客户添加到Customers表对象。调用SubmitChanges将此新Customer保存到数据库。

说明:Category与Product是一对多的关系,提交Category(一端)的数据时,LINQtoSQL会自动将Product(多端)的数据一起提交。

varnewCategory=newCategory

CategoryName="Widgets",

Description="Widgetsarethe……"

varnewProduct=newProduct

ProductName="BlueWidget",

UnitPrice=34.56M,

Category=newCategory

db.Categories.InsertOnSubmit(newCategory);

语句描述:使用InsertOnSubmit方法将新类别添加到Categories表中,并将新Product对象添加到与此新Category有外键关系的Products表中。调用SubmitChanges将这些新对象及其关系保存到数据库。

说明:在多对多关系中,我们需要依次提交。

varnewEmployee=newEmployee

FirstName="Kira",

LastName="Smith"

varnewTerritory=newTerritory

TerritoryID="12345",

TerritoryDescription="Anytown",

Region=db.Regions.First()

varnewEmployeeTerritory=newEmployeeTerritory

Employee=newEmployee,

Territory=newTerritory

db.Employees.InsertOnSubmit(newEmployee);

db.Territories.InsertOnSubmit(newTerritory);

db.EmployeeTerritories.InsertOnSubmit(newEmployeeTerritory);

语句描述:使用InsertOnSubmit方法将新雇员添加到Employees表中,将新Territory添加到Territories表中,并将新EmployeeTerritory对象添加到与此新Employee对象和新Territory对象有外键关系的EmployeeTerritories表中。调用SubmitChanges将这些新对象及其关系保持到数据库。

说明:CUD就是Create、Update、Delete的缩写。下面的例子就是新建一个ID(主键)为32的Region,不考虑数据库中有没有ID为32的数据,如果有则替换原来的数据,没有则插入。

RegionnwRegion=newRegion()

RegionID=32,

RegionDescription="Rainy"

db.Regions.InsertOnSubmit(nwRegion);

语句描述:使用DataContext提供的分部方法InsertRegion插入一个区域。对SubmitChanges的调用调用InsertRegion重写,后者使用动态CUD运行LinqToSQL生成的默认SQL查询。

说明:更新操作,先获取对象,进行修改操作之后,直接调用SubmitChanges()方法即可提交。注意,这里是在同一个DataContext中,对于不同的DataContex看下面的讲解。

Customercust=

db.Customers.First(c=>c.CustomerID=="ALFKI");

cust.ContactTitle="VicePresident";

语句描述:使用SubmitChanges将对检索到的一个Customer对象做出的更新保持回数据库。

varq=frompindb.Products

wherep.CategoryID==1

foreach(varpinq)

p.UnitPrice+=1.00M;

语句描述:使用SubmitChanges将对检索到的进行的更新保持回数据库。

说明:调用DeleteOnSubmit方法即可。

OrderDetailorderDetail=

db.OrderDetails.First

(c=>c.OrderID==10255&&c.ProductID==36);

db.OrderDetails.DeleteOnSubmit(orderDetail);

语句描述:使用DeleteOnSubmit方法从OrderDetail表中删除OrderDetail对象。调用SubmitChanges将此删除保持到数据库。

说明:Order与OrderDetail是一对多关系,首先DeleteOnSubmit其OrderDetail(多端),其次DeleteOnSubmit其Order(一端)。因为一端是主键。

varorderDetails=

fromoindb.OrderDetails

whereo.Order.CustomerID=="WARTH"&&

o.Order.EmployeeID==3

varorder=

(fromoindb.Orders

whereo.CustomerID=="WARTH"&&o.EmployeeID==3

foreach(OrderDetailodinorderDetails)

db.OrderDetails.DeleteOnSubmit(od);

db.Orders.DeleteOnSubmit(order);

语句描述语句描述:使用DeleteOnSubmit方法从Order和OrderDetails表中删除Order和OrderDetail对象。首先从OrderDetails删除,然后从Orders删除。调用SubmitChanges将此删除保持到数据库。

说明:Order与OrderDetail是一对多关系,在上面的例子,我们全部删除CustomerID为WARTH和EmployeeID为3的数据,那么我们不须全部删除呢?例如Order的OrderID为10248的OrderDetail有很多,但是我们只要删除ProductID为11的OrderDetail。这时就用Remove方法。

Orderorder=db.Orders.First(x=>x.OrderID==10248);

OrderDetailod=

order.OrderDetails.First(d=>d.ProductID==11);

order.OrderDetails.Remove(od);

语句描述语句描述:这个例子说明在实体对象的引用实体将该对象从其EntitySet中移除时,推理删除如何导致在该对象上发生实际的删除操作。仅当实体的关联映射将DeleteOnNull设置为true且CanBeNull为false时,才会发生推理删除行为。

说明:在对于在不同的DataContext之间,使用Attach方法来更新数据。例如在一个名为tempdb的NorthwindDataContext中,查询出Customer和Order,在另一个NorthwindDataContext中,Customer的地址更新为123FirstAve,Order的CustomerID更新为CHOPS。

//通常,通过从其他层反序列化XML来获取要附加的实体

//不支持将实体从一个DataContext附加到另一个DataContext

//因此若要复制反序列化实体的操作,将在此处重新创建这些实体

Customerc1;

ListdeserializedOrders=newList();

CustomerdeserializedC1;

using(NorthwindDataContexttempdb=newNorthwindDataContext())

c1=tempdb.Customers.Single(c=>c.CustomerID=="ALFKI");

deserializedC1=newCustomer

Address=c1.Address,

City=c1.City,

CompanyName=c1.CompanyName,

ContactName=c1.ContactName,

ContactTitle=c1.ContactTitle,

Country=c1.Country,

CustomerID=c1.CustomerID,

Fax=c1.Fax,

Phone=c1.Phone,

PostalCode=c1.PostalCode,

Region=c1.Region

Customertempcust=

tempdb.Customers.Single(c=>c.CustomerID=="ANTON");

foreach(Orderointempcust.Orders)

deserializedOrders.Add(newOrder

CustomerID=o.CustomerID,

EmployeeID=o.EmployeeID,

Freight=o.Freight,

OrderDate=o.OrderDate,

OrderID=o.OrderID,

RequiredDate=o.RequiredDate,

ShipAddress=o.ShipAddress,

ShipCity=o.ShipCity,

ShipName=o.ShipName,

ShipCountry=o.ShipCountry,

ShippedDate=o.ShippedDate,

ShipPostalCode=o.ShipPostalCode,

ShipRegion=o.ShipRegion,

ShipVia=o.ShipVia

});

using(NorthwindDataContextdb2=newNorthwindDataContext())

//将第一个实体附加到当前数据上下文,以跟踪更改

//对Customer更新,不能写错

db2.Customers.Attach(deserializedC1);

//更改所跟踪的实体

deserializedC1.Address="123FirstAve";

//附加订单列表中的所有实体

db2.Orders.AttachAll(deserializedOrders);

//将订单更新为属于其他客户

foreach(OrderoindeserializedOrders)

o.CustomerID="CHOPS";

//在当前数据上下文中提交更改

db2.SubmitChanges();

语句描述:从另一个层中获取实体,使用Attach和AttachAll将反序列化后的实体附加到数据上下文,然后更新实体。更改被提交到数据库。

使用Attach更新和删除(UpdateandDeletewithAttach)

说明:在不同的DataContext中,实现插入、更新、删除。看下面的一个例子:

//通常,通过从其他层反序列化XML获取要附加的实体

//此示例使用LoadWith在一个查询中预先加载客户和订单,

//并禁用延迟加载

Customercust=null;

DataLoadOptionsshape=newDataLoadOptions();

shape.LoadWith(c=>c.Orders);

//加载第一个客户实体及其订单

tempdb.LoadOptions=shape;

tempdb.DeferredLoadingEnabled=false;

cust=tempdb.Customers.First(x=>x.CustomerID=="ALFKI");

OrderorderA=cust.Orders.First();

OrderorderB=cust.Orders.First(x=>x.OrderID>orderA.OrderID);

db2.Customers.Attach(cust);

db2.Orders.AttachAll(cust.Orders.ToList());

//更新客户的Phone.

cust.Phone="23455436";

//更新第一个订单OrderA的ShipCity.

orderA.ShipCity="Redmond";

//移除第二个订单OrderB.

cust.Orders.Remove(orderB);

//添加一个新的订单Order到客户Customer中.

OrderorderC=newOrder(){ShipCity="NewYork"};

cust.Orders.Add(orderC);

//提交执行

语句描述:从一个上下文提取实体,并使用Attach和AttachAll附加来自其他上下文的实体,然后更新这两个实体,删除一个实体,添加另一个实体。更改被提交到数据库。

下表介绍LINQtoSQL文档中涉及开放式并发的术语:

术语说明

并发两个或更多用户同时尝试更新同一数据库行的情形。

并发冲突两个或更多用户同时尝试向一行的一列或多列提交冲突值的情形。

并发控制用于解决并发冲突的技术。

开放式并发控制先调查其他事务是否已更改了行中的值,再允许提交更改的技术。相比之下,保守式并发控制则是通过锁定记录来避免发生并发冲突。之所以称作开放式控制,是因为它将一个事务干扰另一事务视为不太可能发生。

冲突解决通过重新查询数据库刷新出现冲突的项,然后协调差异的过程。刷新对象时,LINQtoSQL更改跟踪器会保留以下数据:最初从数据库获取并用于更新检查的值通过后续查询获得的新数据库值。LINQtoSQL随后会确定相应对象是否发生冲突(即它的一个或多个成员值是否已发生更改)。如果此对象发生冲突,LINQtoSQL下一步会确定它的哪些成员发生冲突。LINQtoSQL发现的任何成员冲突都会添加到冲突列表中。

在LINQtoSQL对象模型中,当以下两个条件都得到满足时,就会发生“开放式并发冲突”:客户端尝试向数据库提交更改;数据库中的一个或多个更新检查值自客户端上次读取它们以来已得到更新。此冲突的解决过程包括查明对象的哪些成员发生冲突,然后决定您希望如何进行处理。

说明:这个例子中在你读取数据之前,另外一个用户已经修改并提交更新了这个数据,所以不会出现冲突。

//我们打开一个新的连接来模拟另外一个用户

NorthwindDataContextotherUser_db=newNorthwindDataContext();

varotherUser_product=

otherUser_db.Products.First(p=>p.ProductID==1);

otherUser_product.UnitPrice=999.99M;

otherUser_db.SubmitChanges();

//我们当前连接

varproduct=db.Products.First(p=>p.ProductID==1);

product.UnitPrice=777.77M;

try

db.SubmitChanges();//当前连接执行成功

catch(ChangeConflictException)

说明:我们读取数据之后,另外一个用户获取并提交更新了这个数据,这时,我们更新这个数据时,引起了一个并发冲突。系统发生回滚,允许你可以从数据库检索新更新的数据,并决定如何继续进行您自己的更新。

//当前用户

//当前用户修改

//发生异常!

Transactions事务

LINQtoSQL支持三种事务模型,分别是:

显式本地事务:调用SubmitChanges时,如果Transaction属性设置为事务,则在同一事务的上下文中执行SubmitChanges调用。成功执行事务后,要由您来提交或回滚事务。与事务对应的连接必须与用于构造DataContext的连接匹配。如果使用其他连接,则会引发异常。

显式可分发事务:可以在当前Transaction的作用域中调用LINQtoSQLAPI(包括但不限于SubmitChanges)。LINQtoSQL检测到调用是在事务的作用域内,因而不会创建新的事务。在这种情况下,vbtecdlinq还会避免关闭连接。您可以在此类事务的上下文中执行查询和SubmitChanges操作。

隐式事务:当您调用SubmitChanges时,LINQtoSQL会检查此调用是否在Transaction的作用域内或者Transaction属性是否设置为由用户启动的本地事务。如果这两个事务它均未找到,则LINQtoSQL启动本地事务,并使用此事务执行所生成的SQL命令。当所有SQL命令均已成功执行完毕时,LINQtoSQL提交本地事务并返回。

说明:这个例子在执行SubmitChanges()操作时,隐式地使用了事务。因为在更新2种产品的库存数量时,第二个产品库存数量为负数了,违反了服务器上的CHECK约束。这导致了更新产品全部失败了,系统回滚到这个操作的初始状态。

Productprod1=db.Products.First(p=>p.ProductID==4);

Productprod2=db.Products.First(p=>p.ProductID==5);

prod1.UnitsInStock-=3;

prod2.UnitsInStock-=5;//错误:库存数量的单位不能是负数

//要么全部成功要么全部失败

catch(System.Data.SqlClient.SqlExceptione)

//执行异常处理

说明:这个例子使用显式事务。通过在事务中加入对数据的读取以防止出现开放式并发异常,显式事务可以提供更多的保护。如同上一个查询中,更新prod2的UnitsInStock字段将使该字段为负值,而这违反了数据库中的CHECK约束。这导致更新这两个产品的事务失败,此时将回滚所有更改。

using(TransactionScopets=newTransactionScope())

说明:下面第一个例子说明查询ReportsToEmployee为null的雇员。第二个例子使用Nullable.HasValue查询雇员,其结果与第一个例子相同。在第三个例子中,使用Nullable.Value来返回ReportsToEmployee不为null的雇员的ReportsTo的值。

查找不隶属于另一个雇员的所有雇员:

wheree.ReportsToEmployee==null

where!e.ReportsTo.HasValue

3.Nullable.Value

返回前者的EmployeeID编号。请注意.Value为可选:

wheree.ReportsTo.HasValue

ReportsTo=e.ReportsTo.Value

LINQtoSQL支持以下DateTime方法。但是,SQLServer和CLR的DateTime类型在范围和计时周期精度上不同,如下表。

类型最小值最大值计时周期

System.DateTime0001年1月1日9999年12月31日100毫微秒(0.0000001秒)

T-SQLDateTime1753年1月1日9999年12月31日3.33…毫秒(0.0033333秒)

T-SQLSmallDateTime1900年1月1日2079年6月6日1分钟(60秒)

CLRDateTime类型与SQLServer类型相比,前者范围更大、精度更高。因此来自SQLServer的数据用CLR类型表示时,绝不会损失量值或精度。但如果反过来的话,则范围可能会减小,精度可能会降低;SQLServer日期不存在TimeZone概念,而在CLR中支持这个功能。

下面用三个实例说明一下。

whereo.OrderDate.Value.Year==1997

语句描述:这个例子使用DateTime的Year属性查找1997年下的订单。

whereo.OrderDate.Value.Month==12

语句描述:这个例子使用DateTime的Month属性查找十二月下的订单。

whereo.OrderDate.Value.Day==31

语句描述:这个例子使用DateTime的Day属性查找某月31日下的订单。

LINQtoSQL支持以下String方法。但是不同的是默认情况下System.String方法区分大小写。而SQL则不区分大小写。

Location=c.City+","+c.Country

语句描述:这个例子使用+运算符在形成经计算得出的客户Location值过程中将字符串字段和字符串串联在一起。

wherep.ProductName.Length<10

语句描述:这个例子使用Length属性查找名称短于10个字符的所有产品。

wherec.ContactName.Contains("Anders")

语句描述:这个例子使用Contains方法查找所有其联系人姓名中包含“Anders”的客户。

SpacePos=c.ContactName.IndexOf("")

语句描述:这个例子使用IndexOf方法查找每个客户联系人姓名中出现第一个空格的位置。

wherec.ContactName.StartsWith("Maria")

语句描述:这个例子使用StartsWith方法查找联系人姓名以“Maria”开头的客户。

wherec.ContactName.EndsWith("Anders")

语句描述:这个例子使用EndsWith方法查找联系人姓名以“Anders”结尾的客户。

selectp.ProductName.Substring(3);

语句描述:这个例子使用Substring方法返回产品名称中从第四个字母开始的部分。

wheree.HomePhone.Substring(6,3)=="555"

LastName=e.LastName.ToUpper(),

语句描述:这个例子使用ToUpper方法返回姓氏已转换为大写的雇员姓名。

selectc.CategoryName.ToLower();

语句描述:这个例子使用ToLower方法返回已转换为小写的类别名称。

selecte.HomePhone.Substring(0,5).Trim();

wheree.HomePhone.Substring(4,1)==")"

selecte.HomePhone.Insert(5,":");

selecte.HomePhone.Remove(9);

selecte.HomePhone.Remove(0,6);

fromsindb.Suppliers

s.CompanyName,

Country=s.Country

.Replace("UK","UnitedKingdom")

.Replace("USA","UnitedStatesofAmerica")

语句描述:这个例子使用Replace方法返回Country字段中UK被替换为UnitedKingdom以及USA被替换为UnitedStatesofAmerica的供应商信息。

运行库中的对象具有唯一标识。引用同一对象的两个变量实际上是引用此对象的同一实例。你更改一个变量后,可以通过另一个变量看到这些更改。

关系数据库表中的行不具有唯一标识。由于每一行都具有唯一的主键,因此任何两行都不会共用同一键值。

实际上,通常我们是将数据从数据库中提取出来放入另一层中,应用程序在该层对数据进行处理。这就是LINQtoSQL支持的模型。将数据作为行从数据库中提取出来时,你不期望表示相同数据的两行实际上对应于相同的行实例。如果您查询特定客户两次,您将获得两行数据。每一行包含相同的信息。

对于对象。你期望在你反复向DataContext索取相同的信息时,它实际上会为你提供同一对象实例。你将它们设计为层次结构或关系图。你希望像检索实物一样检索它们,而不希望仅仅因为你多次索要同一内容而收到大量的复制实例。

在LINQtoSQL中,DataContext管理对象标识。只要你从数据库中检索新行,该行就会由其主键记录到标识表中,并且会创建一个新的对象。只要您检索该行,就会将原始对象实例传递回应用程序。通过这种方式,DataContext将数据库看到的标识(即主键)的概念转换成相应语言看到的标识(即实例)的概念。应用程序只看到处于第一次检索时的状态的对象。新数据如果不同,则会被丢弃。

LINQtoSQL使用此方法来管理本地对象的完整性,以支持开放式更新。由于在最初创建对象后唯一发生的更改是由应用程序做出的,因此应用程序的意向是很明确的。如果在中间阶段外部某一方做了更改,则在调用SubmitChanges()时会识别出这些更改。

以上来自MSDN,的确,看了有点“正规”,下面我用两个例子说明一下。

在第一个示例中,如果我们执行同一查询两次,则每次都会收到对内存中同一对象的引用。很明显,cust1和cust2是同一个对象引用。

Customercust1=db.Customers.First(c=>c.CustomerID=="BONAP");

Customercust2=db.Customers.First(c=>c.CustomerID=="BONAP");

下面的示例中,如果您执行返回数据库中同一行的不同查询,则您每次都会收到对内存中同一对象的引用。cust1和cust2是同一个对象引用,但是数据库查询了两次。

Customercust2=(

whereo.Customer.CustomerID=="BONAP"

selecto)

.First()

.Customer;

在查询某对象时,实际上你只查询该对象。不会同时自动获取这个对象。这就是延迟加载。

例如,您可能需要查看客户数据和订单数据。你最初不一定需要检索与每个客户有关的所有订单数据。其优点是你可以使用延迟加载将额外信息的检索操作延迟到你确实需要检索它们时再进行。请看下面的示例:检索出来CustomerID,就根据这个ID查询出OrderID。

varcusts=

wherec.City=="SaoPaulo"

//上面的查询句法不会导致语句立即执行,仅仅是一个描述性的语句,

只有需要的时候才会执行它

foreach(varcustincusts)

//同时查看客户数据和订单数据

语句描述:原始查询未请求数据,在所检索到各个对象的链接中导航如何能导致触发对数据库的新查询。

你如果想要同时查询出一些对象的集合的方法。LINQtoSQL提供了DataLoadOptions用于立即加载对象。方法包括:

AssociateWith方法,用于筛选为特定关系检索到的对象。

在下面的示例中,我们通过设置DataLoadOptions,来指示DataContext在加载Customers的同时把对应的Orders一起加载,在执行查询时会检索位于SaoPaulo的所有Customers的所有Orders。这样一来,连续访问Customer对象的Orders属性不会触发新的数据库查询。在执行时生成的SQL语句使用了左连接。

DataLoadOptionsds=newDataLoadOptions();

ds.LoadWith(p=>p.Orders);

db.LoadOptions=ds;

varcusts=(

Console.WriteLine("CustomerID{0}hasanOrderID{1}.",

cust.CustomerID,

ord.OrderID);

运算符转换

使用AsEnumerable可返回类型化为泛型IEnumerable的参数。在此示例中,LINQtoSQL(使用默认泛型Query)会尝试将查询转换为SQL并在服务器上执行。但where子句引用用户定义的客户端方法(isValidProduct),此方法无法转换为SQL。

解决方法是指定where的客户端泛型IEnumerable实现以替换泛型IQueryable。可通过调用AsEnumerable运算符来执行此操作。

frompindb.Products.AsEnumerable()

whereisValidProduct(p)

语句描述:这个例子就是使用AsEnumerable以便使用Where的客户端IEnumerable实现,而不是默认的IQueryable将在服务器上转换为SQL并执行的默认Query实现。这很有必要,因为Where子句引用了用户定义的客户端方法isValidProduct,该方法不能转换为SQL。

使用ToArray可从序列创建数组。

Customer[]qArray=q.ToArray();

语句描述:这个例子使用ToArray将查询直接计算为数组。

使用ToList可从序列创建泛型列表。下面的示例使用ToList直接将查询的计算结果放入泛型List

ListqList=q.ToList();

使用Enumerable.ToDictionary方法可以将序列转化为字典。TSource表示source中的元素的类型;TKey表示keySelector返回的键的类型。其返回一个包含键和值的Dictionary

DictionaryqDictionary=

q.ToDictionary(p=>p.ProductID);

foreach(intkeyinqDictionary.Keys)

Console.WriteLine(key);

语句描述:这个例子使用ToDictionary将查询和键表达式直接键表达式直接计算为Dictionary

ADO.NET与LINQtoSQL

它基于由ADO.NET提供程序模型提供的服务。因此,我们可以将LINQtoSQL代码与现有的ADO.NET应用程序混合在一起,将当前ADO.NET解决方案迁移到LINQtoSQL。

在创建LINQtoSQLDataContext时,可以提供现有ADO.NET连接。对DataContext的所有操作(包括查询)都使用所提供的这个连接。如果此连接已经打开,则在您使用完此连接时,LINQtoSQL会保持它的打开状态不变。我们始终可以访问此连接,另外还可以使用Connection属性自行关闭它。

//新建一个标准的ADO.NET连接:

SqlConnectionnwindConn=newSqlConnection(connString);

nwindConn.Open();

//...其它的ADO.NET数据操作代码...//

//利用现有的ADO.NET连接来创建一个DataContext:

Northwindinterop_db=newNorthwind(nwindConn);

varorders=

fromoininterop_db.Orders

whereo.Freight>500.00M

//返回Freight>500.00M的订单

nwindConn.Close();

语句描述:这个例子使用预先存在的ADO.NET连接创建Northwind对象,本例中的查询返回运费至少为500.00的所有订单。

当我们已经启动了自己的数据库事务并且我们希望DataContext包含在内时,我们可以向DataContext提供此事务。

通过.NETFramework创建事务的首选方法是使用TransactionScope对象。通过使用此方法,我们可以创建跨数据库及其他驻留在内存中的资源管理器执行的分布式事务。事务范围几乎不需要资源就可以启动。它们仅在事务范围内存在多个连接时才将自身提升为分布式事务。

ts.Complete();

注意:不能将此方法用于所有数据库。例如,SqlClient连接在针对SQLServer2000服务器使用时无法提升系统事务。它采取的方法是,只要它发现有使用事务范围的情况,它就会自动向完整的分布式事务登记。

下面用一个例子说明一下事务的使用方法。在这里,也说明了重用ADO.NET命令和DataContext之间的同一连接。

wherep.ProductID==3

//使用LINQtoSQL查询出来

SqlTransactionnwindTxn=nwindConn.BeginTransaction();

SqlCommandcmd=newSqlCommand("UPDATEProductsSET"

+"QuantityPerUnit='singleitem'WHEREProductID=3");

cmd.Connection=nwindConn;

cmd.Transaction=nwindTxn;

cmd.ExecuteNonQuery();

interop_db.Transaction=nwindTxn;

Productprod1=interop_db.Products.First(p=>p.ProductID==4);

Productprod2=interop_db.Products.First(p=>p.ProductID==5);

prod2.UnitsInStock-=5;//这有一个错误,不能为负数

interop_db.SubmitChanges();

nwindTxn.Commit();

catch(Exceptione)

//如果有一个错误,所有的操作回滚

Console.WriteLine(e.Message);

语句描述:这个例子使用预先存在的ADO.NET连接创建Northwind对象,然后与此对象共享一个ADO.NET事务。此事务既用于通过ADO.NET连接执行SQL命令,又用于通过Northwind对象提交更改。当事务因违反CHECK约束而中止时,将回滚所有更改,包括通过SqlCommand做出的更改,以及通过Northwind对象做出的更改。

存储过程

在我们编写程序中,往往需要一些存储过程,在LINQtoSQL中怎么使用呢?也许比原来的更简单些。下面我们以NORTHWND.MDF数据库中自带的几个存储过程来理解一下。

在数据库中,有名为CustomersCountByRegion的存储过程。该存储过程返回顾客所在"WA"区域的数量。

ALTERPROCEDURE[dbo].[NonRowset]

(@param1NVARCHAR(15))

AS

BEGIN

SETNOCOUNTON;

DECLARE@countint

SELECT@count=COUNT(*)FROMCustomers

WHERECustomers.Region=@Param1

RETURN@count

END

我们只要把这个存储过程拖到O/R设计器内,它自动生成了以下代码段:

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

publicintCustomers_Count_By_Region([Parameter

(DbType="NVarChar(15)")]stringparam1)

IExecuteResultresult=this.ExecuteMethodCall(this,

((MethodInfo)(MethodInfo.GetCurrentMethod())),param1);

return((int)(result.ReturnValue));

我们需要时,直接调用就可以了,例如:

intcount=db.CustomersCountByRegion("WA");

Console.WriteLine(count);

语句描述:这个实例使用存储过程返回在“WA”地区的客户数。

从数据库中返回行集合,并包含用于筛选结果的输入参数。当我们执行返回行集合的存储过程时,会用到结果类,它存储从存储过程中返回的结果。

下面的示例表示一个存储过程,该存储过程返回客户行并使用输入参数来仅返回将“London”列为客户城市的那些行的固定几列。

ALTERPROCEDURE[dbo].[CustomersByCity]

--Addtheparametersforthestoredprocedurehere

(@param1NVARCHAR(20))

--SETNOCOUNTONaddedtopreventextraresultsetsfrom

--interferingwithSELECTstatements.

SELECTCustomerID,ContactName,CompanyName,Cityfrom

Customersascwherec.City=@param1

拖到O/R设计器内,它自动生成了以下代码段:

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

publicISingleResultCustomers_By_City(

[Parameter(DbType="NVarChar(20)")]stringparam1)

IExecuteResultresult=this.ExecuteMethodCall(this,(

(MethodInfo)(MethodInfo.GetCurrentMethod())),param1);

return((ISingleResult)

(result.ReturnValue));

我们用下面的代码调用:

ISingleResultresult=

db.Customers_By_City("London");

foreach(Customers_By_CityResultcustinresult)

Console.WriteLine("CustID={0};City={1}",cust.CustomerID,

cust.City);

语句描述:这个实例使用存储过程返回在伦敦的客户的CustomerID和City。

当存储过程可以返回多个结果形状时,返回类型无法强类型化为单个投影形状。尽管LINQtoSQL可以生成所有可能的投影类型,但它无法获知将以何种顺序返回它们。ResultTypeAttribute属性适用于返回多个结果类型的存储过程,用以指定该过程可以返回的类型的集合。

在下面的SQL代码示例中,结果形状取决于输入(param1=1或param1=2)。我们不知道先返回哪个投影。

ALTERPROCEDURE[dbo].[SingleRowset_MultiShape]

(@param1int)

if(@param1=1)

SELECT*fromCustomersascwherec.Region='WA'

elseif(@param1=2)

SELECTCustomerID,ContactName,CompanyNamefrom

Customersascwherec.Region='WA'

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

publicISingleResult

Whole_Or_Partial_Customers_Set([Parameter(DbType="Int")]

System.Nullableparam1)

return((ISingleResult)

但是,VS2008会把多结果集存储过程识别为单结果集的存储过程,默认生成的代码我们要手动修改一下,要求返回多个结果集,像这样:

[ResultType(typeof(WholeCustomersSetResult))]

[ResultType(typeof(PartialCustomersSetResult))]

publicIMultipleResultsWhole_Or_Partial_Customers_Set([Parameter

(DbType="Int")]System.Nullableparam1)

return((IMultipleResults)(result.ReturnValue));

我们分别定义了两个分部类,用于指定返回的类型。WholeCustomersSetResult类如下:

publicpartialclassWholeCustomersSetResult

privatestring_CustomerID;

privatestring_CompanyName;

privatestring_ContactName;

privatestring_ContactTitle;

privatestring_Address;

privatestring_City;

privatestring_Region;

privatestring_PostalCode;

privatestring_Country;

privatestring_Phone;

privatestring_Fax;

publicWholeCustomersSetResult()

[Column(Storage="_CustomerID",DbType="NChar(5)")]

publicstringCustomerID

get{returnthis._CustomerID;}

set

if((this._CustomerID!=value))

this._CustomerID=value;

[Column(Storage="_CompanyName",DbType="NVarChar(40)")]

publicstringCompanyName

get{returnthis._CompanyName;}

if((this._CompanyName!=value))

this._CompanyName=value;

[Column(Storage="_ContactName",DbType="NVarChar(30)")]

publicstringContactName

get{returnthis._ContactName;}

if((this._ContactName!=value))

this._ContactName=value;

[Column(Storage="_ContactTitle",DbType="NVarChar(30)")]

publicstringContactTitle

get{returnthis._ContactTitle;}

if((this._ContactTitle!=value))

this._ContactTitle=value;

[Column(Storage="_Address",DbType="NVarChar(60)")]

publicstringAddress

get{returnthis._Address;}

if((this._Address!=value))

this._Address=value;

[Column(Storage="_City",DbType="NVarChar(15)")]

publicstringCity

get{returnthis._City;}

if((this._City!=value))

this._City=value;

[Column(Storage="_Region",DbType="NVarChar(15)")]

publicstringRegion

get{returnthis._Region;}

if((this._Region!=value))

this._Region=value;

[Column(Storage="_PostalCode",DbType="NVarChar(10)")]

publicstringPostalCode

get{returnthis._PostalCode;}

if((this._PostalCode!=value))

this._PostalCode=value;

[Column(Storage="_Country",DbType="NVarChar(15)")]

publicstringCountry

get{returnthis._Country;}

if((this._Country!=value))

this._Country=value;

[Column(Storage="_Phone",DbType="NVarChar(24)")]

publicstringPhone

get{returnthis._Phone;}

if((this._Phone!=value))

this._Phone=value;

[Column(Storage="_Fax",DbType="NVarChar(24)")]

publicstringFax

get{returnthis._Fax;}

if((this._Fax!=value))

this._Fax=value;

PartialCustomersSetResult类如下:

publicpartialclassPartialCustomersSetResult

publicPartialCustomersSetResult()

这样就可以使用了,下面代码直接调用,分别返回各自的结果集合。

//返回全部Customer结果集

IMultipleResultsresult=db.Whole_Or_Partial_Customers_Set(1);

IEnumerableshape1=

result.GetResult();

foreach(WholeCustomersSetResultcompNameinshape1)

Console.WriteLine(compName.CompanyName);

//返回部分Customer结果集

result=db.Whole_Or_Partial_Customers_Set(2);

IEnumerableshape2=

result.GetResult();

foreach(PartialCustomersSetResultconinshape2)

Console.WriteLine(con.ContactName);

语句描述:这个实例使用存储过程返回“WA”地区中的一组客户。返回的结果集形状取决于传入的参数。如果参数等于1,则返回所有客户属性。如果参数等于2,则返回ContactName属性。

这种存储过程可以生成多个结果形状,但我们已经知道结果的返回顺序。

下面是一个按顺序返回多个结果集的存储过程GetCustomerAndOrders。返回顾客ID为"SEVES"的顾客和他们所有的订单。

ALTERPROCEDURE[dbo].[GetCustomerAndOrders]

(@CustomerIDnchar(5))

SELECT*FROMCustomersAScWHEREc.CustomerID=@CustomerID

SELECT*FROMOrdersASoWHEREo.CustomerID=@CustomerID

拖到设计器代码如下:

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

publicISingleResult

Get_Customer_And_Orders([Parameter(Name="CustomerID",

DbType="NChar(5)")]stringcustomerID)

((MethodInfo)(MethodInfo.GetCurrentMethod())),customerID);

return((ISingleResult)

同样,我们要修改自动生成的代码:

[ResultType(typeof(CustomerResultSet))]

[ResultType(typeof(OrdersResultSet))]

publicIMultipleResultsGet_Customer_And_Orders

([Parameter(Name="CustomerID",DbType="NChar(5)")]

stringcustomerID)

同样,自己手写类,让其存储过程返回各自的结果集。

CustomerResultSet类

publicpartialclassCustomerResultSet

publicCustomerResultSet()

OrdersResultSet类

publicpartialclassOrdersResultSet

privateSystem.Nullable_OrderID;

privateSystem.Nullable_EmployeeID;

privateSystem.Nullable_OrderDate;

privateSystem.Nullable_RequiredDate;

privateSystem.Nullable_ShippedDate;

privateSystem.Nullable_ShipVia;

privateSystem.Nullable_Freight;

privatestring_ShipName;

privatestring_ShipAddress;

privatestring_ShipCity;

privatestring_ShipRegion;

privatestring_ShipPostalCode;

privatestring_ShipCountry;

publicOrdersResultSet()

[Column(Storage="_OrderID",DbType="Int")]

publicSystem.NullableOrderID

get{returnthis._OrderID;}

if((this._OrderID!=value))

this._OrderID=value;

[Column(Storage="_EmployeeID",DbType="Int")]

publicSystem.NullableEmployeeID

get{returnthis._EmployeeID;}

if((this._EmployeeID!=value))

this._EmployeeID=value;

[Column(Storage="_OrderDate",DbType="DateTime")]

publicSystem.NullableOrderDate

get{returnthis._OrderDate;}

if((this._OrderDate!=value))

this._OrderDate=value;

[Column(Storage="_RequiredDate",DbType="DateTime")]

publicSystem.NullableRequiredDate

get{returnthis._RequiredDate;}

if((this._RequiredDate!=value))

this._RequiredDate=value;

[Column(Storage="_ShippedDate",DbType="DateTime")]

publicSystem.NullableShippedDate

get{returnthis._ShippedDate;}

if((this._ShippedDate!=value))

this._ShippedDate=value;

[Column(Storage="_ShipVia",DbType="Int")]

publicSystem.NullableShipVia

get{returnthis._ShipVia;}

if((this._ShipVia!=value))

this._ShipVia=value;

[Column(Storage="_Freight",DbType="Money")]

publicSystem.NullableFreight

get{returnthis._Freight;}

if((this._Freight!=value))

this._Freight=value;

[Column(Storage="_ShipName",DbType="NVarChar(40)")]

publicstringShipName

get{returnthis._ShipName;}

if((this._ShipName!=value))

this._ShipName=value;

[Column(Storage="_ShipAddress",DbType="NVarChar(60)")]

publicstringShipAddress

get{returnthis._ShipAddress;}

if((this._ShipAddress!=value))

this._ShipAddress=value;

[Column(Storage="_ShipCity",DbType="NVarChar(15)")]

publicstringShipCity

get{returnthis._ShipCity;}

if((this._ShipCity!=value))

this._ShipCity=value;

[Column(Storage="_ShipRegion",DbType="NVarChar(15)")]

publicstringShipRegion

get{returnthis._ShipRegion;}

if((this._ShipRegion!=value))

this._ShipRegion=value;

[Column(Storage="_ShipPostalCode",DbType="NVarChar(10)")]

publicstringShipPostalCode

get{returnthis._ShipPostalCode;}

if((this._ShipPostalCode!=value))

this._ShipPostalCode=value;

[Column(Storage="_ShipCountry",DbType="NVarChar(15)")]

publicstringShipCountry

get{returnthis._ShipCountry;}

if((this._ShipCountry!=value))

this._ShipCountry=value;

这时,只要调用就可以了。

IMultipleResultsresult=db.Get_Customer_And_Orders("SEVES");

//返回Customer结果集

IEnumerablecustomer=

result.GetResult();

//返回Orders结果集

IEnumerableorders=

result.GetResult();

//在这里,我们读取CustomerResultSet中的数据

foreach(CustomerResultSetcustincustomer)

Console.WriteLine(cust.CustomerID);

语句描述:这个实例使用存储过程返回客户“SEVES”及其所有订单。

下面的示例带有单个输入参数(客户ID)并返回一个输出参数(该客户的总销售额)。

ALTERPROCEDURE[dbo].[CustOrderTotal]

@CustomerIDnchar(5),

@TotalSalesmoneyOUTPUT

SELECT@TotalSales=SUM(OD.UNITPRICE*(1-OD.DISCOUNT)*OD.QUANTITY)

FROMORDERSO,"ORDERDETAILS"OD

whereO.CUSTOMERID=@CustomerIDANDO.ORDERID=OD.ORDERID

把这个存储过程拖到设计器中,图片如下:

其生成代码如下:

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

publicintCustOrderTotal(

[Parameter(Name="CustomerID",DbType="NChar(5)")]stringcustomerID,

[Parameter(Name="TotalSales",DbType="Money")]

refSystem.NullabletotalSales)

((MethodInfo)(MethodInfo.GetCurrentMethod())),

customerID,totalSales);

totalSales=((System.Nullable)

(result.GetParameterValue(1)));

我们使用下面的语句调用此存储过程:注意:输出参数是按引用传递的,以支持参数为“in/out”的方案。在这种情况下,参数仅为“out”。

decimaltotalSales=0;

stringcustomerID="ALFKI";

db.CustOrderTotal(customerID,reftotalSales);

Console.WriteLine("TotalSalesforCustomer'{0}'={1:C}",

语句描述:这个实例使用返回Out参数的存储过程。

好了,就说到这里了,其增删改操作同理。相信大家通过这5个实例理解了存储过程。

用户定义函数

我们可以在LINQtoSQL中使用用户定义函数。只要把用户定义函数拖到O/R设计器中,LINQtoSQL自动使用FunctionAttribute属性和ParameterAttribute属性(如果需要)将其函数指定为方法。这时,我们只需简单调用即可。

在这里注意:使用用户定义函数的时候必须满足以下形式之一,否则会出现InvalidOperationException异常情况。

具有正确映射属性的方法调用的函数。这里使用FunctionAttribute属性和ParameterAttribute属性。

特定于LINQtoSQL的静态SQL方法。

.NETFramework方法支持的函数。

下面介绍几个例子:

所谓标量函数是指返回在RETURNS子句中定义的类型的单个数据值。可以使用所有标量数据类型,包括bigint和sql_variant。不支持timestamp数据类型、用户定义数据类型和非标量类型(如table或cursor)。在BEGIN...END块中定义的函数主体包含返回该值的Transact-SQL语句系列。返回类型可以是除text、ntext、image、cursor和timestamp之外的任何数据类型。我们在系统自带的NORTHWND.MDF数据库中,有3个自定义函数,这里使用TotalProductUnitPriceByCategory,其代码如下:

ALTERFUNCTION[dbo].[TotalProductUnitPriceByCategory]

(@categoryIDint)

RETURNSMoney

--Declarethereturnvariablehere

DECLARE@ResultVarMoney

--AddtheT-SQLstatementstocomputethereturnvaluehere

SELECT@ResultVar=(SelectSUM(UnitPrice)

fromProducts

whereCategoryID=@categoryID)

--Returntheresultofthefunction

RETURN@ResultVar

我们将其拖到设计器中,LINQtoSQL通过使用FunctionAttribute属性将类中定义的客户端方法映射到用户定义的函数。请注意,这个方法体会构造一个捕获方法调用意向的表达式,并将该表达式传递给DataContext进行转换和执行。

[Function(Name="dbo.TotalProductUnitPriceByCategory",

IsComposable=true)]

publicSystem.NullableTotalProductUnitPriceByCategory(

[Parameter(DbType="Int")]System.NullablecategoryID)

return((System.Nullable)(this.ExecuteMethodCall(this,

((MethodInfo)(MethodInfo.GetCurrentMethod())),categoryID)

.ReturnValue));

我们使用时,可以用以下代码来调用:

varq=fromcindb.Categories

c.CategoryID,

TotalUnitPrice=

db.TotalProductUnitPriceByCategory(c.CategoryID)

这时,LINQtoSQL自动生成SQL语句如下:

SELECT[t0].[CategoryID],CONVERT(Decimal(29,4),

[dbo].[TotalProductUnitPriceByCategory]([t0].[CategoryID]))

AS[TotalUnitPrice]FROM[dbo].[Categories]AS[t0]

这个例子使用方法同上一个例子原理基本相同了,MinUnitPriceByCategory自定义函数如下:

ALTERFUNCTION[dbo].[MinUnitPriceByCategory]

(@categoryIDINT

SELECT@ResultVar=MIN(p.UnitPrice)FROMProductsasp

WHEREp.CategoryID=@categoryID

拖到设计器中,生成代码如下:

[Function(Name="dbo.MinUnitPriceByCategory",IsComposable=true)]

publicSystem.NullableMinUnitPriceByCategory(

return((System.Nullable)(this.ExecuteMethodCall(

this,((MethodInfo)(MethodInfo.GetCurrentMethod())),

categoryID).ReturnValue));

这时可以使用了:注意这里在LINQtoSQL查询中,对生成的用户定义函数方法MinUnitPriceByCategory的内联调用。此函数不会立即执行,这是因为查询会延迟执行。延迟执行的查询中包含的函数直到此查询执行时才会执行。为此查询生成的SQL会转换成对数据库中用户定义函数的调用(请参见此查询后面的生成的SQL语句),当在查询外部调用这个函数时,LINQtoSQL会用方法调用表达式创建一个简单查询并执行。

wherep.UnitPrice==

db.MinUnitPriceByCategory(p.CategoryID)

它自动生成的SQL语句如下:

WHERE[t0].[UnitPrice]=

[dbo].[MinUnitPriceByCategory]([t0].[CategoryID])

表值函数返回单个行集(与存储过程不同,存储过程可返回多个结果形状)。由于表值函数的返回类型为Table,因此在SQL中可以使用表的任何地方均可以使用表值函数。此外,您还可以完全像处理表那样来处理表值函数。

ALTERFUNCTION[dbo].[ProductsUnderThisUnitPrice]

(@priceMoney

RETURNSTABLE

RETURN

SELECT*

FROMProductsasP

Wherep.UnitPrice<@price

拖到设计器中,LINQtoSQL按如下方式映射此函数:

[Function(Name="dbo.ProductsUnderThisUnitPrice",

publicIQueryable

ProductsUnderThisUnitPrice([Parameter(DbType="Money")]

System.Nullableprice)

returnthis.CreateMethodCallQuery

(this,

((MethodInfo)(MethodInfo.GetCurrentMethod())),price);

这时我们小小的修改一下Discontinued属性为可空的bool类型。

privateSystem.Nullable_Discontinued;

publicSystem.NullableDiscontinued

我们可以这样调用使用了:

varq=frompindb.ProductsUnderThisUnitPrice(10.25M)

where!(p.Discontinuedfalse)

其生成SQL语句如下:

[t0].[Discontinued]

FROM[dbo].[ProductsUnderThisUnitPrice](@p0)AS[t0]

WHERENOT((COALESCE([t0].[Discontinued],@p1))=1)

--@p0:InputMoney(Size=0;Prec=19;Scale=4)[10.25]

我们利用上面的ProductsUnderThisUnitPrice用户定义函数,在LINQtoSQL中,调用如下:

joinpindb.ProductsUnderThisUnitPrice(8.50M)on

c.CategoryIDequalsp.CategoryIDintoprods

frompinprods

c.CategoryName,

p.UnitPrice

其生成的SQL代码说明对此函数返回的表执行联接。

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

[t1].[ProductName],[t1].[UnitPrice]

FROM[dbo].[Categories]AS[t0]

CROSSJOIN[dbo].[ProductsUnderThisUnitPrice](@p0)AS[t1]

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

--@p0:InputMoney(Size=0;Prec=19;Scale=4)[8.50]

DataContext

DataContext作为LINQtoSQL框架的主入口点,为我们提供了一些方法和属性,本文用几个例子说明DataContext几个典型的应用。

CreateDatabase方法用于在服务器上创建数据库。

DeleteDatabase方法用于删除由DataContext连接字符串标识的数据库。

数据库的名称有以下方法来定义:

如果数据库在连接字符串中标识,则使用该连接字符串的名称。

如果存在DatabaseAttribute属性(Attribute),则将其Name属性(Property)用作数据库的名称。

如果连接字符串中没有数据库标记,并且使用强类型的DataContext,则会检查与DataContext继承类名称相同的数据库。如果使用弱类型的DataContext,则会引发异常。

如果已通过使用文件名创建了DataContext,则会创建与该文件名相对应的数据库。

我们首先用实体类描述关系数据库表和列的结构的属性。再调用DataContext的CreateDatabase方法,LINQtoSQL会用我们的定义的实体类结构来构造一个新的数据库实例。还可以通过使用.mdf文件或只使用目录名(取决于连接字符串),将CreateDatabase与SQLServer一起使用。LINQtoSQL使用连接字符串来定义要创建的数据库和作为数据库创建位置的服务器。

说了这么多,用一段实例说明一下吧!

首先,我们新建一个NewCreateDB类用于创建一个名为NewCreateDB.mdf的新数据库,该数据库有一个Person表,有三个字段,分别为PersonID、PersonName、Age。

publicclassNewCreateDB:DataContext

publicTablePersons;

publicNewCreateDB(stringconnection)

:

base(connection)

publicNewCreateDB(System.Data.IDbConnectionconnection)

[Table(Name="Person")]

publicpartialclassPerson:INotifyPropertyChanged

privateint_PersonID;

privatestring_PersonName;

privateSystem.Nullable_Age;

publicPerson(){}

[Column(Storage="_PersonID",DbType="INT",

IsPrimaryKey=true)]

publicintPersonID

get{returnthis._PersonID;}

if((this._PersonID!=value))

this.OnPropertyChanged("PersonID");

this._PersonID=value;

[Column(Storage="_PersonName",DbType="NVarChar(30)")]

publicstringPersonName

get{returnthis._PersonName;}

if((this._PersonName!=value))

this.OnPropertyChanged("PersonName");

this._PersonName=value;

[Column(Storage="_Age",DbType="INT")]

publicSystem.NullableAge

get{returnthis._Age;}

if((this._Age!=value))

this.OnPropertyChanged("Age");

this._Age=value;

publiceventPropertyChangedEventHandlerPropertyChanged;

protectedvirtualvoidOnPropertyChanged(stringPropertyName)

if((this.PropertyChanged!=null))

this.PropertyChanged(this,

newPropertyChangedEventArgs(PropertyName));

接下来的一段代码先创建一个数据库,在调用CreateDatabase后,新的数据库就会存在并且会接受一般的查询和命令。接着插入一条记录并且查询。最后删除这个数据库。

//1.新建一个临时文件夹来存放新建的数据库

stringuserTempFolder=Environment.GetEnvironmentVariable

("SystemDrive")+@"YJingLee";

Directory.CreateDirectory(userTempFolder);

//2.新建数据库NewCreateDB

stringuserMDF=System.IO.Path.Combine(userTempFolder,

@"NewCreateDB.mdf");

stringconnStr=String.Format(@"DataSource=.SQLEXPRESS;

AttachDbFilename={0};IntegratedSecurity=True;

ConnectTimeout=30;UserInstance=True;

IntegratedSecurity=SSPI;",userMDF);

NewCreateDBnewDB=newNewCreateDB(connStr);

newDB.CreateDatabase();

//3.插入数据并查询

varnewRow=newPerson

PersonID=1,

PersonName="YJingLee",

Age=22

newDB.Persons.InsertOnSubmit(newRow);

newDB.SubmitChanges();

varq=fromxinnewDB.Persons

selectx;

//4.删除数据库

newDB.DeleteDatabase();

//5.删除临时目录

Directory.Delete(userTempFolder);

DatabaseExists方法用于尝试通过使用DataContext中的连接打开数据库,如果成功返回true。

下面代码说明是否存在Northwind数据库和NewCreateDB数据库。

//检测Northwind数据库是否存在

if(db.DatabaseExists())

Console.WriteLine("Northwind数据库存在");

else

Console.WriteLine("Northwind数据库不存在");

//检测NewCreateDB数据库是否存在

stringuserTempFolder=Environment.GetEnvironmentVariable("Temp");

NewCreateDBnewDB=newNewCreateDB(userMDF);

if(newDB.DatabaseExists())

Console.WriteLine("NewCreateDB数据库存在");

Console.WriteLine("NewCreateDB数据库不存在");

SubmitChanges方法计算要插入、更新或删除的已修改对象的集,并执行相应命令以实现对数据库的更改。

无论对象做了多少项更改,都只是在更改内存中的副本。并未对数据库中的实际数据做任何更改。直到对DataContext显式调用SubmitChanges,所做的更改才会传输到服务器。调用时,DataContext会设法将我们所做的更改转换为等效的SQL命令。我们也可以使用自己的自定义逻辑来重写这些操作,但提交顺序是由DataContext的一项称作“更改处理器”的服务来协调的。事件的顺序如下:

当调用SubmitChanges时,LINQtoSQL会检查已知对象的集合以确定新实例是否已附加到它们。如果已附加,这些新实例将添加到被跟踪对象的集合。

所有具有挂起更改的对象将按照它们之间的依赖关系排序成一个对象序列。如果一个对象的更改依赖于其他对象,则这个对象将排在其依赖项之后。

在即将传输任何实际更改时,LINQtoSQL会启动一个事务来封装由各条命令组成的系列。

对对象的更改会逐个转换为SQL命令,然后发送到服务器。

如果数据库检测到任何错误,都会造成提交进程停止并引发异常。将回滚对数据库的所有更改,就像未进行过提交一样。DataContext仍具有所有更改的完整记录。

下面代码说明的是在数据库中查询CustomerID为ALFKI的顾客,然后修改其公司名称,第一次更新并调用SubmitChanges()方法,第二次更新了数据但并未调用SubmitChanges()方法。

//查询

Customercust=db.Customers.First(c=>c.CustomerID=="ALFKI");

//更新数据并调用SubmitChanges()方法

cust.CompanyName="YJingLee'sBlog";

//更新数据没有调用SubmitChanges()方法

使用动态查询,这个例子用CreateQuery()方法创建一个IQueryable类型表达式输出查询的语句。这里给个例子说明一下。有关动态查询具体内容,下一篇介绍。

varc1=Expression.Parameter(typeof(Customer),"c");

PropertyInfoCity=typeof(Customer).GetProperty("City");

varpred=Expression.Lambda>(

Expression.Equal(

Expression.Property(c1,City),

Expression.Constant("Seattle")

),c1

IQueryablecusts=db.Customers;

Expressionexpr=Expression.Call(typeof(Queryable),"Where",

newType[]{custs.ElementType},custs.Expression,pred);

IQueryableq=db.Customers.AsQueryable().

Provider.CreateQuery(expr);

Log属性用于将SQL查询或命令打印到TextReader。此方法对了解LINQtoSQL功能和调试特定的问题可能很有用。

下面的示例使用Log属性在SQL代码执行前在控制台窗口中显示此代码。我们可以将此属性与查询、插入、更新和删除命令一起使用。

//关闭日志功能

//db.Log=null;

//使用日志功能:日志输出到控制台窗口

db.Log=Console.Out;

//日志输出到文件

StreamWritersw=newStreamWriter(Server.MapPath("log.txt"),true);

db.Log=sw;

sw.Close();

动态查询

有这样一个场景:应用程序可能会提供一个用户界面,用户可以使用该用户界面指定一个或多个谓词来筛选数据。这种情况在编译时不知道查询的细节,动态查询将十分有用。

在LINQ中,Lambda表达式是许多标准查询运算符的基础,编译器创建lambda表达式以捕获基础查询方法(例如Where、Select、OrderBy、TakeWhile以及其他方法)中定义的计算。表达式目录树用于针对数据源的结构化查询,这些数据源实现IQueryable。例如,LINQtoSQL提供程序实现IQueryable接口,用于查询关系数据存储。C#和VisualBasic编译器会针对此类数据源的查询编译为代码,该代码在运行时将生成一个表达式目录树。然后,查询提供程序可以遍历表达式目录树数据结构,并将其转换为适合于数据源的查询语言。

表达式目录树在LINQ中用于表示分配给类型为Expression的变量的Lambda表达式。还可用于创建动态LINQ查询。

System.Linq.Expressions命名空间提供用于手动生成表达式目录树的API。Expression类包含创建特定类型的表达式目录树节点的静态工厂方法,例如,ParameterExpression(表示一个已命名的参数表达式)或MethodCallExpression(表示一个方法调用)。编译器生成的表达式目录树的根始终在类型Expression的节点中,其中TDelegate是包含至多五个输入参数的任何TDelegate委托;也就是说,其根节点是表示一个lambda表达式。

下面几个例子描述如何使用表达式目录树来创建动态LINQ查询。

下面例子说明如何使用表达式树依据IQueryable数据源构造一个动态查询,查询出每个顾客的ContactName,并用GetCommand方法获取其生成SQL语句。

//依据IQueryable数据源构造一个查询

IQueryablecusts=db.Customers;

//组建一个表达式树来创建一个参数

ParameterExpressionparam=

Expression.Parameter(typeof(Customer),"c");

//组建表达式树:c.ContactName

Expressionselector=Expression.Property(param,

typeof(Customer).GetProperty("ContactName"));

Expressionpred=Expression.Lambda(selector,param);

//组建表达式树:Select(c=>c.ContactName)

Expressionexpr=Expression.Call(typeof(Queryable),"Select",

newType[]{typeof(Customer),typeof(string)},

Expression.Constant(custs),pred);

//使用表达式树来生成动态查询

IQueryablequery=db.Customers.AsQueryable()

.Provider.CreateQuery(expr);

//使用GetCommand方法获取SQL语句

System.Data.Common.DbCommandcmd=db.GetCommand(query);

Console.WriteLine(cmd.CommandText);

生成的SQL语句为:

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

下面一个例子是“搭建”Where用法来动态查询城市在伦敦的顾客。

//创建一个参数c

//c.City=="London"

Expressionleft=Expression.Property(param,

typeof(Customer).GetProperty("City"));

Expressionright=Expression.Constant("London");

Expressionfilter=Expression.Equal(left,right);

Expressionpred=Expression.Lambda(filter,param);

//Where(c=>c.City=="London")

newType[]{typeof(Customer)},

//生成动态查询

IQueryablequery=db.Customers.AsQueryable()

.Provider.CreateQuery(expr);

FROM[dbo].[Customers]AS[t0]WHERE[t0].[City]=@p0

--@p0:InputNVarChar(Size=6;Prec=0;Scale=0)[London]

3.OrderBy本例既实现排序功能又实现了过滤功能。

MethodCallExpressionwhereCallExpression=Expression.Call(

typeof(Queryable),"Where",

//OrderBy(ContactName=>ContactName)

MethodCallExpressionorderByCallExpression=Expression.Call(

typeof(Queryable),"OrderBy",

whereCallExpression,

Expression.Lambda(Expression.Property

(param,"ContactName"),param));

.Provider.CreateQuery(orderByCallExpression);

下面一张截图显示了怎么动态生成动态查询的过程

ORDERBY[t0].[ContactName]

4.Union

下面的例子使用表达式树动态查询顾客和雇员同在的城市。

//e.City

ParameterExpressionparam1=

Expression.Parameter(typeof(Customer),"e");

Expressionleft1=Expression.Property(param1,

Expressionpred1=Expression.Lambda(left1,param1);

//c.City

IQueryableemployees=db.Employees;

ParameterExpressionparam2=

Expression.Parameter(typeof(Employee),"c");

Expressionleft2=Expression.Property(param2,

typeof(Employee).GetProperty("City"));

Expressionpred2=Expression.Lambda(left2,param2);

//Select(e=>e.City)

Expressionexpr1=Expression.Call(typeof(Queryable),"Select",

Expression.Constant(custs),pred1);

//Select(c=>c.City)

Expressionexpr2=Expression.Call(typeof(Queryable),"Select",

newType[]{typeof(Employee),typeof(string)},

Expression.Constant(employees),pred2);

IQueryableq1=db.Customers.AsQueryable()

.Provider.CreateQuery(expr1);

IQueryableq2=db.Employees.AsQueryable()

.Provider.CreateQuery(expr2);

//并集

varq3=q1.Union(q2);

SELECT[t2].[City]

FROM(

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

UNION

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

)AS[t2]

视图

查询:匿名类型形式

我们使用下面代码来查询出ShipCity在London的发票。

fromiindb.Invoices

wherei.ShipCity=="London"

i.OrderID,

i.ProductName,

i.Quantity,

i.CustomerName

这里,生成的SQL语句同使用数据表类似:

SELECT[t0].[OrderID],[t0].[ProductName],[t0].[Quantity],

[t0].[CustomerName]FROM[dbo].[Invoices]AS[t0]

WHERE[t0].[ShipCity]=@p0

查询:标识映射形式

下例查询出每季的订单。

fromqoindb.Quarterly_Orders

selectqo;

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

[t0].[Country]FROM[dbo].[QuarterlyOrders]AS[t0]

继承支持

LINQtoSQL支持单表映射,其整个继承层次结构存储在单个数据库表中。该表包含整个层次结构的所有可能数据列的平展联合。(联合是将两个表组合成一个表的结果,组合后的表包含任一原始表中存在的行。)每行中不适用于该行所表示的实例类型的列为null。

单表映射策略是最简单的继承表示形式,为许多不同类别的查询提供了良好的性能特征,如果我们要在LINQtoSQL中实现这种映射,必须在继承层次结构的根类中指定属性(Attribute)和属性(Attribute)的属性(Property)。我们还可以使用O/R设计器来映射继承层次结构,它自动生成了代码。

下面为了演示下面的几个例子,我们在O/R设计器内设计如下图所示的类及其继承关系。

我们学习的时候还是看看其生成的代码吧!

具体设置映射继承层次结构有如下几步:

根类添加TableAttribute属性。

为层次结构中的每个类添加InheritanceMappingAttribute属性,同样是添加到根类中。每个InheritanceMappingAttribute属性,定义一个Code属性和一个Type属性。Code属性的值显示在数据库表的IsDiscriminator列中,用来指示该行数据所属的类或子类。Type属性值指定键值所表示的类或子类。

仅在其中一个InheritanceMappingAttribute属性上,添加一个IsDefault属性用来在数据库表中的鉴别器值在继承映射中不与任何Code值匹配时指定回退映射。

为ColumnAttribute属性添加一个IsDiscriminator属性来表示这是保存Code值的列。

下面是这张图生成的代码的框架(由于生成的代码太多,我删除了很多“枝叶”,仅仅保留了主要的框架用于指出其实质的东西):

[Table(Name="dbo.Contacts")]

[InheritanceMapping(Code="Unknown",Type=typeof(Contact),

IsDefault=true)]

[InheritanceMapping(Code="Employee",Type=typeof(EmployeeContact))]

[InheritanceMapping(Code="Supplier",Type=typeof(SupplierContact))]

[InheritanceMapping(Code="Customer",Type=typeof(CustomerContact))]

[InheritanceMapping(Code="Shipper",Type=typeof(ShipperContact))]

publicpartialclassContact:

INotifyPropertyChanging,INotifyPropertyChanged

[Column(Storage="_ContactID",IsPrimaryKey=true,

IsDbGenerated=true)]

publicintContactID{}

[Column(Storage="_ContactType",IsDiscriminator=true)]

publicstringContactType{}

publicabstractpartialclassFullContact:Contact{}

publicpartialclassEmployeeContact:FullContact{}

publicpartialclassSupplierContact:FullContact{}

publicpartialclassCustomerContact:FullContact{}

publicpartialclassShipperContact:Contact{}

日常我们经常写的形式,对单表查询。

varcons=fromcindb.Contacts

foreach(varconincons){

Console.WriteLine("Companyname:{0}",con.CompanyName);

Console.WriteLine("Phone:{0}",con.Phone);

Console.WriteLine("Thisisa{0}",con.GetType());

这里我仅仅让其返回顾客的联系方式。

varcons=fromcindb.Contacts.OfType()

初步学习,我们还是看看生成的SQL语句,这样容易理解。在SQL语句中查询了ContactType为Customer的联系方式。

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

[t0].[Address],[t0].[City],[t0].[Region],[t0].[PostalCode],

[t0].[Country],[t0].[Fax],[t0].[ContactID],[t0].[CompanyName],

[t0].[Phone]FROM[dbo].[Contacts]AS[t0]

WHERE([t0].[ContactType]=@p0)AND([t0].[ContactType]ISNOTNULL)

--@p0:InputNVarChar(Size=8;Prec=0;Scale=0)[Customer]

这个例子查找一下发货人的联系方式。

wherecisShipperContact

生成的SQL语句如下:查询了ContactType为Shipper的联系方式。大致一看好像很上面的一样,其实这里查询出来的列多了很多。实际上是Contacts表的全部字段。

SELECT[t0].[ContactType],[t0].[ContactID],[t0].[CompanyName],

[t0].[Phone],[t0].[HomePage],[t0].[ContactName],

[t0].[ContactTitle],[t0].[Address],[t0].[City],

[t0].[Region],[t0].[PostalCode],[t0].[Country],

[t0].[Fax],[t0].[PhotoPath],[t0].[Photo],[t0].[Extension]

FROM[dbo].[Contacts]AS[t0]WHERE([t0].[ContactType]=@p0)

AND([t0].[ContactType]ISNOTNULL)

--@p0:InputNVarChar(Size=7;Prec=0;Scale=0)[Shipper]

这个例子就通吃了,全部查找了一番。

selectcasFullContact;

生成SQL语句如下:查询整个Contacts表。

SELECT[t0].[ContactType],[t0].[HomePage],[t0].[ContactName],

[t0].[Fax],[t0].[ContactID],[t0].[CompanyName],

[t0].[Phone],[t0].[PhotoPath],[t0].[Photo],[t0].[Extension]

FROM[dbo].[Contacts]AS[t0]

使用Case形式查找出在伦敦的顾客的联系方式。

wherec.ContactType=="Customer"&&

((CustomerContact)c).City=="London"

生成SQL语句如下,自己可以看懂了。

[t0].[PostalCode],[t0].[Country],[t0].[Fax],[t0].[PhotoPath],

[t0].[Photo],[t0].[Extension]FROM[dbo].[Contacts]AS[t0]

WHERE([t0].[ContactType]=@p0)AND([t0].[City]=@p1)

--@p1:InputNVarChar(Size=6;Prec=0;Scale=0)[London]

当插入一条记录时,使用默认的映射关系了,但是在查询时,使用继承的关系了。具体看看生成的SQL语句就直截了当了。

//插入一条数据默认使用正常的映射关系

Contactcontact=newContact()

ContactType=null,

CompanyName="UnknownCompany",

Phone="333-444-5555"

db.Contacts.InsertOnSubmit(contact);

//查询一条数据默认使用继承映射关系

varcon=

(fromcindb.Contacts

wherec.CompanyName=="UnknownCompany"&&

c.Phone=="333-444-5555"

selectc).First();

生成SQL语句如下:

INSERTINTO[dbo].[Contacts]([ContactType],[CompanyName],

[Phone])VALUES(@p0,@p1,@p2)

SELECTTOP(1)[t0].[ContactType],[t0].[ContactID],

[t0].[CompanyName],[t0].[Phone],[t0].[HomePage],

[t0].[ContactName],[t0].[ContactTitle],[t0].[Address],

[t0].[City],[t0].[Region],[t0].[PostalCode],[t0].[Country],

WHERE([t0].[CompanyName]=@p0)AND([t0].[Phone]=@p1)

--@p0:InputNVarChar(Size=15;Prec=0;Scale=0)

[UnknownCompany]

--@p1:InputNVarChar(Size=12;Prec=0;Scale=0)

[333-444-5555]

这个例子说明如何插入发货人的联系方式的一条记录。

//

varShipperContacts=

fromscindb.Contacts.OfType()

wheresc.CompanyName=="NorthwindShipper"

selectsc;

ShipperContactnsc=newShipperContact()

CompanyName="NorthwindShipper",

Phone="(123)-456-7890"

db.Contacts.InsertOnSubmit(nsc);

ShipperContacts=

db.Contacts.DeleteOnSubmit(nsc);

SELECTCOUNT(*)AS[value]FROM[dbo].[Contacts]AS[t0]

WHERE([t0].[CompanyName]=@p0)AND([t0].[ContactType]=@p1)

--@p0:InputNVarChar[NorthwindShipper]

--@p1:InputNVarChar[Shipper]

INSERTINTO[dbo].[Contacts]([ContactType],[CompanyName],[Phone])

VALUES(@p0,@p1,@p2)

--@p0:InputNVarChar[Shipper]

--@p1:InputNVarChar[NorthwindShipper]

--@p2:InputNVarChar[(123)-456-7890]

DELETEFROM[dbo].[Contacts]WHERE([ContactID]=@p0)AND

([ContactType]=@p1)AND([CompanyName]=@p2)AND([Phone]=@p3)

THE END
1.有哪些常见的一单一结赚佣金方式?(一单一结赚佣金方法介绍)(一单一结赚佣金方法介绍) 在如今的商业世界中,赚取佣金已经成为一种常见且有效的收入途径。一单一结赚佣金方法,正因为其简单而高效的特点,受到越来越多人的青睐。本文将深入探讨一单一结赚佣金的多种常见方式,帮助您更好地了解并选择适合自己的方法。https://www.jianshu.com/p/a55d1bd5909b
2.学生一单一结的三大陷阱?之前有一个学生加到我微信,说自己网上加了一个这种学生一单一结的兼职,结果给了6K多购买了教程,一分钱没有赚到。我一个朋友的爱人,2019年网上看到别人发打字赚钱,后来花了1W元加入学习,结果什么也没有学到,还被拉黑了。 捷径背后不是套路,就是陷阱。为什么很多朋友会被割韭菜?因为,能力匹配不了欲望,会的东西https://zhuanlan.zhihu.com/p/656883388
3.做任务一单一结给佣金兼职,分享几款适合宝妈在家的手机兼职做任务一单一结给佣金兼职有那些?如今在手机做任务赚取佣金已成为最受欢迎的赚钱方式。主要是因为任务操作简单,支持一单一结,1元起提现。小卓来分享几款适合宝妈在家的手机兼职软件。在手机上做任务完成后即可得到收入,非常适合想要利用闲暇时间做副业赚收入的小伙伴。 http://www.anzhuoshouzhuan.com/n/3801.html
4.网上兼职赚钱一单一结骗局揭秘随着互联网的高速发展,网络兼职已经成为了很多人增加收入的一种途径。在众多的网络兼职项目中,有一种以“一单一结”为特点的兼职,吸引了大量的求职者。然而,这种看似诱人的兼职背后,却隐藏着许多不为人知的骗局。本文将为大家揭秘这些网上兼职赚钱一单一结的骗局,帮助大家避免上当受骗。 https://vwjq.com/6095.html
5.一单一结的赚钱平台有哪些?10个正规赚佣金的平台推荐现在这种赚钱平台还挺多的,比如手机做任务、试玩应用等,这些都是一单一结的结算方式,比较靠谱,这里给大家推荐10个正规赚佣金的平台,都是在手机上就可以做的,有空了拿起手机就能做做任务,赚点零花钱,非常适合想要做兼职的人,有需要的朋友可以看看。 1、一单一结的赚钱平台:赏帮赚http://www.17zhuan.net/n/141.html
6.微信兼职一单一结(涛哥利用这个方法早已月入过万)?微信兼职一单一结,微信类的兼职比较多,也比较受迎欢,主要是操作简单,不需要花太多的时间,重点是一单一结,当天可提现,但是问题又来啦,网上微信类的兼职这么多,那些微信兼职一单一结的软件比较好呢,赚钱快呢?今天就让涛哥给大家推荐几个不错的。 https://weibo.com/ttarticle/p/show?id=2309404637531143798976
7.手机上兼职一单10到20(手机兼职一单一结50元)V商人就在刚才,我不知道我是不是差点被骗,但在我要付款的一瞬间,我犹豫了!然后把人删掉,关闭了交易!究竟是怎么回事呢,我让我整理一下思绪,现在还有点心有余悸。刚才有个人联系我说,他那里有兼职,是做乐高积木代拼的,拼一个小积木10-20,大积木30-50,还有超大积木,具https://www.vsaren.com/2603.html
8.支付宝代收款,佣金高,一单一结,十分钟左右完成一单,一天可以做2单你遭遇的情况是,前面的一两个小单都能提现,随后都是大单或连单,但钱投进去以后,对方会找各种借口https://www.64365.com/ask/21379804.aspx
9.中西医结合外科学执业医师考试精华考点总结(第1~25单元)表现:疼痛渐加重,有麻木感,伤口周围皮肤迅速红中,有水疱血疱。重者伤口坏死溃烂,区域淋巴结肿大压痛。 混合毒的死亡主要原因仍为神经毒。 第十四单元 肿瘤概论 一、概述 1、恶性肿瘤的生物学行为: 自主性生长 浸润性生长, 转移, 肿瘤的自发消退 肿瘤的逆转 https://www.med66.com/zhongxiyiyishi/fudaopeixun/sl1905297119.shtml
10.新手配音怎么接单(配音赚钱平台学生一单一结)十、游戏配音如何接单? 游戏配音接单一般可以在qq群,或者猪八戒寻找相应资源进行合作 这篇关于《新手配音怎么接单(配音赚钱平台学生一单一结)》A5工具!https://tool.a5.cn/article/show/4285.html
11.简单手工活随时可做一单一结高清在线观看PP视频首页 频道 搜索热搜榜 开通会员 客户端 看过 收藏 消息 您的Flash插件已过期或被禁止 升级或启用Flash 简单手工活 随时可做 一单一结 295 简单手工活 随时可做 一单一结 内容简介https://v.pptv.com/show/q5NK2UGnF1W4Np4.html
12.打字赚钱平台学生一单一结学生打字赚钱app是一款快速上手的手机赚钱客户端应用,学生打字赚钱软件上的主要任务就是打字,用户24小时都能在线选择任务赚钱,学生赚打字赚任务多轻松就能躺赚。任务多多,轻松躺赚,收益丰厚~感兴趣的朋友欢迎前来下载体验! 特色 一、学生打字赚钱app是真的吗 https://www.xgbbs.net/app/44746.html
13.最简单手机挣钱的方法,一单一结,稳!最简单手机挣钱的方法,一单一结,稳! 说到手机赚钱其实真没啥难的,不管是在车上,在宿舍,在卧室,哪怕是在厕所,只要说你有手机,那么都可以通过这部手机在空闲时间赚得一份儿零花钱。 最简单手机赚钱方法 手机做任务赚佣金,说简单点就是帮助有需求的人达到对方的要求即可。举个简单的例子,比如说目前朋友圈有http://www.52thing.com/3885.html
14.初中生打字接单的app不用花钱初中生打字接单的app下载_初中生打字接单的app不用花钱_初中生打字兼职一单一结软件 来源:网络时间:2023-11-13 19:07:11 对于"初中生打字接单的app下载"这个问题感兴趣的朋友应该很多,这个也是目前大家比较关注的问题,那么下面小好小编就收集了一些"初中生打字接单的app下载"相关软件,来分享给大家希望能够帮助https://www.duote.com/tech/rjxz/509061.html
15.有人关注抖音截图赚佣金被骗,关注抖音截图赚佣金是真的吗三单一结?警惕:刷单已成为占比类最高的诈骗类型!迷惑性极高! 提醒:本文有点长,想说得细一点。 不少人应该都收到过这样的兼职广告,动动手指,点个赞赚2-10元,保证日赚100-1500元轻松赚钱。 近日,我的手机通知铃声一直响个不停,看了看,不知道什么时候莫名其妙的被拉入一个QQ群,群里的抖音关注截图、支付宝收款截图不https://www.sumwb.com/13315.html
16.Java基础1语法准备文章浏览阅读703次。77集程序底层执行初识Java1.1计算机语言发展史以及未来计算机语言经历了三代:第一代是机器语言,第二代是汇编语言,第三代是高级语言。·第一代语言:机器语言(相当于人类的原始阶段)机器语言通常由数字串组成(最终被简化成01),对于人类来说,机器语https://blog.csdn.net/AthlenaA/article/details/84194077
17.内部利息结息单(3)步骤3:内部利息结息单保存时,会更新本次冲销的内部利息预提单的已冲销金额及未冲销金额,并将内部利息结息单记录在内部利息预提单冲销明细页签。 (4)步骤4:内部利息结算单的实际结算利息为正数时,审核后自动生成成员单位利息计入账户的收款单(已收款状态)及内部交易明细,增加成员单位利息计入账户的余额。内部交易https://vip.kingdee.com/article/565986498605123328?lang=zh-CN
18.懒人兼职软件做任务赚钱是真的吗?15单一结靠谱吗?后面朋友推荐了一个靠谱的任务平台,里面有很多商家在投放任务,任务量也多,喜欢做哪个就做哪个,全部的任务单价一目了然。 最主要是这个APP是一单一结的,是的你没有听错,提现门槛只要1元,自己做的任务佣金超过1元就可以直接兑换秒到账。 接单赚钱软件 https://www.wuxianwz.cn/wd/851.html
19.下,刚刚有个刷单,作一单一结账让把二维码收款码发给他会有什么风险刷单不合法,不要做 https://www.66law.cn/question/31691971.aspx
20.真实挣钱的游戏一天一结一单一结平台也存在一些挑战和风险。由于任务的竞争性较大,可能会导致报酬较低。个人工作的不稳定性也需要考虑,特别是在没有足够任务的情况下,可能会面临经济上的困境。 赚钱的平台一单一结为人们提供了一种灵活多样的工作机会,能够满足人们的经济需求,提供更多的选择和灵活性,激发个人的自我推动力。在选择这种工作http://www.wzlmcn.com/chuangye/2463.html
21.一月一结。跑一单有一单的钱。没有低工资。没有五险一金,没有高温服务地区-上海上海·电话-173-0186-1583 https://www.lawtime.cn/wenda/q_24490082.html
22.河北省石家庄市鹿泉区:一工一单一月一结劳动用工套上“监管笼头”一、进村入户“揪”问题 2018年,鹿泉区农经站专门组织人员,深入到乡镇、村,通过座谈、走访、查账等形式,对村级劳动用工进行了深入细致的调查、分析,发现村集体劳动用工主要表现在村级清理街道、打扫卫生、清理渠道等临时 河北省石家庄市鹿泉区:一工一单 一月一结 劳动用工套上 “监管笼头”9 2021.6 Copyrighhttps://wenku.baidu.com/view/b43304556aeae009581b6bd97f1922791688be03.html
23.代练平台接了一单,号主看着我完成单了,平台结账不结完整友爱喵投诉:6月22号在代练平台接了一单单子,前后打了六天我发起申请验收,但是发单人那边呢就开始锁单锁了一天我等不了这么久了我就开始仲裁,仲裁的时候由于我没有截图https://tousu.sina.com.cn/complaint/view/17374327158/
24.艾滋病淋巴结肿大是单侧还是多侧如果肿大一有问必答病情分析:你好,根据你的描述看,只凭淋巴结肿大是不能确诊是否感染艾滋病毒的。指导意见:艾滋病可伴https://www.120ask.com/question/38786867.htm
25.U8+年结早知道1、用友U8+10.0以上版本系统提供三种年结模式,分别是:新建会计期间、账套库初始化、数据卸出,一般采用新建会计期间模式年度结转,请根据具体年结模式关注年结前准备事项。 2、重要提醒:任何年度结转模式都建议做好数据备份。 备份操作:在服务器的系统管理的【系统】下的【注册】用admin登录,点击【账套】下的【输出】https://fwq.yonyou.com/community-mobile/askDetail?aId=b19295369e594c2b9d6804e6eb1e990d15b0987f359dae95&themeType=2
26.研发工作总结15篇总结就是对一个时期的学习、工作或其完成情况进行一次全面系统的回顾和分析的书面材料,它可以使我们更有效率,因此我们需要回头归纳,写一份总结了。那么总结要注意有什么内容呢?下面是小编整理的研发工作总结,欢迎阅读与收藏。 研发工作总结1 20xx年是我们公司全面走向市场至关重要的一年,在这一年中,我们研发部积极配https://www.yuwenmi.com/fanwen/gongzuo/4246738.html
27.餐厅收银管理制度1、餐厅结帐单一式二联:第一联为财务联、第二联为客人联。 2、客人要求结帐时,收银员根据厅面人员报结的台号打印出暂结单,厅面人员应先将帐单核对后签上姓名,然后凭帐单与客人结帐。如果厅面人员没签名,收银员应提醒其签名。 3、客人结帐现付的,厅面人员应将两联帐单拿回交收银员总结后,将第二联结帐单交https://www.yjbys.com/zhidu/4506154.html
28.尚桂凤女儿王鸣女婿单旭新婚大喜贺单旭王鸣新婚大喜 一朝选在君王侧; 百鸟和鸣旭日边。 尚佐文 浙江 贺王鸣单旭新婚大喜 仁心爱心,宜家淑世无双侣; 东北西北,凤翥鹏飞九万程。 汪星群 安徽潜山 贺王鸣单旭新婚大喜 相知相爱相携,相信一缘,一轮东旭肝脾暖; 同德同心同梦,同行百载,百鸟南鸣夫妇和。 https://www.meipian.cn/387zo1g2