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
日期函数...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;
List
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
//加载第一个客户实体及其订单
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检测到调用是在事务的作用域内,因而不会创建新的事务。在这种情况下,
隐式事务:当您调用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
查找不隶属于另一个雇员的所有雇员:
wheree.ReportsToEmployee==null
where!e.ReportsTo.HasValue
3.Nullable
返回前者的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
db.LoadOptions=ds;
varcusts=(
Console.WriteLine("CustomerID{0}hasanOrderID{1}.",
cust.CustomerID,
ord.OrderID);
运算符转换
使用AsEnumerable
解决方法是指定where的客户端泛型IEnumerable
frompindb.Products.AsEnumerable()
whereisValidProduct(p)
语句描述:这个例子就是使用AsEnumerable以便使用Where的客户端IEnumerable实现,而不是默认的IQueryable将在服务器上转换为SQL并执行的默认Query
使用ToArray
Customer[]qArray=q.ToArray();
语句描述:这个例子使用ToArray将查询直接计算为数组。
使用ToList
List
使用Enumerable.ToDictionary
Dictionary
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]")]
publicISingleResult
[Parameter(DbType="NVarChar(20)")]stringparam1)
IExecuteResultresult=this.ExecuteMethodCall(this,(
(MethodInfo)(MethodInfo.GetCurrentMethod())),param1);
return((ISingleResult
(result.ReturnValue));
我们用下面的代码调用:
ISingleResult
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.Nullable
return((ISingleResult
但是,VS2008会把多结果集存储过程识别为单结果集的存储过程,默认生成的代码我们要手动修改一下,要求返回多个结果集,像这样:
[ResultType(typeof(WholeCustomersSetResult))]
[ResultType(typeof(PartialCustomersSetResult))]
publicIMultipleResultsWhole_Or_Partial_Customers_Set([Parameter
(DbType="Int")]System.Nullable
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);
IEnumerable
result.GetResult
foreach(WholeCustomersSetResultcompNameinshape1)
Console.WriteLine(compName.CompanyName);
//返回部分Customer结果集
result=db.Whole_Or_Partial_Customers_Set(2);
IEnumerable
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
privateSystem.Nullable
privateSystem.Nullable
privateSystem.Nullable
privateSystem.Nullable
privateSystem.Nullable
privateSystem.Nullable
privatestring_ShipName;
privatestring_ShipAddress;
privatestring_ShipCity;
privatestring_ShipRegion;
privatestring_ShipPostalCode;
privatestring_ShipCountry;
publicOrdersResultSet()
[Column(Storage="_OrderID",DbType="Int")]
publicSystem.Nullable
get{returnthis._OrderID;}
if((this._OrderID!=value))
this._OrderID=value;
[Column(Storage="_EmployeeID",DbType="Int")]
publicSystem.Nullable
get{returnthis._EmployeeID;}
if((this._EmployeeID!=value))
this._EmployeeID=value;
[Column(Storage="_OrderDate",DbType="DateTime")]
publicSystem.Nullable
get{returnthis._OrderDate;}
if((this._OrderDate!=value))
this._OrderDate=value;
[Column(Storage="_RequiredDate",DbType="DateTime")]
publicSystem.Nullable
get{returnthis._RequiredDate;}
if((this._RequiredDate!=value))
this._RequiredDate=value;
[Column(Storage="_ShippedDate",DbType="DateTime")]
publicSystem.Nullable
get{returnthis._ShippedDate;}
if((this._ShippedDate!=value))
this._ShippedDate=value;
[Column(Storage="_ShipVia",DbType="Int")]
publicSystem.Nullable
get{returnthis._ShipVia;}
if((this._ShipVia!=value))
this._ShipVia=value;
[Column(Storage="_Freight",DbType="Money")]
publicSystem.Nullable
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结果集
IEnumerable
result.GetResult
//返回Orders结果集
IEnumerable
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.Nullable
((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.Nullable
[Parameter(DbType="Int")]System.Nullable
return((System.Nullable
((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.Nullable
return((System.Nullable
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.Nullable
returnthis.CreateMethodCallQuery
((MethodInfo)(MethodInfo.GetCurrentMethod())),price);
这时我们小小的修改一下Discontinued属性为可空的bool类型。
privateSystem.Nullable
publicSystem.Nullable
我们可以这样调用使用了:
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
publicTable
publicNewCreateDB(stringconnection)
:
base(connection)
publicNewCreateDB(System.Data.IDbConnectionconnection)
[Table(Name="Person")]
publicpartialclassPerson:INotifyPropertyChanged
privateint_PersonID;
privatestring_PersonName;
privateSystem.Nullable
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.Nullable
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);
IQueryable
Provider.CreateQuery
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
表达式目录树在LINQ中用于表示分配给类型为Expression
System.Linq.Expressions命名空间提供用于手动生成表达式目录树的API。Expression类包含创建特定类型的表达式目录树节点的静态工厂方法,例如,ParameterExpression(表示一个已命名的参数表达式)或MethodCallExpression(表示一个方法调用)。编译器生成的表达式目录树的根始终在类型Expression
下面几个例子描述如何使用表达式目录树来创建动态LINQ查询。
下面例子说明如何使用表达式树依据IQueryable数据源构造一个动态查询,查询出每个顾客的ContactName,并用GetCommand方法获取其生成SQL语句。
//依据IQueryable数据源构造一个查询
IQueryable
//组建一个表达式树来创建一个参数
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);
//使用表达式树来生成动态查询
IQueryable
.Provider.CreateQuery
//使用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)},
//生成动态查询
IQueryable
.Provider.CreateQuery
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
下面一张截图显示了怎么动态生成动态查询的过程
ORDERBY[t0].[ContactName]
4.Union
下面的例子使用表达式树动态查询顾客和雇员同在的城市。
//e.City
ParameterExpressionparam1=
Expression.Parameter(typeof(Customer),"e");
Expressionleft1=Expression.Property(param1,
Expressionpred1=Expression.Lambda(left1,param1);
//c.City
IQueryable
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);
IQueryable
.Provider.CreateQuery
IQueryable
.Provider.CreateQuery
//并集
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)