程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> LINQ TO SQL 大全,linqtosql大全

LINQ TO SQL 大全,linqtosql大全

編輯:關於.NET

LINQ TO SQL 大全,linqtosql大全


最近悟出來一個道理,在這兒分享給大家:學歷代表你的過去,能力代表你的現在,學習代表你的將來。

十年河東十年河西,莫欺少年窮

學無止境,精益求精

LINQ to SQL語句(1)之Where

適用場景:實現過濾,查詢等功能。

說明:與SQL命令中的Where作用相似,都是起到范圍限定也就是過濾作用的,而判斷條件就是它後面所接的子句。Where操作包括3種形式,分別為簡單形式、關系條件形式、First()形式。下面分別用實例舉例下:

1.簡單形式:

例如:使用where篩選在倫敦的客戶

var q =
    from c in db.Customers
    where c.City == "London"
    select c;

再如:篩選1994 年或之後雇用的雇員:

var q =
    from e in db.Employees
    where e.HireDate >= new DateTime(1994, 1, 1)
    select e;

2.關系條件形式:

篩選庫存量在訂貨點水平之下但未斷貨的產品:

var q =
    from p in db.Products
    where p.UnitsInStock <= p.ReorderLevel && !p.Discontinued
    select p;

篩選出UnitPrice 大於10 或已停產的產品:

var q =
    from p in db.Products
    where p.UnitPrice > 10m || p.Discontinued
    select p;

下面這個例子是調用兩次where以篩選出UnitPrice大於10且已停產的產品。

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

3.First()形式:

返回集合中的一個元素,其實質就是在SQL語句中加TOP (1)。

簡單用法:選擇表中的第一個發貨方。

Shipper shipper = db.Shippers.First();

元素:選擇CustomerID 為“BONAP”的單個客戶

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

條件:選擇運費大於 10.00 的訂單:

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

LINQ to SQL語句(2)之Select/Distinct

適用場景:o(∩_∩)o… 查詢呗。

說明:和SQL命令中的select作用相似但位置不同,查詢表達式中的select及所接子句是放在表達式最後並把子句中的變量也就是結果返回回來;延遲。Select/Distinct操作包括9種形式,分別為簡單用法、匿名類型形式、條件形式、指定類型形式、篩選形式、整形類型形式、嵌套類型形式、本地方法調用形式、Distinct形式。

1.簡單用法:

這個示例返回僅含客戶聯系人姓名的序列。

var q =
    from c in db.Customers
    select c.ContactName;

注意:這個語句只是一個聲明或者一個描述,並沒有真正把數據取出來,只有當你需要該數據的時候,它才會執行這個語句,這就是延遲加載(deferred loading)。如果,在聲明的時候就返回的結果集是對象的集合。你可以使用ToList() 或ToArray()方法把查詢結果先進行保存,然後再對這個集合進行查詢。當然延遲加載(deferred loading)可以像拼接SQL語句那樣拼接查詢語法,再執行它。

2.匿名類型形式:

說明:匿名類型是C#3.0中新特性。其實質是編譯器根據我們自定義自動產生一個匿名的類來幫助我們實現臨時變量的儲存。匿名類型還依賴於另外一個特性:支持根據property來創建對象。比如,var d = new { Name = "s" };編譯器自動產生一個有property叫做Name的匿名類,然後按這個類型分配內存,並初始化對象。但是var d = new {"s"};是編譯不通過的。因為,編譯器不知道匿名類中的property的名字。例如string c = "d";var d = new { c}; 則是可以通過編譯的。編譯器會創建一個叫做匿名類帶有叫c的property。 例如下例:new{c,ContactName,c.Phone};ContactName和Phone都是在映射文件中定義與表中字段相對應的property。編譯器讀取數據並創建對象時,會創建一個匿名類,這個類有兩個屬性,為ContactName和Phone,然後根據數據初始化對象。另外編譯器還可以重命名property的名字。

var q =
    from c in db.Customers
    select new {c.ContactName, c.Phone};

上面語句描述:使用 SELECT 和匿名類型返回僅含客戶聯系人姓名和電話號碼的序列

復制代碼
var q =
    from e in db.Employees
    select new
    {
        Name = e.FirstName + " " + e.LastName,
        Phone = e.HomePhone
    };
復制代碼

上面語句描述:使用SELECT和匿名類型返回僅含雇員姓名和電話號碼的序列,並將FirstName和LastName字段合並為一個字段“Name”,此外在所得的序列中將HomePhone字段重命名為Phone。

復制代碼
var q =
    from p in db.Products
    select new
    {
        p.ProductID,
        HalfPrice = p.UnitPrice / 2
    };
復制代碼

上面語句描述:使用SELECT和匿名類型返回所有產品的ID以及HalfPrice(設置為產品單價除以2所得的值)的序列。  

3.條件形式:

說明:生成SQL語句為:case when condition then else。

復制代碼
var q =
    from p in db.Products
    select new
    {
        p.ProductName,
        Availability =
        p.UnitsInStock - p.UnitsOnOrder < 0 ? 
        "Out Of Stock" : "In Stock"
    };
復制代碼

上面語句描述:使用SELECT和條件語句返回產品名稱和產品供貨狀態的序列。

4.指定類型形式:

說明:該形式返回你自定義類型的對象集。

復制代碼
var q =
    from e in db.Employees
    select new Name
    {
        FirstName = e.FirstName,
        LastName = e.LastName
    };
復制代碼

上面語句描述:使用SELECT和已知類型返回雇員姓名的序列。

5.篩選形式:

說明:結合where使用,起到過濾作用。

var q =
    from c in db.Customers
    where c.City == "London"
    select c.ContactName;

上面語句描述:使用SELECT和WHERE返回僅含倫敦客戶聯系人姓名的序列。

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

說明:其select操作使用了匿名對象,而這個匿名對象中,其屬性也是個匿名對象。

復制代碼
var q =
    from c in db.Customers
    select new {
        c.CustomerID,
        CompanyInfo = new {c.CompanyName, c.City, c.Country},
        ContactInfo = new {c.ContactName, c.ContactTitle}
    };
復制代碼

語句描述:使用SELECT 和匿名類型返回有關客戶的數據的整形子集。查詢顧客的ID和公司信息(公司名稱,城市,國家)以及聯系信息(聯系人和職位)。  

7.嵌套類型形式:

說明:返回的對象集中的每個對象DiscountedProducts屬性中,又包含一個集合。也就是每個對象也是一個集合類。

復制代碼
var q =
    from o in db.Orders
    select new {
        o.OrderID,
        DiscountedProducts =
            from od in o.OrderDetails
            where od.Discount > 0.0
            select od,
        FreeShippingDiscount = o.Freight
    };
復制代碼

語句描述:使用嵌套查詢返回所有訂單及其OrderID 的序列、打折訂單中項目的子序列以及免送貨所省下的金額。

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

這個例子在查詢中調用本地方法PhoneNumberConverter將電話號碼轉換為國際格式。

復制代碼
var q = from c in db.Customers
         where c.Country == "UK" || c.Country == "USA"
         select new
         {
             c.CustomerID,
             c.CompanyName,
             Phone = c.Phone,
             InternationalPhone = 
             PhoneNumberConverter(c.Country, c.Phone)
         };
復制代碼

PhoneNumberConverter方法如下:

復制代碼
public string PhoneNumberConverter(string Country, string Phone)
{
    Phone = Phone.Replace(" ", "").Replace(")", ")-");
    switch (Country)
    {
        case "USA":
            return "1-" + Phone;
        case "UK":
            return "44-" + Phone;
        default:
            return Phone;
    }
}
復制代碼

下面也是使用了這個方法將電話號碼轉換為國際格式並創建XDocument

復制代碼
XDocument doc = new XDocument(
    new XElement("Customers", from c in db.Customers
              where c.Country == "UK" || c.Country == "USA"
              select (new XElement("Customer",
                      new XAttribute("CustomerID", c.CustomerID),
                      new XAttribute("CompanyName", c.CompanyName),
                      new XAttribute("InterationalPhone", 
                       PhoneNumberConverter(c.Country, c.Phone))
                     ))));
復制代碼

9.Distinct形式:

說明:篩選字段中不相同的值。用於查詢不重復的結果集。生成SQL語句為:SELECT DISTINCT [City] FROM [Customers]

var q = (
    from c in db.Customers
    select c.City )
    .Distinct();

語句描述:查詢顧客覆蓋的國家。

LINQ to SQL語句(3)之Count/Sum/Min/Max/Avg

適用場景:統計數據吧,比如統計一些數據的個數,求和,最小值,最大值,平均數。

Count

說明:返回集合中的元素個數,返回INT類型;不延遲。生成SQL語句為:SELECT COUNT(*) FROM

1.簡單形式:

得到數據庫中客戶的數量:

var q = db.Customers.Count();

2.帶條件形式:

得到數據庫中未斷貨產品的數量:

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

LongCount

說明:返回集合中的元素個數,返回LONG類型;不延遲。對於元素個數較多的集合可視情況可以選用LongCount來統計元素個數,它返回long類型,比較精確。生成SQL語句為:SELECT COUNT_BIG(*) FROM

var q = db.Customers.LongCount();

Sum

說明:返回集合中數值類型元素之和,集合應為INT類型集合;不延遲。生成SQL語句為:SELECT SUM(…) FROM

1.簡單形式:

得到所有訂單的總運費:

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

2.映射形式:

得到所有產品的訂貨總數:

var q = db.Products.Sum(p => p.UnitsOnOrder);

Min

說明:返回集合中元素的最小值;不延遲。生成SQL語句為:SELECT MIN(…) FROM

1.簡單形式:

查找任意產品的最低單價:

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

2.映射形式:

查找任意訂單的最低運費:

var q = db.Orders.Min(o => o.Freight);

3.元素:

查找每個類別中單價最低的產品:

復制代碼
var categories =
    from p in db.Products
    group p by p.CategoryID into g
    select new {
        CategoryID = g.Key,
        CheapestProducts =
            from p2 in g
            where p2.UnitPrice == g.Min(p3 => p3.UnitPrice)
            select p2
    };
復制代碼

Max

說明:返回集合中元素的最大值;不延遲。生成SQL語句為:SELECT MAX(…) FROM

1.簡單形式:

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

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

2.映射形式:

查找任意產品的最大庫存量:

var q = db.Products.Max(p => p.UnitsInStock);

3.元素:

查找每個類別中單價最高的產品:

復制代碼
var categories =
    from p in db.Products
    group p by p.CategoryID into g
    select new {
        g.Key,
        MostExpensiveProducts =
            from p2 in g
            where p2.UnitPrice == g.Max(p3 => p3.UnitPrice)
            select p2
    };
復制代碼

Average

說明:返回集合中的數值類型元素的平均值。集合應為數字類型集合,其返回值類型為double;不延遲。生成SQL語句為:SELECT AVG(…) FROM

1.簡單形式:

得到所有訂單的平均運費:

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

2.映射形式:

得到所有產品的平均單價:

var q = db.Products.Average(p => p.UnitPrice);

3.元素:

查找每個類別中單價高於該類別平均單價的產品:

復制代碼
var categories =
    from p in db.Products
    group p by p.CategoryID into g
    select new {
        g.Key, 
        ExpensiveProducts =
            from p2 in g
            where p2.UnitPrice > g.Average(p3 => p3.UnitPrice)
            select p2
    };
復制代碼

Aggregate

說明:根據輸入的表達式獲取聚合值;不延遲。即是說:用一個種子值與當前元素通過指定的函數來進行對比來遍歷集合中的元素,符合條件的元素保留下來。如果沒有指定種子值的話,種子值默認為集合的第一個元素。

LINQ to SQL語句(4)之Join

適用場景:在我們表關系中有一對一關系,一對多關系,多對多關系等。對各個表之間的關系,就用這些實現對多個表的操作。

說明:在Join操作中,分別為Join(Join查詢), SelectMany(Select一對多選擇)和GroupJoin(分組Join查詢)。 該擴展方法對兩個序列中鍵匹配的元素進行inner join操作

SelectMany

說明:我們在寫查詢語句時,如果被翻譯成SelectMany需要滿足2個條件。1:查詢語句中沒有join和into,2:必須出現EntitySet。在我們表關系中有一對一關系,一對多關系,多對多關系等,下面分別介紹一下。

1.一對多關系(1 to Many):

var q =
    from c in db.Customers
    from o in c.Orders
    where c.City == "London"
    select o;

語句描述:Customers與Orders是一對多關系。即Orders在Customers類中以EntitySet形式出現。所以第二個from是從c.Orders而不是db.Orders裡進行篩選。這個例子在From子句中使用外鍵導航選擇倫敦客戶的所有訂單。

var q =
    from p in db.Products
    where p.Supplier.Country == "USA" && p.UnitsInStock == 0
    select p;

語句描述:這一句使用了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]
LEFT OUTER JOIN [dbo].[Suppliers] AS [t1] ON 
[t1].[SupplierID] = [t0].[SupplierID]
WHERE ([t1].[Country] = @p0) AND ([t0].[UnitsInStock] = @p1)
-- @p0: Input NVarChar (Size = 3; Prec = 0; Scale = 0) [USA]
-- @p1: Input Int (Size = 0; Prec = 0; Scale = 0) [0]
復制代碼

2.多對多關系(Many to Many):

復制代碼
var q =
    from e in db.Employees
    from et in e.EmployeeTerritories
    where e.City == "Seattle"
    select new
    {
        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] CROSS JOIN [dbo].[EmployeeTerritories]
AS [t1] INNER JOIN [dbo].[Territories] AS [t2] ON 
[t2].[TerritoryID] = [t1].[TerritoryID]
WHERE ([t0].[City] = @p0) AND ([t1].[EmployeeID] = [t0].[EmployeeID])
-- @p0: Input NVarChar (Size = 7; Prec = 0; Scale = 0) [Seattle]

3.自聯接關系:

復制代碼
var q =
    from e1 in db.Employees
    from e2 in e1.Employees
    where e1.City == e2.City
    select new {
        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的概念是對其結果進行重新命名。

1.雙向聯接(Two way join):

此示例顯式聯接兩個表並從這兩個表投影出結果:

復制代碼
var q =
    from c in db.Customers
    join o in db.Orders on c.CustomerID
    equals o.CustomerID into orders
    select new
    {
        c.ContactName,
        OrderCount = orders.Count()
    };
復制代碼

說明:在一對多關系中,左邊是1,它每條記錄為c(from c in db.Customers),右邊是Many,其每條記錄叫做o ( join o in db.Orders ),每對應左邊的一個c,就會有一組o,那這一組o,就叫做orders,也就是說,我們把一組o命名為orders,這就是into用途。這也就是為什麼在select語句中,orders可以調用聚合函數Count。在T-SQL中,使用其內嵌的T-SQL返回值作為字段值。如圖所示:

 

生成SQL語句為:
SELECT [t0].[ContactName], (
    SELECT COUNT(*)
    FROM [dbo].[Orders] AS [t1]
    WHERE [t0].[CustomerID] = [t1].[CustomerID]
) AS [OrderCount]
FROM [dbo].[Customers] AS [t0]

2.三向聯接(There way join):

此示例顯式聯接三個表並分別從每個表投影出結果:

復制代碼
var q =
    from c in db.Customers
    join o in db.Orders on c.CustomerID
    equals o.CustomerID into ords
    join e in db.Employees on c.City
    equals e.City into emps
    select new
    {
        c.ContactName,
        ords = ords.Count(),
        emps = emps.Count()
    };
復制代碼

生成SQL語句為:

復制代碼
SELECT [t0].[ContactName], (
    SELECT COUNT(*)
    FROM [dbo].[Orders] AS [t1]
    WHERE [t0].[CustomerID] = [t1].[CustomerID]
) AS [ords], (
SELECT COUNT(*)
    FROM [dbo].[Employees] AS [t2]
    WHERE [t0].[City] = [t2].[City]
) AS [emps]
FROM [dbo].[Customers] AS [t0]
復制代碼

3.左外部聯接(Left Outer Join):

此示例說明如何通過使用 此示例說明如何通過使用DefaultIfEmpty() 獲取左外部聯接。在雇員沒有訂單時,DefaultIfEmpty()方法返回null:

復制代碼
var q =
    from e in db.Employees
    join o in db.Orders on e equals o.Employee into ords
    from o in ords.DefaultIfEmpty()
    select new
    {
        e.FirstName,
        e.LastName,
        Order = o
    };
復制代碼

說明:以Employees左表,Orders右表,Orders 表中為空時,用null值填充。Join的結果重命名ords,使用DefaultIfEmpty()函數對其再次查詢。其最後的結果中有個Order,因為from o in ords.DefaultIfEmpty() 是對ords組再一次遍歷,所以,最後結果中的Order並不是一個集合。但是,如果沒有from o in ords.DefaultIfEmpty() 這句,最後的select語句寫成select new { e.FirstName, e.LastName, Order = ords }的話,那麼Order就是一個集合。

4.投影的Let賦值(Projected let assignment):

說明:let語句是重命名。let位於第一個from和select語句之間。

這個例子從聯接投影出最終“Let”表達式:

復制代碼
var q =
    from c in db.Customers
    join o in db.Orders on c.CustomerID
    equals o.CustomerID into ords
    let z = c.City + c.Country
    from o in ords
    select new
    {
        c.ContactName,
        o.OrderID,
        z
    };
復制代碼

5.組合鍵(Composite Key):

這個例子顯示帶有組合鍵的聯接:

復制代碼
var q =
    from o in db.Orders
    from p in db.Products
    join d in db.OrderDetails
        on new
        {
            o.OrderID,
            p.ProductID
        } equals
            new
            {
                d.OrderID,
                d.ProductID
            }
        into details
    from d in details
    select new
    {
        o.OrderID,
        p.ProductID,
        d.UnitPrice
    };
復制代碼

說明:使用三個表,並且用匿名類來說明:使用三個表,並且用匿名類來表示它們之間的關系。它們之間的關系不能用一個鍵描述清楚,所以用匿名類,來表示組合鍵。還有一種是兩個表之間是用組合鍵表示關系的,不需要使用匿名類。

6.可為null/不可為null的鍵關系(Nullable/Nonnullable Key Relationship):

這個實例顯示如何構造一側可為 null 而另一側不可為 null 的聯接:

復制代碼
var q =
    from o in db.Orders
    join e in db.Employees
        on o.EmployeeID equals
        (int?)e.EmployeeID into emps
    from e in emps
    select new
    {
        o.OrderID,
        e.FirstName
    };
復制代碼

LINQ to SQL語句(5)之Order By

適用場景:對查詢出的語句進行排序,比如按時間排序等等。

說明:按指定表達式對集合排序;延遲,:按指定表達式對集合排序;延遲,默認是升序,加上descending表示降序,對應的擴展方法是OrderBy和OrderByDescending

1.簡單形式

這個例子使用 orderby 按雇用日期對雇員進行排序:

var q =
    from e in db.Employees
    orderby e.HireDate
    select e;

說明:默認為升序

2.帶條件形式

注意:Where和Order By的順序並不重要。而在T-SQL中,Where和Order By有嚴格的位置限制。

var q =
    from o in db.Orders
    where o.ShipCity == "London"
    orderby o.Freight
    select o;

語句描述:使用where和orderby按運費進行排序。

3.降序排序

var q = 
    from p in db.Products
    orderby p.UnitPrice descending
    select p;

4.ThenBy

語句描述:使用復合的 orderby 對客戶進行排序,進行排序:

var q =
    from c in db.Customers
    orderby c.City, c.ContactName
    select c;

說明:按多個表達式進行排序,例如先按City排序,當City相同時,按ContactName排序。這一句用Lambda表達式像這樣寫:

var q = 
    .OrderBy(c => c.City)
    .ThenBy(c => c.ContactName).ToList();

在T-SQL中沒有ThenBy語句,其依然翻譯為OrderBy,所以也可以用下面語句來表達:

var q = 
    db.Customers
    .OrderBy(c => c.ContactName)
    .OrderBy(c => c.City).ToList();

所要注意的是,多個OrderBy操作時,級連方式是按逆序。對於降序的,用相應的降序操作符替換即可。

var q = 
    db.Customers
    .OrderByDescending(c => c.City)
    .ThenByDescending(c => c.ContactName).ToList();

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

復制代碼
var q = 
    db.Customers
    .OrderBy(c => new
    {
        c.City,
        c.ContactName
    }).ToList();
復制代碼

會被拋出異常。錯誤是前面的操作有匿名類,再跟OrderBy時,比較的是類別。比如

復制代碼
var q = 
    db.Customers
    .Select(c => new
    {
        c.City,
        c.Address
    })
    .OrderBy(c => c).ToList();
復制代碼

如果你想使用OrderBy(c => c),其前提條件是,前面步驟中,所產生的對象的類別必須為C#語言的基本類型。比如下句,這裡City為string類型。

var q = 
    db.Customers
    .Select(c => c.City)
    .OrderBy(c => c).ToList();

5.ThenByDescending

這兩個擴展方式都是用在OrderBy/OrderByDescending之後的,第一個ThenBy/ThenByDescending擴展方法作為第二位排序依據,第二個ThenBy/ThenByDescending則作為第三位排序依據,以此類推

var q =
    from o in db.Orders
    where o.EmployeeID == 1
    orderby o.ShipCountry, o.Freight descending
    select o;

語句描述:使用orderby先按發往國家再按運費從高到低的順序對 EmployeeID 1 的訂單進行排序。

6.帶GroupBy形式

復制代碼
var q = 
    from p in db.Products
    group p by p.CategoryID into g
    orderby g.Key
    select new {
        g.Key,
        MostExpensiveProducts =
            from p2 in g
            where p2.UnitPrice == g.Max(p3 => p3.UnitPrice)
            select p2
    };
復制代碼

語句描述:使用orderby、Max 和 Group By 得出每種類別中單價最高的產品,並按 CategoryID 對這組產品進行排序。

LINQ to SQL語句(6)之Group By/Having

適用場景:分組數據,為我們查找數據縮小范圍。

說明:分配並返回對傳入參數進行分組操作後的可枚舉對象。分組;延遲

1.簡單形式:

var q =
    from p in db.Products
    group p by p.CategoryID into g
    select g;

語句描述:使用Group By按CategoryID劃分產品。

說明:from p in db.Products 表示從表中將產品對象取出來。group p by p.CategoryID into g表示對p按CategoryID字段歸類。其結果命名為g,一旦重新命名,p的作用域就結束了,所以,最後select時,只能select g。當然,也不必重新命名可以這樣寫:

var q =
    from p in db.Products
    group p by p.CategoryID;

我們用示意圖表示: 

 

如果想遍歷某類別中所有記錄,這樣:

復制代碼
foreach (var gp in q)
{
    if (gp.Key == 2)
    {
        foreach (var item in gp)
        {
            //do something
        }
    }
}
復制代碼

2.Select匿名類:

var q =
    from p in db.Products
    group p by p.CategoryID into g
    select new { CategoryID = g.Key, g }; 

說明:在這句LINQ語句中,有2個property:CategoryID和g。這個匿名類,其實質是對返回結果集重新進行了包裝。把g的property封裝成一個完整的分組。如下圖所示:

如果想遍歷某匿名類中所有記錄,要這麼做:

復制代碼
foreach (var gp in q)
{
    if (gp.CategoryID == 2)
    {
        foreach (var item in gp.g)
        {
            //do something
        }
    }
}
復制代碼

3.最大值

復制代碼
var q =
    from p in db.Products
    group p by p.CategoryID into g
    select new {
        g.Key,
        MaxPrice = g.Max(p => p.UnitPrice)
    };
復制代碼

語句描述:使用Group By和Max查找每個CategoryID的最高單價。

說明:先按CategoryID歸類,判斷各個分類產品中單價最大的Products。取出CategoryID值,並把UnitPrice值賦給MaxPrice。

4.最小值

復制代碼
var q =
    from p in db.Products
    group p by p.CategoryID into g
    select new {
        g.Key,
        MinPrice = g.Min(p => p.UnitPrice)
    };
復制代碼

語句描述:使用Group By和Min查找每個CategoryID的最低單價。

說明:先按CategoryID歸類,判斷各個分類產品中單價最小的Products。取出CategoryID值,並把UnitPrice值賦給MinPrice。

5.平均值

復制代碼
var q =
    from p in db.Products
    group p by p.CategoryID into g
    select new {
        g.Key,
        AveragePrice = g.Average(p => p.UnitPrice)
    };
復制代碼

語句描述:使用Group By和Average得到每個CategoryID的平均單價。

說明:先按CategoryID歸類,取出CategoryID值和各個分類產品中單價的平均值。

6.求和

復制代碼
var q =
    from p in db.Products
    group p by p.CategoryID into g
    select new {
        g.Key,
        TotalPrice = g.Sum(p => p.UnitPrice)
    };
復制代碼

語句描述:使用Group By和Sum得到每個CategoryID 的單價總計。

說明:先按CategoryID歸類,取出CategoryID值和各個分類產品中單價的總和。  

7.計數

復制代碼
var q =
    from p in db.Products
    group p by p.CategoryID into g
    select new {
        g.Key,
        NumProducts = g.Count()
    };
復制代碼

語句描述:使用Group By和Count得到每個CategoryID中產品的數量。

說明:先按CategoryID歸類,取出CategoryID值和各個分類產品的數量。

8.帶條件計數

復制代碼
var q =
    from p in db.Products
    group p by p.CategoryID into g
    select new {
        g.Key,
        NumProducts = g.Count(p => p.Discontinued)
    };
復制代碼

語句描述:使用Group By和Count得到每個CategoryID中斷貨產品的數量。

說明:先按CategoryID歸類,取出CategoryID值和各個分類產品的斷貨數量。 Count函數裡,使用了Lambda表達式,Lambda表達式中的p,代表這個組裡的一個元素或對象,即某一個產品。

9.Where限制

復制代碼
var q =
    from p in db.Products
    group p by p.CategoryID into g
    where g.Count() >= 10
    select new {
        g.Key,
        ProductCount = g.Count()
    };
復制代碼

語句描述:根據產品的―ID分組,查詢產品數量大於10的ID和產品數量。這個示例在Group By子句後使用Where子句查找所有至少有10種產品的類別。

說明:在翻譯成SQL語句時,在最外層嵌套了Where條件。

10.多列(Multiple Columns)

復制代碼
var categories =
    from p in db.Products
    group p by new
    {
        p.CategoryID,
        p.SupplierID
    }
        into g
        select new
            {
                g.Key,
                g
            };
復制代碼

語句描述:使用Group By按CategoryID和SupplierID將產品分組。

說明:既按產品的分類,又按供應商分類。在by後面,new出來一個匿名類。這裡,Key其實質是一個類的對象,Key包含兩個Property:CategoryID、SupplierID。用g.Key.CategoryID可以遍歷CategoryID的值。

11.表達式(Expression)

var categories =
    from p in db.Products
    group p by new { Criterion = p.UnitPrice > 10 } into g
    select g;

語句描述:使用Group By返回兩個產品序列。第一個序列包含單價大於10的產品。第二個序列包含單價小於或等於10的產品。

說明:按產品單價是否大於10分類。其結果分為兩類,大於的是一類,小於及等於為另一類。

LINQ to SQL語句(7)之Exists/In/Any/All/Contains

適用場景:用於判斷集合中元素,進一步縮小范圍。

Any

說明:用於判斷集合中是否有元素滿足某一條件;不延遲。(若條件為空,則集合只要不為空就返回True,否則為False)。有2種形式,分別為簡單形式和帶條件形式。

1.簡單形式:

僅返回沒有訂單的客戶:

var q =
    from c in db.Customers
    where !c.Orders.Any()
    select c;

生成SQL語句為:

復制代碼
SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName],
[t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region],
[t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax]
FROM [dbo].[Customers] AS [t0]
WHERE NOT (EXISTS(
    SELECT NULL AS [EMPTY] FROM [dbo].[Orders] AS [t1]
    WHERE [t1].[CustomerID] = [t0].[CustomerID]
   ))
復制代碼

2.帶條件形式:

僅返回至少有一種產品斷貨的類別:

var q =
    from c in db.Categories
    where c.Products.Any(p => p.Discontinued)
    select c;

生成SQL語句為:

復制代碼
SELECT [t0].[CategoryID], [t0].[CategoryName], [t0].[Description],
[t0].[Picture] FROM [dbo].[Categories] AS [t0]
WHERE EXISTS(
    SELECT NULL AS [EMPTY] FROM [dbo].[Products] AS [t1]
    WHERE ([t1].[Discontinued] = 1) AND 
    ([t1].[CategoryID] = [t0].[CategoryID])
    )
復制代碼

All

說明:用於判斷集合中所有元素是否都滿足某一條件;不延遲

1.帶條件形式

var q =
    from c in db.Customers
    where c.Orders.All(o => o.ShipCity == c.City)
    select c;

語句描述:這個例子返回所有訂單都運往其所在城市的客戶或未下訂單的客戶。

Contains

說明:用於判斷集合中是否包含有某一元素;不延遲。它是對兩個序列進行連接操作的。

string[] customerID_Set =
    new string[] { "AROUT", "BOLID", "FISSA" };
var q = (
    from o in db.Orders
    where customerID_Set.Contains(o.CustomerID)
    select o).ToList();

語句描述:查找"AROUT", "BOLID" 和 "FISSA" 這三個客戶的訂單。先定義了一個數組,在LINQ to SQL中使用Contains,數組中包含了所有的CustomerID,即返回結果中,所有的CustomerID都在這個集合內。也就是in。 你也可以把數組的定義放在LINQ to SQL語句裡。比如:

var q = (
    from o in db.Orders
    where (
    new string[] { "AROUT", "BOLID", "FISSA" })
    .Contains(o.CustomerID)
    select o).ToList();

Not Contains則取反:

var q = (
    from o in db.Orders
    where !(
    new string[] { "AROUT", "BOLID", "FISSA" })
    .Contains(o.CustomerID)
    select o).ToList();

1.包含一個對象:

復制代碼
var order = (from o in db.Orders
             where o.OrderID == 10248
             select o).First();
var q = db.Customers.Where(p => p.Orders.Contains(order)).ToList();
foreach (var cust in q)
{
    foreach (var ord in cust.Orders)
    {
        //do something
    }
}
復制代碼

語句描述:這個例子使用Contain查找哪個客戶包含OrderID為10248的訂單。

2.包含多個值:

string[] cities = 
    new string[] { "Seattle", "London", "Vancouver", "Paris" };
var q = db.Customers.Where(p=>cities.Contains(p.City)).ToList();

語句描述:這個例子使用Contains查找其所在城市為西雅圖、倫敦、巴黎或溫哥華的客戶。

LINQ to SQL語句(8)之Concat/Union/Intersect/Except

適用場景:對兩個集合的處理,例如追加、合並、取相同項、相交項等等。

Concat(連接)

說明:連接不同的集合,不會自動過濾相同項;延遲。

1.簡單形式:

復制代碼
var q = (
         from c in db.Customers
         select c.Phone
        ).Concat(
         from c in db.Customers
         select c.Fax
        ).Concat(
         from e in db.Employees
         select e.HomePhone
        );
復制代碼

語句描述:返回所有消費者和雇員的電話和傳真。

2.復合形式:

復制代碼
var q = (
         from c in db.Customers
         select new
         {
             Name = c.CompanyName,
             c.Phone
         }
        ).Concat(
         from e in db.Employees
         select new
         {
             Name = e.FirstName + " " + e.LastName,
             Phone = e.HomePhone
         }
        );
復制代碼

語句描述:返回所有消費者和雇員的姓名和電話。

Union(合並)

說明:連接不同的集合,自動過濾相同項;延遲。即是將兩個集合進行合並操作,過濾相同的項。

復制代碼
var q = (
         from c in db.Customers
         select c.Country
        ).Union(
         from e in db.Employees
         select e.Country
        );
復制代碼

語句描述:查詢顧客和職員所在的國家。

Intersect(相交)

說明:取相交項;延遲。即是獲取不同集合的相同項(交集)。即先遍歷第一個集合,找出所有唯一的元素,然後遍歷第二個集合,並將每個元素與前面找出的元素作對比,返回所有在兩個集合內都出現的元素。

復制代碼
var q = (
         from c in db.Customers
         select c.Country
        ).Intersect(
         from e in db.Employees
         select e.Country
        );
復制代碼

語句描述:查詢顧客和職員同在的國家。

Except(與非)

說明:排除相交項;延遲。即是從某集合中刪除與另一個集合中相同的項。先遍歷第一個集合,找出所有唯一的元素,然後再遍歷第二個集合,返回第二個集合中所有未出現在前面所得元素集合中的元素。

復制代碼
var q = (
         from c in db.Customers
         select c.Country
        ).Except(
         from e in db.Employees
         select e.Country
        );
復制代碼

語句描述:查詢顧客和職員不同的國家。

LINQ to SQL語句(9)之Top/Bottom和Paging和SqlMethods

適用場景:適量的取出自己想要的數據,不是全部取出,這樣性能有所加強。

Take

說明:獲取集合的前n個元素;延遲。即只返回限定數量的結果集。

var q = (
    from e in db.Employees
    orderby e.HireDate
    select e)
    .Take(5);

語句描述:選擇所雇用的前5個雇員。

Skip

說明:跳過集合的前n個元素;延遲。即我們跳過給定的數目返回後面的結果集。

var q = (
    from p in db.Products
    orderby p.UnitPrice descending
    select p)
    .Skip(10);

語句描述:選擇10種最貴產品之外的所有產品。

TakeWhile

說明:直到某一條件成立就停止獲取;延遲。即用其條件去依次判斷源序列中的元素,返回符合判斷條件的元素,該判斷操作將在返回false或源序列的末尾結束 。

SkipWhile

說明:直到某一條件成立就停止跳過;延遲。即用其條件去判斷源序列中的元素並且跳過第一個符合判斷條件的元素,一旦判斷返回false,接下來將不再進行判斷並返回剩下的所有元素。

Paging(分頁)操作

適用場景:結合Skip和Take就可實現對數據分頁操作。

1.索引

var q = (
    from c in db.Customers
    orderby c.ContactName
    select c)
    .Skip(50)
    .Take(10);

語句描述:使用Skip和Take運算符進行分頁,跳過前50條記錄,然後返回接下來10條記錄,因此提供顯示Products表第6頁的數據。

2.按唯一鍵排序

var q = (
    from p in db.Products
    where p.ProductID > 50
    orderby p.ProductID
    select p)
    .Take(10);

語句描述:使用Where子句和Take運算符進行分頁,首先篩選得到僅50 (第5頁最後一個ProductID)以上的ProductID,然後按ProductID排序,最後取前10個結果,因此提供Products表第6頁的數據。請注意,此方法僅適用於按唯一鍵排序的情況。  

SqlMethods操作

在LINQ to SQL語句中,為我們提供了SqlMethods操作,進一步為我們提供了方便,例如Like方法用於自定義通配表達式,Equals用於相比較是否相等。

Like

自定義的通配表達式。%表示零長度或任意長度的字符串;_表示一個字符;[]表示在某范圍區間的一個字符;[^]表示不在某范圍區間的一個字符。比如查詢消費者ID以“C”開頭的消費者。 

var q = from c in db.Customers
        where SqlMethods.Like(c.CustomerID, "C%")
        select c;

比如查詢消費者ID沒有“AXOXT”形式的消費者:

var q = from c in db.Customers
        where !SqlMethods.Like(c.CustomerID, "A_O_T")
        select c;

DateDiffDay

說明:在兩個變量之間比較。分別有:DateDiffDay、DateDiffHour、DateDiffMillisecond、DateDiffMinute、DateDiffMonth、DateDiffSecond、DateDiffYear 

var q = from o in db.Orders
        where SqlMethods
        .DateDiffDay(o.OrderDate, o.ShippedDate) < 10
        select o;

語句描述:查詢在創建訂單後的 10 天內已發貨的所有訂單。

已編譯查詢操作(Compiled Query)

說明:在之前我們沒有好的方法對寫出的SQL語句進行編輯重新查詢,現在我們可以這樣做,看下面一個例子:

復制代碼
//1.創建compiled query
NorthwindDataContext db = new NorthwindDataContext();
var fn = CompiledQuery.Compile(
    (NorthwindDataContext db2, string city) =>
    from c in db2.Customers
    where c.City == city
    select c);
//2.查詢城市為London的消費者,用LonCusts集合表示,這時可以用數據控件綁定
var LonCusts = fn(db, "London");
//3.查詢城市為Seattle的消費者
var SeaCusts = fn(db, "Seattle");
復制代碼

語句描述:這個例子創建一個已編譯查詢,然後使用它檢索輸入城市的客戶。

LINQ to SQL語句(10)之Insert

1.簡單形式

說明:new一個對象,使用InsertOnSubmit方法將其加入到對應的集合中,使用SubmitChanges()提交到數據庫。

復制代碼
NorthwindDataContext db = new NorthwindDataContext();
var newCustomer = new Customer
{
    CustomerID = "MCSFT",
    CompanyName = "Microsoft",
    ContactName = "John Doe",
    ContactTitle = "Sales Manager",
    Address = "1 Microsoft Way",
    City = "Redmond",
    Region = "WA",
    PostalCode = "98052",
    Country = "USA",
    Phone = "(425) 555-1234",
    Fax = null
};
db.Customers.InsertOnSubmit(newCustomer);
db.SubmitChanges();
復制代碼

語句描述:使用InsertOnSubmit方法將新客戶添加到Customers 表對象。調用SubmitChanges 將此新Customer保存到數據庫。

2.一對多關系

說明:Category與Product是一對多的關系,提交Category(一端)的數據時,LINQ to SQL會自動將Product(多端)的數據一起提交。

復制代碼
var newCategory = new Category
{
    CategoryName = "Widgets",
    Description = "Widgets are the ……"
};
var newProduct = new Product
{
    ProductName = "Blue Widget",
    UnitPrice = 34.56M,
    Category = newCategory
};
db.Categories.InsertOnSubmit(newCategory);
db.SubmitChanges();
復制代碼

語句描述:使用InsertOnSubmit方法將新類別添加到Categories表中,並將新Product對象添加到與此新Category有外鍵關系的Products表中。調用SubmitChanges將這些新對象及其關系保存到數據庫。

3.多對多關系

說明:在多對多關系中,我們需要依次提交。

復制代碼
var newEmployee = new Employee
{
    FirstName = "Kira",
    LastName = "Smith"
};
var newTerritory = new Territory
{
    TerritoryID = "12345",
    TerritoryDescription = "Anytown",
    Region = db.Regions.First()
};
var newEmployeeTerritory = new EmployeeTerritory
{
    Employee = newEmployee,
    Territory = newTerritory
};
db.Employees.InsertOnSubmit(newEmployee);
db.Territories.InsertOnSubmit(newTerritory);
db.EmployeeTerritories.InsertOnSubmit(newEmployeeTerritory);
db.SubmitChanges();
復制代碼

語句描述:使用InsertOnSubmit方法將新雇員添加到Employees 表中,將新Territory添加到Territories表中,並將新EmployeeTerritory對象添加到與此新Employee對象和新Territory對象有外鍵關系的EmployeeTerritories表中。調用SubmitChanges將這些新對象及其關系保持到數據庫。

4.使用動態CUD重寫(Override using Dynamic CUD)

說明:CUD就是Create、Update、Delete的縮寫。下面的例子就是新建一個ID(主鍵)為32的Region,不考慮數據庫中有沒有ID為32的數據,如果有則替換原來的數據,沒有則插入。

復制代碼
Region nwRegion = new Region()
{
    RegionID = 32,
    RegionDescription = "Rainy"
};
db.Regions.InsertOnSubmit(nwRegion);
db.SubmitChanges();
復制代碼

語句描述:使用DataContext提供的分部方法InsertRegion插入一個區域。對SubmitChanges 的調用調用InsertRegion 重寫,後者使用動態CUD運行Linq To SQL生成的默認SQL查詢。

LINQ to SQL語句(11)之Update

說明:更新操作,先獲取對象,進行修改操作之後,直接調用SubmitChanges()方法即可提交。注意,這裡是在同一個DataContext中,對於不同的DataContex看下面的講解。

1.簡單形式

Customer cust =
    db.Customers.First(c => c.CustomerID == "ALFKI");
cust.ContactTitle = "Vice President";
db.SubmitChanges();

語句描述:使用SubmitChanges將對檢索到的一個Customer對象做出的更新保持回數據庫。

2.多項更改

復制代碼
var q = from p in db.Products
        where p.CategoryID == 1
        select p;
foreach (var p in q)
{
    p.UnitPrice += 1.00M;
}
db.SubmitChanges();
復制代碼

語句描述:使用SubmitChanges將對檢索到的進行的更新保持回數據庫。

 LINQ to SQL語句(12)之Delete和使用Attach

1.簡單形式

說明:調用DeleteOnSubmit方法即可。

OrderDetail orderDetail =
    db.OrderDetails.First
    (c => c.OrderID == 10255 && c.ProductID == 36);
db.OrderDetails.DeleteOnSubmit(orderDetail);
db.SubmitChanges();

語句描述:使用DeleteOnSubmit方法從OrderDetail 表中刪除OrderDetail對象。調用SubmitChanges 將此刪除保持到數據庫。

2.一對多關系

說明:Order與OrderDetail是一對多關系,首先DeleteOnSubmit其OrderDetail(多端),其次DeleteOnSubmit其Order(一端)。因為一端是主鍵。

復制代碼
var orderDetails =
    from o in db.OrderDetails
    where o.Order.CustomerID == "WARTH" &&
    o.Order.EmployeeID == 3
    select o;
var order =
    (from o in db.Orders
     where o.CustomerID == "WARTH" && o.EmployeeID == 3
     select o).First();
foreach (OrderDetail od in orderDetails)
{
    db.OrderDetails.DeleteOnSubmit(od);
}
db.Orders.DeleteOnSubmit(order);
db.SubmitChanges();
復制代碼

語句描述語句描述:使用DeleteOnSubmit方法從Order 和Order Details表中刪除Order和Order Detail對象。首先從Order Details刪除,然後從Orders刪除。調用SubmitChanges將此刪除保持到數據庫。

3.推理刪除(Inferred Delete)

說明:Order與OrderDetail是一對多關系,在上面的例子,我們全部刪除CustomerID為WARTH和EmployeeID為3 的數據,那麼我們不須全部刪除呢?例如Order的OrderID為10248的OrderDetail有很多,但是我們只要刪除ProductID為11的OrderDetail。這時就用Remove方法。

Order order = db.Orders.First(x => x.OrderID == 10248);
OrderDetail od = 
    order.OrderDetails.First(d => d.ProductID == 11);
order.OrderDetails.Remove(od);
db.SubmitChanges();

語句描述語句描述:這個例子說明在實體對象的引用實體將該對象從其EntitySet 中移除時,推理刪除如何導致在該對象上發生實際的刪除操作。僅當實體的關聯映射將DeleteOnNull設置為true且CanBeNull 為false 時,才會發生推理刪除行為。  

使用Attach更新(Update with Attach)

說明:在對於在不同的DataContext之間,使用Attach方法來更新數據。例如在一個名為tempdb的NorthwindDataContext中,查詢出Customer和Order,在另一個NorthwindDataContext中,Customer的地址更新為123 First Ave,Order的CustomerID 更新為CHOPS。

復制代碼
//通常,通過從其他層反序列化 XML 來獲取要附加的實體
//不支持將實體從一個DataContext附加到另一個DataContext
//因此若要復制反序列化實體的操作,將在此處重新創建這些實體
Customer c1;
List<Order> deserializedOrders = new List<Order>();
Customer deserializedC1;
using (NorthwindDataContext tempdb = new NorthwindDataContext())
{
    c1 = tempdb.Customers.Single(c => c.CustomerID == "ALFKI");
    deserializedC1 = new Customer
    {
        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
    };
    Customer tempcust =
        tempdb.Customers.Single(c => c.CustomerID == "ANTON");
    foreach (Order o in tempcust.Orders)
    {
        deserializedOrders.Add(new Order
        {
            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 (NorthwindDataContext db2 = new NorthwindDataContext())
{
    //將第一個實體附加到當前數據上下文,以跟蹤更改
    //對Customer更新,不能寫錯
    db2.Customers.Attach(deserializedC1);
    //更改所跟蹤的實體
    deserializedC1.Address = "123 First Ave";
    //附加訂單列表中的所有實體
    db2.Orders.AttachAll(deserializedOrders);
    //將訂單更新為屬於其他客戶
    foreach (Order o in deserializedOrders)
    {
        o.CustomerID = "CHOPS";
    }
    //在當前數據上下文中提交更改
    db2.SubmitChanges();
}
復制代碼

語句描述:從另一個層中獲取實體,使用Attach和AttachAll將反序列化後的實體附加到數據上下文,然後更新實體。更改被提交到數據庫。

使用Attach更新和刪除(Update and Delete with Attach)

說明:在不同的DataContext中,實現插入、更新、刪除。看下面的一個例子:

復制代碼
//通常,通過從其他層反序列化XML獲取要附加的實體
//此示例使用 LoadWith 在一個查詢中預先加載客戶和訂單,
//並禁用延遲加載
Customer cust = null;
using (NorthwindDataContext tempdb = new NorthwindDataContext())
{
    DataLoadOptions shape = new DataLoadOptions();
    shape.LoadWith<Customer>(c => c.Orders);
    //加載第一個客戶實體及其訂單
    tempdb.LoadOptions = shape;
    tempdb.DeferredLoadingEnabled = false;
    cust = tempdb.Customers.First(x => x.CustomerID == "ALFKI");
}
Order orderA = cust.Orders.First();
Order orderB = cust.Orders.First(x => x.OrderID > orderA.OrderID);
using (NorthwindDataContext db2 = new NorthwindDataContext())
{
    //將第一個實體附加到當前數據上下文,以跟蹤更改
    db2.Customers.Attach(cust);
    //附加相關訂單以進行跟蹤; 否則將在提交時插入它們
    db2.Orders.AttachAll(cust.Orders.ToList());
    //更新客戶的Phone.
    cust.Phone = "2345 5436";
    //更新第一個訂單OrderA的ShipCity.
    orderA.ShipCity = "Redmond";
    //移除第二個訂單OrderB.
    cust.Orders.Remove(orderB);
    //添加一個新的訂單Order到客戶Customer中.
    Order orderC = new Order() { ShipCity = "New York" };
    cust.Orders.Add(orderC);
    //提交執行
    db2.SubmitChanges();
}
復制代碼

語句描述:從一個上下文提取實體,並使用 Attach 和 AttachAll 附加來自其他上下文的實體,然後更新這兩個實體,刪除一個實體,添加另一個實體。更改被提交到數據庫。

LINQ to SQL語句(13)之開放式並發控制和事務

Simultaneous Changes開放式並發控制

下表介紹 LINQ to SQL 文檔中涉及開放式並發的術語:

術語說明 並發 兩個或更多用戶同時嘗試更新同一數據庫行的情形。 並發沖突 兩個或更多用戶同時嘗試向一行的一列或多列提交沖突值的情形。 並發控制 用於解決並發沖突的技術。 開放式並發控制 先調查其他事務是否已更改了行中的值,再允許提交更改的技術。相比之下,保守式並發控制則是通過鎖定記錄來避免發生並發沖突。之所以稱作開放式控制,是因為它將一個事務干擾另一事務視為不太可能發生。 沖突解決 通過重新查詢數據庫刷新出現沖突的項,然後協調差異的過程。刷新對象時,LINQ to SQL 更改跟蹤器會保留以下數據: 最初從數據庫獲取並用於更新檢查的值 通過後續查詢獲得的新數據庫值。  LINQ to SQL 隨後會確定相應對象是否發生沖突(即它的一個或多個成員值是否已發生更改)。如果此對象發生沖突,LINQ to SQL 下一步會確定它的哪些成員發生沖突。LINQ to SQL 發現的任何成員沖突都會添加到沖突列表中。

在 LINQ to SQL 對象模型中,當以下兩個條件都得到滿足時,就會發生“開放式並發沖突”:客戶端嘗試向數據庫提交更改;數據庫中的一個或多個更新檢查值自客戶端上次讀取它們以來已得到更新。 此沖突的解決過程包括查明對象的哪些成員發生沖突,然後決定您希望如何進行處理。

開放式並發(Optimistic Concurrency)

說明:這個例子中在你讀取數據之前,另外一個用戶已經修改並提交更新了這個數據,所以不會出現沖突。

復制代碼
//我們打開一個新的連接來模擬另外一個用戶
NorthwindDataContext otherUser_db = new NorthwindDataContext();
var otherUser_product =
    otherUser_db.Products.First(p => p.ProductID == 1);
otherUser_product.UnitPrice = 999.99M;
otherUser_db.SubmitChanges();
//我們當前連接
var product = db.Products.First(p => p.ProductID == 1);
product.UnitPrice = 777.77M;
try
{
    db.SubmitChanges();//當前連接執行成功
}
catch (ChangeConflictException)
{
}
復制代碼

說明:我們讀取數據之後,另外一個用戶獲取並提交更新了這個數據,這時,我們更新這個數據時,引起了一個並發沖突。系統發生回滾,允許你可以從數據庫檢索新更新的數據,並決定如何繼續進行您自己的更新。

復制代碼
//當前用戶
var product = db.Products.First(p => p.ProductID == 1);
//我們打開一個新的連接來模擬另外一個用戶
NorthwindDataContext otherUser_db = new NorthwindDataContext() ;
var otherUser_product = 
    otherUser_db.Products.First(p => p.ProductID == 1);
otherUser_product.UnitPrice = 999.99M;
otherUser_db.SubmitChanges();
//當前用戶修改
product.UnitPrice = 777.77M;
try
{
    db.SubmitChanges();
}
catch (ChangeConflictException)
{
    //發生異常!
}
復制代碼

Transactions事務

LINQ to SQL 支持三種事務模型,分別是:

  • 顯式本地事務:調用 SubmitChanges 時,如果 Transaction 屬性設置為事務,則在同一事務的上下文中執行 SubmitChanges 調用。成功執行事務後,要由您來提交或回滾事務。與事務對應的連接必須與用於構造 DataContext 的連接匹配。如果使用其他連接,則會引發異常。
  • 顯式可分發事務:可以在當前 Transaction 的作用域中調用 LINQ to SQL API(包括但不限於 SubmitChanges)。LINQ to SQL 檢測到調用是在事務的作用域內,因而不會創建新的事務。在這種情況下,<token>vbtecdlinq</token> 還會避免關閉連接。您可以在此類事務的上下文中執行查詢和 SubmitChanges 操作。
  • 隱式事務:當您調用 SubmitChanges 時,LINQ to SQL 會檢查此調用是否在 Transaction 的作用域內或者 Transaction 屬性是否設置為由用戶啟動的本地事務。如果這兩個事務它均未找到,則 LINQ to SQL 啟動本地事務,並使用此事務執行所生成的 SQL 命令。當所有 SQL 命令均已成功執行完畢時,LINQ to SQL 提交本地事務並返回。

1.Implicit(隱式)

說明:這個例子在執行SubmitChanges()操作時,隱式地使用了事務。因為在更新2種產品的庫存數量時,第二個產品庫存數量為負數了,違反了服務器上的 CHECK 約束。這導致了更新產品全部失敗了,系統回滾到這個操作的初始狀態。

復制代碼
try
{
    Product prod1 = db.Products.First(p => p.ProductID == 4);
    Product prod2 = db.Products.First(p => p.ProductID == 5);
    prod1.UnitsInStock -= 3;
    prod2.UnitsInStock -= 5;//錯誤:庫存數量的單位不能是負數
    //要麼全部成功要麼全部失敗
    db.SubmitChanges();
}
catch (System.Data.SqlClient.SqlException e)
{
    //執行異常處理
}
復制代碼

2.Explicit(顯式)

說明:這個例子使用顯式事務。通過在事務中加入對數據的讀取以防止出現開放式並發異常,顯式事務可以提供更多的保護。如同上一個查詢中,更新 prod2 的 UnitsInStock 字段將使該字段為負值,而這違反了數據庫中的 CHECK 約束。這導致更新這兩個產品的事務失敗,此時將回滾所有更改。 

復制代碼
using (TransactionScope ts = new TransactionScope())
{
    try
    {
        Product prod1 = db.Products.First(p => p.ProductID == 4);
        Product prod2 = db.Products.First(p => p.ProductID == 5);
        prod1.UnitsInStock -= 3;
        prod2.UnitsInStock -= 5;//錯誤:庫存數量的單位不能是負數
        db.SubmitChanges();
    }
    catch (System.Data.SqlClient.SqlException e)
    {
        //執行異常處理
    }
}
復制代碼

LINQ to SQL語句(14)之Null語義和DateTime

Null語義

說明:下面第一個例子說明查詢ReportsToEmployee為null的雇員。第二個例子使用Nullable<T>.HasValue查詢雇員,其結果與第一個例子相同。在第三個例子中,使用Nullable<T>.Value來返回ReportsToEmployee不為null的雇員的ReportsTo的值。

1.Null

查找不隸屬於另一個雇員的所有雇員:

var q =
    from e in db.Employees
    where e.ReportsToEmployee == null
    select e;

2.Nullable<T>.HasValue

查找不隸屬於另一個雇員的所有雇員:

var q =
    from e in db.Employees
    where !e.ReportsTo.HasValue
    select e;

3.Nullable<T>.Value

返回前者的EmployeeID 編號。請注意.Value 為可選:

復制代碼
var q =
    from e in db.Employees
    where e.ReportsTo.HasValue
    select new
    {
        e.FirstName,
        e.LastName,
        ReportsTo = e.ReportsTo.Value
    };
復制代碼

日期函數

LINQ to SQL支持以下DateTime方法。但是,SQL Server和CLR的DateTime類型在范圍和計時周期精度上不同,如下表。

類型

最小值

最大值

計時周期

System.DateTime

0001 年 1 月 1 日

9999 年 12 月 31 日

100 毫微秒(0.0000001 秒)

T-SQL DateTime

1753 年 1 月 1 日

9999 年 12 月 31 日

3.33… 毫秒(0.0033333 秒)

T-SQL SmallDateTime

1900 年 1 月 1 日

2079 年 6 月 6 日

1 分鐘(60 秒)

CLR DateTime 類型與SQL Server類型相比,前者范圍更大、精度更高。因此來自SQL Server的數據用CLR類型表示時,絕不會損失量值或精度。但如果反過來的話,則范圍可能會減小,精度可能會降低;SQL Server日期不存在TimeZone概念,而在CLR中支持這個功能。 我們在LINQ to SQL查詢使用以當地時間、UTC 或固定時間要自己執行轉換。

下面用三個實例說明一下。

1.DateTime.Year

var q =
    from o in db.Orders
    where o.OrderDate.Value.Year == 1997
    select o;

語句描述:這個例子使用DateTime 的Year 屬性查找1997 年下的訂單。

2.DateTime.Month

var q =
    from o in db.Orders
    where o.OrderDate.Value.Month == 12
    select o;

語句描述:這個例子使用DateTime的Month屬性查找十二月下的訂單。

3.DateTime.Day

var q =
    from o in db.Orders
    where o.OrderDate.Value.Day == 31
    select o;

語句描述:這個例子使用DateTime的Day屬性查找某月 31 日下的訂單。

LINQ to SQL語句(15)之String

LINQ to SQL支持以下String方法。但是不同的是默認情況下System.String方法區分大小寫。而SQL則不區分大小寫。

1.字符串串聯(String Concatenation)

復制代碼
var q =
    from c in db.Customers
    select new
    {
        c.CustomerID,
        Location = c.City + ", " + c.Country
    };
復制代碼

語句描述:這個例子使用+運算符在形成經計算得出的客戶Location值過程中將字符串字段和字符串串聯在一起。

2.String.Length

var q =
    from p in db.Products
    where p.ProductName.Length < 10
    select p;

語句描述:這個例子使用Length屬性查找名稱短於10個字符的所有產品。

3.String.Contains(substring)

var q =
    from c in db.Customers
    where c.ContactName.Contains("Anders")
    select c;

語句描述:這個例子使用Contains方法查找所有其聯系人姓名中包含“Anders”的客戶。

4.String.IndexOf(substring)

復制代碼
var q =
    from c in db.Customers
    select new
    {
        c.ContactName,
        SpacePos = c.ContactName.IndexOf(" ")
    };
復制代碼

語句描述:這個例子使用IndexOf方法查找每個客戶聯系人姓名中出現第一個空格的位置。

5.String.StartsWith(prefix)

var q =
    from c in db.Customers
    where c.ContactName.StartsWith("Maria")
    select c;

語句描述:這個例子使用StartsWith方法查找聯系人姓名以“Maria”開頭的客戶。

6.String.EndsWith(suffix)

var q =
    from c in db.Customers
    where c.ContactName.EndsWith("Anders")
    select c;

語句描述:這個例子使用EndsWith方法查找聯系人姓名以“Anders”結尾的客戶。

7.String.Substring(start)

var q =
    from p in db.Products
    select p.ProductName.Substring(3);

語句描述:這個例子使用Substring方法返回產品名稱中從第四個字母開始的部分。

8.String.Substring(start, length)

var q =
    from e in db.Employees
    where e.HomePhone.Substring(6, 3) == "555"
    select e;

語句描述:這個例子使用Substring方法查找家庭電話號碼第七位到第九位是“555”的雇員。  

9.String.ToUpper()

復制代碼
var q =
    from e in db.Employees
    select new
    {
        LastName = e.LastName.ToUpper(),
        e.FirstName
    };
復制代碼

語句描述:這個例子使用ToUpper方法返回姓氏已轉換為大寫的雇員姓名。

10.String.ToLower()

var q =
    from c in db.Categories
    select c.CategoryName.ToLower();

語句描述:這個例子使用ToLower方法返回已轉換為小寫的類別名稱。

11.String.Trim()

var q =
    from e in db.Employees
    select e.HomePhone.Substring(0, 5).Trim();

語句描述:這個例子使用Trim方法返回雇員家庭電話號碼的前五位,並移除前導和尾隨空格。

12.String.Insert(pos, str)

var q =
    from e in db.Employees
    where e.HomePhone.Substring(4, 1) == ")"
    select e.HomePhone.Insert(5, ":");

語句描述:這個例子使用Insert方法返回第五位為 ) 的雇員電話號碼的序列,並在 ) 後面插入一個 :。

13.String.Remove(start)

var q =
    from e in db.Employees
    where e.HomePhone.Substring(4, 1) == ")"
    select e.HomePhone.Remove(9);

語句描述:這個例子使用Remove方法返回第五位為 ) 的雇員電話號碼的序列,並移除從第十個字符開始的所有字符。

14.String.Remove(start, length)

var q =
    from e in db.Employees
    where e.HomePhone.Substring(4, 1) == ")"
    select e.HomePhone.Remove(0, 6);

語句描述:這個例子使用Remove方法返回第五位為 ) 的雇員電話號碼的序列,並移除前六個字符。

15.String.Replace(find, replace)

復制代碼
var q =
    from s in db.Suppliers
    select new
    {
        s.CompanyName,
        Country = s.Country
        .Replace("UK", "United Kingdom")
        .Replace("USA", "United States of America")
    };
復制代碼

語句描述:這個例子使用 Replace 方法返回 Country 字段中UK 被替換為 United Kingdom 以及USA 被替換為 United States of America 的供應商信息。

LINQ to SQL語句(16)之對象標識

對象標識

  • 運行庫中的對象具有唯一標識。引用同一對象的兩個變量實際上是引用此對象的同一實例。你更改一個變量後,可以通過另一個變量看到這些更改。
  • 關系數據庫表中的行不具有唯一標識。由於每一行都具有唯一的主鍵,因此任何兩行都不會共用同一鍵值。

     實際上,通常我們是將數據從數據庫中提取出來放入另一層中,應用程序在該層對數據進行處理。這就是 LINQ to SQL 支持的模型。將數據作為行從數據庫中提取出來時,你不期望表示相同數據的兩行實際上對應於相同的行實例。如果您查詢特定客戶兩次,您將獲得兩行數據。每一行包含相同的信息。

     對於對象。你期望在你反復向 DataContext 索取相同的信息時,它實際上會為你提供同一對象實例。你將它們設計為層次結構或關系圖。你希望像檢索實物一樣檢索它們,而不希望僅僅因為你多次索要同一內容而收到大量的復制實例。

     在 LINQ to SQL 中,DataContext 管理對象標識。只要你從數據庫中檢索新行,該行就會由其主鍵記錄到標識表中,並且會創建一個新的對象。只要您檢索該行,就會將原始對象實例傳遞回應用程序。通過這種方式,DataContext 將數據庫看到的標識(即主鍵)的概念轉換成相應語言看到的標識(即實例)的概念。應用程序只看到處於第一次檢索時的狀態的對象。新數據如果不同,則會被丟棄。

      LINQ to SQL 使用此方法來管理本地對象的完整性,以支持開放式更新。由於在最初創建對象後唯一發生的更改是由應用程序做出的,因此應用程序的意向是很明確的。如果在中間階段外部某一方做了更改,則在調用 SubmitChanges() 時會識別出這些更改。

      以上來自MSDN,的確,看了有點“正規”,下面我用兩個例子說明一下。

對象緩存

在第一個示例中,如果我們執行同一查詢兩次,則每次都會收到對內存中同一對象的引用。很明顯,cust1和cust2是同一個對象引用。

Customer cust1 = db.Customers.First(c => c.CustomerID == "BONAP");
Customer cust2 = db.Customers.First(c => c.CustomerID == "BONAP");

下面的示例中,如果您執行返回數據庫中同一行的不同查詢,則您每次都會收到對內存中同一對象的引用。cust1和cust2是同一個對象引用,但是數據庫查詢了兩次。

復制代碼
Customer cust1 = db.Customers.First(c => c.CustomerID == "BONAP");
Customer cust2 = (
    from o in db.Orders
    where o.Customer.CustomerID == "BONAP"
    select o )
    .First()
    .Customer;
復制代碼

LINQ to SQL語句(17)之對象加載

對象加載

延遲加載

      在查詢某對象時,實際上你只查詢該對象。不會同時自動獲取這個對象。這就是延遲加載。

      例如,您可能需要查看客戶數據和訂單數據。你最初不一定需要檢索與每個客戶有關的所有訂單數據。其優點是你可以使用延遲加載將額外信息的檢索操作延遲到你確實需要檢索它們時再進行。請看下面的示例:檢索出來CustomerID,就根據這個ID查詢出OrderID。

復制代碼
var custs =
     from c in db.Customers
     where c.City == "Sao Paulo"
     select c;
//上面的查詢句法不會導致語句立即執行,僅僅是一個描述性的語句,
只有需要的時候才會執行它
foreach (var cust in custs)
{
    foreach (var ord in cust.Orders)
    {
        //同時查看客戶數據和訂單數據
    }
}
復制代碼

語句描述:原始查詢未請求數據,在所檢索到各個對象的鏈接中導航如何能導致觸發對數據庫的新查詢。

預先加載:LoadWith 方法

     你如果想要同時查詢出一些對象的集合的方法。LINQ to SQL 提供了 DataLoadOptions用於立即加載對象。方法包括: LoadWith 方法,用於立即加載與主目標相關的數據。 AssociateWith 方法,用於篩選為特定關系檢索到的對象。

     使用 LoadWith方法指定應同時檢索與主目標相關的哪些數據。例如,如果你知道你需要有關客戶的訂單的信息,則可以使用 LoadWith 來確保在檢索客戶信息的同時檢索訂單信息。使用此方法可僅訪問一次數據庫,但同時獲取兩組信息。 在下面的示例中,我們通過設置DataLoadOptions,來指示DataContext在加載Customers的同時把對應的Orders一起加載,在執行查詢時會檢索位於Sao Paulo的所有 Customers 的所有 Orders。這樣一來,連續訪問 Customer 對象的 Orders 屬性不會觸發新的數據庫查詢。在執行時生成的SQL語句使用了左連接。

復制代碼
NorthwindDataContext db = new NorthwindDataContext();
DataLoadOptions ds = new DataLoadOptions();
ds.LoadWith<Customer>(p => p.Orders);
db.LoadOptions = ds;
var custs = (
     from c in db2.Customers
     where c.City == "Sao Paulo"
     select c);
foreach (var cust in custs)
{
    foreach (var ord in cust.Orders)
    {
        Console.WriteLine("CustomerID {0} has an OrderID {1}.",
            cust.CustomerID,
            ord.OrderID);
    }
}
復制代碼

語句描述:在原始查詢過程中使用 LoadWith 請求相關數據,以便稍後在檢索到的各個對象中導航時不需要對數據庫進行額外的往返。

延遲加載:AssociateWith方法

     使用 AssociateWith 方法指定子查詢以限制檢索的數據量。 在下面的示例中,AssociateWith 方法將檢索的 Orders 限制為當天尚未裝運的那些 Orders。如果沒有此方法,則會檢索所有 Orders,即使只需要一個子集。但是生成SQL語句會發現生成了很多SQL語句。

復制代碼
NorthwindDataContext db2 = new NorthwindDataContext();
DataLoadOptions ds = new DataLoadOptions();
ds.AssociateWith<Customer>(
     p => p.Orders.Where(o => o.ShipVia > 1));
db2.LoadOptions = ds;
var custs =
     from c in db2.Customers
     where c.City == "London"
     select c;
foreach (var cust in custs)
{
    foreach (var ord in cust.Orders)
    {
        foreach (var orderDetail in ord.OrderDetails)
        {
           //可以查詢出cust.CustomerID, ord.OrderID, ord.ShipVia,
           //orderDetail.ProductID, orderDetail.Product.ProductName
        }
    }
}
復制代碼

語句描述:原始查詢未請求數據,在所檢索到各個對象的鏈接中導航如何以觸發對數據庫的新查詢而告終。此示例還說明在延遲加載關系對象時可以使用 Assoicate With 篩選它們。  

預先加載:LoadWith方法和Associate With方法

     這個例子說明:使用LoadWith方法來確保在檢索客戶信息的同時檢索訂單信息,在檢索訂單信息的同時檢索訂單詳細信息, 僅僅訪問一次數據庫。即可以在一個查詢中檢索許多對象。使用Associate With方法來限制訂單詳細信息的排序規則。

復制代碼
NorthwindDataContext db2 = new NorthwindDataContext();
DataLoadOptions ds = new DataLoadOptions();
ds.LoadWith<Customer>(p => p.Orders);
ds.LoadWith<Order>(p => p.OrderDetails);
ds.AssociateWith<Order>(
     p => p.OrderDetails.OrderBy(o => o.Quantity));
db2.LoadOptions = ds;
var custs = (
     from c in db2.Customers
     where c.City == "London"
     select c);
foreach (var cust in custs)
{
    foreach (var ord in cust.Orders)
    {
        foreach (var orderDetail in ord.OrderDetails)
        {
           //查詢cust.CustomerID, ord.OrderID
           //orderDetail.ProductID, orderDetail.Quantity
        }
    }
}
復制代碼

語句描述:在原始查詢過程中使用 LoadWith 請求相關數據,以便稍後在檢索到的各個對象中導航時此示例還說明在急切加載關系對象時可以使用 Assoicate With 對它們進行排序。

加載重寫

這個例子在Category類裡提供了一個LoadProducts分部方法。當產品的類別被加載的時候,就直接優先調用了LoadProducts方法來查詢沒有貨源的產品。

復制代碼
private IEnumerable<Product> LoadProducts(Category category)
{
    //在執行LINQ to SQL的時候,這個LoadProducts分部方法
    //優先加載執行,這裡用存儲過程也可以. 
    return this.Products
        .Where(p => p.CategoryID == category.CategoryID)
        .Where(p => !p.Discontinued);
}
復制代碼

執行下面的查詢時,利用上面方法返回的數據進行下面的操作:

復制代碼
NorthwindDataContext db2 = new NorthwindDataContext();
DataLoadOptions ds = new DataLoadOptions();
ds.LoadWith<Category>(p => p.Products);
db2.LoadOptions = ds;
var q = (
     from c in db2.Categories
     where c.CategoryID < 3
     select c);
foreach (var cat in q)
{
    foreach (var prod in cat.Products)
    {
       //查詢cat.CategoryID, prod.ProductID
    }
}
復制代碼

語句描述:重寫 Category 類中的分部方法 LoadProducts。加載某種類別的產品時,調用 LoadProducts 以加載此類別中未停產的產品。

LINQ to SQL語句(18)之運算符轉換

運算符轉換

1.AsEnumerable:將類型轉換為泛型 IEnumerable

     使用 AsEnumerable<TSource> 可返回類型化為泛型 IEnumerable 的參數。在此示例中,LINQ to SQL(使用默認泛型 Query)會嘗試將查詢轉換為 SQL 並在服務器上執行。但 where 子句引用用戶定義的客戶端方法 (isValidProduct),此方法無法轉換為 SQL。 解決方法是指定 where 的客戶端泛型 IEnumerable<T> 實現以替換泛型 IQueryable<T>。可通過調用 AsEnumerable<TSource>運算符來執行此操作。

var q =
    from p in db.Products.AsEnumerable()
    where isValidProduct(p)
    select p;

語句描述:這個例子就是使用AsEnumerable以便使用Where的客戶端IEnumerable實現,而不是默認的IQueryable將在服務器上轉換為SQL並執行的默認Query<T>實現。這很有必要,因為Where子句引用了用戶定義的客戶端方法isValidProduct,該方法不能轉換為SQL。

2.ToArray:將序列轉換為數組

使用 ToArray <TSource>可從序列創建數組。

var q =
    from c in db.Customers
    where c.City == "London"
    select c;
Customer[] qArray = q.ToArray();

語句描述:這個例子使用 ToArray 將查詢直接計算為數組。

3.ToList:將序列轉換為泛型列表

使用 ToList<TSource>可從序列創建泛型列表。下面的示例使用 ToList<TSource>直接將查詢的計算結果放入泛型 List<T>。

var q =
    from e in db.Employees
    where e.HireDate >= new DateTime(1994, 1, 1)
    select e;
List<Employee> qList = q.ToList();

4.ToDictionary:將序列轉化為字典

     使用Enumerable.ToDictionary<TSource, TKey>方法可以將序列轉化為字典。TSource表示source中的元素的類型;TKey表示keySelector返回的鍵的類型。其返回一個包含鍵和值的Dictionary<TKey, TValue>。

復制代碼
var q =
    from p in db.Products
    where p.UnitsInStock <= p.ReorderLevel && !p.Discontinued
    select p;
Dictionary<int, Product> qDictionary =
    q.ToDictionary(p => p.ProductID);
foreach (int key in qDictionary.Keys)
{
    Console.WriteLine(key);
}
復制代碼

語句描述:這個例子使用 ToDictionary 將查詢和鍵表達式直接鍵表達式直接計算為 Dictionary<K, T>。

LINQ to SQL語句(19)之ADO.NET與LINQ to SQL

它基於由 ADO.NET 提供程序模型提供的服務。因此,我們可以將 LINQ to SQL 代碼與現有的 ADO.Net 應用程序混合在一起,將當前 ADO.NET 解決方案遷移到 LINQ to SQL。

1.連接

     在創建 LINQ to SQL DataContext 時,可以提供現有 ADO.NET 連接。對 DataContext 的所有操作(包括查詢)都使用所提供的這個連接。如果此連接已經打開,則在您使用完此連接時,LINQ to SQL 會保持它的打開狀態不變。我們始終可以訪問此連接,另外還可以使用 Connection 屬性自行關閉它。

復制代碼
//新建一個標准的ADO.NET連接:
SqlConnection nwindConn = new SqlConnection(connString);
nwindConn.Open();
// ... 其它的ADO.NET數據操作代碼... //
//利用現有的ADO.NET連接來創建一個DataContext:
Northwind interop_db = new Northwind(nwindConn);
var orders =
     from o in interop_db.Orders
     where o.Freight > 500.00M
     select o;
//返回Freight>500.00M的訂單
nwindConn.Close();
復制代碼

語句描述:這個例子使用預先存在的ADO.NET連接創建Northwind對象,本例中的查詢返回運費至少為500.00 的所有訂單。

2.事務

      當我們已經啟動了自己的數據庫事務並且我們希望DataContext 包含在內時,我們可以向 DataContext 提供此事務。 通過 .NET Framework 創建事務的首選方法是使用 TransactionScope 對象。通過使用此方法,我們可以創建跨數據庫及其他駐留在內存中的資源管理器執行的分布式事務。事務范圍幾乎不需要資源就可以啟動。它們僅在事務范圍內存在多個連接時才將自身提升為分布式事務。

using (TransactionScope ts = new TransactionScope())
{
    db.SubmitChanges();
    ts.Complete();
}

注意:不能將此方法用於所有數據庫。例如,SqlClient 連接在針對 SQL Server 2000 服務器使用時無法提升系統事務。它采取的方法是,只要它發現有使用事務范圍的情況,它就會自動向完整的分布式事務登記。

下面用一個例子說明一下事務的使用方法。在這裡,也說明了重用 ADO.NET 命令和 DataContext 之間的同一連接。

復制代碼
var q =
     from p in db.Products
     where p.ProductID == 3
     select p;
//使用LINQ to SQL查詢出來
//新建一個標准的ADO.NET連接:
SqlConnection nwindConn = new SqlConnection(connString);
nwindConn.Open();
//利用現有的ADO.NET連接來創建一個DataContext:
Northwind interop_db = new Northwind(nwindConn);
SqlTransaction nwindTxn = nwindConn.BeginTransaction();
try
{
    SqlCommand cmd = new SqlCommand("UPDATE Products SET"
    +"QuantityPerUnit = 'single item' WHERE ProductID = 3");
    cmd.Connection = nwindConn;
    cmd.Transaction = nwindTxn;
    cmd.ExecuteNonQuery();
    interop_db.Transaction = nwindTxn;
    Product prod1 = interop_db.Products.First(p => p.ProductID == 4);
    Product prod2 = interop_db.Products.First(p => p.ProductID == 5);
    prod1.UnitsInStock -= 3;
    prod2.UnitsInStock -= 5;//這有一個錯誤,不能為負數
    interop_db.SubmitChanges();
    nwindTxn.Commit();
}
catch (Exception e)
{
    //如果有一個錯誤,所有的操作回滾
    Console.WriteLine(e.Message);
}
nwindConn.Close();
復制代碼

語句描述:這個例子使用預先存在的 ADO.NET 連接創建 Northwind 對象,然後與此對象共享一個 ADO.NET 事務。此事務既用於通過 ADO.NET 連接執行 SQL 命令,又用於通過 Northwind 對象提交更改。當事務因違反 CHECK 約束而中止時,將回滾所有更改,包括通過 SqlCommand 做出的更改,以及通過Northwind 對象做出的更改。  

3.直接執行SQL語句

1.直接執行SQL查詢

如果 LINQ to SQL 查詢不足以滿足專門任務的需要,我們可以使用 ExecuteQuery 方法來執行 SQL 查詢,然後將查詢的結果直接轉換成對象。

復制代碼
var products = db.ExecuteQuery<Product>(
    "SELECT [Product List].ProductID,"+
    "[Product List].ProductName " +
    "FROM Products AS [Product List] " +
    "WHERE [Product List].Discontinued = 0 " +
    "ORDER BY [Product List].ProductName;"
);
復制代碼

語句描述:這個例子使用ExecuteQuery<T>執行任意 SQL 查詢,並將所得的行映射為 Product 對象的序列。

2.直接執行SQL命令

采用DataContext 連接時,可以使用ExecuteCommand來執行不返回對象的 SQL 命令。

db.ExecuteCommand
    ("UPDATE Products SET UnitPrice = UnitPrice + 1.00");

語句描述:使用ExecuteCommand執行任意SQL命令,本例中為將所有產品單價提高 1.00 的批量更新。

LINQ to SQL語句(20)之存儲過程

在我們編寫程序中,往往需要一些存儲過程,在LINQ to SQL中怎麼使用呢?也許比原來的更簡單些。下面我們以NORTHWND.MDF數據庫中自帶的幾個存儲過程來理解一下。

1.標量返回

在數據庫中,有名為Customers Count By Region的存儲過程。該存儲過程返回顧客所在"WA"區域的數量。

復制代碼
ALTER PROCEDURE [dbo].[NonRowset]
    (@param1 NVARCHAR(15))
AS
BEGIN
    SET NOCOUNT ON;
     DECLARE @count int
     SELECT @count = COUNT(*)FROM Customers 
     WHERECustomers.Region = @Param1
     RETURN @count
END
復制代碼

我們只要把這個存儲過程拖到O/R設計器內,它自動生成了以下代碼段:

復制代碼
[Function(Name = "dbo.[Customers Count By Region]")]
public int Customers_Count_By_Region([Parameter
(DbType = "NVarChar(15)")] string param1)
{
    IExecuteResult result = this.ExecuteMethodCall(this,
    ((MethodInfo)(MethodInfo.GetCurrentMethod())), param1);
    return ((int)(result.ReturnValue));
}
復制代碼

我們需要時,直接調用就可以了,例如:

int count = db.CustomersCountByRegion("WA");
Console.WriteLine(count);

語句描述:這個實例使用存儲過程返回在“WA”地區的客戶數。

2.單一結果集

從數據庫中返回行集合,並包含用於篩選結果的輸入參數。 當我們執行返回行集合的存儲過程時,會用到結果類,它存儲從存儲過程中返回的結果。

下面的示例表示一個存儲過程,該存儲過程返回客戶行並使用輸入參數來僅返回將“London”列為客戶城市的那些行的固定幾列。 

復制代碼
ALTER PROCEDURE [dbo].[Customers By City]
     -- Add the parameters for the stored procedure here
     (@param1 NVARCHAR(20))
AS
BEGIN
     -- SET NOCOUNT ON added to prevent extra result sets from
     -- interfering with SELECT statements.
     SET NOCOUNT ON;
     SELECT CustomerID, ContactName, CompanyName, City from 
     Customers as c where c.City=@param1
END
復制代碼

拖到O/R設計器內,它自動生成了以下代碼段:

復制代碼
[Function(Name="dbo.[Customers By City]")]
public ISingleResult<Customers_By_CityResult> Customers_By_City(
[Parameter(DbType="NVarChar(20)")] string param1)
{
    IExecuteResult result = this.ExecuteMethodCall(this, (
    (MethodInfo)(MethodInfo.GetCurrentMethod())), param1);
    return ((ISingleResult<Customers_By_CityResult>)
    (result.ReturnValue));
}
復制代碼

我們用下面的代碼調用:

復制代碼
ISingleResult<Customers_By_CityResult> result =
 db.Customers_By_City("London");
foreach (Customers_By_CityResult cust in result)
{
    Console.WriteLine("CustID={0}; City={1}", cust.CustomerID,
        cust.City);
}
復制代碼

語句描述:這個實例使用存儲過程返回在倫敦的客戶的 CustomerID和City。  

3.多個可能形狀的單一結果集

當存儲過程可以返回多個結果形狀時,返回類型無法強類型化為單個投影形狀。盡管 LINQ to SQL 可以生成所有可能的投影類型,但它無法獲知將以何種順序返回它們。 ResultTypeAttribute 屬性適用於返回多個結果類型的存儲過程,用以指定該過程可以返回的類型的集合。

在下面的 SQL 代碼示例中,結果形狀取決於輸入(param1 = 1或param1 = 2)。我們不知道先返回哪個投影。

復制代碼
ALTER PROCEDURE [dbo].[SingleRowset_MultiShape]
     -- Add the parameters for the stored procedure here
     (@param1 int )
AS
BEGIN
     -- SET NOCOUNT ON added to prevent extra result sets from
     -- interfering with SELECT statements.
     SET NOCOUNT ON;
     if(@param1 = 1)
     SELECT * from Customers as c where c.Region = 'WA'
     else if (@param1 = 2)
     SELECT CustomerID, ContactName, CompanyName from 
     Customers as c where c.Region = 'WA'
END
復制代碼

拖到O/R設計器內,它自動生成了以下代碼段:

復制代碼
[Function(Name="dbo.[Whole Or Partial Customers Set]")]
public ISingleResult<Whole_Or_Partial_Customers_SetResult> 
Whole_Or_Partial_Customers_Set([Parameter(DbType="Int")] 
System.Nullable<int> param1)
{
    IExecuteResult result = this.ExecuteMethodCall(this, 
    ((MethodInfo)(MethodInfo.GetCurrentMethod())), param1);
    return ((ISingleResult<Whole_Or_Partial_Customers_SetResult>)
    (result.ReturnValue));
}
復制代碼

但是,VS2008會把多結果集存儲過程識別為單結果集的存儲過程,默認生成的代碼我們要手動修改一下,要求返回多個結果集,像這樣:

復制代碼
[Function(Name="dbo.[Whole Or Partial Customers Set]")]
[ResultType(typeof(WholeCustomersSetResult))]
[ResultType(typeof(PartialCustomersSetResult))]
public IMultipleResults Whole_Or_Partial_Customers_Set([Parameter
(DbType="Int")] System.Nullable<int> param1)
{
    IExecuteResult result = this.ExecuteMethodCall(this, 
    ((MethodInfo)(MethodInfo.GetCurrentMethod())), param1);
    return ((IMultipleResults)(result.ReturnValue));
}
復制代碼

我們分別定義了兩個分部類,用於指定返回的類型。WholeCustomersSetResult類 如下:

PartialCustomersSetResult類 如下:

這樣就可以使用了,下面代碼直接調用,分別返回各自的結果集合。

復制代碼
//返回全部Customer結果集
IMultipleResults result = db.Whole_Or_Partial_Customers_Set(1);
IEnumerable<WholeCustomersSetResult> shape1 =
 result.GetResult<WholeCustomersSetResult>();
foreach (WholeCustomersSetResult compName in shape1)
{
    Console.WriteLine(compName.CompanyName);
}
//返回部分Customer結果集
result = db.Whole_Or_Partial_Customers_Set(2);
IEnumerable<PartialCustomersSetResult> shape2 =
 result.GetResult<PartialCustomersSetResult>();
foreach (PartialCustomersSetResult con in shape2)
{
    Console.WriteLine(con.ContactName);
}
復制代碼

語句描述:這個實例使用存儲過程返回“WA”地區中的一組客戶。返回的結果集形狀取決於傳入的參數。如果參數等於 1,則返回所有客戶屬性。如果參數等於2,則返回ContactName屬性。  

4.多個結果集

這種存儲過程可以生成多個結果形狀,但我們已經知道結果的返回順序。

下面是一個按順序返回多個結果集的存儲過程Get Customer And Orders。 返回顧客ID為"SEVES"的顧客和他們所有的訂單。

復制代碼
ALTER PROCEDURE [dbo].[Get Customer And Orders]
(@CustomerID nchar(5))
    -- Add the parameters for the stored procedure here
AS
BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;
    SELECT * FROM Customers AS c WHERE c.CustomerID = @CustomerID  
    SELECT * FROM Orders AS o WHERE o.CustomerID = @CustomerID
END
復制代碼

拖到設計器代碼如下:

復制代碼
[Function(Name="dbo.[Get Customer And Orders]")]
public ISingleResult<Get_Customer_And_OrdersResult>
Get_Customer_And_Orders([Parameter(Name="CustomerID",
DbType="NChar(5)")] string customerID)
{
     IExecuteResult result = this.ExecuteMethodCall(this,
     ((MethodInfo)(MethodInfo.GetCurrentMethod())), customerID);
     return ((ISingleResult<Get_Customer_And_OrdersResult>)
     (result.ReturnValue));
}
復制代碼

同樣,我們要修改自動生成的代碼:

復制代碼
[Function(Name="dbo.[Get Customer And Orders]")]
[ResultType(typeof(CustomerResultSet))]
[ResultType(typeof(OrdersResultSet))]
public IMultipleResults Get_Customer_And_Orders
([Parameter(Name="CustomerID",DbType="NChar(5)")]
string customerID)
{
    IExecuteResult result = this.ExecuteMethodCall(this,
    ((MethodInfo)(MethodInfo.GetCurrentMethod())), customerID);
    return ((IMultipleResults)(result.ReturnValue));
}
復制代碼

同樣,自己手寫類,讓其存儲過程返回各自的結果集。

CustomerResultSet類

OrdersResultSet類

這時,只要調用就可以了。

復制代碼
IMultipleResults result = db.Get_Customer_And_Orders("SEVES");
//返回Customer結果集
IEnumerable<CustomerResultSet> customer = 
result.GetResult<CustomerResultSet>();
//返回Orders結果集
IEnumerable<OrdersResultSet> orders = 
 result.GetResult<OrdersResultSet>();
//在這裡,我們讀取CustomerResultSet中的數據
foreach (CustomerResultSet cust in customer)
{
    Console.WriteLine(cust.CustomerID);
}
復制代碼

語句描述:這個實例使用存儲過程返回客戶“SEVES”及其所有訂單。  

5.帶輸出參數

LINQ to SQL 將輸出參數映射到引用參數,並且對於值類型,它將參數聲明為可以為 null。

下面的示例帶有單個輸入參數(客戶 ID)並返回一個輸出參數(該客戶的總銷售額)。

復制代碼
ALTER PROCEDURE [dbo].[CustOrderTotal] 
@CustomerID nchar(5),
@TotalSales money OUTPUT
AS
SELECT @TotalSales = SUM(OD.UNITPRICE*(1-OD.DISCOUNT) * OD.QUANTITY)
FROM ORDERS O, "ORDER DETAILS" OD
where O.CUSTOMERID = @CustomerID AND O.ORDERID = OD.ORDERID
復制代碼

其生成代碼如下:

復制代碼
[Function(Name="dbo.CustOrderTotal")]
public int CustOrderTotal(
[Parameter(Name="CustomerID", DbType="NChar(5)")]string customerID,
[Parameter(Name="TotalSales", DbType="Money")]
  ref System.Nullable<decimal> totalSales)
{
    IExecuteResult result = this.ExecuteMethodCall(this,
    ((MethodInfo)(MethodInfo.GetCurrentMethod())),
    customerID, totalSales);
    totalSales = ((System.Nullable<decimal>)
    (result.GetParameterValue(1)));
    return ((int)(result.ReturnValue));
}
復制代碼

我們使用下面的語句調用此存儲過程:注意:輸出參數是按引用傳遞的,以支持參數為“in/out”的方案。在這種情況下,參數僅為“out”。

decimal? totalSales = 0;
string customerID = "ALFKI";
db.CustOrderTotal(customerID, ref totalSales);
Console.WriteLine("Total Sales for Customer '{0}' = {1:C}", 
customerID, totalSales);

語句描述:這個實例使用返回 Out 參數的存儲過程。

好了,就說到這裡了,其增刪改操作同理。相信大家通過這5個實例理解了存儲過程。

文章很好,看看都想轉載,於是轉了!感謝您的耐心閱讀。

@陳臥龍的博客

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved