共用方式為


LINQ to SQL:關聯式資料的 .NET Language-Integrated Query

 

擷拉夫文、Luca Georgeese、Matt Warren、Anders Hejlsberg、Kit George

2007 年 3 月

適用於:
   Visual Studio Code名稱 「Orcas」
   .Net Framework 3.5

摘要:LINQ to SQL提供執行時間基礎結構,以物件方式管理關聯式資料,而不會遺失查詢的能力。 您的應用程式可以自由地操作物件,而LINQ to SQL會自動留在背景追蹤您的變更。 (119 個列印頁面)

目錄

簡介
快速導覽
   建立實體類別
   The DataCoNtext
   定義關聯性
   跨關聯性查詢
   修改和儲存實體
查詢In-Depth
   查詢執行
   物件識別
   關聯性
   聯結
   投影
   編譯的查詢
   SQL 翻譯
實體生命週期
   追蹤變更
   提交變更
   同時變更
   交易
   預存程序
實體類別In-Depth
   使用屬性
   圖形一致性
   變更通知
   繼承
進階主題
   建立資料庫
   與 ADO.NET 交互操作
   變更衝突解決
   預存程式調用
   實體類別產生器工具
   產生器工具 DBML 參考
   多層式實體
   外部對應
   NET Framework 函式支援和附注
   偵錯支援

簡介

現今撰寫的大部分程式會以一種方式或另一種方式運算元據,而且通常此資料會儲存在關係資料庫中。 不過,新式程式設計語言和資料庫在代表及操作資訊的方式之間有很大的差異。 這種不相符的情況以多種方式顯示。 最值得注意的是,程式設計語言會透過需要將查詢指定為文字字串的 API 來存取資料庫中的資訊。 這些查詢是程式邏輯的重要部分。 但語言不透明,無法受益于編譯時間驗證和設計階段功能,例如 IntelliSense。

當然,差異遠遠高於該差異。 資訊表示方式-資料模型在兩者之間相當不同。 新式程式設計語言會以物件形式定義資訊。 關係資料庫會使用資料列。 物件具有唯一的身分識別,因為每個實例實際上與另一個實例不同。 資料列是由主鍵值所識別。 物件具有可識別實例並連結在一起的參考。 資料列會刻意相異,需要使用外鍵將相關的資料列鬆散系結在一起。 只要物件仍由另一個物件參考,就獨立存在。 資料列會以資料表元素的形式存在,一旦移除它們就會消失。

不知道預期要橋接此差距的應用程式很難建置和維護。 它當然會簡化方程式,以移除一端或另一端。 但關係資料庫提供長期儲存和查詢處理的重要基礎結構,而新式程式設計語言對於敏捷式開發和豐富的計算而言相當重要。

到目前為止,應用程式開發人員已是應用程式開發人員的工作,可個別解決此不相符的情況。 到目前為止,最佳解決方案是詳細的資料庫抽象層,可讓應用程式領域特定物件模型與資料庫的表格式表示之間傳遞資訊,並透過每個方式重新調整和重新格式化資料。 不過,藉由遮蔽真正的資料來源,這些解決方案最終會捨棄關係資料庫最吸引人的功能;查詢資料的能力。

LINQ to SQL,Visual Studio Code名稱 「Orcas」 的元件,提供執行時間基礎結構來管理關聯式資料做為物件,而不會遺失查詢的能力。 其作法是將語言整合查詢轉譯成 SQL 以供資料庫執行,然後將表格式結果轉譯回您定義的物件。 然後,您的應用程式可以自由地操作物件,而LINQ to SQL會自動留在背景追蹤您的變更。

  • LINQ to SQL的設計目的是要對您的應用程式不具干擾性。
    • 您可以透過分次方式將目前的 ADO.NET 解決方案移轉至LINQ to SQL, (共用相同連線和交易) ,因為LINQ to SQL只是 ADO.NET 系列中的其他元件。 LINQ to SQL也對預存程式具有廣泛的支援,允許重複使用現有的企業資產。
  • LINQ to SQL應用程式很容易開始使用。
    • 連結至關聯式資料的物件可以像一般物件一樣定義,只以屬性裝飾,以識別屬性對應至資料行的方式。 當然,甚至不需要手動執行此動作。 提供設計階段工具,可將預先存在的關係資料庫架構自動轉譯為您的物件定義。

一起,LINQ to SQL執行時間基礎結構和設計階段工具可大幅減少資料庫應用程式開發人員的工作負載。 下列章節提供如何使用LINQ to SQL來執行一般資料庫相關工作的概觀。 假設讀者熟悉 Language-Integrated Query 和標準查詢運算子。

LINQ to SQL與語言無關。 任何建置為提供Language-Integrated查詢的語言,都可以使用它來存取儲存在關係資料庫中的資訊。 本檔中的範例會顯示在 C# 和 Visual Basic 中;LINQ to SQL也可以搭配啟用 LINQ 的 Visual Basic 編譯器版本使用。

快速導覽

建置LINQ to SQL應用程式的第一個步驟是宣告您將用來代表應用程式資料的物件類別。 讓我們逐步解說範例。

建立實體類別

我們將從簡單的類別 Customer 開始,並將它與 Northwind 範例資料庫中的客戶資料表產生關聯。 若要這樣做,我們只需要將自訂屬性套用至類別宣告的頂端。 LINQ to SQL會為此目的定義Table屬性。

C#

[Table(Name="Customers")]
public class Customer
{
   public string CustomerID;
   public string City;
}

Visual Basic

<Table(Name:="Customers")> _
Public Class Customer
   Public CustomerID As String
   Public City As String
End Class

Table屬性具有Name屬性,可用來指定資料庫資料表的確切名稱。 如果未提供Name屬性,LINQ to SQL會假設資料庫資料表的名稱與 類別相同。 只有宣告為數據表的類別實例會儲存在資料庫中。 這些類別類型的實例稱為 實體。 類別本身稱為 實體類別

除了將類別關聯至資料表之外,您還需要表示您想要與資料庫資料行相關聯的每個欄位或屬性。 為此,LINQ to SQL定義Column屬性。

C#

[Table(Name="Customers")]
public class Customer
{
   [Column(IsPrimaryKey=true)]
   public string CustomerID;
   [Column]
   public string City;
}

Visual Basic

<Table(Name:="Customers")> _
Public Class Customer
   <Column(IsPrimaryKey:=true)> _
   Public CustomerID As String

   <Column> _
   Public City As String

End Class

Column屬性具有各種不同的屬性,可用來自訂欄位與資料庫資料行之間的確切對應。 注意的其中一個屬性是 Id 屬性。 它會告訴LINQ to SQL資料庫資料行是資料表中主鍵的一部分。

如同 Table 屬性,只有在 Column 屬性與可從欄位或屬性宣告推斷的內容不同時,才需要在 Column 屬性中提供資訊。 在此範例中,您必須告訴LINQ to SQL CustomerID欄位是資料表中主鍵的一部分,但您不需要指定確切的名稱或類型。

只有宣告為數據行的欄位和屬性會保存到資料庫或從資料庫中擷取。 其他專案會被視為應用程式邏輯的暫時性部分。

The DataCoNtext

DataCoNtext是您從資料庫擷取物件並重新提交變更的主要管道。 您可以使用與使用 ADO.NET Connection 相同的方式使用它。 事實上, DataCoNtext 會以您提供的連接字串或連接字串初始化。 DataCoNtext的目的是將您的物件要求轉譯成對資料庫進行的 SQL 查詢,然後將物件組合出結果。 DataCoNtext藉由實作與標準查詢運算子相同的運算子模式,例如WhereSelect,來啟用語言整合查詢。

例如,您可以使用 DataCoNtext 來擷取其城市為倫敦的客戶物件,如下所示:

C#

// DataContext takes a connection string 
DataContext db = new   DataContext("c:\\northwind\\northwnd.mdf");
// Get a typed table to run queries
Table<Customer> Customers = db.GetTable<Customer>();
// Query for customers from London
var q =
   from c in Customers
   where c.City == "London"
   select c;
foreach (var cust in q)
   Console.WriteLine("id = {0}, City = {1}", cust.CustomerID, cust.City);

Visual Basic

' DataContext takes a connection string 
Dim db As DataContext  = New DataContext("c:\northwind\northwnd.mdf")
' Get a typed table to run queries
Dim Customers As Customers(Of Customer) = db.GetTable(Of Customer)()
' Query for customers from London
Dim londonCustomers = From customer in Customers _
                      Where customer.City = "London" _
                      Select customer
For Each cust in londonCustomers
   Console.WriteLine("id = " & cust.CustomerID & ", City = " & cust.City)
Next

每個資料庫資料表都會以 Table 集合表示,可透過 GetTable () 方法使用其實體類別來識別它。 建議您宣告強型別 的 DataCoNtext ,而不是依賴基本 DataCoNtext 類別和 GetTable () 方法。 強型別 DataCoNtext 會將所有 Table 集合宣告為內容的成員。

C#

public partial class Northwind : DataContext
{
   public Table<Customer> Customers;
   public Table<Order> Orders;
   public Northwind(string connection): base(connection) {}
}

Visual Basic

Partial Public Class Northwind 
              Inherits DataContext

   Public Customers As Table(Of Customers)
   Public Orders As Table(Of Orders)
         Public Sub New(ByVal connection As String)
            MyBase.New(connection)
   End Sub
End Class

然後,從倫敦的客戶查詢可以更直接表示為:

C#

Northwind db = new Northwind("c:\\northwind\\northwnd.mdf");
var q =
   from c in db.Customers
   where c.City == "London"
   select c;
foreach (var cust in q)
   Console.WriteLine("id = {0}, City = {1}",cust.CustomerID, cust.City);

Visual Basic

Dim db = New Northwind("c:\northwind\northwnd.mdf")
Dim londonCustomers = From cust In db.Customers _
                      Where cust.City = "London" _
                      Select cust
For Each cust in londonCustomers
   Console.WriteLine("id = {0}, City = {1}", cust.CustomerID, cust.City) 

Next

我們會繼續針對概觀檔的其餘部分使用強型別 Northwind 類別。

定義關聯性

關係資料庫中的關聯性通常會模型化為參考其他資料表中主鍵的外鍵值。 若要在它們之間巡覽,您必須使用關聯式聯結作業明確地將兩個數據表結合在一起。 另一方面,物件會使用屬性參考或使用「點」標記法巡覽的參考集合來彼此參考。 很明顯地,作法比聯結更簡單,因為每次流覽時都不需要重新叫用明確的聯結條件。

對於一律相同的資料關聯性,將它們編碼為實體類別中的屬性參考會變得相當方便。 LINQ to SQL定義可套用至用來代表關聯性的成員的Association屬性。 關聯性就像是外鍵與主鍵關聯性的關聯性,其是由在資料表之間比對資料行值所建立。

C#

[Table(Name="Customers")]
public class Customer
{
   [Column(Id=true)]
   public string CustomerID;
   ...
   private EntitySet<Order> _Orders;
   [Association(Storage="_Orders", OtherKey="CustomerID")]
   public EntitySet<Order> Orders {
      get { return this._Orders; }
      set { this._Orders.Assign(value); }
   }
}

Visual Basic

<Table(Name:="Customers")> _
Public Class Customer

   <Column(Id:=true)> _
   Public CustomerID As String
   ...
   Private _Orders As EntitySet(Of Order)
                  <Association(Storage:="_Orders", OtherKey:="CustomerID")> _
         Public Property Orders() As EntitySet(Of Order)
            Get
               Return Me._Orders
            End Get
            Set(ByVal value As EntitySet(Of Order))
            End Set
   End Property

End Class

Customer類別現在有屬性,可宣告客戶與其訂單之間的關聯性。 Orders屬性的類型為EntitySet,因為關聯性是一對多。 我們會使用Association屬性中的OtherKey屬性來描述如何完成此關聯。 它會指定要與這個類別比較之相關類別中的屬性名稱。 我們也未指定 ThisKey 屬性。 一般而言,我們會使用它來列出關聯性這一端的成員。 不過,藉由省略它,我們允許LINQ to SQL從組成主鍵的成員推斷它們。

請注意 在 Order 類別的定義中,這會如何反轉。

C#

[Table(Name="Orders")]
public class Order
{
   [Column(Id=true)]
   public int OrderID;
   [Column]
   public string CustomerID;
   private EntityRef<Customer> _Customer;    
   [Association(Storage="_Customer", ThisKey="CustomerID")]
   public Customer Customer {
      get { return this._Customer.Entity; }
      set { this._Customer.Entity = value; }
   }
}

Visual Basic

<Table(Name:="Orders")> _
Public Class Order

   <Column(Id:=true)> _
   Public OrderID As String
   <Column> _
   Public CustomerID As String
   Private _Customer As EntityRef(Of Customer)
         <Association(Storage:="_Customer", ThisKey:="CustomerID")> _
         Public Property Customer() As Customer
            Get
               Return Me._Customer.Entity
            End Get
            Set(ByVal value As Customer)
               Me._Customers.Entity = value
            End Set
   End Property
End Class

Order類別會使用EntityRef類型,將關聯性描述回客戶。 需要 使用 EntityRef 類別來支援 延遲載入 , (稍後討論) 。 Customer屬性的Association屬性會指定ThisKey屬性,因為不可推斷的成員現在位於關聯性的這個端。

另請參閱 Storage 屬性。 它會告訴LINQ to SQL使用哪一個私用成員來保存 屬性的值。 這可讓LINQ to SQL在儲存和擷取其值時略過您的公用屬性存取子。 如果您想要LINQ to SQL避免寫入存取子的任何自訂商務邏輯,這很重要。 如果未指定儲存體屬性,則會改用公用存取子。 您也可以搭配Column 屬性使用Storage屬性。

在實體類別中導入關聯性之後,當您引進通知和圖形一致性的支援時,您需要撰寫的程式碼數量就會成長。 幸運的是,有一個工具 (稍後所述的) ,可用來產生所有必要的定義做為部分類別,讓您能夠混合產生的程式碼和自訂商務邏輯。

在本檔的其餘部分,我們假設此工具已用來產生完整的 Northwind 資料內容和所有實體類別。

跨關聯性查詢

既然您有關聯性,只要參考類別中定義的關聯性屬性,就可以在撰寫查詢時使用它們。

C#

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

Visual Basic

Dim londonCustOrders = From cust In db.Customers, ord In cust.Orders _
                       Where cust.City = "London" _
                       Select Customer = cust, Order = ord

上述查詢會使用 Orders 屬性來形成客戶與訂單之間的交叉產品,產生新的 客戶訂單 配對序列。

您也可以反向執行。

C#

var q =
   from o in db.Orders
   where o.Customer.City == "London"
   select new { c = o.Customer, o };

Visual Basic

Dim londonCustOrders = From ord In db.Orders _
                       Where ord.Customer.City = "London" _
                       Select Customer = ord.Customer, Order = ord

在此範例中,會查詢訂單,並使用 Customer 關聯性來存取相關聯 Customer 物件的資訊。

修改和儲存實體

少數應用程式只會考慮查詢。 也必須建立和修改資料。 LINQ to SQL的設計目的是為了在操作和保存對物件所做的變更時提供最大的彈性。 只要實體物件可供使用,只要透過查詢擷取它們或重新建構它們,您就可以在應用程式中將其當作一般物件操作、變更其值,或視需要從集合中新增和移除它們。 LINQ to SQL追蹤所有變更,並準備好在完成時立即將其傳輸回資料庫。

下列範例會使用工具從整個 Northwind 範例資料庫的中繼資料產生的 CustomerOrder 類別。 為了簡潔起見,類別定義尚未顯示。

C#

Northwind db = new Northwind("c:\\northwind\\northwnd.mdf");
// Query for a specific customer
string id = "ALFKI";
var cust = db.Customers.Single(c => c.CustomerID == id);
// Change the name of the contact
cust.ContactName = "New Contact";
// Create and add a new Order to Orders collection
Order ord = new Order { OrderDate = DateTime.Now };
cust.Orders.Add(ord);
// Ask the DataContext to save all the changes
db.SubmitChanges();

Visual Basic

Dim db As New Northwind("c:\northwind\northwnd.mdf")
' Query for a specific customer
Dim id As String = "ALFKI"
Dim targetCustomer = (From cust In db.Customers _ 
                     Where cust.CustomerID = id).First
' Change the name of the contact
targetCustomer.ContactName = "New Contact"
' Create and add a new Order to Orders collection
Dim id = New Order With { .OrderDate = DateTime.Now }
targetCustomer.Orders.Add(ord)
' Ask the DataContext to save all the changes
db.SubmitChanges()

呼叫SubmitChanges () 時,LINQ to SQL會自動產生並執行 SQL 命令,以便將變更傳回資料庫。 您也可以使用自訂邏輯覆寫此行為。 自訂邏輯可以呼叫資料庫預存程式。

查詢In-Depth

LINQ to SQL會針對與關係資料庫中資料表相關聯的物件,提供標準查詢運算子的實作。 本章說明查詢LINQ to SQL特定層面。

查詢執行

無論您是將查詢撰寫為高階 查詢運算式 ,還是從個別運算子中建置一個查詢,您撰寫的查詢不是立即執行的命令式語句。 這是描述。 例如,在區域變數 q 下方的宣告中,會參考查詢的描述,而不是執行查詢的結果。

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select c;
foreach (Customer c in q)
   Console.WriteLine(c.CompanyName);

Visual Basic

Dim londonCustomers = From cust In db.Customers _
                       where cust.City = "London"
For Each cust  In londonCustomers
   Console.WriteLine(cust.CompanyName) 
Next

此實例中q的實際類型為IQueryable < Customer >。 直到應用程式嘗試列舉它實際執行的查詢內容為止。 在此範例中, foreach 語句會導致執行。

IQueryable物件類似于 ADO.NET 命令物件。 手邊有一個並不表示已執行查詢。 命令物件會保留至描述查詢的字串。 同樣地, IQueryable 物件會保留編碼為稱為 Expression之資料結構之查詢的描述。 命令物件具有 ExecuteReader () 方法,會導致執行,以 DataReader傳回結果。 IQueryable物件具有GetEnumerator () 方法,導致執行,以IEnumerator < Customer >的形式傳回結果。

因此,如果列舉查詢兩次,則會執行兩次查詢。

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select c;
// Execute first time
foreach (Customer c in q)
   Console.WriteLine(c.CompanyName);
// Execute second time
foreach (Customer c in q)
   Console.WriteLine(c.CompanyName);

Visual Basic

Dim londonCustomers = From cust In db.Customers _
                       where cust.City = "London"
' Execute first time
For Each cust In londonCustomers
   Console.WriteLine(cust.CompanyName) 
Next
' Execute second time
For Each cust In londonCustomers
   Console.WriteLine(cust.CustomerID) 
Next

此行為稱為 延後執行。 就像使用 ADO.NET 命令物件一樣,可以保留查詢並重新執行。

當然,應用程式寫入器通常必須明確說明執行查詢的位置和時機。 如果應用程式只是因為需要多次檢查結果,所以執行查詢會是非預期的。 例如,您可能想要將查詢的結果系結至類似 DataGrid 的內容。 控制項可能會在每次在螢幕上繪製時列舉結果。

若要避免多次執行,請將結果轉換成任意數目的標準集合類別。 使用標準查詢運算子 ToList () ToArray () 輕鬆地將結果轉換成清單或陣列。

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select c;
// Execute once using ToList() or ToArray()
var list = q.ToList();
foreach (Customer c in list)
   Console.WriteLine(c.CompanyName);
foreach (Customer c in list)
   Console.WriteLine(c.CompanyName);

Visual Basic

Dim londonCustomers = From cust In db.Customers _
                       where cust.City = "London"
' Execute once using ToList() or ToArray()
Dim londonCustList = londonCustomers.ToList()
' Neither of these iterations re-executes the query
For Each cust In londonCustList
   Console.WriteLine(cust.CompanyName)
Next
For Each cust In londonCustList
   Console.WriteLine(cust.CompanyName)
Next 

延後執行的其中一個優點是,只有在建構完成時,才會分次建構查詢並執行。 您可以開始撰寫查詢的一部分,並將它指派給區域變數,稍後再繼續對其套用更多運算子。

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select c;
if (orderByLocation) {
   q =
      from c in q
      orderby c.Country, c.City
      select c;
}
else if (orderByName) {
   q =
      from c in q
      orderby c.ContactName
      select c;
}
foreach (Customer c in q)
   Console.WriteLine(c.CompanyName);

Visual Basic

Dim londonCustomers = From cust In db.Customers _
                       where cust.City = "London"
if orderByLocation Then
   londonCustomers = From cust in londonCustomers _
                     Order By cust.Country, cust.City

Else If orderByName Then
   londonCustomers = From cust in londonCustomers _
                     Order By cust.ContactName
End If
For Each cust In londonCustList
   Console.WriteLine(cust.CompanyName)
Next 

在此範例中, q 會從查詢倫敦的所有客戶開始。 之後,它會根據應用程式狀態變更為已排序的查詢。 藉由順延強制,可以建構查詢以符合應用程式的實際需求,而不需要有風險的字串操作。

物件識別

執行時間中的物件具有唯一的身分識別。 如果兩個變數參考相同的物件,它們實際上是參考相同的物件實例。 因此,透過路徑透過某個變數所做的變更會立即透過另一個變數顯示。 關係資料庫資料表中的資料列沒有唯一的身分識別。 不過,它們確實有主鍵,而且主鍵可能是唯一的,這表示沒有任何兩個數據列可以共用相同的索引鍵。 不過,這只會限制資料庫資料表的內容。 因此,只要我們只透過遠端命令與資料互動,它就相當於相同的內容。

不過,這種情況很少。 通常資料會從資料庫取出,並進入應用程式操作它的不同層。 很明顯地,這是LINQ to SQL設計來支援的模型。 當資料以資料列的形式從資料庫取出時,預期兩個代表相同資料的資料列實際上對應到相同的資料列實例。 如果您查詢特定客戶兩次,您會取得兩個數據列,每個資料列都包含相同的資訊。

不過,使用 物件時,您預期會有相當不同的內容。 您預期如果您再次要求 DataCoNtext 提供相同的資訊,它實際上會提供您相同的物件實例。 您預期如此,因為物件對於您的應用程式有特殊意義,而且您預期它們的行為就像一般物件一樣。 您將其設計為階層或圖形,而且您當然會預期擷取它們,而不需要大量複寫的實例,因為您要求相同的專案兩次。

因此, DataCoNtext 會管理物件識別。 每當從資料庫擷取新的資料列時,就會以其主鍵記錄在識別資料表中,並建立新的 物件。 每當再次擷取相同的資料列時,原始物件實例就會傳回給應用程式。 如此一來, DataCoNtext 會將身分識別 () 金鑰的資料庫概念轉譯成語言概念 (實例) 。 應用程式只會看到物件處於第一次擷取的狀態。 如果不同,則會擲回新的資料。

您可能會感到困惑,因為為何任何應用程式都會擲回資料? 如此一來,LINQ to SQL管理本機物件的完整性,而且能夠支援開放式更新。 由於最初建立物件之後所發生的唯一變更是由應用程式所做,所以應用程式的意圖是清楚的。 如果外部合作物件所做的變更是在過渡期間發生,則會在呼叫 SubmitChanges () 時識別這些變更。 如需詳細資訊,請參閱同時變更一節。

請注意,如果資料庫包含不含主鍵的資料表,LINQ to SQL允許透過資料表提交查詢,但不允許更新。 這是因為架構無法識別因缺少唯一索引鍵而要更新的資料列。

當然,如果查詢所要求的物件很容易被其主鍵識別,因為一個已經擷取的查詢完全沒有執行。 識別資料表可作為儲存所有先前擷取物件的快取。

關聯性

如我們在快速導覽中所見,您類別定義中其他物件的參考會直接對應至資料庫中的外鍵關聯性。 只要使用點標記法來存取關聯性屬性,從一個物件巡覽到另一個物件,就可以使用這些關聯性。 這些存取作業會轉譯成對等 SQL 中更複雜的聯結或相互關聯的子查詢,讓您在查詢期間逐步解說物件圖形。 例如,下列查詢會從訂單巡覽至客戶,而將查詢結果限制在位於倫敦 (London) 之客戶的訂單。

C#

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

Visual Basic

Dim londonOrders = From ord In db.Orders _
                       where ord.Customer.City = "London"

如果關聯性屬性不存在,您必須手動寫出它們,就像您在 SQL 查詢中所做的一樣。

C#

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

Visual Basic

Dim londonOrders = From cust In db.Customers _
                            Join ord In db.Orders _
                            On cust.CustomerID Equals ord.CustomerID _
                   Where ord.Customer.City = "London" _
                   Select ord

關聯性屬性可讓您在啟用更方便的點語法使用之後,定義這個特定關聯性。 不過,這不是關聯性屬性存在的原因。 它們存在,因為我們通常會將領域特定物件模型定義為階層或圖形。 我們選擇針對的物件進行程式設計,具有其他物件的參考。 只有一個快樂的一點,因為物件對物件關聯性對應至資料庫中的外鍵樣式關聯性,屬性存取會導致撰寫聯結的便利方式。

因此,關聯性屬性的存在在查詢結果端的重要性高於查詢本身的一部分。 當您實際操作特定客戶之後,其類別定義會告訴您客戶有訂單。 因此,當您查看特定客戶的 Orders 屬性時,您預期會看到填入所有客戶訂單的集合,因為這是您以這種方式定義類別所宣告的合約。 即使您未特別預先要求訂單,您仍預期會看到該處的訂單。 您預期物件模型會維持其為資料庫記憶體內部延伸的假像,並立即提供相關物件。

LINQ to SQL實作稱為延後載入的技術,以協助維護這種錯覺。 當您查詢物件時,實際上只會擷取您要求的物件。 不會同時自動擷取「相關」(Related) 物件 不過,因為當您嘗試存取相關物件時,就會立即嘗試存取相關物件來擷取這些物件,所以無法觀察。

C#

var q =
   from o in db.Orders
   where o.ShipVia == 3
   select o;
foreach (Order o in q) {
   if (o.Freight > 200)
      SendCustomerNotification(o.Customer);
   ProcessOrder(o);
}

Visual Basic

Dim shippedOrders = From ord In db.Orders _
                    where ord.ShipVia = 3
For Each ord In shippedOrders
   If ord.Freight > 200 Then
      SendCustomerNotification(ord.Customer) 
      ProcessOrder(ord)
   End If
Next

例如,您可能想要查詢一組特定的訂單,然後偶爾只傳送電子郵件通知給特定客戶。 您不需要先擷取每個訂單的所有客戶資料。 延後載入可讓您延遲擷取額外資訊的成本,直到您絕對必須完成為止。

當然,相反的也可能是正確的。 您可能有一個應用程式需要同時查看客戶和訂單資料。 您知道您需要這兩組資料, 您知道您的應用程式會在收到訂單時,立即向下切入每個客戶的訂單。 可惜的是,針對每位客戶的訂單引發個別查詢會很抱歉。 您真正想要發生的情況是讓訂單資料與客戶一起擷取。

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select c;
foreach (Customer c in q) {
   foreach (Order o in c.Orders) {
      ProcessCustomerOrder(o);
   }
}

Visual Basic

Dim londonCustomers = From cust In db.Customer _
                   Where cust.City = "London"
For Each cust In londonCustomers
   For Each ord In cust.Orders
      ProcessCustomerOrder(ord) 
   End If
Next

當然,您一律可以藉由形成交叉產品並擷取所有資料的相對位做為一個大型投影,來尋找將客戶和訂單聯結在查詢中的方法。 但結果不會是實體。 實體是具有身分識別的物件,您可以在結果為無法變更和保存的投影時修改。 更糟的是,您會擷取大量的備援資料,因為每個客戶在扁平化聯結輸出中重複每個訂單。

您真正需要的方法是同時擷取一組相關物件的方法,也就是圖形的已描述部分,因此您永遠不會擷取超過或小於您預期用途所需的部分。

LINQ to SQL可讓您要求立即載入物件模型的區域,只是基於這個原因。 其作法是允許DataCoNtextDataShape規格。 DataShape類別可用來指示架構擷取特定類型時要擷取的物件。 這是使用 LoadWith 方法完成的,如下列所示:

C#

DataShape ds = new DataShape();
ds.LoadWith<Customer>(c => c.Orders);
db.Shape = ds;
var q = 
   from c in db.Customers
   where c.City == "London"
   select c;

Visual Basic

Dim ds As DataShape = New DataShape()
ds.LoadWith(Of Customer)(Function(c As Customer) c.Orders)
db.Shape = ds
Dim londonCustomers = From cust In db.Customers _
                      Where cust.City = "London" _
                      Select cust

在上一個查詢中,執行查詢時,會擷取位於倫敦的所有客戶的所有訂單,以便客戶物件上Orders屬性的後續存取不會觸發資料庫查詢。

DataShape類別也可以用來指定套用至關聯性導覽的子查詢。 例如,如果您想要只擷取今天出貨的訂單,您可以使用DataShape上的AssociateWith方法,如下所示:

C#

DataShape ds = new DataShape();
ds.AssociateWith<Customer>(
   c => c.Orders.Where(p => p.ShippedDate != DateTime.Today));
db.Shape = ds;
var q = 
   from c in db.Customers
   where c.City == "London"
   select c;
foreach(Customer c in q) {
   foreach(Order o in c.Orders) {}
}

Visual Basic

Dim ds As DataShape = New DataShape()
ds.AssociateWith(Of Customer)( _
         Function(cust As Customer) From cust In db.Customers _
                                 Where order.ShippedDate <> Today _
                                 Select cust)
db.Shape = ds
Dim londonCustomers = From cust In db.Customers _
                      Where cust.City = "London" _
                      Select cust
For Each cust in londonCustomers
   For Each ord In cust.Orders …
   Next
   Next

在先前的程式碼中,內部 foreach 語句只會逐一查看今天出貨的 Orders ,因為只有這類訂單已從資料庫擷取。

請務必注意有關 DataShape 類別的兩個事實:

  1. DataShape 指派給 DataCoNtext之後,就無法修改 DataShape 。 這類DataShape上的任何LoadWithAssociateWith方法呼叫都會在執行時間傳回錯誤。

  2. 無法使用 LoadWithAssociateWith建立迴圈。 例如,下列會在執行時間產生錯誤:

    C#

    DataShape ds = new DataShape();
    ds.AssociateWith<Customer>(
             c=>c.Orders.Where(o=> o.Customer.Orders.Count() < 35);
    

    Visual Basic

    Dim ds As DataShape = New DataShape()
    ds.AssociateWith(Of Customer)( _
             Function(cust As Customer) From ord In cust.Orders _
                          Where ord.Customer.Orders.Count() < 35)
    

聯結

大部分對物件模型的查詢都依賴在物件模型中巡覽物件參考。 不過,在物件模型中可能無法擷取為參考的實體之間有有趣的「關聯性」。 例如 ,Customer.Orders 是以 Northwind 資料庫中的外鍵關聯性為基礎的實用關聯性。 不過,相同城市或國家/地區的供應商和客戶是一種 臨機操作 關係,不是以外鍵關聯性為基礎,而且無法在物件模型中擷取。 聯結提供額外的機制來處理這類關聯性。 LINQ to SQL支援 LINQ 中引進的新聯結運算子。

請考慮下列問題:尋找位於相同城市中的供應商和客戶。 下列查詢會以扁平化的結果傳回供應商和客戶公司名稱和通用城市。 這相當於關係資料庫中的內部等聯結:

C#

var q = 
   from s in db.Suppliers
   join c in db.Customers on s.City equals c.City
   select new {
      Supplier = s.CompanyName,
      Customer = c.CompanyName,
      City = c.City
   };

Visual Basic

Dim customerSuppliers = From sup In db.Suppliers _
                        Join cust In db.Customers _
                              On sup.City Equals cust.City _
                        Select Supplier = sup.CompanyName, _
                        CustomerName = cust.CompanyName, _
                        City = cust.City

上述查詢會排除與特定客戶不在相同城市中的供應商。 不過,有時候我們不想在 臨機操作 關聯性中排除其中一個實體。 下列查詢會列出每個供應商的客戶群組的所有供應商。 如果特定供應商沒有同一城市中的任何客戶,結果就是對應至該供應商的客戶空集合。 請注意,結果並非一般,每個供應商都有相關聯的 集合。 實際上,這會提供群組聯結—它會依第一個序列的元素聯結兩個序列和群組第二個序列的元素。

C#

var q = 
   from s in db.Suppliers
   join c in db.Customers on s.City equals c.City into scusts
   select new { s, scusts };

Visual Basic

Dim customerSuppliers = From sup In db.Suppliers _
                        Group Join cust In db.Customers _
                              On sup.City Equals cust.City _
                              Into supCusts _
                        Select Supplier = sup, _
                        Customers = supCusts

群組聯結也可以延伸至多個集合。 下列查詢會列出與供應商位於相同城市的員工,藉此擴充上述查詢。 在這裡,結果會顯示 (客戶和員工的空) 集合的供應商。

C#

var q = 
   from s in db.Suppliers
   join c in db.Customers on s.City equals c.City into scusts
   join e in db.Employees on s.City equals e.City into semps
   select new { s, scusts, semps };

Visual Basic

Dim customerSuppliers = From sup In db.Suppliers _
                        Group Join cust In db.Customers _
                              On sup.City Equals cust.City _
                              Into supCusts _
                        Group Join emp In db.Employees _
                              On sup.City Equals emp.City _
                              Into supEmps _
                        Select Supplier = sup, _
                        Customers = supCusts, Employees = supEmps

群組聯結的結果也可以扁平化。 將供應商與客戶之間的群組聯結壓平合併結果,是具有城市中多個客戶之供應商的多個專案,每個客戶各有一個專案。 空集合會以 Null 取代。 這相當於關係資料庫中的左外部等聯結。

C#

var q = 
   from s in db.Suppliers
   join c in db.Customers on s.City equals c.City into sc
   from x in sc.DefaultIfEmpty()
   select new {
      Supplier = s.CompanyName, 
      Customer = x.CompanyName, 
      City = x.City 
   };

Visual Basic

Dim customerSuppliers = From sup In db.Suppliers _
                        Group Join cust In db.Customers _
                              On sup.City Equals cust.City _
                              Into supCusts _
                        Select Supplier = sup, _
                        CustomerName = supCusts.CompanyName, sup.City

基礎聯結運算子的簽章定義于標準查詢運算子檔中。 只支援等聯結,且 兩個等 號的運算元必須具有相同的類型。

投影

到目前為止,我們只會查看擷取 實體的查詢,也就是直接與資料庫資料表相關聯的物件。 我們不需要只將自己限制在內。 查詢語言的優點是,您可以擷取您想要的任何格式的資訊。 當您這麼做時,您將無法利用自動變更追蹤或身分識別管理。 不過,您可以只取得您想要的資料。

例如,您可能只需要知道倫敦所有客戶的公司名稱。 如果是這種情況,就沒有任何特定原因可以只擷取整個客戶物件來挑選名稱。 您可以將名稱投射為查詢的一部分。

C#

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

Visual Basic

Dim londonCustomerNames = From cust In db.Customer _
                          Where cust.City = "London" _
                          Select cust.CompanyName

在此情況下, q 會變成擷取字串序列的查詢。

如果您想要取回不只是單一名稱,但不足以證明擷取整個客戶物件,您可以藉由建構結果做為查詢的一部分來指定您想要的任何子集。

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select new { c.CompanyName, c.Phone };

Visual Basic

Dim londonCustomerInfo = From cust In db.Customer _
                         Where cust.City = "London" _
                         Select cust.CompanyName, cust.Phone

這個範例會使用 匿名物件初始化運算式 來建立同時保存公司名稱和電話號碼的結構。 您可能不知道要呼叫類型的內容,但以您不一定需要的語言使用 隱含類型區域變數宣告

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select new { c.CompanyName, c.Phone };
foreach(var c in q)
   Console.WriteLine("{0}, {1}", c.CompanyName, c.Phone);

Visual Basic

Dim londonCustomerInfo = From cust In db.Customer _
                         Where cust.City = "London" _
                         Select cust.CompanyName, cust.Phone
For Each cust In londonCustomerInfo 
   Console.WriteLine(cust.CompanyName & ", " & cust.Phone) 
Next

如果您要立即取用資料, 匿名型 別可讓您明確地定義類別來保存查詢結果。

您也可以形成整個物件的跨產品,但您可能很少有理由這麼做。

C#

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

Visual Basic

Dim londonOrders = From cust In db.Customer, _
                   ord In db.Orders _
                   Where cust.City = "London" _
                   Select Customer = cust, Order = ord

此查詢會建構一連串的客戶和訂單物件。

您也可以在查詢的任何階段進行投影。 您可以將資料投影到新建構的物件,然後在後續的查詢作業中參考這些物件的成員。

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select new {Name = c.ContactName, c.Phone} into x
   orderby x.Name
   select x;

Visual Basic

Dim londonItems = From cust In db.Customer _
                  Where cust.City = "London" _
                  Select Name = cust.ContactName, cust.Phone _
                  Order By Name

不過,請小心在此階段使用參數化建構函式。 技術上是有效的,但無法LINQ to SQL追蹤建構函式使用方式如何影響成員狀態,而不需要瞭解建構函式內的實際程式碼。

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select new MyType(c.ContactName, c.Phone) into x
   orderby x.Name
   select x;

Visual Basic

Dim londonItems = From cust In db.Customer _
                  Where cust.City = "London" _
                  Select MyType = New MyType(cust.ContactName, cust.Phone) _
                  Order By MyType.Name

因為LINQ to SQL嘗試將查詢轉譯為純關聯式 SQL 本機定義的物件類型,無法在伺服器上實際建構。 所有物件建構實際上都會延後,直到從資料庫擷取資料之後為止。 在取代實際建構函式時,產生的 SQL 會使用一般 SQL 資料行投影。 由於查詢翻譯工具無法瞭解建構函式呼叫期間發生的情況,因此無法為MyTypeName欄位建立意義。

相反地,最佳做法是一律使用 物件初始化運算式 來編碼投影。

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select new MyType { Name = c.ContactName, HomePhone = c.Phone } into x
   orderby x.Name
   select x;

Visual Basic

Dim londonCustomers = From cust In db.Customer _
                      Where cust.City = "London" _
                      Select Contact = New With {.Name = cust.ContactName, _
                      .Phone = cust.Phone} _
                      Order By Contact.Name

唯一使用參數化建構函式的安全位置是在查詢的最終投影中。

C#

var e =
   new XElement("results",
      from c in db.Customers
      where c.City == "London"
      select new XElement("customer",
         new XElement("name", c.ContactName),
         new XElement("phone", c.Phone)
      )
   );

Visual Basic

      Dim x = <results>
                  <%= From cust In db.Customers _
                      Where cust.City = "London" _
                      Select <customer>
                         <name><%= cust.ContactName %></name>
                         <phone><%= cust.Phone %></phone>
                      </customer> 
                 %>
        </results>

如有需要,您甚至可以使用物件建構函式的詳盡巢狀結構,例如此範例會直接從查詢結果建構 XML。 只要它是查詢的最後一個投影,它就可以運作。

不過,即使瞭解建構函式呼叫,對本機方法的呼叫可能不是。 如果您的最終投影需要叫用本機方法,則不太可能LINQ to SQL能夠產生問題。 沒有已知轉譯至 SQL 的方法呼叫無法當做查詢的一部分使用。 此規則的其中一個例外狀況是沒有相依于查詢變數之引數的方法呼叫。 這些不會被視為已翻譯查詢的一部分,而是視為參數。

) 轉換 (的詳細說明,可能需要實作本機程式邏輯。 若要在最終投影中使用自己的本機方法,您必須投影兩次。 第一個投影會擷取您需要參考的所有資料值,而第二個投影會執行轉換。 在這兩個投影之間,是呼叫 AsEnumerable () 運算子,將處理從LINQ to SQL查詢移轉至本機執行的查詢。

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select new { c.ContactName, c.Phone };
var q2 =
   from c in q.AsEnumerable()
   select new MyType {
      Name = DoNameProcessing(c.ContactName),
      Phone = DoPhoneProcessing(c.Phone)
   };

Visual Basic

Dim londonCustomers = From cust In db.Customer _
                      Where cust.City = "London" _
                      Select cust.ContactName, cust.Phone

Dim processedCustomers = From cust In londonCustomers.AsEnumerable() _
                         Select Contact = New With { _
                         .Name = DoNameProcessing(cust.ContactName), _
                         .Phone = DoPhoneProcessing(cust.Phone)}

注意AsEnumerable () 運算子與ToList () ToArray () 不同,不會造成查詢的執行。 它仍會延遲。 AsEnumerable () 運算子只會變更查詢的靜態類型,將 Visual Basic 中IQueryable < T > (IQueryable (ofT) 轉換成IEnumerable T > (IEnumerable < (visual) Basic ) ) ,讓編譯器將其餘查詢視為本機執行。

編譯的查詢

許多應用程式通常會多次執行結構類似的查詢。 在這種情況下,可以編譯查詢一次,並在應用程式中使用不同的參數執行數次,以提升效能。 使用CompiledQuery類別在 LINQ to SQL 中取得此結果。 下列程式碼示範如何定義已編譯的查詢:

C#

static class Queries
{
   public static Func<Northwind, string, IQueryable<Customer>>
      CustomersByCity = CompiledQuery.Compile((Northwind db, string city) =>
         from c in db.Customers where c.City == city select c);
}

Visual Basic

Class Queries
   public Shared Function(Of Northwind, String, IQueryable(Of Customer)) _      CustomersByCity = CompiledQuery.Compile( _
                Function(db As Northwind, city As String) _
                From cust In db.Customers Where cust.City = city)
End Class

Compile方法會傳回委派,只要變更輸入參數即可快取並執行數次。 下列程式碼顯示此範例:

C#

public IEnumerable<Customer> GetCustomersByCity(string city) {
         Northwind db = new Northwind();
         return Queries.CustomersByCity(myDb, city);
}

Visual Basic

Public Function GetCustomersByCity(city As String) _ 
               As IEnumerable(Of Customer)
         Dim db As Northwind = New Northwind()
         Return Queries.CustomersByCity(myDb, city)
End Function

SQL 翻譯

LINQ to SQL實際上不會執行查詢;關係資料庫會執行。 LINQ to SQL會將您撰寫的查詢轉譯成對等的 SQL 查詢,並將其傳送至伺服器進行處理。 因為執行會延遲,所以即使從多個元件組合,LINQ to SQL仍能夠檢查整個查詢。

由於關係資料庫伺服器實際上不會執行 IL (,除了 SQL Server 2005) 中的 CLR 整合之外;查詢不會以 IL 形式傳輸至伺服器。 它們實際上會以文字形式以參數化 SQL 查詢的形式傳輸。

當然,SQL 甚至是具有 CLR 整合的 T-SQL,都無法執行程式可在本機使用的各種方法。 因此,您撰寫的查詢必須轉譯成 SQL 環境內可用的對等作業和函式。

.Net Framework 內建類型上大部分的方法和運算子都會直接轉譯成 SQL。 有些可以從可用的函式中產生。 不允許轉譯的例外狀況,如果您嘗試使用這些例外狀況,則會產生運行時例外狀況。 檔中稍後有一節會詳細說明實作以轉譯成 SQL 的架構方法。

實體生命週期

LINQ to SQL不只是關係資料庫的標準查詢運算子實作。 除了翻譯查詢之外,它是一項服務,可管理整個存留期的物件,協助您維護資料的完整性,以及將修改轉譯回存放區的程式自動化。

在一般案例中,物件會透過一或多個查詢擷取,然後以某種方式或其他方式操作,直到應用程式準備好將變更傳回伺服器為止。 此程式可能會重複多次,直到應用程式不再使用這項資訊為止。 此時,執行時間會回收物件,就像一般物件一樣。 不過,資料會保留在資料庫中。 即使從執行時間存在中清除,仍可擷取代表相同資料的物件。 就這個意義而言,物件真正的存留期會存在於任何單一運行時程表現之外。

本章的重點在於 實體生命週期 ,其中迴圈是指特定執行時間內容中實體物件之單一表現的時間範圍。 當 DataCoNtext知道新的實例,並在不再需要物件或DataCoNtext時結束時,迴圈就會開始。

追蹤變更

從資料庫擷取實體之後,您可以隨意操作它們。 它們是您的物件;使用它們作為您的用途。 如此一來,LINQ to SQL追蹤變更,以便在呼叫SubmitChanges () 時將其保存在資料庫中。

LINQ to SQL在從資料庫擷取實體時開始追蹤您的實體,然後再進行交握。 事實上,稍早所討論的 身分識別管理服務 也已經啟動。 在您實際開始進行變更之前,變更追蹤成本會非常少的額外負荷。

C#

Customer cust = db.Customers.Single(c => c.CustomerID == "ALFKI");
cust.CompanyName = "Dr. Frogg's Croakers";

Visual Basic

' Query for a specific customer
Dim id As String = "ALFKI"
Dim targetCustomer = (From cust In db.Customers _ 
                      Where cust.CustomerID = id).First
targetCustomer.CompanyName = "Dr. Frogg's Croakers"

在上述範例中指派CompanyName之後,LINQ to SQL就會察覺變更並能夠加以記錄。 變更 追蹤服務會保留所有資料成員的原始值。

變更追蹤服務也會記錄關聯性屬性的所有操作。 您可以使用關聯性屬性來建立實體之間的連結,即使這些連結可能是由資料庫中的索引鍵值所連結。 不需要直接修改與索引鍵資料行相關聯的成員。 LINQ to SQL會在提交變更之前自動為您同步處理。

C#

Customer cust1 = db.Customers.Single(c => c.CustomerID == custId1);
foreach (Order o in db.Orders.Where(o => o.CustomerID == custId2)) {
   o.Customer = cust1;
}

Visual Basic

Dim targetCustomer = (From cust In db.Customers _ 
                      Where cust.CustomerID = custId1).First

For Each ord In (From o In db.Orders _ 
                 Where o.CustomerID = custId2) 
   o.Customer = targetCustomer
Next

只要將訂單指派給其 Customer 屬性,您就可以將訂單從一個客戶移至另一個 客戶 。 由於客戶與訂單之間存在關聯性,因此您可以修改任一端來變更關聯性。 您可以輕鬆地從cust2 Orders集合中移除它們,並將其新增至cust1的 orders 集合,如下所示。

C#

Customer cust1 = db.Customers.Single(c => c.CustomerID == custId1);
Customer cust2 = db.Customers.Single(c => c.CustomerID == custId2); 
// Pick some order
Order o = cust2.Orders[0]; 
// Remove from one, add to the other
cust2.Orders.Remove(o);
cust1.Orders.Add(o);
// Displays 'true'
Console.WriteLine(o.Customer == cust1);

Visual Basic

Dim targetCustomer1 = (From cust In db.Customers _ 
                       Where cust.CustomerID = custId1).First
Dim targetCustomer2 = (From cust In db.Customers _ 
                       Where cust.CustomerID = custId1).First
' Pick some order
Dim o As Order = targetCustomer2.Orders(0) 
' Remove from one, add to the other
targetCustomer2.Orders.Remove(o)
targetCustomer1.Orders.Add(o)
' Displays 'True'
MsgBox(o.Customer = targetCustomer1)

當然,如果您將關聯性指派為 null的值,您實際上會完全移除關聯性。 將訂單的 Customer 屬性指派給 null 實際上會從客戶的清單中移除訂單。

C#

Customer cust = db.Customers.Single(c => c.CustomerID == custId1); 
// Pick some order
Order o = cust.Orders[0];
// Assign null value
o.Customer = null;
// Displays 'false'
Console.WriteLine(cust.Orders.Contains(o));

Visual Basic

Dim targetCustomer = (From cust In db.Customers _ 
                       Where cust.CustomerID = custId1).First
' Pick some order
Dim o As Order = targetCustomer.Orders(0)
' Assign null value
o.Customer = Nothing
' Displays 'False'
Msgbox(targetCustomer.Orders.Contains(o))

自動更新關聯性的兩端是維護物件圖形一致性的必要條件。 不同于一般物件,資料之間的關聯性通常是雙向的。 LINQ to SQL可讓您使用屬性來表示關聯性。 不過,它不提供服務來自動讓這些雙向屬性保持同步。這是一種服務層級,必須直接製作到您的類別定義中。 使用程式碼產生工具產生的實體類別具有這項功能。 在下一章中,我們將說明如何對您自己的手寫類別執行這項操作。

不過,請務必注意,移除關聯性並不表示物件已經從資料庫刪除。 請記住,基礎資料的存留期會保存在資料庫中,直到資料列從資料表中刪除為止。 實際刪除物件的唯一方法是將其從其 Table 集合中移除。

C#

Customer cust = db.Customers.Single(c => c.CustomerID == custId1); 
// Pick some order
Order o = cust.Orders[0];
// Remove it directly from the table (I want it gone!)
db.Orders.Remove(o);
// Displays 'false'.. gone from customer's Orders
Console.WriteLine(cust.Orders.Contains(o));
// Displays 'true'.. order is detached from its customer
Console.WriteLine(o.Customer == null);

Visual Basic

Dim targetCustomer = (From cust In db.Customers _ 
                          Where cust.CustomerID = custId1).First
' Pick some order
Dim o As Order = targetCustomer.Orders(0)
' Remove it directly from the table (I want it gone!)
db.Orders.Remove(o)
' Displays 'False'.. gone from customer’s Orders
Msgbox(targetCustomer.Orders.Contains(o))
' Displays 'True'.. order is detached from its customer
Msgbox(o.Customer = Nothing)

就像所有其他變更一樣,訂單實際上尚未刪除。 它看起來就像這樣,因為它已從其餘的物件中移除和中斷連結。 當 order 物件從 Orders 資料表中移除時,變更追蹤服務會將其標示為要刪除。 在呼叫 SubmitChanges () 時,將會發生從資料庫實際刪除的變更。 請注意,永遠不會刪除物件本身。 執行時間會管理物件實例的存留期,因此只要您仍然保留它的參考,它就會維持在周圍。 不過,從其 Table 移除物件並提交變更之後,變更追蹤服務就不會再追蹤該物件。

DataCoNtext 知道實體之前,唯一未追蹤實體的時間才會存在。 每當您在程式碼中建立新的物件時,就會發生這種情況。 您可以在應用程式中免費使用實體類別的實例,而不需要從資料庫擷取它們。 變更堆疊和身分識別管理僅適用于 DataCoNtext 所察覺的物件。 因此,在您將其新增至 DataCoNtext之前,不會針對新建立的實例啟用服務。

這可以透過兩種方式之一發生。 您可以在相關的Table集合上手動呼叫Add () 方法。

C#

Customer cust =
   new Customer {
      CustomerID = "ABCDE",
      ContactName = "Frond Smooty",
      CompanyTitle = "Eggbert's Eduware",
      Phone = "888-925-6000"
   };
// Add new customer to Customers table
db.Customers.Add(cust);

Visual Basic

Dim targetCustomer = New Customer With { _
         .CustomerID = “ABCDE”, _
         .ContactName = “Frond Smooty”, _
         .CompanyTitle = “Eggbert’s Eduware”, _
         .Phone = “888-925-6000”}
' Add new customer to Customers table
db.Customers.Add(cust)

或者,您可以將新的實例附加至 DataCoNtext 已經知道的物件。

C#

// Add an order to a customer's Orders
cust.Orders.Add(
   new Order { OrderDate = DateTime.Now }
); 

Visual Basic

' Add an order to a customer's Orders
targetCustomer.Orders.Add( _
   New Order With { .OrderDate = DateTime.Now } )

即使這些實例附加至其他新實例, DataCoNtext 也會探索新的物件實例。

C#

// Add an order and details to a customer's Orders
Cust.Orders.Add(
   new Order {
      OrderDate = DateTime.Now,
      OrderDetails = {
         new OrderDetail {
            Quantity = 1,
            UnitPrice = 1.25M,
            Product = someProduct
         }
      }
   }
); 

Visual Basic

' Add an order and details to a customer's Orders
targetCustomer.Orders.Add( _
   New Order With { _
      .OrderDate = DateTime.Now, _
      .OrderDetails = New OrderDetail With { _
               .Quantity = 1,
               .UnitPrice = 1.25M,
               .Product = someProduct 
      }
   } )

基本上,不論您呼叫Add () 方法,DataCoNtext都會辨識目前未追蹤為新實例的物件圖形中的任何實體。

使用唯讀 DataCoNtext

許多案例都不需要更新從資料庫擷取的實體。 在網頁上顯示客戶資料表是一個明顯的範例。 在所有這類情況下,都可以指示 DataCoNtext 不要追蹤實體的變更來改善效能。 這可藉由指定DataCoNtext上的ObjectTracking屬性為 false 來達成,如下列程式碼所示:

C#

      db.ObjectTracking = false;
      
      var q = db.Customers.Where( c => c.City = "London");
      foreach(Customer c in q)
         Display(c);

Visual Basic

db.ObjectTracking = False
      
      Dim londonCustomers = From cust In db.Customer _
                      Where cust.City = "London"
      For Each c in londonCustomers
         Display(c)
Next

提交變更

無論您對物件進行多少變更,這些變更只會對記憶體內部複本進行。 資料庫中的實際資料尚未發生任何事。 除非您在DataCoNtext上呼叫SubmitChanges () 明確要求此資訊,否則不會將這項資訊傳輸至伺服器。

C#

Northwind db = new Northwind("c:\\northwind\\northwnd.mdf");
// make changes here
db.SubmitChanges();

Visual Basic

Dim db As New Northwind("c:\northwind\northwnd.mdf")
' make changes here
db.SubmitChanges()

當您呼叫 SubmitChanges () 時, DataCoNtext 會嘗試將所有變更轉譯成相等的 SQL 命令、插入、更新或刪除對應資料表中的資料列。 如有需要,您可以自行自訂邏輯覆寫這些動作,不過提交順序是由稱為變更處理器DataCoNtext服務所協調。

呼叫 SubmitChanges () 時所發生的第一件事,就是會檢查一組已知的物件,以判斷新實例是否已附加至它們。 這些新實例會新增至追蹤物件的集合。 接下來,具有暫止變更的所有物件都會根據物件之間的相依性,排序成一連串的物件。 這些變更相依于其他物件的物件會在相依性之後排序。 資料庫中的外鍵條件約束和唯一性條件約束在判斷變更的正確順序時扮演了很大一部分。 然後,在傳輸任何實際變更之前,交易會開始封裝個別命令系列,除非其中一個已在範圍內。 最後,物件的變更逐一轉譯為 SQL 命令,並傳送至伺服器。

此時,資料庫偵測到的任何錯誤都會造成提交程式中止,並引發例外狀況。 資料庫的所有變更都會回復,就像沒有進行任何提交一樣。 DataCoNtext仍然會完整記錄所有變更,因此可以嘗試修正問題,並再次呼叫SubmitChanges () 重新提交。

C#

Northwind db = new Northwind("c:\\northwind\\northwnd.mdf");
// make changes here 
try {
   db.SubmitChanges();
}
catch (Exception e) {
   // make some adjustments
   ...
   // try again
   db.SubmitChanges();
}

Visual Basic

Dim db As New Northwind("c:\northwind\northwnd.mdf")
' make changes here 
Try 
   db.SubmitChanges()
Catch e As Exception
   ' make some adjustments
   ...
   ' try again
   db.SubmitChanges()
End Try

當提交周圍的交易成功完成時, DataCoNtext 會只忘記變更追蹤資訊,以接受物件的變更。

同時變更

呼叫 SubmitChanges () 可能會失敗的各種原因。 您可能已建立具有無效主鍵的物件;一個已在使用中,或具有違反資料庫某些檢查條件約束的值。 這類檢查很難製作商務邏輯,因為它們通常需要對整個資料庫狀態的絕對知識。 不過,失敗的最可能原因是其他人在您之前對物件進行了變更。

當然,如果您在資料庫中鎖定每個物件,並使用完全序列化的交易,就不可能這麼做。 不過,這種程式設計樣式 (封閉式並行) 很少使用,因為成本昂貴且很少發生衝突。 管理同時變更的最受歡迎形式是採用 開放式平行存取的形式。 在此模型中,完全不會鎖定資料庫資料列。 這表示在您第一次擷取物件與提交變更的時間之間,可能會發生資料庫的任何數目變更。

因此,除非您想要使用上次更新優先的原則,並抹除您之前發生的任何其他事件,否則您可能會想要收到其他人變更基礎資料的事實警示。

DataCoNtext透過自動偵測變更衝突,提供開放式平行存取的內建支援。 只有在資料庫的目前狀態符合您第一次擷取物件時所瞭解資料的狀態時,個別更新才會成功。 這會以每個物件為基礎進行,只有在您對物件進行變更時,才會對違規發出警示。

您可以在定義實體類別時控制 DataCoNtext 偵測到變更衝突的程度。 每個 Column 屬性都有一個名為 UpdateCheck 的屬性,可以指派下列三個值之一: AlwaysNeverWhenChanged。 如果未設定 Column 屬性的預設值為 Always,表示該成員所代表的資料值一律會檢查是否有衝突,也就是說,除非版本戳記有明顯的系結中斷器。 Column屬性具有IsVersion屬性,可讓您指定資料值是否構成資料庫所維護的版本戳記。 如果版本存在,則單獨使用版本來判斷是否發生衝突。

發生變更衝突時,將會擲回例外狀況,就像是任何其他錯誤一樣。 提交周圍的交易將會中止,但 DataCoNtext 會維持不變,讓您有機會修正問題,然後再試一次。

C#

while (retries < maxRetries) {
   Northwind db = new Northwind("c:\\northwind\\northwnd.mdf");

   // fetch objects and make changes here

   try {
      db.SubmitChanges();
      break;
   }
   catch (ChangeConflictException e) {
      retries++;
   }
}

Visual Basic

Do While retries < maxRetries
   Dim db As New Northwind("c:\northwind\northwnd.mdf")

   ' fetch objects and make changes here

   Try
      db.SubmitChanges()
      Exit Do
   
   catch cce As ChangeConflictException
      retries += 1
   End Try
Loop

如果您要在仲介層或伺服器上進行變更,最簡單的方法是修正變更衝突,只是要重新開始再試一次、重新建立內容並重新套用變更。 下一節將說明其他選項。

交易

交易是由資料庫或任何其他資源管理員所提供的服務,可用來保證會自動執行一系列個別動作;這表示它們全都成功或全部不成功。 如果沒有,則也會在允許任何其他專案發生之前自動復原。 如果範圍中還沒有交易, DataCoNtext 會自動啟動資料庫交易,以在您呼叫 SubmitChanges () 時保護更新。

您可以選擇控制所使用的交易類型、其隔離等級,或藉由自行起始交易來實際包含的內容。 DataCoNtext將使用的交易隔離稱為ReadCommitted

C#

Product prod = db.Products.Single(p => p.ProductID == 15);

if (prod.UnitsInStock > 0)
   prod.UnitsInStock--;

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

Visual Basic

Dim product = (From prod In db.Products _ 
                         Where prod.ProductID = 15).First

If product.UnitsInStock > 0) Then
   product.UnitsInStock -= 1
End If

Using ts As TransactionScope = New TransactionScope())
   db.SubmitChanges()
   ts.Complete()
End Using

上述範例會藉由建立新的交易範圍物件來起始完全序列化的交易。 交易範圍內執行的所有資料庫命令都會受到交易保護。

C#

Product prod = db.Products.Single(p => p.ProductId == 15);

if (prod.UnitsInStock > 0)
   prod.UnitsInStock--;

using(TransactionScope ts = new TransactionScope()) {
   db.ExecuteCommand("exec sp_BeforeSubmit");
   db.SubmitChanges();
   ts.Complete();
}

Visual Basic

Dim product = (From prod In db.Products _ 
                         Where prod.ProductID = 15).First

If product.UnitsInStock > 0) Then
   product.UnitsInStock -= 1
End If

Using ts As TransactionScope = New TransactionScope())
   db.ExecuteCommand(“exec sp_BeforeSubmit”)
   db.SubmitChanges()
   ts.Complete()
End Using

這個修改過的相同範例版本會使用DataCoNtext上的ExecuteCommand () 方法,在提交變更之前立即在資料庫中執行預存程式。 不論預存程式對資料庫執行什麼動作,我們都可以確定其動作是相同交易的一部分。

如果交易成功完成, DataCoNtext 會擲回所有累積的追蹤資訊,並將實體的新狀態視為未變更。 不過,如果交易失敗,則不會回復物件變更。 這可讓您在變更提交期間處理問題的最大彈性。

您也可以使用本機 SQL 交易,而不是新的 TransactionScope。 LINQ to SQL提供這項功能,可協助您將LINQ to SQL功能整合到預先存在的 ADO.NET 應用程式中。 不過,如果您前往此路由,則必須負責更多工作。

C#

Product prod = q.Single(p => p.ProductId == 15);

if (prod.UnitsInStock > 0)
   prod.UnitsInStock--;

db.Transaction = db.Connection.BeginTransaction();
try {
   db.SubmitChanges();
   db.Transaction.Commit();
}
catch {
   db.Transaction.Rollback();
   throw;
}
finally {
   db.Transaction = null;
}

Visual Basic

Dim product = (From prod In db.Products _ 
                         Where prod.ProductID = 15).First

If product.UnitsInStock > 0) Then
   product.UnitsInStock -= 1
End If

db.Transaction = db.Connection.BeginTransaction()
Try
   db.SubmitChanges()
   db.Transaction.Commit()

catch e As Exception
   db.Transaction.Rollback()
   Throw e
Finally
   db.Transaction = Nothing
End Try

如您所見,使用手動控制的資料庫交易會更加相關。 您不僅必須自行啟動它,您也必須先將 DataCoNtext 指派給 Transaction 屬性,明確地告訴它使用它。 然後,您必須使用 try-catch 區塊來將提交邏輯加總,並記得明確地告訴交易認可,並明確告訴 DataCoNtext 接受變更,或在任何時間點發生失敗時中止交易。 此外,當您完成時,別忘了將 Transaction 屬性設定回 Null

預存程序

呼叫SubmitChanges () 時,LINQ to SQL會產生並執行 SQL 命令,以插入、更新和刪除資料庫中的資料列。 應用程式開發人員可以覆寫這些動作,並就地使用自訂程式碼來執行所需的動作。 如此一來, 變更處理器就可以自動叫用資料庫預存程式之類的替代功能。

請考慮預存程式,以更新 Northwind 範例資料庫中 Products 資料表的存貨單位。 程式的 SQL 宣告如下所示。

SQL

create proc UpdateProductStock
   @id               int,
   @originalUnits    int,
   @decrement         int
as

您可以在強型別 DataCoNtext上定義 方法,來使用預存程式,而不是一般自動產生的更新命令。 即使DataCoNtext類別是由LINQ to SQL程式碼產生工具自動產生,您仍然可以在自己的部分類別中指定這些方法。

C#

public partial class Northwind : DataContext
{
   ...

   public void UpdateProduct(Product original, Product current) {
      // Execute the stored procedure for UnitsInStock update
      if (original.UnitsInStock != current.UnitsInStock) {
         int rowCount = this.ExecuteCommand(
            "exec UpdateProductStock " +
            "@id={0}, @originalUnits={1}, @decrement={2}",
            original.ProductID,
            original.UnitsInStock,
            (original.UnitsInStock - current.UnitsInStock)
         );
         if (rowCount < 1)
            throw new Exception("Error updating");
      }
      ...
   }
}

Visual Basic

Partial Public Class Northwind
         Inherits DataContext

   ...

   Public Sub UpdateProduct(original As Product, current As Product)
      ‘ Execute the stored procedure for UnitsInStock update
      If original.UnitsInStock <> current.UnitsInStock Then
         Dim rowCount As Integer = ExecuteCommand( _
            "exec UpdateProductStock " & _
            "@id={0}, @originalUnits={1}, @decrement={2}", _
            original.ProductID, _
            original.UnitsInStock, _
            (original.UnitsInStock - current.UnitsInStock) )
         If rowCount < 1 Then
            Throw New Exception(“Error updating”)
         End If
      End If
      ...
   End Sub
End Class

方法和泛型參數的簽章會指示 DataCoNtext 使用此方法來取代產生的 update 語句。 LINQ to SQL會使用原始和目前參數來傳入指定型別之 物件的原始和目前複本。 這兩個參數可用於開放式平行存取衝突偵測。

注意 如果您覆寫預設更新邏輯,則衝突偵測是您的責任。

使用DataCoNtextExecuteCommand () 方法來叫用預存程式UpdateProductStock。 它會傳回受影響的資料列數目,並具有下列簽章:

C#

public int ExecuteCommand(string command, params object[] parameters);

Visual Basic

Public Function ExecuteCommand(command As String, _
         ParamArray parameters() As Object) As Integer

物件陣列用於傳遞執行 命令所需的參數。

類似于 update 方法,可以指定插入和刪除方法。 插入和刪除方法只需要更新實體類型的一個參數。 例如,您可以指定插入和刪除 Product 實例的方法,如下所示:

C#

public void InsertProduct(Product prod) { ... }
public void DeleteProudct(Product prod) { ... }

Visual Basic

Public Sub InsertProduct(prod As Product)  ... 
Public Sub DeleteProudct(prod As Product)  ... 

實體類別In-Depth

使用屬性

實體類別就像您可能定義為應用程式一部分的任何一般物件類別一樣,不同之處在于它會加上與特定資料庫資料表建立關聯的特殊資訊。 這些批註會做為類別宣告上的自訂屬性。 只有在您將 類別與LINQ to SQL搭配使用時,屬性才有意義。 它們類似于.NET Framework中的 XML 序列化屬性。 這些「資料」屬性提供LINQ to SQL有足夠的資訊,可針對資料庫將物件的查詢轉譯為 SQL 查詢,並將物件的變更轉譯為 SQL 插入、更新和刪除命令。

您也可以使用 XML 對應檔而非屬性來表示對應資訊。 外部對應一節會更詳細地描述此案例。

Database 屬性

如果資料庫不是由連接提供,則會使用 Database 屬性來指定資料庫的預設名稱。 資料庫 屬性可以套用至強型別 DataCoNtext 宣告。 此屬性是選擇性的。

Database 屬性

屬性 類型 Description
名稱 String 指定資料庫的名稱。 只有在連接本身未指定資料庫名稱時,才會使用此資訊。 如果此 Database 屬性不存在於內容宣告上,且連接未指定一個屬性,則會假設資料庫的名稱與內容類別別相同。

C#

[Database(Name="Database#5")]
public class Database5 : DataContext {
   ...
}

Visual Basic

<Database(Name:="Database#5")> _
Public Class Database5 
               Inherits DataContext
   ...
End Class

資料表屬性

Table屬性可用來將類別指定為與資料庫資料表相關聯的實體類別。 具有Table屬性的類別會由LINQ to SQL特別處理。

資料表屬性

屬性 類型 Description
名稱 String 指定資料表的名稱。 如果未指定這項資訊,則會假設資料表的名稱與實體類別相同。

C#

[Table(Name="Customers")]
public class Customer {
   ...
}

Visual Basic

<Table(Name:="Customers")> _
Public Class Customer 
   ...
End Class

資料行屬性

Column屬性是用來指定實體類別的成員,該成員代表資料庫資料表中的資料行。 它可以套用至任何欄位或屬性、公用、私人或內部。 LINQ to SQL儲存資料庫的變更時,只會保存識別為數據行的成員。

資料行屬性

屬性 類型 Description
名稱 String 資料表或檢視中資料行的名稱。 如果未指定,則會假設資料行的名稱與類別成員相同。
儲存體 String 基礎儲存體的名稱。 如果指定,它會告訴LINQ to SQL如何略過資料成員的公用屬性存取子,並與原始值本身互動。 如果未指定LINQ to SQL會取得並使用公用存取子設定值。
DBType String 使用資料庫類型和修飾詞指定的資料庫資料行類型。 這會是用來定義 T-SQL 資料表宣告命令中資料行的確切文字。 如果未指定,則會從成員類型推斷資料庫資料行類型。 只有在 CreateDatabase () 方法預期用來建立資料庫的實例時,才需要特定資料庫類型。
IsPrimaryKey Bool 如果設定為 true,則類別成員代表屬於資料表主鍵一部分的資料行。 如果將類別的多個成員指定為 Id,主鍵即為相關聯資料行的複合。
IsDbGenerated Boolean 識別成員的資料行值是由資料庫自動產生。 指定IsDbGenerated=true的主鍵也應該具有具有IDENTITY修飾詞的DBTypeIsDbGenerated 成員會在插入資料列之後立即同步處理,並在SubmitChanges () 完成之後可供使用。
IsVersion Boolean 將成員的資料行類型識別為資料庫時間戳記或版本號碼。 每次更新相關聯的資料列時,資料庫會遞增版本號碼,而且時間戳記資料行會更新。 IsVersion=true的成員會在更新資料列之後立即同步處理。 在 SubmitChanges () 完成之後,會顯示新的值。
UpdateCheck UpdateCheck 決定LINQ to SQL如何實作開放式並行存取衝突偵測。 如果未將任何成員指定為 IsVersion=true 偵測,則會藉由比較原始成員值與目前的資料庫狀態來完成。 您可以藉由為每個成員提供UpdateCheck列舉值,來控制在衝突偵測期間LINQ to SQL使用的成員。
  • 律:一律使用此資料行進行衝突偵測
  • 永不:永不使用此資料行進行衝突偵測
  • WhenChanged:只有在應用程式變更成員時,才使用此資料行
IsDiscriminator Boolean 判斷類別成員是否保存繼承階層的鑒別子值。
運算是 String 不會影響LINQ to SQL的作業,但在 期間會使用 。CreateDatabase () 做為代表計算資料行運算式的原始 SQL 運算式。
CanBeNull Boolean 表示值可以包含 Null 值。 這通常是從實體成員的 CLR 類型推斷。 使用這個屬性工作表示字串值表示為資料庫中不可為 Null 的資料行。
AutoSync AutoSync 指定是否從資料庫在插入或更新命令上產生的值自動同步處理資料行。 此標籤的有效值為 OnInsertAlwaysNever

典型的實體類別會在公用屬性上使用 Column 屬性,並將實際值儲存在私人欄位中。

C#

private string _city;

[Column(Storage="_city", DBType="NVarChar(15)")]
public string City {
   get { ... }
   set { ... }
}

Visual Basic

Private _city As String

<Column(Storage:="_city", DBType:="NVarChar(15)")> _
public Property City As String
   Get
   set
End Property

只有指定 DBTypeCreateDatabase () 方法才能建構最精確的資料表。 否則,基礎資料行的知識僅限於未使用 15 個字元。

代表資料庫類型主鍵的成員通常會與自動產生的值相關聯。

C#

private string _orderId;

[Column(Storage="_orderId", IsPrimaryKey=true, IsDbGenerated = true,
   DBType="int NOT NULL IDENTITY")]
public string OrderId {
   get { ... }
   set { ... }
}

Visual Basic

Private _orderId As String

<Column(Storage:="_orderId", IsPrimaryKey:=true, _
           IsDbGenerated:= true, DBType:="int NOT NULL IDENTITY")> _
public Property OrderId As String
   Get
   Set
End Property

如果您確實指定 DBType,請務必包含 IDENTITY 修飾詞。 LINQ to SQL不會擴增自訂指定的DBType。 不過,如果DBType保持未指定LINQ to SQL會推斷透過CreateDatabase () 方法建立資料庫時需要IDENTITY修飾詞。

同樣地,如果 IsVersion 屬性為 true, DBType 必須指定正確的修飾詞,才能指定版本號碼或時間戳記資料行。 如果未指定DBType,LINQ to SQL會推斷正確的修飾詞。

您可以藉由指定成員的存取層級,或甚至限制存取子本身,來控制與自動產生之資料行、版本戳記或任何想要隱藏之資料行相關聯的成員存取權。

C#

private string _customerId;

[Column(Storage="_customerId", DBType="NCHAR(5) ")]
public string CustomerID {
   get { ... }
}

Visual Basic

Private _customerId As String

<Column(Storage:="_customerId", DBType:="NCHAR(5)")> _
Public Property CustomerID As String
   Get
End Property

Order 的 CustomerID 屬性可以透過不定義 set 存取子來設為唯讀。 LINQ to SQL仍然可以透過儲存體成員取得並設定基礎值。

您也可以將 Column 屬性放在私人成員上,讓應用程式的其餘部分完全無法存取成員。 這可讓實體類別包含與類別商務邏輯相關的資訊,而不會一般公開它。 即使私用成員是翻譯資料的一部分,因為它們是私人的,您無法在語言整合的查詢中參考它們。

根據預設,所有成員都會用來執行開放式並行衝突偵測。 您可以藉由指定其 UpdateCheck 值來控制特定成員是否使用。

C#

[Column(Storage="_city", UpdateCheck=UpdateCheck.WhenChanged)]
public string City {
   get { ... }
   set { ... }
}

Visual Basic

<Column(Storage:="_city", UpdateCheck:=UpdateCheck.WhenChanged)> _
Public Property City As String
   Get
   Set
End Property

下表顯示資料庫類型與對應 CLR 類型之間的允許對應。 判斷要用來代表特定資料庫資料行的 CLR 類型時,請使用這個資料表作為指南。

資料庫類型和對應的 CLR 類型允許對應

資料庫類型 .NET CLR 類型 註解
bit、Tinyint、Smallint、int、Bigint Bye、Int16、Uint16、Int32、Uint32、Int64、Uint64 可能進行遺失轉換。 值可能不會往返。
bit Boolean  
decimal, numeric, smallmoney, money Decimal 縮放差異可能會導致遺失轉換。 可能不會往返。
real、float Single、Double 精確度差異。
char, Varchar, text, Nchar, Nvarchar, Ntext String 可能的地區設定差異。
datetime, smalldatetime Datetime 不同的精確度可能會導致遺失轉換和往返問題。
UNIQUEIDENTIFIER Guid 不同的定序規則。 排序可能無法如預期般運作。
timestamp Byte[] (Byte () in Visual Basic) , Binary 位元組陣列會被視為純量型別。 呼叫建構函式時,使用者須負責配置適當的儲存體。 它被視為不可變,而且不會追蹤變更。
binary、varbinary Byte[] (Byte () in Visual Basic) , Binary  

關聯屬性

Association屬性是用來指定屬性,代表資料庫關聯性,例如外鍵與主鍵關聯性。

關聯屬性

屬性 類型 Description
名稱 String 關聯的名稱。 這通常與資料庫的外鍵條件約束名稱相同。 當 CreateDatabase () 用來建立資料庫的實例,以產生相關的條件約束時,就會使用它。 它也可用來協助區分單一實體類別中參考相同目標實體類別的多個關聯性。 在此情況下,如果兩者都定義) 必須具有相同名稱,則關聯性 (的關聯性屬性。
儲存體 String 基礎儲存體成員的名稱。 如果指定,它會告訴LINQ to SQL如何略過資料成員的公用屬性存取子,並與原始值本身互動。 如果未指定LINQ to SQL取得並使用公用存取子設定值。 建議所有關聯成員都是已識別個別儲存體成員的屬性。
ThisKey String 這個實體類別之一或多個成員名稱的逗號分隔清單,代表關聯這個端的索引鍵值。 如果未指定,則會假設成員是組成主鍵的成員。
OtherKey String 目標實體類別之一或多個成員名稱的逗號分隔清單,代表關聯另一端的索引鍵值。 如果未指定,則會假設成員是組成其他實體類別主鍵的成員。
IsUnique Boolean 如果 外鍵上有唯一性條件約束,則為 True,表示 true 1:1 關聯性。 這個屬性很少會當做 1:1 關聯性使用,幾乎不可能在資料庫內管理。 大部分的實體模型是使用 1:n 關聯性來定義,即使應用程式開發人員將其視為 1:1。
IsForeignKey Boolean 如果 關聯的目標 「其他」類型是來源類型的父系,則為 True。 使用外鍵與主鍵關聯性時,持有外鍵的側是子系,而保留主鍵的側則是父系。
DeleteRule String 用來將刪除行為新增至此關聯。 例如,「CASCADE」 會將 「ON DELETE CASCADE」 新增至 FK 關聯性。 如果設定為 null,則不會新增任何刪除行為。

關聯屬性代表另一個實體類別實例的單一參考,或代表參考的集合。 單一參考必須在實體類別中使用EntityRef T > (EntityRef < (OfT) Visual Basic) 實數值型別來儲存實際參考。 EntityRef類型是LINQ to SQL啟用延遲載入參考的方式。

C#

class Order
{
   ...
   private EntityRef<Customer> _Customer;

   [Association(Name="FK_Orders_Customers", Storage="_Customer",
      ThisKey="CustomerID")]
   public Customer Customer {
      get { return this._Customer.Entity; }
      set { this._Customer.Entity = value;
            // Additional code to manage changes }
   }
}

Visual Basic

Class Order

   ...
   Private _customer As EntityRef(Of Customer)

   <Association(Name:="FK_Orders_Customers", _
            Storage:="_Customer", ThisKey:="CustomerID")> _
   public Property Customer() As Customer
      Get  
         Return _customer.Entity
      End Get   
   Set (value As Customer)
      _customer.Entity = value
      ‘ Additional code to manage changes
   End Set
End Class

公用屬性的類型為Customer,而不是EntityRef < Customer >。 請務必不要將 EntityRef 類型公開為公用 API 的一部分,因為查詢中此類型的參考將不會轉譯為 SQL。

同樣地,代表集合的關聯屬性必須使用 Visual) Basic 中的EntitySet T > (EntitySet < (OfT) 來儲存關聯性。

C#

class Customer
{
   ...
   private EntitySet<Order> _Orders;

   [Association(Name="FK_Orders_Customers", Storage="_Orders",
      OtherKey="CustomerID")]
   public EntitySet<Order> Orders {
      get { return this._Orders; }
      set { this._Orders.Assign(value); }
   }
} 

Visual Basic

Class Customer

   ...
   Private _Orders As EntitySet(Of Order)

   <Association(Name:="FK_Orders_Customers", _
         Storage:="_Orders", OtherKey:="CustomerID")> _
   public Property Orders() As EntitySet(Of Order)
      Get
           Return _Orders
      End Get
   Set (value As EntitySet(Of Order))
      _Orders.Assign(value)
   End Property
End Class

不過,由於 Visual) Basic 中的EntitySet T > (EntitySet < (OfT) 是集合,所以使用EntitySet做為傳回類型是有效的。 在 Visual Basic) 介面中使用ICollection T > (ICollection < (OfT) ,也有效區分集合的真實類型。

C#

class Customer
{
   ...

   private EntitySet<Order> _Orders;

   [Association(Name="FK_Orders_Customers", Storage="_Orders",
      OtherKey="CustomerID")]
   public ICollection<Order> Orders {
      get { return this._Orders; }
      set { this._Orders.Assign(value); }
   }
}

Visual Basic

Class Customer

   ...
   Private _orders As EntitySet(Of Order)

   <Association(Name:="FK_Orders_Customers", _
         Storage:="_Orders", OtherKey:="CustomerID")> _
   public Property Orders() As ICollection (Of Order)
      Get
           Return _orders
      End Get
Set (value As ICollection (Of Order))
         _orders.Assign(value)
      End Property
End Class

如果您公開屬性的公用 setter,請務必在EntitySet上使用Assign () 方法。 這可讓實體類別繼續使用相同的集合實例,因為它可能已經系結至 變更追蹤服務

ResultType 屬性

這個屬性會指定可列舉序列的專案類型,可以從宣告為傳回 IMultipleResults 介面的函式傳回。 這個屬性可以多次指定。

ResultType 屬性

屬性 類型 描述
類型 類型 傳回結果的類型。

StoredProcedure 屬性

StoredProcedure屬性是用來宣告對 DataCoNtextSchema類型上定義之方法的呼叫會轉譯為資料庫預存程式的呼叫。

StoredProcedure 屬性

屬性 類型 Description
名稱 String 資料庫中預存程式的名稱。 如果未指定,則會假設預存程式的名稱與 方法相同

函式屬性

Function屬性是用來宣告對 DataCoNtextSchema上定義之方法的呼叫會轉譯為對資料庫使用者定義純量或資料表值函式的呼叫。

函式屬性

屬性 類型 Description
名稱 String 資料庫中函式的名稱。 如果未指定,則會假設函式的名稱與 方法相同

參數屬性

Parameter屬性可用來宣告方法與資料庫預存程式或使用者定義函數參數之間的對應。

參數屬性

屬性 類型 Description
名稱 String 資料庫中參數的名稱。 如果未指定,則會從方法參數名稱推斷參數。
DBType String 使用資料庫類型和修飾詞指定的參數類型。

InheritanceMapping 屬性

InheritanceMapping屬性是用來描述特定鑒別子代碼與繼承子類型之間的對應。 用於繼承階層的所有 InheritanceMapping 屬性都必須在階層的根類型上宣告。

InheritanceMapping 屬性

Propety 類型 描述
程式碼 Object 鑒別副程式代碼值。
類型 類型 繼承子類型。 這可能是繼承階層中的任何非抽象類別型,包括根類型。
IsDefault Boolean 判斷指定的繼承子類型是否為建構的預設型別,LINQ to SQL找到未由 InheritanceMapping屬性定義的鑒別副程式代碼。 只有其中一個 InheritanceMapping 屬性必須使用 IsDefault 宣告為 true。

圖表一致性

圖表是物件資料結構的一般詞彙,這些物件都是藉由參考來參照彼此。 階層 (或樹狀結構) 是圖形的變質形式。 領域特定物件模型通常會描述最能描述為物件圖形的參考網路。 物件圖形的健康情況對於應用程式的穩定性非常重要。 這就是為什麼請務必確定圖形內的參考與資料庫中定義的商務規則和/或條件約束保持一致。

LINQ to SQL不會為您自動管理關聯性參考的一致性。 當關聯性雙向變更至關聯性的其中一端時,應該會自動更新另一端。 請注意,一般物件的行為不常見,因此您不太可能以這種方式設計物件。

LINQ to SQL確實提供一些機制,讓這項工作變得簡單,而且有一個模式可供您遵循,以確保您正確管理您的參考。 程式碼產生工具所產生的實體類別會自動實作正確的模式。

C#

public class Customer() {
   this._Orders =
      new EntitySet<Order>(
         new Action<Order>(this.attach_Orders),
         new Action<Order>(this.detach_Orders));
);}

Visual Basic

Public Class Customer()
         _Orders = New EntitySet(Of Order)( _
              New Action(Of Order)(attach_Orders), _
                 New Action(Of Order)(detach_Orders))
      End Class
);}

Visual Basic) 類型中的EntitySet < T > ( (EntitySet (OfT) 具有建構函式,可讓您提供兩個委派做為回呼;第一個當專案新增至集合時,第二個委派會在移除時使用。 如您在範例中所見,您為這些委派指定的程式碼可以且應該寫入以更新反向關聯性屬性。 這是當訂單新增至客戶的Orders集合時,Order實例上的Customer屬性會自動變更的方式。

另一端實作關聯性並不簡單。 Visual Basic) 中的EntityRef < T > (EntityRef (OfT) 是定義為盡可能包含實際物件參考的額外額外負荷的數值型別。 它沒有一對委派的空間。 相反地,管理單一參考圖形一致性的程式碼應該內嵌在屬性存取子本身中。

C#

[Association(Name="FK_Orders_Customers", Storage="_Customer",
   ThisKey="CustomerID")]
public Customer Customer {
   get {
      return this._Customer.Entity;
   }
   set {
      Customer v = this._Customer.Entity;
      if (v != value) {
         if (v != null) {
            this._Customer.Entity = null;
            v.Orders.Remove(this);
         }
         this._Customer.Entity = value;
         if (value != null) {
            value.Orders.Add(this);
         }
      }
   }
}

Visual Basic

<Association(Name:="FK_Orders_Customers", _
         Storage:="_Customer", ThisKey:="CustomerID")> _
Public Property Customer As Customer 
   Get
      Return _Customer.Entity
   End Get
   Set (value As Customer)
      Dim cust As Customer v = _customer.Entity
      if cust IsNot value Then
         If cust IsNot Nothing Then
            _Customer.Entity = Nothing
            cust.Orders.Remove(Me)
         End If

         _customer.Entity = value
         if value IsNot Nothing Then
            value.Orders.Add(Me)
         End If
      End If
   End Set
End Property

看看 setter。 變更 Customer 屬性時,訂單實例會先從目前的客戶的 Orders 集合中移除,然後稍後才新增至新客戶的集合。 請注意,在呼叫 Remove () 之前,實際實體參考會設定為 null。 這會在呼叫 Remove () 方法時避免遞迴。 請記住, EntitySet 會使用回呼委派,將這個物件的 Customer 屬性指派給 Null。 呼叫 Add () 之前會發生相同的情況。 實際實體參考會更新為新的值。 這同樣會限制任何可能的遞迴,當然也會在第一個位置完成 setter 的工作。

一對一關聯性的定義與單一參考端的一對多關聯性定義非常類似。 系統會指派新的物件,而不是Add () Remove () ,或指派Null給關聯性。

同樣地,關聯性屬性會維持物件圖形的一致性非常重要。 如果記憶體內建物件圖形與資料庫資料不一致,則會在呼叫 SubmitChanges 方法時產生運行時例外狀況。 請考慮使用程式碼產生工具來維護一致性工作。

變更通知

您的物件可能會參與變更追蹤程式。 它們不需要這麼做,但可以大幅減少追蹤潛在物件變更所需的額外負荷。 您的應用程式可能會從查詢擷取更多物件,而不是最後修改的物件。 若沒有物件的主動式協助,變更追蹤服務會限制其實際追蹤變更的方式。

由於執行時間中沒有真正的攔截服務,因此不會實際執行正式追蹤。 相反地,第一次擷取物件時,會儲存物件的重複複本。 稍後,當您呼叫 SubmitChanges () 時,這些複本會用來與您指定的複本進行比較。 如果其值不同,則已修改物件。 這表示即使您從未變更記憶體中,每個物件都需要兩個複本。

更好的解決方案是讓物件在確實變更時向變更追蹤服務宣告。 這可以藉由讓 物件實作公開回呼事件的介面來完成。 變更追蹤服務接著可以連接每個物件,並在變更時接收通知。

C#

[Table(Name="Customers")]
public partial class Customer: INotifyPropertyChanging {

   public event PropertyChangingEventHandler PropertyChanging;

   private void OnPropertyChanging() {
      if (this.PropertyChanging != null) {
         this.PropertyChanging(this, emptyEventArgs);
      }
   }

   private string _CustomerID;

   [Column(Storage="_CustomerID", IsPrimaryKey=true)]
   public string CustomerID {
      get {
         return this._CustomerID;
      }
      set {
         if ((this._CustomerID != value)) {
            this.OnPropertyChanging("CustomerID");
            this._CustomerID = value;
         }
      }
   }
}

Visual Basic

<Table(Name:="Customers")> _
Partial Public Class Customer 
         Inherits INotifyPropertyChanging
Public Event PropertyChanging As PropertyChangingEventHandler _
        Implements INotifyPropertyChanging.PropertyChanging

   Private Sub OnPropertyChanging()
         RaiseEvent PropertyChanging(Me, emptyEventArgs)
   End Sub

   private _customerID As String 

   <Column(Storage:="_CustomerID", IsPrimaryKey:=True)>
   public Property CustomerID() As String
      Get
         Return_customerID
      End Get
      Set (value As Customer)
         If _customerID IsNot value Then
            OnPropertyChanging(“CustomerID”)
            _CustomerID = value
         End IF
      End Set
   End Function
End Class

為了協助改善變更追蹤,您的實體類別必須實作 INotifyPropertyChanging 介面。 它只需要定義名為 PropertyChanging的事件—變更追蹤服務會在物件進入其擁有權時向事件註冊。 您只需要立即引發此事件,才能變更屬性值。

也別忘了將相同的事件引發邏輯放在您的關聯屬性 setter 中。 針對 EntitySets,請在您提供的委派中引發事件。

C#

public Customer() {
   this._Orders =
      new EntitySet<Order>(
         delegate(Order entity) {
            this.OnPropertyChanging("Orders");
            entity.Customer = this;
         },
         delegate(Order entity) {
            this.onPropertyChanging("Orders");
            entity.Customer = null;
         }
      );
}

Visual Basic

Dim _orders As EntitySet(Of Order)
Public Sub New()
   _orders = New EntitySet(Of Order)( _
      AddressOf OrderAdding, AddressOf OrderRemoving)
End Sub

Sub OrderAdding(ByVal o As Order)
   OnPropertyChanging()
   o.Customer = Me
End Sub

Sub OrderRemoving(ByVal o As Order)
   OnPropertyChanging()
   o.Customer = Nothing
End Sub

繼承

LINQ to SQL支援單一資料表對應,其中整個繼承階層會儲存在單一資料庫資料表中。 資料表包含整個階層中所有可能資料行的扁平化聯集,而且每個資料列在資料行中都有 Null,不適用於資料列所代表之實例的類型。 單一資料表對應策略是最簡單的繼承表示,並且可以在許多不同類別的查詢中提供良好的效能特性。

對應

若要使用LINQ to SQL實作此對應,您必須在繼承階層的根類別上指定下列屬性和屬性屬性:

  • [Table] (< Visual Basic) 屬性中的 Table >
  • 階層結構中每個類別的[InheritanceMapping] (< Visual Basic) 屬性中的 InheritanceMapping >。 對於非抽象類別,此屬性必須定義 Code 屬性 (出現在 [繼承辨別子] 資料行之資料庫資料表中的值,以指出此資料列) 屬於哪一個類別或子類別,而 Type 屬性 (指定索引鍵值表示) 的類別或子類別。
  • Visual Basic) 屬性中,< 單一 [InheritanceMapping] 上的 IsDefault 屬性 (InheritanceMapping > 這個屬性可用來指定「後援」對應,以防來自資料庫資料表的鑒別子值不符合繼承對應中的任何 Code 值。
  • Visual Basic) 屬性中[Column] (< Column >IsDiscriminator屬性,表示這是保存繼承對應程式碼值的資料行。

子類別上不需要任何特別的屬性 (Attribute) 或屬性 (Property)。 請注意,子類別在 Visual Basic) 屬性中沒有[Table] (< Table >

在下列範例中, CarTruck 子類別中包含的資料會對應至單一資料庫資料表 Vehicle。 (為了簡化範例,範例程式碼會使用欄位而非資料行對應的屬性。)

C#

[Table]
[InheritanceMapping(Code = "C", Type = typeof(Car))]
[InheritanceMapping(Code = "T", Type = typeof(Truck))]
[InheritanceMapping(Code = "V", Type = typeof(Vehicle),
   IsDefault = true)]
public class Vehicle
{
   [Column(IsDiscriminator = true)]
   public string Key;
   [Column(IsPrimaryKey = true)]
   public string VIN;
   [Column]
   public string MfgPlant;
}
public class Car : Vehicle
{
   [Column]
   public int TrimCode;
   [Column]
   public string ModelName;
}

public class Truck : Vehicle
{
   [Column]
   public int Tonnage;
   [Column]
   public int Axles;
}

Visual Basic

<Table> _
<InheritanceMapping(Code:="C", Type:=Typeof(Car))> _
<InheritanceMapping(Code:="T", Type:=Typeof(Truck))> _
<InheritanceMapping(Code:="V", Type:=Typeof(Vehicle), _
              IsDefault:=true)> _
Public Class Vehicle

   <Column(IsDiscriminator:=True)> _
   Public Key As String
   <Column(IsPrimaryKey:=True)> _
   Public VIN As String
   <Column> _
   Public MfgPlant As String
End Class
Public Class Car
       Inherits Vehicle
   <Column> _
   Public TrimCode As Integer
   <Column> _
   Public ModelName As String
End Class

Public class Truck
       Inherits Vehicle 
   <Column> _
   public Tonnage As Integer
   <Column> _
   public Axles As Integer
End Class

類別圖表如下所示:

圖 1. 車輛類別圖表

當您在 [伺服器總管] 中檢視產生的資料庫關係圖時,您會看到資料行都已對應到單一資料表,如下所示:

圖 2. 對應至單一資料表的資料行

請注意,代表子類型中欄位的資料行類型必須是可為 Null,或必須指定預設值。 這是插入命令成功的必要專案。

查詢

下列程式碼提供如何在查詢中使用衍生型別的類別:

C#

var q = db.Vehicle.Where(p => p is Truck);
//or
var q = db.Vehicle.OfType<Truck>();
//or
var q = db.Vehicle.Select(p => p as Truck).Where(p => p != null);
foreach (Truck p in q)
   Console.WriteLine(p.Axles);

Visual Basic

Dim trucks = From veh In db.Vehicle _ 
             Where TypeOf(veh) Is Truck

For Each truck In trucks
   Console.WriteLine(p.Axles) 
Next

進階

您可以擴充階層,遠超出已提供的簡單範例。

範例 1

以下是更深入的階層和更複雜的查詢:

C#

[Table]
[InheritanceMapping(Code = "V", Type = typeof(Vehicle), IsDefault = true)]
[InheritanceMapping(Code = "C", Type = typeof(Car))]
[InheritanceMapping(Code = "T", Type = typeof(Truck))]
[InheritanceMapping(Code = "S", Type = typeof(Semi))]
[InheritanceMapping(Code = "D", Type = typeof(DumpTruck))]
public class Truck: Vehicle { ... }

public class Semi: Truck { ... }

public class DumpTruck: Truck { ... }

...
// Get all trucks along with a flag indicating industrial application.

db.Vehicles.OfType<Truck>.Select(t => 
   new {Truck=t, IsIndustrial=t is Semi || t is DumpTruck }
);

Visual Basic

<Table> _
<InheritanceMapping(Code:="V", Type:=Typeof(Vehicle), IsDefault:=True)> _
<InheritanceMapping(Code:="C", Type:=Typeof(Car))> _
<InheritanceMapping(Code:="T", Type:=Typeof(Truck))> _
<InheritanceMapping(Code:="S", Type:=Typeof(Semi))> _
<InheritanceMapping(Code:="D", Type:=Typeof(DumpTruck))> _
Public Class Truck
       InheritsVehicle
Public Class Semi
       Inherits Truck

Public Class DumpTruck
       InheritsTruck 
...
' Get all trucks along with a flag indicating industrial application.
Dim trucks = From veh In db.Vehicle _ 
             Where Typeof(veh) Is Truck And _ 
             IsIndustrial = (Typeof(veh) Is Semi _ 
             Or Typeof(veh) Is DumpTruck)

範例 2

下列階層包含介面:

C#

[Table]
[InheritanceMapping(Code = "V", Type = typeof(Vehicle),
   IsDefault = true)]
[InheritanceMapping(Code = "C", Type = typeof(Car))]
[InheritanceMapping(Code = "T", Type = typeof(Truck))]
[InheritanceMapping(Code = "S", Type = typeof(Semi))]
[InheritanceMapping(Code = "H", Type = typeof(Helicopter))]

public class Truck: Vehicle
public class Semi: Truck, IRentableVehicle
public class Helicopter: Vehicle, IRentableVehicle

Visual Basic

<Table> _
<InheritanceMapping(Code:="V", Type:=TypeOf(Vehicle),
   IsDefault:=True) > _
<InheritanceMapping(Code:="C", Type:=TypeOf(Car)) > _
<InheritanceMapping(Code:="T", Type:=TypeOf(Truck)) > _
<InheritanceMapping(Code:="S", Type:=TypeOf(Semi)) > _
<InheritanceMapping(Code:="H", Type:=TypeOf(Helicopter)) > _
Public Class Truck
       Inherits Vehicle
Public Class Semi
       InheritsTruck, IRentableVehicle
Public Class Helicopter
       InheritsVehicle, IRentableVehicle

可能的查詢包括下列專案:

C#

// Get commercial vehicles ordered by cost to rent.
db.Vehicles.OfType<IRentableVehicle>.OrderBy(cv => cv.RentalRate);

// Get all non-rentable vehicles
db.Vehicles.Where(v => !(v is IRentableVehicle));

Visual Basic

' Get commercial vehicles ordered by cost to rent.
Dim rentableVehicles = From veh In _ 
                       db.Vehicles.OfType(Of IRentableVehicle).OrderBy( _ 
                       Function(cv) cv.RentalRate)

' Get all non-rentable vehicles
Dim unrentableVehicles = From veh In _ 
                         db.Vehicles.OfType(Of Vehicle).Where( _ 
                         Function(uv) Not (TypeOf(uv) Is IRentableVehicle))

進階主題

建立資料庫

由於實體類別具有描述關係資料庫資料表和資料行結構的屬性,因此可以使用這項資訊來建立資料庫的新實例。 您可以在DataCoNtext上呼叫CreateDatabase () 方法,讓LINQ to SQL以物件所定義的結構來建構新的資料庫實例。 您可能會想要這樣做有許多原因:您可能要建置應用程式,以在客戶系統上自動安裝本身,或需要本機資料庫的用戶端應用程式來儲存其離線狀態。 在這些案例中,CreateDatabase () 很理想,特別是在有已知資料提供者,例如 SQL Server Express 2005 時。

不過,資料屬性可能不會編碼現有資料庫結構的所有專案。 使用者定義函式、預存程式、觸發程式和檢查條件約束的內容不會由屬性工作表示。 CreateDatabase () 函式只會使用它知道的資訊來建立資料庫的複本,也就是資料庫的結構,以及每個資料表中的資料行類型。 不過,對於各種資料庫而言,這已足夠。

以下範例說明如何建立名為 MyDVDs.mdf的新資料庫:

C#

[Table(Name="DVDTable")]
public class DVD
{
   [Column(Id = true)]
   public string Title;
   [Column]
   public string Rating;
}

public class MyDVDs : DataContext
{
   public Table<DVD> DVDs;

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

Visual Basic

<Table(Name:="DVDTable")> _
Public Class DVD

   <Column(Id:=True)> _
   public Title As String
   <Column> _
   Public Rating As String
End Class

Public Class MyDVDs  
         Inherits DataContext

   Public DVDs As Table(Of DVD)

   Public Sub New(connection As String) 
End Class

物件模型可用來使用 SQL Server Express 2005 建立資料庫,如下所示:

C#

MyDVDs db = new MyDVDs("c:\\mydvds.mdf");
db.CreateDatabase();

Visual Basic

Dim db As MyDVDs = new MyDVDs("c:\mydvds.mdf")
db.CreateDatabase()

LINQ to SQL也提供 API,以在建立新資料庫之前卸載現有的資料庫。 您可以修改上述資料庫建立程式碼,先使用 DatabaseExists () 檢查現有的資料庫版本,然後使用 DeleteDatabase () 卸載它。

C#

MyDVDs db = new MyDVDs("c:\\mydvds.mdf");

if (db.DatabaseExists()) {
   Console.WriteLine("Deleting old database...");
   db.DeleteDatabase();
}

db.CreateDatabase();

Visual Basic

Dim db As MyDVDs = New MyDVDs("c:\mydvds.mdf")

If (db.DatabaseExists()) Then
   Console.WriteLine("Deleting old database...")
   db.DeleteDatabase()
End If

db.CreateDatabase()

在呼叫 CreateDatabase () 之後,新的資料庫就可以接受查詢和命令,例如 SubmitChanges () ,將物件新增至 MDF 檔案。

您也可以使用 MDF 檔案或只是目錄名稱,將CreateDatabase () 與 SQL Server Express 以外的 SKU 搭配使用。 這全都取決於您用於連接字串的內容。 連接字串中的資訊是用來定義將存在的資料庫,不一定是已經存在的資料庫。 LINQ to SQL會擷取相關的資訊位,並用它來判斷要建立的資料庫,以及要建立它的伺服器。 當然,您需要伺服器上的資料庫管理員許可權或對等許可權才能這麼做。

與 ADO.NET 交互操作

LINQ to SQL是 ADO.NET 系列技術的一部分。 它是以 ADO.NET 提供者模型所提供的服務為基礎,因此可以將LINQ to SQL程式碼與現有的 ADO.NET 應用程式混合。

當您建立 LINQ to SQL DataCoNtext時,可以使用現有的 ADO.NET 連線來提供它。 針對 DataCoNtext的所有作業,包括查詢,都會使用您提供的連線。 如果連線已開啟LINQ to SQL將會接受您連線的授權,並在完成連線時保持原狀。 一般而言,除非交易位於範圍內,否則LINQ to SQL在作業完成時關閉其連線。

C#

SqlConnection con = new SqlConnection( ... );
con.Open(); 
...

// DataContext takes a connection
Northwind db = new Northwind(con);
...

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

Visual Basic

Dim con As SqlConnection = New SqlConnection( ... )
con.Open()
...

' DataContext takes a connection
Dim db As Northwind = new Northwind(con)
...

Dim q = From c In db.Customers _
        Where c.City = "London" _
        Select c

您一律可以透過Connection屬性存取DataCoNtext所使用的連線,並自行關閉。

C#

db.Connection.Close(); 

Visual Basic

db.Connection.Close()

您也可以使用自己的資料庫交易來提供 DataCoNtext ,以防您的應用程式已經起始一個,而且您希望 DataCoNtext 與其一起播放。

C#

IDbTransaction = con.BeginTransaction();
...

db.Transaction = myTransaction;
db.SubmitChanges();
db.Transaction = null;

Visual Basic

Dim db As IDbTransaction = con.BeginTransaction()
...

db.Transaction = myTransaction
db.SubmitChanges()
db.Transaction = Nothing

每當設定 交易 時, DataCoNtext 就會在發出查詢或執行命令時使用它。 當您完成時,別忘了將屬性指派回 Null

不過,使用 .NET Framework 執行交易的慣用方法是使用TransactionScope物件。 它可讓您建立跨資料庫和其他記憶體駐留資源管理員運作的分散式交易。 此概念是交易範圍從便宜開始,只有在實際參考交易範圍內的多個資料庫或多個連線時,才會在分散式交易上自行提升為完整。

C#

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

Visual Basic

Using ts As TransactionScope= New TransactionScope()
   db.SubmitChanges()
   ts.Complete()
End Using

直接執行 SQL 語句

連線和交易不是您可以與 ADO.NET 交互操作的唯一方式。 在某些情況下,您可能會發現 DataCoNtext 的查詢或提交變更設施不足,無法用於您可能想要執行的特殊工作。 在這些情況下,您可以使用 DataCoNtext 直接向資料庫發出原始 SQL 命令。

ExecuteQuery () 方法可讓您執行原始 SQL 查詢,並將查詢的結果直接轉換成 物件。 例如,假設 Customer 類別的資料分散在 customer1customer2的兩個數據表上,下列查詢會傳回 Customer 物件的序列。

C#

IEnumerable<Customer> results = db.ExecuteQuery<Customer>(
   @"select c1.custid as CustomerID, c2.custName as ContactName
      from customer1 as c1, customer2 as c2
      where c1.custid = c2.custid"
);

Visual Basic

Dim results As IEnumerable(Of Customer) = _
          db.ExecuteQuery(Of Customer)( _
          "select c1.custid as CustomerID, " & _
          "c2.custName as ContactName " & _
          "from customer1 as c1, customer2 as c2 "& _
          "where c1.custid = c2.custid" )

只要表格式結果中的資料行名稱符合實體類別的資料行屬性,LINQ to SQL就會將物件具體化成任何 SQL 查詢。

ExecuteQuery () 方法也允許參數。 在下列程式碼中,會執行參數化查詢:

C#

IEnumerable<Customer> results = db.ExecuteQuery<Customer>(
   "select contactname from customers where city = {0}",
   "London"
);

Visual Basic

Dim results As IEnumerable(Of Customer) = _
          db.ExecuteQuery(Of Customer)( _
   "select contactname from customers where city = {0}", _
   "London" )

參數會使用 Console.WriteLine () String.Format () 所使用的相同大寫標記法,以查詢文字表示。 事實上,您提供的查詢字串上實際上會呼叫 String.Format () ,並以 p0@p1 ...p (n) 等產生的參數名稱取代大括弧參數。

變更衝突解決

Description

當用戶端嘗試將變更提交至物件,且在更新檢查中使用的一或多個值自用戶端上次讀取之後,就會發生 變更衝突

注意 只有對應為 UpdateCheck.AlwaysUpdateCheck.WhenChanged 的成員才會參與開放式並行檢查。 不會對標示 為 UpdateCheck.Never的成員執行任何檢查。

此衝突的解決方式包括探索物件的哪些成員發生衝突,然後決定該怎麼做。 請注意,開放式平行存取可能不是您特定情況的最佳策略。 有時候「讓最後一次更新勝出」是合理的。

偵測、報告及解決LINQ to SQL中的衝突

衝突解決是重新整理衝突專案的程式,方法是重新查詢資料庫並協調任何差異。 重新整理物件時,變更追蹤器具有舊的原始值和新資料庫值。 接著,LINQ to SQL判斷物件是否衝突。 如果是,LINQ to SQL會決定涉及哪些成員。 如果成員的新資料庫值不同于用於更新檢查失敗) 的舊原始 (,則這是衝突。 任何成員衝突會新增至衝突清單。

例如,在下列案例中,User1 會藉由查詢資料庫以取得資料列來開始準備更新。 在 User1 可以提交變更之前,User2 已變更資料庫。 User1 的提交失敗,因為 Col B 和 Col C 預期的值已變更。

資料庫更新衝突

User Col A Col B Col C
原始狀態 Alfreds Maria Sales
使用者 1 Alfred   Marketing
使用者 2   Mary 服務

在LINQ to SQL中,因為開放式平行存取衝突而無法更新的物件,會導致擲回changeConflictException (ChangeConflictException) 例外狀況。 您可以指定是否應該在第一次失敗時擲回例外狀況,還是應該嘗試所有更新,並在例外狀況中累積並報告任何失敗。

// [C#]
db.SubmitChanges(ConflictMode.FailOnFirstConflict);
db.SubmitChanges(ConflictMode.ContinueOnConflict);
' [Visual Basic]
db.SubmitChanges(ConflictMode.FailOnFirstConflict)
db.SubmitChanges(ConflictMode.ContinueOnConflict)

擲回時,例外狀況會提供 ObjectChangeConflict 集合的存取權。 每個衝突 (對應至單一失敗的更新嘗試) 都有詳細資料,包括 MemberConflicts 清單的存取權。 每個成員衝突都會對應至未通過並行存取檢查之更新中的單一成員。

衝突處理

在上述案例中,User1 具有以下描述的 RefreshMode 選項,可用來在嘗試重新提交之前協調差異。 在所有情況下,用戶端上的記錄都會先「重新整理」,方法是從資料庫提取更新的資料。 此動作可確保下一次更新嘗試不會在相同的並行檢查上失敗。

在這裡,User1 選擇將資料庫值與目前的用戶端值合併,因此只有在目前的變更集也修改該值時,才會覆寫資料庫值。 (請參閱本節稍後的範例 1。)

在上述案例中,在衝突解決之後,資料庫中的結果如下所示:

KeepChanges

  Col A Col B Col C
KeepChanges Alfred (使用者 1) Mary (User 2) 行銷 (使用者 1)
  • Col A:User1 的變更 (Alfred) 隨即出現。
  • Col B:User2 的變更 (Mary) 隨即出現。 此值已合併,因為 User1 尚未變更此值。
  • Col C:User1 的變更 (Marketing) 隨即出現。 User2 的變更 (Service) 不會合並,因為 User1 也變更了該專案。

以下,User1 選擇以目前值覆寫任何資料庫值。 (請參閱本節稍後的範例 2。)

重新整理之後,會提交 User1 的變更。 資料庫中的結果如下所示:

KeepCurrentValues

  Col A Col B Col C
KeepCurrentValues Alfred (使用者 1) Maria (原始) 行銷 (使用者 1)
  • Col A:User1 的變更 (Alfred) 隨即出現。
  • Col B:原始 Maria 會保留;會捨棄 User2 的變更。
  • Col C:User1 的變更 (Marketing) 隨即出現。 會捨棄 User2 的變更 (Service) 。

在下一個案例中,User1 選擇允許資料庫值覆寫用戶端中的目前值。 (請參閱本節稍後的範例 3。)

在上述案例中,在衝突解決之後,資料庫中的結果如下所示:

OverwriteCurrentValues

  Col A Col B Col C
OverwriteCurrentValues Alfreds (Original) Mary (User 2) 服務 (使用者 2)
  • Col A:Alfreds) (的原始值會維持不變;會捨棄 User1 的值 (Alfred) 。
  • Col B:User2 的變更 (Mary) 隨即出現。
  • Col C:User2 的變更 (服務) 隨即出現。 會捨棄 User1 的變更 (Marketing) 。

解決衝突之後,您可以嘗試重新提交。 因為第二次更新可能也會失敗,請考慮使用迴圈進行更新嘗試。

範例

下列程式碼摘錄顯示各種資訊成員和技術,供您探索及解決成員衝突。

範例 1

在此範例中,衝突會「自動」解決。也就是說,除非用戶端也已變更該值,否則資料庫值會與目前的用戶端值合併, (KeepChanges) 。 不會檢查或自訂個別成員衝突的處理。

C#

try {
   context.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException e) {
   //automerge database values into current for members
   //that client has not modified
   context.ChangeConflicts.Resolve(RefreshMode.KeepChanges);
}
//submit succeeds on second try
context.SubmitChanges(ConflictMode.FailOnFirstConflict);

Visual Basic

Try 
   context.SubmitChanges(ConflictMode.ContinueOnConflict)
Catch e As ChangeConflictException
   ' automerge database values into current for members
   ' that client has not modified   context.ChangeConflicts.Resolve(RefreshMode.KeepChanges)
End Try
' submit succeeds on second try
context.SubmitChanges(ConflictMode.FailOnFirstConflict)

範例 2

在此範例中,衝突會再次解決,而不需要任何自訂處理。 但這次,資料庫值不會合並到目前的用戶端值。

C#

try {
   context.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException e) {
   foreach (ObjectChangeConflict cc in context.ChangeConflicts) {
      //No database values are automerged into current
      cc.Resolve(RefreshMode.KeepCurrentValues);
   }
}

Visual Basic

Try 
   context.SubmitChanges(ConflictMode.ContinueOnConflict)
Catch e As ChangeConflictException
   For Each cc As ObjectChangeConflict In context.ChangeConflicts
      ‘No database values are automerged into current
      cc.Resolve(RefreshMode.KeepCurrentValues)
   Next
End Try

範例 3

同樣地,不會進行任何自訂處理。 但在此情況下,所有用戶端值都會以目前的資料庫值更新。

C#

try {
   context.SubmitChanges(ConflictMode.ContinueOnConflict); 
}
catch (ChangeConflictException e) {
   foreach (ObjectChangeConflict cc in context.ChangeConflicts) {
      //No database values are automerged into current
      cc.Resolve(RefreshMode.OverwriteCurrentValues);
   }
}

Visual Basic

Try 
   context.SubmitChanges(ConflictMode.ContinueOnConflict)
Catch e As ChangeConflictException
   For Each cc As ObjectChangeConflict In context.ChangeConflicts
      ' No database values are automerged into current
      cc.Resolve(RefreshMode. OverwriteCurrentValues)
   Next
End Try

範例 4

此範例示範如何存取衝突中實體的資訊。

C#

try {
   user1.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException e) {
   Console.WriteLine("Optimistic concurrency error");
   Console.ReadLine();
   foreach (ObjectChangeConflict cc in user1.ChangeConflicts) {
      ITable table = cc.Table;
      Customers entityInConflict = (Customers)cc.Object;
      Console.WriteLine("Table name: {0}", table.Name);
      Console.Write("Customer ID: ");
      Console.WriteLine(entityInConflict.CustomerID);
   }
}

Visual Basic

Try 
   context.SubmitChanges(ConflictMode.ContinueOnConflict)
Catch e As ChangeConflictException
   Console.WriteLine("Optimistic concurrency error")
   Console.ReadLine()
   For Each cc As ObjectChangeConflict In context.ChangeConflicts
      Dim table As ITable = cc.Table
      Dim entityInConflict As Customers = CType(cc.Object, Customers)
      Console.WriteLine("Table name: {0}", table.Name)
      Console.Write("Customer ID: ")
      Console.WriteLine(entityInConflict.CustomerID)
   Next
End Try

範例 5

本範例會透過個別成員新增迴圈。 您可以在這裡提供任何成員的自訂處理。

注意使用 System.Reflection新增;提供MemberInfo

C#

try {
   user1.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException e) {
   Console.WriteLine("Optimistic concurrency error");
   Console.ReadLine();
   foreach (ObjectChangeConflict cc in user1.ChangeConflicts) {
      ITable table = cc.Table;
      Customers entityInConflict = (Customers)cc.Object;
      Console.WriteLine("Table name: {0}", table.Name);
      Console.Write("Customer ID: ");
      Console.WriteLine(entityInConflict.CustomerID);
      foreach (MemberChangeConflict mc in         cc.MemberConflicts) {
         object currVal = mc.CurrentValue;
         object origVal = mc.OriginalValue;
         object databaseVal = mc.DatabaseValue;
         MemberInfo mi = mc. Member;
         Console.WriteLine("Member: {0}", mi.Name);
         Console.WriteLine("current value: {0}", currVal);
         Console.WriteLine("original value: {0}", origVal);
         Console.WriteLine("database value: {0}", databaseVal);
         Console.ReadLine();
      }
   }
}

Visual Basic

Try 
   user1.SubmitChanges(ConflictMode.ContinueOnConflict)
Catch e As ChangeConflictException
   Console.WriteLine("Optimistic concurrency error")
   Console.ReadLine()
   For Each cc As ObjectChangeConflict In context.ChangeConflicts
      Dim table As ITable = cc.Table
      Dim entityInConflict As Customers = CType(cc.Object, Customers)
      Console.WriteLine("Table name: {0}", table.Name)
      Console.Write("Customer ID: ")
      Console.WriteLine(entityInConflict.CustomerID)
   For Each mc As MemberChangeConflict In   cc.MemberConflicts
         Dim currVal As Object = mc.CurrentValue
         Dim origVal As Object = mc.OriginalValue
         Dim databaseVal As Object = mc.DatabaseValue
         Dim mi As MemberInfo = mc.Member
         Console.WriteLine("Member: {0}", mi.Name)
         Console.WriteLine("current value: {0}", currVal)
         Console.WriteLine("original value: {0}", origVal)
         Console.WriteLine("database value: {0}", databaseVal)
         Console.ReadLine()
      Next

   Next
End Try

預存程式調用

LINQ to SQL 支援預存程序和使用者定義函式。 LINQ to SQL將這些資料庫定義的抽象概念對應至程式碼產生的用戶端物件,讓您可以從用戶端程式代碼以強型別的方式存取它們。 您可以使用 IntelliSense 輕鬆地探索這些方法,而方法簽章會與資料庫中定義的程式和函式簽章類似。 呼叫對應程式所傳回的結果集是強型別集合。 LINQ to SQL可以自動產生對應的方法,但在您選擇不使用程式碼產生的情況下,也支援手動對應。

LINQ to SQL透過使用屬性,將預存程式和函式對應至方法。 StoredProcedureParameterFunction屬性全都支援Name屬性,而Parameter屬性也支援DBType屬性。 以下是兩個範例:

C#

   [StoredProcedure()]
   public IEnumerable<CustOrderHistResult> CustOrderHist(
      [Parameter(Name="CustomerID", DBType="NChar(5)")] string customerID) {

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

      return ((IEnumerable<CustOrderHistResult>)(result.ReturnValue));
   }

[Function(Name="[dbo].[ConvertTemp]")]
public string ConvertTemp(string string) { ... }

Visual Basic

<StoredProcedure()> _
   Public Function CustOrderHist( _
         <Parameter(Name:="CustomerID", DBType:="NChar(5)")> _
         customerID As String) As IEnumerable(Of CustOrderHistResult)

         Dim result As IExecuteResult = ExecuteMethodCall(Me, _
                 CType(MethodInfo.GetCurrentMethod(), MethodInfo), customerID)

         Return CType(result.ReturnValue, IEnumerable(Of CustOrderHistResult))
   End Function

<Function(Name:="[dbo].[ConvertTemp]")> _
Public Function ConvertTemp(str As String) As String

下列範例顯示各種預存程式的對應。

範例 1

下列預存程式會採用單一輸入參數並傳回整數:

CREATE PROCEDURE GetCustomerOrderCount(@CustomerID nchar(5))
AS
Declare @count int
SELECT @count = COUNT(*) FROM ORDERS WHERE CustomerID = @CustomerID
RETURN @count

對應的方法如下所示:

C#

[StoredProcedure(Name = "GetCustomerOrderCount")]
public int GetCustomerOrderCount(
      [Parameter(Name = "CustomerID")] string customerID) {
         IExecuteResult result = this.ExecuteMethodCall(this, 
            ((MethodInfo)(MethodInfo.GetCurrentMethod())), customerID); 
          return (int) result.ReturnValue;
}

Visual Basic

<StoredProcedure (Name:="GetCustomerOrderCount")> _
public Function GetCustomerOrderCount( _
      <Parameter(Name:= "CustomerID")> customerID As String) As Integer
         Dim result As IExecuteResult = ExecuteMethodCall(Me, _
               CType(MethodInfo.GetCurrentMethod(), MethodInfo), customerID)
          return CInt(result.ReturnValue)
End Function

範例 2

如果預存程序 (Stored Procedure) 可以傳回多個結果圖案,則傳回型別不可以強型別 (Strongly Typed) 為單一投影圖案。 在下列範例中,結果圖形取決於輸入:

CREATE PROCEDURE VariableResultShapes(@shape int)
AS
if(@shape = 1)
   select CustomerID, ContactTitle, CompanyName from customers
else if(@shape = 2)
   select OrderID, ShipName from orders

對應的方法如下所示:

C#

      [StoredProcedure(Name = "VariableResultShapes")]
      [ResultType(typeof(Customer))]
      [ResultType(typeof(Order))]
      public IMultipleResults VariableResultShapes(System.Nullable<int> shape) {
         IExecuteResult result = this.ExecuteMethodCallWithMultipleResults(this,
            ((MethodInfo)(MethodInfo.GetCurrentMethod())), shape);
         return (IMultipleResults) result.ReturnValue;
      }

Visual Basic

<StoredProcedure(Name:= "VariableResultShapes")> _
      <ResultType(typeof(Customer))> _
      <ResultType(typeof(Order))> _
   public VariableResultShapes(shape As Integer?) As IMultipleResults
      Dim result As IExecuteResult =
                ExecuteMethodCallWithMultipleResults(Me, _
               CType(MethodInfo.GetCurrentMethod(), MethodInfo), shape)
         return CType(result.ReturnValue, IMultipleResults)
      End Function

您可以使用此預存程式,如下所示:

C#

      IMultipleResults result = db.VariableResultShapes(1);
      foreach (Customer c in result.GetResult<Customer>()) {
         Console.WriteLine(c.CompanyName);
      }

      result = db.VariableResultShapes(2);
      foreach (Order o in result.GetResult<Order>()) {
         Console.WriteLine(o.OrderID);
      }           

Visual Basic

Dim result As IMultipleResults = db.VariableResultShapes(1)
      For Each c As Customer In result.GetResult(Of Customer)()
         Console.WriteLine(c.CompanyName)
      Next

      result = db.VariableResultShapes(2);
      For Each o As Order In result.GetResult(Of Order)()
         Console.WriteLine(o.OrderID)
      Next 
         
      }           

在這裡,您必須使用 GetResult 模式,根據預存程式的知識,取得正確類型的列舉值。 LINQ to SQL可以產生所有可能的投影類型,但無法根據傳回的順序得知它們的順序。 您知道哪些產生的投影類型對應至對應方法的唯一方式,是在方法上使用產生的程式碼批註。

範例 3

以下是 循序傳回多個結果圖形之預存程式的 T-SQL:

CREATE PROCEDURE MultipleResultTypesSequentially
AS
select * from products
select * from customers

LINQ to SQL會對應此程式,就像上述範例 2 一樣。 不過,在此情況下,有兩個 循序 的結果集。

C#

[StoredProcedure(Name="MultipleResultTypesSequentially")]      
[ResultType(typeof(Product))]
[ResultType(typeof(Customer))]
public IMultipleResults MultipleResultTypesSequentially() {
   return ((IMultipleResults)(
      this.ExecuteMethodCallWithMultipleResults (this, 
         ((MethodInfo)(MethodInfo.GetCurrentMethod()))).ReturnValue
      )
   );
}

Visual Basic

<StoredProcedure(Name:="MultipleResultTypesSequentially")> _
<ResultType(typeof(Customer))> _
<ResultType(typeof(Order))> _
public Function MultipleResultTypesSequentially() As IMultipleResults
   Return CType( ExecuteMethodCallWithMultipleResults (Me, _
         CType(MethodInfo.GetCurrentMethod(), MethodInfo)), _
         IMultipleResults).ReturnValue
      
End Function

您可以使用此預存程式,如下所示:

C#

      IMultipleResults sprocResults = db.MultipleResultTypesSequentially();

      //first read products
      foreach (Product p in sprocResults.GetResult<Product>()) {
         Console.WriteLine(p.ProductID);
      }

      //next read customers
      foreach (Customer c in sprocResults.GetResult<Customer>()){
         Console.WriteLine(c.CustomerID);
      }

Visual Basic

Dim sprocResults As IMultipleResults = db.MultipleResultTypesSequentially()

   ' first read products
   For Each P As Product In sprocResults.GetResult(Of Product)()
      Console.WriteLine(p.ProductID)
   Next

   ' next read customers
   For Each c As Customer c In sprocResults.GetResult(Of Customer)()
      Console.WriteLine(c.CustomerID) 
   Next

範例 4

LINQ to SQL將參數對應 out 至參考參數 (ref關鍵字) ,而實值型別則會將參數宣告為可為 null (例如int?) 。 下列範例中的程式會採用單一輸入參數並傳 out 回參數。

CREATE PROCEDURE GetCustomerCompanyName(
   @customerID nchar(5),
   @companyName nvarchar(40) output
   )
AS
SELECT @companyName = CompanyName FROM Customers
WHERE CustomerID=@CustomerID

對應的方法如下所示:

C#

      [StoredProcedure(Name = "GetCustomerCompanyName")]
      public int GetCustomerCompanyName(
         string customerID, ref string companyName) {

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

         companyName = (string)result.GetParameterValue(1);
         return (int)result.ReturnValue;
      }

Visual Basic

   <StoredProcedure(Name:="GetCustomerCompanyName")> _
      Public Function GetCustomerCompanyName( _
               customerID As String, ByRef companyName As String) As Integer

      Dim result As IExecuteResult = ExecuteMethodCall(Me, _
               CType(MethodInfo.GetCurrentMethod(), MethodInfo), customerID, _
               companyName)

         companyName = CStr(result.GetParameterValue(1))
         return CInt(result.ReturnValue)
      End Function

在此情況下,方法沒有明確的傳回值,但預設傳回值仍然會對應。 針對輸出參數,對應的輸出參數會如預期般使用。

您可以呼叫上述預存程式,如下所示:

C#

string CompanyName = "";
string customerID = "ALFKI";
db.GetCustomerCompanyName(customerID, ref CompanyName);
Console.WriteLine(CompanyName);

Visual Basic

Dim CompanyName As String = ""
Dim customerID As String = "ALFKI"
db.GetCustomerCompanyName(customerID, CompanyName)
Console.WriteLine(CompanyName)

使用者定義函數

LINQ to SQL同時支援純量值和資料表值函式,並支援這兩者的內嵌對應專案。

LINQ to SQL處理內嵌純量呼叫的方式與呼叫系統定義函式的方式類似。 請考慮下列查詢:

C#

var q =
   from p in db.Products
   select
      new {
         pid = p.ProductID,
         unitp = Math.Floor(p.UnitPrice.Value)
      };

Visual Basic

Dim productInfos = From prod In db.Products _
                   Select p.ProductID, price = Math.Floor(p.UnitPrice.Value)

在這裡,方法呼叫 Math.Floor 會轉譯為系統函式 'FLOOR' 的呼叫。 同樣地,對應至 UDF 的函式呼叫會轉譯為 SQL 中 UDF 的呼叫。

範例 1

以下是 UDF) ReverseCustName () (純量使用者定義函數。 在 SQL Server 中,函式的定義如下:

CREATE FUNCTION ReverseCustName(@string varchar(100))
RETURNS varchar(100)
AS
BEGIN
   DECLARE @custName varchar(100)
   -- Impl. left as exercise for the reader
   RETURN @custName
END

您可以使用下列程式碼,將架構類別上定義的用戶端方法對應至此 UDF。 請注意,方法主體會建構可擷取方法呼叫意圖的運算式,並將該運算式傳遞至 DataCoNtext 以進行轉譯和執行。 (只有在呼叫 函式時,才會發生此直接執行。)

C#

[Function(Name = "[dbo].[ReverseCustName]")]
public string ReverseCustName(string string1) {
   IExecuteResult result = this.ExecuteMethodCall(this,
      (MethodInfo)(MethodInfo.GetCurrentMethod())), string1);
   return (string) result.ReturnValue;
}

Visual Basic

Function(Name:= "[dbo].[ReverseCustName]")> _
Public Function ReverseCustName(string1 As String) As String

    Dim result As IExecuteResult = ExecuteMethodCall(Me, _
             CType(MethodInfo.GetCurrentMethod(), MethodInfo), string1)
   return CStr(result.ReturnValue)

範例 2

在下列查詢中,您可以看到所產生 UDF 方法 ReverseCustName的內嵌呼叫。 在此情況下,函式不會立即執行。 針對此查詢建置的 SQL 會轉譯為資料庫中定義的 UDF 呼叫, (請參閱查詢之後的 SQL 程式碼) 。

C#

var q =
   from c in db.Customers
   select
      new {
         c.ContactName,
         Title = db.ReverseCustName(c.ContactTitle)
      };

Visual Basic

Dim customerInfos = From cust In db.Customers _
                    Select c.ContactName, _
                    Title = db.ReverseCustName(c.ContactTitle)



SELECT [t0].[ContactName],
   dbo.ReverseCustName([t0].[ContactTitle]) AS [Title]
FROM [Customers] AS [t0]

當您在查詢外部呼叫相同的函式,LINQ to SQL使用下列 SQL 語法從方法呼叫運算式建立簡單的查詢, (其中參數 @p0 系結至傳入的常數) :

在 LINQ to SQL:

C#

string str = db.ReverseCustName("LINQ to SQL");

Visual Basic

Dim str As String = db.ReverseCustName("LINQ to SQL")

轉換成:

SELECT dbo.ReverseCustName(@p0)

範例 3

資料表值函式 (TVF) 會傳回與預存程式不同的單一結果集 (,這可以傳回多個結果圖形) 。 因為 TVF 傳回類型是資料表,所以您可以在 SQL 中的任何位置使用 TVF,而您可以使用資料表的方式處理 TVF,就像是資料表一樣。

請考慮下列資料表值函式SQL Server定義:

CREATE FUNCTION ProductsCostingMoreThan(@cost money)
RETURNS TABLE
AS
RETURN
   SELECT ProductID, UnitPrice
   FROM Products
   WHERE UnitPrice > @cost

此函式會明確指出它會傳回 TABLE,因此會隱含定義傳回的結果集結構。 LINQ to SQL 對應函式,如下所示:

C#

       [Function(Name = "[dbo].[ProductsCostingMoreThan]")]
      public IQueryable<Product> ProductsCostingMoreThan(
            System.Nullable<decimal> cost) {

         return this.CreateMethodCallQuery<Product>(this,
            (MethodInfo)MethodInfo.GetCurrentMethod(),
            cost);
      }

Visual Basic

   <Function(Name:="[dbo].[ProductsCostingMoreThan]")> _
      Public Function ProductsCostingMoreThan(
            cost As System.Nullable(Of Decimal)) As IQueryable(Of Product)

    Return CreateMethodCallQuery(Of Product)(Me, _
             CType(MethodInfo.GetCurrentMethod(), MethodInfo), cost)

下列 SQL 程式碼顯示您可以聯結至函式所傳回的資料表,否則將它視為任何其他資料表:

SELECT p2.ProductName, p1.UnitPrice
FROM dbo.ProductsCostingMoreThan(80.50)
AS p1 INNER JOIN Products AS p2 ON p1.ProductID = p2.ProductID

在LINQ to SQL中,查詢會以下列方式轉譯, (使用新的 'join' 語法) :

C#

var q =
   from p in db.ProductsCostingMoreThan(80.50m)
   join s in db.Products on p.ProductID equals s.ProductID
   select new {p.ProductID, s.UnitPrice};

Visual Basic

Dim productInfos = From costlyProd In db.ProductsCostingMoreThan(80.50m) _
                   Join prod In db.Products _
                   On costlyProd.ProductID Equals prod.ProductID _
                   Select costlyProd.ProductID, prod.UnitPrice

預存程式LINQ to SQL限制

LINQ to SQL支援針對傳回靜態決定結果集的預存程式產生程式碼。 因此,LINQ to SQL程式碼產生器不支援下列專案:

  • 使用動態 SQL 傳回結果集的預存程式。 當預存套裝程式含用來建置動態 SQL 語句的條件式邏輯時,LINQ to SQL無法取得結果集的中繼資料,因為用來產生結果集的查詢在執行時間之前是未知的。
  • 根據臨時表產生結果的預存程式。

實體類別產生器工具

如果您有現有的資料庫,就不需要手動建立完整的物件模型來表示它。 LINQ to SQL散發隨附名為 SQLMetal 的工具。 它是命令列公用程式,可藉由從資料庫中繼資料推斷適當的類別,將建立實體類別的工作自動化。

您可以使用 SQLMetal 從資料庫擷取 SQL 中繼資料,並產生包含實體類別宣告的來源檔案。 或者,您可以將程式分割成兩個步驟,先產生代表 SQL 中繼資料的 XML 檔案,然後再將該 XML 檔案轉譯成包含類別宣告的來源檔案。 此分割程式可讓您將中繼資料保留為檔案,以便進行編輯。 產生檔案的擷取程式會依資料庫資料表和資料行名稱,沿著適當的類別和屬性名稱進行一些推斷。 您可能需要編輯 XML 檔案,讓產生器產生更美觀的結果,或隱藏您不想出現在物件中的資料庫層面。

使用 SQLMetal 的最簡單案例是從現有的資料庫直接產生類別。 以下是叫用工具的方式:

C#

SqlMetal /server:.\SQLExpress /database:Northwind /pluralize /namespace:nwind /code:Northwind.cs

Visual Basic

SqlMetal /server:.\SQLExpress /database:Northwind /pluralize /namespace:nwind /code:Northwind.vb /language:vb

執行此工具會建立Northwind.cs.vb檔案,其中包含讀取資料庫中繼資料所產生的物件模型。 如果資料庫中的資料表名稱類似于您想要產生之物件的名稱,此用法就很適合使用。 如果不是,您會想要採用雙步驟方法。

若要指示 SQLMetal 產生 DBML 檔案,請使用此工具,如下所示:

SqlMetal /server:.\SQLExpress /database:Northwind /pluralize
   /xml:Northwind.dbml

產生 dbml 檔案之後,您可以繼續並標注 類別屬性 屬性,以描述資料表和資料行如何對應至類別和屬性。 完成批註 dbml 檔案之後,您可以執行下列命令來產生物件模型:

C#

SqlMetal /namespace:nwind /code:Northwind.cs Northwind.dbml

Visual Basic

SqlMetal /namespace:nwind /code:Northwind.vb Northwind.dbml /language:vb

SQLMetal 使用簽章如下所示:

SqlMetal [options] [filename]

下表顯示 SQLMetal 的可用命令列選項。

SQLMetal 的命令列選項

選項 Description
/server: <name> 表示要連接的伺服器,以便存取資料庫。
/database: <name > 指出要從中讀取中繼資料的資料庫名稱。
/user: <name > 伺服器的登入使用者識別碼。
/password: <name> 伺服器的登入密碼。
/views 擷取資料庫檢視。
/functions 擷取資料庫函式。
/sprocs 擷取預存程式。
/code[: <filename> ] 表示工具的輸出是實體類別宣告的來源檔案。
/language:< language > 使用 Visual Basic 或 C# (預設) 。
/xml[: <filename> ] 指出工具的輸出是描述資料庫中繼資料的 DBML 檔案,以及類別和屬性名稱的第一個猜測近似值。
/map[: <filename> ] 表示應該使用外部對應檔案,而不是屬性。
/pluralize 表示此工具應該對資料表的名稱執行英文複數複寫/取消複數化啟發學習法,以產生適當的類別和屬性名稱。
/namespace: <name> 指出將產生實體類別的命名空間。
/timeout:<seconds> 用於資料庫命令的逾時值,以秒為單位。

注意 若要從 MDF 檔案擷取中繼資料,您必須在所有其他選項後面指定 MDF 檔案名。 如果未指定 任何 /server ,則會假設為 localhost

產生器工具 DBML 參考

DBML (資料庫對應語言) 檔案是指定資料庫 SQL 中繼資料的描述。 SQLMetal 會藉由查看資料庫中繼資料來擷取。 SQLMetal 也會使用相同的檔案來產生預設物件模型來表示資料庫。

以下是 DBML 語法的原型範例:

<?xml version="1.0" encoding="utf-16"?>
<Database Name="Northwind" EntityNamespace="Mappings.FunctionMapping"
   ContextNamespace="Mappings.FunctionMapping"
   Provider="System.Data.Linq.SqlClient.Sql2005Provider"
   xmlns="https://schemas.microsoft.com/dsltools/LINQ to SQLML">
   <Table Name="Categories">
      <Type Name="Category">
         <Column Name="CategoryID" Type="System.Int32"
            DbType="Int NOT NULL IDENTITY" IsReadOnly="False" 
            IsPrimaryKey="True" IsDbGenerated="True" CanBeNull="False" />
         <Column Name="CategoryName" Type="System.String"
            DbType="NVarChar(15) NOT NULL" CanBeNull="False" />
         <Column Name="Description" Type="System.String"
            DbType="NText" CanBeNull="True" UpdateCheck="Never" />
         <Column Name="Picture" Type="System.Byte[]"
            DbType="Image" CanBeNull="True" UpdateCheck="Never" />
         <Association Name="FK_Products_Categories" Member="Products"
            ThisKey="CategoryID" OtherKey="CategoryID"
            OtherTable="Products" DeleteRule="NO ACTION" />
      </Type>
   </Table>

   <Function Name="GetCustomerOrders">
      <Parameter Name="customerID" Type="System.String" DbType="NChar(5)" />
      <ElementType Name="GetCustomerOrdersResult">
         <Column Name="OrderID" Type="System.Int32"
            DbType="Int" CanBeNull="True" />
         <Column Name="ShipName" Type="System.String"
            DbType="NVarChar(40)" CanBeNull="True" />
         <Column Name="OrderDate" Type="System.DateTime"
            DbType="DateTime" CanBeNull="True" />
         <Column Name="Freight" Type="System.Decimal"
            DbType="Money" CanBeNull="True" />
      </ElementType>
   </Function>
</Database>

元素及其屬性如下所述。

資料庫

這是 XML 格式的最外層元素。 此元素會鬆散地對應至所產生 DataCoNtext上的 Database 屬性。

資料庫屬性

屬性 類型 預設 描述
名稱 String None 資料庫的名稱。 如果存在,而且如果產生 DataCoNtext,則會使用此名稱將 Database 屬性附加至該屬性。 如果類別屬性不存在,也會當做 DataCoNtext 類別的名稱使用。
EntityNamespace 強式 None 從 Table 元素內的 Type 專案產生的類別的預設命名空間。 如果未在此指定命名空間,則會在根命名空間中產生實體類別。
CoNtextNamespace String None 產生的 DataCoNtext 類別的預設命名空間。 如果未在此指定命名空間,則會在根命名空間中產生 DataCoNtext 類別。
類別 String Database.Name 產生的 DataCoNtext 類別名稱。 如果沒有,請使用 Database 元素的 Name 屬性。
AccessModifier AccessModifier 公開 產生的 DataCoNtext 類別的存取層級。 有效值為 PublicProtectedInternalPrivate
BaseType String 「System.Data.Linq.DataCoNtext」 DataCoNtext類別的基底類型。
提供者 String 「System.Data.Linq.SqlClient.Sql2005Provider」 DataCoNtext的提供者,使用 Sql2005 提供者作為預設值
ExternalMapping 布林值 指定 DBML 是否用於產生外部對應檔案。
序列化 SerializationMode SerializationMode.None 指定產生的 DataCoNtext 和實體類別是否可序列化。

資料庫Sub-Element屬性

Sub-Element 項目類型 發生次數範圍 Description
<Table> 資料表 0-unbounded 表示SQL Server資料表或檢視表,該資料表或檢視表會對應至單一類型或繼承階層。
<Function> 函式 0-unbounded 表示將對應至所產生DataCoNtext類別中方法的SQL Server預存程式或 db 函式。
<[連接]> Connection 0-1 表示 此 DataCoNtext 將使用的資料庫連接。

資料表

這個專案代表資料庫資料表 (或檢視表) ,該檢視表會對應至單一類型或繼承階層。 這個專案會鬆散地對應至所產生實體類別上的 Table 屬性。

資料表屬性

屬性 類型 預設 描述
名稱 String 必要 () 資料庫內的資料表名稱。 視需要作為資料表配接器的預設名稱基底。
成員 String Table.Name DataCoNtext類別內針對這個資料表產生的成員欄位名稱。
AccessModifier AccessModifier 公開 DataCoNtextTable < T >參考的協助工具層級。 有效值為 PublicProtectedInternalPrivate

資料表Sub-Element屬性

Sub-Element 項目類型 出現範圍 Description
<型別> 類型 1-1 表示對應至這個資料表的類型或繼承階層。
<InsertFunction> TableFunction 0-1 插入的方法。 當它存在時,會產生 InsertT 方法。
<UpdateFunction> TableFunction 0-1 更新的方法。 當它存在時,會產生 UpdateT 方法。
<DeleteFunction> TableFunction 0-1 刪除的方法。 當它存在時,會產生 DeleteT 方法。

類型

這個專案代表 Table 或預存程式結果圖形的類型定義。 這會以指定的資料行和關聯,將程式碼 gen 編碼為新的 CLR 類型。

類型也可能代表繼承階層的元件,且有多個類型對應至同一個資料表。 在此情況下,Type 元素會巢狀表示父子繼承關聯性,而且會依指定的 InheritCode 在資料庫中區分。

類型屬性

屬性 類型 預設 描述
名稱 String (必要) 要產生之 CLR 類型的名稱。
InheritanceCode String 如果此類型參與繼承,它可以有相關聯的繼承程式碼,以區別從資料表載入資料列時的 CLR 類型。 其 InheritanceCode符合IsDiscriminator資料行值的Type是用來具現化載入的物件。 如果繼承程式碼不存在,則產生的實體類別是抽象的。
IsInheritanceDefault 布林值 如果這是繼承階層中的 Type ,則載入不符合任何已定義繼承碼的資料列時,就會使用此類型。
AccessModifier AccessModifier 公開 要建立之 CLR 類型的協助工具層級。 有效值為: PublicProtectedInternalPrivate
識別碼 String 類型可以有唯一的識別碼。其他資料表或函式可以使用類型的識別碼。 識別碼只會出現在 DBML 檔案中,而不是出現在物件模型中。
IdRef String IdRef 是用來參照另一個類型的 識別碼。如果 IdRef 存在於類型專案中,則類型專案必須只包含 IdRef 資訊。 IdRef 只會出現在 DBML 檔案中,而不是出現在物件模型中。

類型Sub-Element屬性

Sub-Element 項目類型 出現範圍 Description
<資料行> 資料行 0-unbounded 表示這個型別中的屬性,這個屬性將會系結至此類型資料表中的欄位。
<關聯> 關聯 0-unbounded 表示這個型別中的屬性,這個屬性將會系結至資料表之間外鍵關聯性的一端。
<型別> 子類型 0-unbounded 表示繼承階層內此類型的子類型。

子類型

這個專案代表繼承階層中的衍生型別。 這會產生至新的 CLR 類型,其中包含此類型中指定的資料行和關聯。 子類型不會產生繼承屬性。

相較于 TypeSubType 元素沒有 AccessModifier ,因為所有衍生類型都必須是公用的。 其他資料表和函式無法重複使用SubTypes,因此其中沒有IdIdRef

SubType 屬性

屬性 類型 預設 描述
名稱 String 必要 () 要產生的 CLR 型別名稱。
InheritanceCode String None 如果此類型參與繼承,它可以有相關聯的繼承程式碼,以在從資料表載入資料列時區分 CLR 類型。 其 InheritanceCode符合IsDiscriminator資料行值的 Type 是用來具現化載入的物件。 如果繼承程式碼不存在,則產生的實體類別是抽象的。
IsInheritanceDefault 布林值 如果繼承階層中的 Type 為 true,則載入不符合任何已定義繼承碼的資料列時,將會使用此類型。

SubType Sub-Element屬性

Sub-Element 項目類型 發生次數範圍 Description
<資料行> 資料行 0-unbounded 表示這個型別內將系結至此類型資料表中欄位的屬性。
<關聯> 關聯 0-unbounded 表示這個型別中的屬性,這個屬性將會系結至資料表之間外鍵關聯性的一端。
<型別> 子類型 0-unbounded 表示繼承階層內此類型的子類型。

資料行

這個專案代表資料表內對應至屬性 (與類別內支援欄位) 的資料行。 不過,外鍵關聯性任一端沒有任何 Column 元素存在,因為關聯專案在兩端) 完全代表該元素 (。

資料行屬性

屬性 類型 預設 描述
名稱 String None 此資料行將對應的資料庫欄位名稱。
成員 String 名稱 要產生于包含類型的 CLR 屬性名稱。
儲存體 String _成員 將儲存此資料行值的私人 CLR 備份欄位名稱。 序列化時請勿移除 儲存體 ,即使它是預設值也一樣。
AccessModifier AccessModifier 公開 要建立之 CLR 屬性的存取層級。 有效值為: PublicProtectedInternalPrivate
類型 String (必要) CLR 屬性和所建立備份欄位的類型名稱。 這可能是從完整名稱到類別直接名稱的任何專案,只要名稱最終會在編譯產生的程式碼時位於範圍中即可。
DbType String None 完整SQL Server類型 (包括此資料行的 NOT Null) 這類注釋。 如果您提供LINQ to SQL來優化所產生的查詢,並在執行CreateDatabase () 時更明確。 一律序列化 DbType
IsReadOnly 布林值 如果已設定 IsReadOnly ,則不會建立屬性 setter,這表示人們無法使用該物件來變更此資料行的值。
IsPrimaryKey 布林值 表示此資料行參與資料表的主鍵。 需要此資訊,LINQ to SQL才能正常運作。
IsDbGenerated 布林值 表示此欄位的資料是由資料庫所產生。 這是主要適用于 AutoNumber 欄位和匯出欄位的情況。 指派值給這些欄位並不有意義,因此它們會自動 為 IsReadOnly
CanBeNull Boolean None 表示值可以包含 Null 值。 如果您想要實際使用 CLR 中的 Null 值,您仍必須將ClrType指定為可為 < Null 的 T >
UpdateCheck UpdateCheck 除非至少有一個其他成員已設定 IsVersion ,否則一律 (,然後永遠不要) 指出在開放式平行存取衝突偵測期間,LINQ to SQL是否應該使用此資料行。 根據預設,除非有 IsVersion 資料行,否則所有資料行預設都會參與,然後自行參與。 可以是: AlwaysNeverWhenChanged (,這表示如果其本身的值已變更) ,則資料行會參與。
IsDiscriminator 布林值 指出此欄位是否包含用於在繼承階層中型別之間選擇的鑒別副程式代碼。
運算是 String None 不會影響LINQ to SQL的作業,但在 期間會使用 。CreateDatabase () 做為代表計算資料行運算式的原始 SQL 運算式。
IsVersion 布林值 表示此欄位代表SQL Server中的TIMESTAMP欄位,該欄位會在每次變更資料列時自動更新。 然後,您可以使用此欄位來啟用更有效率的開放式並行衝突偵測。
IsDelayLoaded 布林值 表示此資料行不應該在物件具體化時立即載入,但只有在第一次存取相關的屬性時。 這適用于不一定需要的資料列中大型備忘欄位或二進位資料。
AutoSync AutoSync 如果 (IsDbGenerated && IsPrimaryKey) OnInsert;

否則,如果 (IsDbGenerated) Always

Else Never

指定資料行是否從資料庫所產生的值自動同步處理。 此標籤的有效值為: OnInsertAlwaysNever

關聯

這個專案代表外鍵關聯性的任一端。 如果是一對多關聯性,這將會是一端的EntitySet < T >,以及多端的EntityRef < T >。 針對一對一關聯性,這兩端都是EntityRef < T >

請注意,關聯兩端不需要有 關聯 專案。 在此情況下,屬性只會在具有專案 (形成單向關聯性) 的端產生。

關聯屬性

屬性 類型 預設 描述
名稱 String (必要) 關聯性 (的名稱通常是外鍵條件約束名稱) 。 這在技術上可以是選擇性的,但應該一律由程式碼產生,以避免在相同兩個數據表之間有多個關聯性時產生模棱兩可。
成員 String 名稱 要在此關聯端產生的 CLR 屬性名稱。
儲存體 String 如果 OneToMany 和 Not IsForeignKey:

_OtherTable

還:

_TypeName (OtherTable)

將儲存此資料行值的私人 CLR 備份欄位名稱。
AccessModifier AccessModifier 公開 要建立之 CLR 屬性的協助工具層級。 有效值為 PublicProtectedInternalPrivate
ThisKey String 包含類別內的 IsIdentity 屬性 關聯這一端索引鍵的逗號分隔清單。
OtherTable String 請參閱描述。 關聯性另一端的資料表。 一般而言,這可由LINQ to SQL執行時間透過比對關聯性名稱來決定,但單向關聯或匿名關聯則不可能。
OtherKey String 外部類別中的主鍵 關聯另一端索引鍵的逗號分隔清單。
IsForeignKey 布林值 指出這是否為關聯性的「子」端,也就是一對多的多端。
RelationshipType RelationshipType OneToMany 指出使用者是否判斷這個關聯相關的資料是否符合一對一資料的準則,或符合一對多的一般案例。 針對一對一,使用者判斷提示主鍵 (「one」) 端的每個資料列,外鍵 (「多」) 端只有一個資料列。 這會導致在 「one」 端產生EntityRef < T >,而不是EntitySet < T >。 有效值為 OneToOneOneToMany
DeleteRule String None 用來將刪除行為新增至此關聯。 例如,「CASCADE」 會將 「ONDELETECASCADE」 新增至 FK 關聯性。 如果設定為 null,則不會新增任何刪除行為。

函式

這個專案代表預存程式或資料庫函式。 對於每個 Function 節點, 會在 DataCoNtext 類別中產生方法。

函式屬性

屬性 類型 預設 描述
名稱 String (必要) 資料庫中預存程式的名稱。
方法 String 方法 要產生之 CLR 方法的名稱,允許叫用預存程式。 方法的預設名稱具有[dbo]等專案,已移除Name
AccessModifier AccessModifier 公開 預存程式方法的協助工具層級。 有效值為 PublicProtectedInternalPrivate
HasMultipleResults Boolean 類型 > 1 的 # 指定這個 式節點所代表的預存程式是否傳回多個結果集。 每個結果集都是表格式圖形,可以是現有的 Type 或一組資料行。 在後者的情況下,將會為數據行集建立 類型 節點。
IsComposable 布林值 指定函式/預存程式是否可以在LINQ to SQL查詢中撰寫。 只能撰寫未傳回 void 的 DB 函式。

函式Sub-Element屬性

Sub-Element 專案類型 發生次數範圍 Description
<參數> 參數 0-unbounded 表示這個預存程式的 in 和 out 參數。
<ElementType> 類型 0-unbounded 表示對應預存程式可以傳回的表格式圖形。
<Return> 傳回 0-1 這個 db 函式或預存程式的傳回純量類型。 如果 Return 為 null,函式會傳回 void。 函式不能同時具有 ReturnElementType

TableFunction

這個專案代表資料表的 CUD 覆寫函式。 LINQ to SQL設計工具允許建立 LINQ TO SQL 的InsertUpdateDelete覆寫方法,並允許將實體屬性名稱對應至預存程式參數名稱。

CUD 函式的方法名稱是固定的,因此TableFunction元素的 DBML 中沒有Method屬性。 例如,針對 Customer 資料表,CUD 方法會命名為 InsertCustomerUpdateCustomerDeleteCustomer

資料表函數無法傳回表格式圖形,因此TableFunction元素中沒有ElementType屬性。

TableFunction 屬性

屬性 類型 預設 描述
名稱 String (必要) 資料庫中預存程式的名稱。
AccessModifier AccessModifier 私人 預存程式方法的協助工具層級。 有效值為 PublicProtectedInternalPrivate
HasMultipleResults Boolean 類型 > 1 的 # 指定這個函式節點所代表的預存程式是否傳回多個結果集。 每個結果集都是表格式圖形,可以是現有的 Type 或一組資料行。 在後者的情況下,將會為數據行集建立 類型 節點。
IsComposable 布林值 指定函式/預存程式是否可以在LINQ to SQL查詢中撰寫。 只能撰寫未傳回 void 的 DB 函式。

TableFunction Sub-Element 屬性

Sub-Elements 項目類型 發生次數範圍 Description
<參數> TableFunctionParameter 0-unbounded 表示這個資料表函式的 in 和 out 參數。
<Return> TableFunctionReturn 0-1 這個資料表函式傳回的純量類型。 如果 Return 為 null,函式會傳回 void。

參數

這個專案代表預存程式/函式參數。 參數可以傳入和傳出資料。

參數屬性

屬性 類型 預設 說明
名稱 String (必要) 預存程式/函式參數的資料庫名稱。
參數 String 名稱 方法參數的 CLR 名稱。
  String (必要) 方法參數的 CLR 名稱。
DbType String None 預存程式/函式參數的 DB 類型。
方向 ParameterDirection 位置 參數流動的方向。 可以是其中一個 InOutInOut

傳回

這個專案代表預存程式/函式的傳回型別。

傳回屬性

屬性 類型 預設 描述
類型 String (必要) 預存程式/函式結果的 CLR 類型。
DbType String None 預存程式/函式結果的 DB 類型。

TableFunctionParameter

這個專案代表 CUD 函式的參數。 參數可以傳入和傳出資料。每個參數都會對應至這個 CUD 函式所屬的 Table 資料行。 這個專案中沒有 TypeDbType 屬性,因為可以從參數對應的資料行取得類型資訊。

TableFunctionParameter 屬性

屬性 類型 預設 描述
名稱 String (必要) CUD 函式參數的資料庫名稱。
參數 String 名稱 方法參數的 CLR 名稱。
資料行 String 名稱 此參數所對應的資料行名稱。
方向 ParameterDirection 位置 參數流動的方向。 可以是其中一個 InOutInOut
版本 版本 目前 PropertyName是否參考指定資料行的目前或原始版本。 僅適用于 更新 覆寫期間。 可以是 [目前 ] 或 [ 原始]。

TableFunctionReturn

這個專案代表 CUD 函式的傳回型別。 它實際上只包含對應至 CUD 函式結果的資料行名稱。 您可以從資料行取得傳回的類型資訊。

TableFunctionReturn 屬性

Attrobite 類型 預設 描述
資料行 String None 傳回所對應的資料行名稱。

Connection

這個專案代表預設資料庫 連接 參數。 這可讓您為已經知道如何連線到資料庫的 DataCoNtext 類型建立預設建構函式。

有兩種類型的預設連線可能,一種是使用直接 ConnectionString,另一種是從 App.Settings讀取。

連接屬性

屬性 類型 預設 描述
UseApplicationSettings 布林值 決定要使用App.Settings檔案,還是從直接ConnectionString取得應用程式設定
ConnectionString String None 要傳送至 SQL 資料提供者的連接字串。
SettingsObjectName String 設定 要從中擷取屬性的 App.Settings 物件
SettingsPropertyName String ConnectionString 包含ConnectionStringApp.Settings屬性。

多層式實體

在兩層式應用程式中,單一 DataCoNtext 會處理查詢和更新。 不過,對於具有額外層的應用程式,通常必須使用個別 的 DataCoNtext 實例來進行查詢和更新。 例如,如果 ASP.NET 應用程式,則會針對 Web 服務器的不同要求執行查詢和更新。 因此,跨多個要求使用相同的 DataCoNtext 實例並不可行。 在這種情況下, DataCoNtext 實例必須能夠更新尚未擷取的物件。 LINQ to SQL中的多層式實體支援透過Attach () 方法提供這類功能。

以下是如何使用不同的 DataCoNtext 實例來變更 Customer 物件的範例:

C#

// Customer entity changed on another tier – for example, through a browser
// Back on the mid-tier, a new context needs to be used
Northwind db2 = new Northwind(…);

// Create a new entity for applying changes
Customer C2 = new Customer();
C2.CustomerID ="NewCustID";

// Set other properties needed for optimistic concurrency check
C2.CompanyName = "New Company Name Co.";

...

// Tell LINQ to SQL to track this object for an update; that is, not for insertion
db2.Customers.Attach(C2);

// Now apply the changes
C2.ContactName = "Mary Anders";

// DataContext now knows how to update the customer
db2.SubmitChanges();

Visual Basic

' Customer entity changed on another tier – for example, through a browser
' Back on the mid-tier, a new context needs to be used
Dim db2 As Northwind = New Northwind(…)

' Create a new entity for applying changes
Dim C2 As New Customer()
C2.CustomerID =”NewCustID”

' Set other properties needed for optimistic concurrency check
C2.CompanyName = ”New Company Name Co.”

...

' Tell LINQ to SQL to track this object for an update; that is, not for insertion
db2.Customers.Attach(C2)

' Now apply the changes
C2.ContactName = "Mary Anders"

' DataContext now knows how to update the customer
db2.SubmitChanges()

在多層式應用程式中,為了簡單、互通性或隱私權,通常不會跨階層傳送整個實體。 例如,供應商可能會定義 Web 服務的資料合約,此合約與仲介層上所使用的 Order 實體不同。 同樣地,網頁可能只會顯示 Employee 實體成員的子集。 因此,多層式支援是設計來容納這類案例。 只有屬於下列一或多個類別的成員必須在階層之間傳輸,並在呼叫 Attach () 之前設定。

  1. 屬於實體身分識別的成員。
  2. 已變更的成員。
  3. 參與開放式平行存取檢查的成員。

如果時間戳記或版本號碼資料行用於開放式平行存取檢查,則必須在呼叫 Attach () 之前設定對應的成員。 呼叫 Attach () 之前,不需要設定其他成員的值。 LINQ to SQL使用具有開放式平行存取檢查的最低更新;也就是說,忽略未設定或檢查開放式平行存取的成員。

開放式平行存取檢查所需的原始值,可以使用LINQ to SQL API 範圍以外的各種機制來保留。 ASP.NET 應用程式可能會使用檢視狀態 (或使用檢視狀態) 的控制項。 Web 服務可以使用 DataContract 進行更新方法,以確保原始值可用於更新處理。 為了互通性和一般性,LINQ to SQL不會指定在階層之間交換的資料圖形,或用於往返原始值的機制。

插入和刪除的實體不需要 Attach () 方法。 用於兩層應用程式的方法:Table.Add () Table.Remove () 可用於插入和刪除。 如同兩層式更新,使用者負責處理外鍵條件約束。 如果資料庫中有外鍵條件約束,無法只移除具有訂單的客戶,而無法只移除具有訂單的客戶。

LINQ to SQL也會以可轉移的方式處理實體附件。 使用者基本上會視需要建立預先更新的物件圖形,並呼叫 Attach () 。 接著,附加圖形上可以「重新執行」所有變更,以完成必要的更新,如下所示:

C#

Northwind db1 = new Northwind(…);
// Assume Customer c1 and related Orders o1, o2 are retrieved

// Back on the mid-tier, a new context needs to be used
Northwind db2 = new Northwind(…);

// Create new entities for applying changes
Customer c2 = new Customer();
c2.CustomerID = c.CustomerID;
Order o2 = new Order();
o2.OrderID = ...;

c2.Orders.Add(o2);

// Add other related objects needed for updates

// Set properties needed for optimistic concurrency check
...
// Order o1 to be deleted
Order o1 = new Order();
o1.OrderID = ...;

// Tell LINQ to SQL to track the graph transitively
db2.Customers.Attach(c2);
// Now "replay" all the changes

// Updates
c2.ContactName = ...;
o2.ShipAddress = ...;

// New object for insertion
Order o3 = new Order();
o3.OrderID = ...;
c2.Orders.Add(o3);

// Remove order o1
db2.Orders.Remove(o1);

// DataContext now knows how to do update/insert/delete
db2.SubmitChanges();

Visual Basic

Dim db1 As Northwind = New Northwind(…)
' Assume Customer c1 and related Orders o1, o2 are retrieved

' Back on the mid-tier, a new context needs to be used
Dim db2 As Northwind = New Northwind(…)

' Create new entities for applying changes
Customer c2 = new Customer()
c2.CustomerID = c.CustomerID
Dim o2 As Order = New Order()
o2.OrderID = ...

c2.Orders.Add(o2)

' Add other related objects needed for updates

' Set properties needed for optimistic concurrency check
...
' Order o1 to be deleted
Dim o1 As Order = New Order()
o1.OrderID = ...

' Tell LINQ to SQL to track the graph transitively
db2.Customers.Attach(c2)
' Now "replay" all the changes

' Updates
c2.ContactName = ...
o2.ShipAddress = ...

' New object for insertion
Dim o3 As Order = New Order()
o3.OrderID = ...
c2.Orders.Add(o3)

' Remove order o1
db2.Orders.Remove(o1)

' DataContext now knows how to do update/insert/delete
db2.SubmitChanges()

外部對應

除了屬性型對應之外,LINQ to SQL也支援外部對應。 最常見的外部對應形式是 XML 檔案。 對應檔案可啟用將對應與程式碼分開的其他案例。

DataCoNtext 提供額外的建構函式來提供 MappingSourceMapSource的其中一種形式是從 XML 對應檔建構的XmlMappingSource

以下是如何使用對應檔案的範例:

C#

String path = @"C:\Mapping\NorthwindMapping.xml";
XmlMappingSource prodMapping = 
   XmlMappingSource.FromXml(File.ReadAllText(path));
Northwind db = new Northwind(
   @"Server=.\SQLExpress;Database=c:\Northwind\Northwnd.mdf",
   prodMapping
   );

Visual Basic

Dim path As String = "C:\Mapping\NorthwindMapping.xml"
Dim prodMapping As XmlMappingSource = _
   XmlMappingSource.FromXml(File.ReadAllText(path))
Dim db As Northwind = New Northwind( _
   "Server=.\SQLExpress;Database=c:\Northwind\Northwnd.mdf", _
   prodMapping   )

以下是對應檔案中的對應程式碼片段,其中顯示 Product 類別的對應。 它會顯示命名空間中對應至Northwind資料庫中Products資料表的Product類別。 元素和屬性與屬性名稱和參數一致。

<?xml version="1.0" encoding="utf-8"?>
<Database xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
   xmlns:xsd="http://www.w3.org/2001/XMLSchema" Name="Northwind"
   ProviderType="System.Data.Linq.SqlClient.Sql2005Provider">
   <Table Name="Products">
      <Type Name="Mappings.FunctionMapping.Product">
         <Column Name="ProductID" Member="ProductID" Storage="_ProductID"
            DbType="Int NOT NULL IDENTITY" IsPrimaryKey="True"
            IsDBGenerated="True" AutoSync="OnInsert" />
         <Column Name="ProductName" Member="ProductName" Storage="_ProductName"
            DbType="NVarChar(40) NOT NULL" CanBeNull="False" />
         <Column Name="SupplierID" Member="SupplierID" Storage="_SupplierID"
            DbType="Int" />
         <Column Name="CategoryID" Member="CategoryID" Storage="_CategoryID"
            DbType="Int" />
         <Column Name="QuantityPerUnit" Member="QuantityPerUnit"
            Storage="_QuantityPerUnit" DbType="NVarChar(20)" />
         <Column Name="UnitPrice" Member="UnitPrice" Storage="_UnitPrice"
            DbType="Money" />
         <Column Name="UnitsInStock" Member="UnitsInStock" Storage="_UnitsInStock"
            DbType="SmallInt" />
         <Column Name="UnitsOnOrder" Member="UnitsOnOrder" Storage="_UnitsOnOrder"
            DbType="SmallInt" />
         <Column Name="ReorderLevel" Member="ReorderLevel" Storage="_ReorderLevel"
            DbType="SmallInt" />
         <Column Name="Discontinued" Member="Discontinued" Storage="_Discontinued"
            DbType="Bit NOT NULL" />
         <Association Name="FK_Order_Details_Products" Member="OrderDetails"
            Storage="_OrderDetails" ThisKey="ProductID" OtherTable="Order Details"
            OtherKey="ProductID" DeleteRule="NO ACTION" />
         <Association Name="FK_Products_Categories" Member="Category"
            Storage="_Category" ThisKey="CategoryID" OtherTable="Categories"
            OtherKey="CategoryID" IsForeignKey="True" />
         <Association Name="FK_Products_Suppliers" Member="Supplier"
            Storage="_Supplier" ThisKey="SupplierID" OtherTable="Suppliers"
            OtherKey="SupplierID" IsForeignKey="True" />
      </Type>
   </Table>

</Database>

NET Framework 函式支援和附注

下列段落提供有關LINQ to SQL類型支援和.NET Framework差異的基本資訊。

基本類型

已實作

  • 算術和比較運算子
  • Shift 運算子: << 和 >>
  • char 與 numeric 之間的轉換是由UNICODE/NCHAR所完成,否則會使用 SQL 的CONVERT

未實作

  • <類型 > 。解析
  • 列舉可用於資料表中,並對應至整數和字串。 針對後者,會使用 ParseToString () 方法。

與 .NET 的差異

  • 雙精度浮點數的 ToString 輸出會使用 CONVERT (NVARCHAR (30) 、 @x 、2) ,一律會在 SQL 上使用 16 位數和「科學標記法」。例如:「0.000000000000000e+000」 表示 0,因此不會提供與 相同的字串。NET 的 Convert.ToString ()

System.String

已實作

  • 非靜態方法:

    • LengthSubstringContainsStartsWithEndWithIndexOfInsertRemoveReplaceTrimToLowerToUpperLastIndexOfPadRightPadLeftEqualsCompareTo. 除了採用 StringComparison 參數等等時,支援所有簽章,如下所示。
  • 靜態方法:

       Concat(...)               all signatures
       Compare(String, String)
       String (indexer) 
       Equals(String, String)
    
  • 構造 函數:

        String(Char, Int32)
    
  • 運營商:

      +, ==, != (+, =, and <> in Visual Basic)
    

未實作

  • 採用或產生 char 陣列的方法。

  • 採用CultureInfo/StringComparison/IFormatProvider的方法。

  • Visual Basic 中的靜態 (共用) :

       Copy(String str)
       Compare(String, String, Boolean)
       Compare(String, String, StringComparison)
       Compare(String, String, Boolean, CultureInfo) 
       Compare(String, Int32, String, Int32, Int32)
       Compare(String, Int32, String, Int32, Int32,   Boolean)
       Compare(String, Int32, String, Int32, Int32, StringComparison)
       Compare(String, Int32, String, Int32, Int32, Boolean, CultureInfo)
       CompareOrdinal(String, String)
       CompareOrdinal(String, Int32, String, Int32, Int32)
       Join(String, ArrayOf String [,...]) All Join version with first three args
    
  • 執行個體:

       ToUpperInvariant()
       Format(String, Object)      + overloads
       IndexOf(String, Int32, StringComparison)
       IndexOfAny(ArrayOf Char)
       Normalize()
       Normalize(NormalizationForm)
       IsNormalized()
       Split(...)
       StartsWith(String, StringComparison)
       ToCharArray()
       ToUpper(CultureInfo)
       TrimEnd(ParamArray Char)
       TrimStart(ParamArray Char)
    

.NET 的限制/差異

SQL 會使用定序來判斷字串的相等和順序。 這些可以在 SQL Server Instance、資料庫、資料表資料行或運算式上指定。

到目前為止實作的函式翻譯不會變更定序,也不會在翻譯的運算式上指定不同的定序。 因此,如果預設定序不區分大小寫, CompareToIndexOf 等函式可以提供與 (區分大小寫) .NET 函式會提供不同的結果。

StartWith (str) /EndWith (str的方法假設引數) str 是常數或用戶端上評估的運算式。 也就是說,目前無法使用 str的資料行。

System.Math

實作的靜態方法

  • 所有簽章:
    • AbsAcosAsinAtan、Atan2BigMulCeilingCosCoshExpFloorLogLog10MaxMinPowSignSinhSqrtTanTanhTruncate

未實作

  • IEEERemainder
  • DivRem 具有 out 參數,因此您無法在運算式中使用。 常數 Math.PIMath.E會在用戶端上進行評估,因此不需要翻譯。

與 .NET 的差異

.NET 函式 Math.Round 的翻譯是 SQL 函式 ROUND。 只有在指定指出 MidpointRounding 列舉值的多載時,才支援轉譯。 MidpointRounding.AwayFromZero是 SQL 行為,MidpointRounding.ToEven表示 CLR 行為。

System.Convert

已實作

  • Form To < Type1 > (< Type2 > x) 的方法,其中 Type1,Type2 是下列其中一項:
    • boolbytecharDateTimedecimaldoublefloatInt16Int32Int64string
  • 行為與轉換相同:
    • 針對 ToString (Double) 有特殊程式碼可取得完整精確度。
    • 針對轉換Int32/Char,LINQ to SQL會使用 SQL 的UNICODE/NCHAR函數。
    • 否則,轉譯為 CONVERT

未實作

  • ToSByteUInt163264:這些類型不存在於 SQL 中。

    To<integer type>(String, Int32) 
    ToString(..., Int32)       any overload ending with an Int32 toBase
    IsDBNull(Object)
    GetTypeCode(Object)
    ChangeType(...)
    
  • 具有 IFormatProvider 參數的版本。

  • 涉及陣列的方法 (To/FromBase64CharArray, To/FromBase64String) 。

System.TimeSpan

已實作

  • 建構函式:

       TimeSpan(Long)
       TimeSpan (year, month, day)
       TimeSpan (year, month, day, hour, minutes, seconds)
       TimeSpan (year, month, day, hour, minutes, seconds, milliseconds)
    
  • 運營商:

       Comparison operators: <,==, and so on in C#; <, =, and so on in Visual Basic
    
       +, -
    
  • Visual Basic 中的靜態 (共用) 方法:

       Compare(t1,t2)
    
  • 非靜態 (實例) 方法/屬性:

       Ticks, Milliseconds, Seconds, Hours, Days
       TotalMilliseconds, TotalSeconds, TotalMinutes, TotalHours, TotalDays,
       Equals, CompareTo(TimeSpan)
       Add(TimeSpan), Subtract(TimeSpan)
       Duration() [= ABS], Negate()
    

未實作

   ToString()
   TimeSpan FromDay(Double), FromHours,   all From Variants
   TimeSpan Parse(String)

System.DateTime

已實作

  • 建構函式:

       DateTime(year, month, day)
       DateTime(year, month, day, hour, minutes, seconds)
       DateTime(year, month, day, hour, minutes, seconds, milliseconds)
    
  • 運營商:

       Comparisons
       DateTime – DateTime (gives TimeSpan)
       DateTime + TimeSpan (gives DateTime)
       DateTime – TimeSpan (gives DateTime)
    
  • 靜態 (共用) 方法:

       Add(TimeSpan), AddTicks(Long),
       AddDays/Hours/Milliseconds/Minutes (Double)
       AddMonths/Years(Int32)
       Equals
    
  • 非靜態 (實例) 方法/屬性:

       Day, Month, Year, Hour, Minute, Second, Millisecond, DayOfWeek
       CompareTo(DateTime)
       TimeOfDay()
       Equals
       ToString()
    

與 .NET 的差異

SQL 的日期時間值會四捨五入為 .000、.003 或 .007 秒,因此比 .NET 的值更精確。

SQL 的日期時間範圍從 1753 年 1 月 1 日開始。

SQL 沒有 TimeSpan的內建類型。 它會使用不同的 DATEDIFF 方法傳回 32 位整數。 其中一個是 DATEDIFF (DAY,...) ,可提供天數;另一個是 DATEDIFF (毫秒,...) ,可提供毫秒數。 如果 DateTimes 相隔超過 24 天,則會產生錯誤。 相反地,.NET 會使用 64 位整數,並以刻度測量 TimeSpans

若要盡可能接近 SQL 中的 .NET 語意,LINQ to SQL將 TimeSpans轉譯為 64 位整數,並使用上述的兩個DATEDIFF方法來計算兩個日期之間的刻度數目。

Datetime 當查詢轉譯 (時,用戶端會評估UtcNow,就像任何未涉及資料庫資料) 的運算式一樣。

未實作

   IsDaylightSavingTime()
   IsLeapYear(Int32)
   DaysInMonth(Int32, Int32)
   ToBinary()
   ToFileTime()
   ToFileTimeUtc()
   ToLongDateString()
   ToLongTimeString()
   ToOADate()
   ToShortDateString()
   ToShortTimeString()
   ToUniversalTime()
   FromBinary(Long), FileTime, FileTimeUtc, OADate
   GetDateTimeFormats(...)
   constructor DateTime(Long)
   Parse(String)
   DayOfYear

偵錯支援

DataCoNtext 提供方法和屬性,以取得針對查詢和變更處理所產生的 SQL。 這些方法對於瞭解LINQ to SQL功能和偵錯特定問題很有用。

取得產生的 SQL 的 DataCoNtext 方法

member 目的
Log 先列印 SQL 再執行。 涵蓋查詢、插入、更新、刪除命令。 Usage :

C#

db.Log = Console.Out;

Visual Basic

db.Log = Console.Out

GetQueryText (查詢) 傳回查詢的查詢文字,而不需要執行查詢。 Usage :

C#

Console.WriteLine(db.GetQueryText(db.Customers));

Visual Basic

Console.WriteLine(db.GetQueryTest(db.Customers))

GetChangeText () 傳回插入/更新/刪除的 SQL 命令文字,而不需執行它們。 Usage :

C#

Console.WriteLine(db.GetChangeText());

Visual Basic

Console.WriteLine(db.GetChangeText())