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藉由實作與標準查詢運算子相同的運算子模式,例如Where和Select,來啟用語言整合查詢。
例如,您可以使用 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 範例資料庫的中繼資料產生的 Customer 和 Order 類別。 為了簡潔起見,類別定義尚未顯示。
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可讓您要求立即載入物件模型的區域,只是基於這個原因。 其作法是允許DataCoNtext的DataShape規格。 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 類別的兩個事實:
將 DataShape 指派給 DataCoNtext之後,就無法修改 DataShape 。 這類DataShape上的任何LoadWith或AssociateWith方法呼叫都會在執行時間傳回錯誤。
無法使用 LoadWith 或 AssociateWith建立迴圈。 例如,下列會在執行時間產生錯誤:
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 資料行投影。 由於查詢翻譯工具無法瞭解建構函式呼叫期間發生的情況,因此無法為MyType的Name欄位建立意義。
相反地,最佳做法是一律使用 物件初始化運算式 來編碼投影。
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 的屬性,可以指派下列三個值之一: Always、 Never和 WhenChanged。 如果未設定 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會使用原始和目前參數來傳入指定型別之 物件的原始和目前複本。 這兩個參數可用於開放式平行存取衝突偵測。
注意 如果您覆寫預設更新邏輯,則衝突偵測是您的責任。
使用DataCoNtext的ExecuteCommand () 方法來叫用預存程式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修飾詞的DBType。
IsDbGenerated 成員會在插入資料列之後立即同步處理,並在SubmitChanges () 完成之後可供使用。 |
IsVersion | Boolean | 將成員的資料行類型識別為資料庫時間戳記或版本號碼。 每次更新相關聯的資料列時,資料庫會遞增版本號碼,而且時間戳記資料行會更新。 IsVersion=true的成員會在更新資料列之後立即同步處理。 在 SubmitChanges () 完成之後,會顯示新的值。 |
UpdateCheck | UpdateCheck | 決定LINQ to SQL如何實作開放式並行存取衝突偵測。 如果未將任何成員指定為 IsVersion=true 偵測,則會藉由比較原始成員值與目前的資料庫狀態來完成。 您可以藉由為每個成員提供UpdateCheck列舉值,來控制在衝突偵測期間LINQ to SQL使用的成員。
|
IsDiscriminator | Boolean | 判斷類別成員是否保存繼承階層的鑒別子值。 |
運算是 | String | 不會影響LINQ to SQL的作業,但在 期間會使用 。CreateDatabase () 做為代表計算資料行運算式的原始 SQL 運算式。 |
CanBeNull | Boolean | 表示值可以包含 Null 值。 這通常是從實體成員的 CLR 類型推斷。 使用這個屬性工作表示字串值表示為資料庫中不可為 Null 的資料行。 |
AutoSync | AutoSync | 指定是否從資料庫在插入或更新命令上產生的值自動同步處理資料行。 此標籤的有效值為 OnInsert、 Always和 Never。 |
典型的實體類別會在公用屬性上使用 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
只有指定 DBType,CreateDatabase () 方法才能建構最精確的資料表。 否則,基礎資料行的知識僅限於未使用 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屬性是用來宣告對 DataCoNtext或Schema類型上定義之方法的呼叫會轉譯為資料庫預存程式的呼叫。
StoredProcedure 屬性
屬性 | 類型 | Description |
---|---|---|
名稱 | String | 資料庫中預存程式的名稱。 如果未指定,則會假設預存程式的名稱與 方法相同 |
函式屬性
Function屬性是用來宣告對 DataCoNtext或Schema上定義之方法的呼叫會轉譯為對資料庫使用者定義純量或資料表值函式的呼叫。
函式屬性
屬性 | 類型 | 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 >。
在下列範例中, Car 和 Truck 子類別中包含的資料會對應至單一資料庫資料表 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 類別的資料分散在 customer1 和 customer2的兩個數據表上,下列查詢會傳回 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.Always 或 UpdateCheck.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透過使用屬性,將預存程式和函式對應至方法。 StoredProcedure、Parameter和Function屬性全都支援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 類別的存取層級。 有效值為 Public、 Protected、 Internal 和 Private。 |
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 | 公開 | DataCoNtext中Table < T >參考的協助工具層級。 有效值為 Public、 Protected、 Internal 和 Private。 |
資料表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 類型的協助工具層級。 有效值為: Public、 Protected、 Internal 和 Private。 |
識別碼 | String | 無 | 類型可以有唯一的識別碼。其他資料表或函式可以使用類型的識別碼。 識別碼只會出現在 DBML 檔案中,而不是出現在物件模型中。 |
IdRef | String | 無 | IdRef 是用來參照另一個類型的 識別碼。如果 IdRef 存在於類型專案中,則類型專案必須只包含 IdRef 資訊。 IdRef 只會出現在 DBML 檔案中,而不是出現在物件模型中。 |
類型Sub-Element屬性
Sub-Element | 項目類型 | 出現範圍 | Description |
---|---|---|---|
<資料行> | 資料行 | 0-unbounded | 表示這個型別中的屬性,這個屬性將會系結至此類型資料表中的欄位。 |
<關聯> | 關聯 | 0-unbounded | 表示這個型別中的屬性,這個屬性將會系結至資料表之間外鍵關聯性的一端。 |
<型別> | 子類型 | 0-unbounded | 表示繼承階層內此類型的子類型。 |
子類型
這個專案代表繼承階層中的衍生型別。 這會產生至新的 CLR 類型,其中包含此類型中指定的資料行和關聯。 子類型不會產生繼承屬性。
相較于 Type, SubType 元素沒有 AccessModifier ,因為所有衍生類型都必須是公用的。 其他資料表和函式無法重複使用SubTypes,因此其中沒有Id和IdRef。
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 屬性的存取層級。 有效值為: Public、 Protected、 Internal 和 Private。 |
類型 | 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 資料行,否則所有資料行預設都會參與,然後自行參與。 可以是: Always、 Never或 WhenChanged (,這表示如果其本身的值已變更) ,則資料行會參與。 |
IsDiscriminator | 布林值 | 否 | 指出此欄位是否包含用於在繼承階層中型別之間選擇的鑒別副程式代碼。 |
運算是 | String | None | 不會影響LINQ to SQL的作業,但在 期間會使用 。CreateDatabase () 做為代表計算資料行運算式的原始 SQL 運算式。 |
IsVersion | 布林值 | 否 | 表示此欄位代表SQL Server中的TIMESTAMP欄位,該欄位會在每次變更資料列時自動更新。 然後,您可以使用此欄位來啟用更有效率的開放式並行衝突偵測。 |
IsDelayLoaded | 布林值 | 否 | 表示此資料行不應該在物件具體化時立即載入,但只有在第一次存取相關的屬性時。 這適用于不一定需要的資料列中大型備忘欄位或二進位資料。 |
AutoSync | AutoSync | 如果 (IsDbGenerated && IsPrimaryKey) OnInsert; 否則,如果 (IsDbGenerated) Always Else Never |
指定資料行是否從資料庫所產生的值自動同步處理。 此標籤的有效值為: OnInsert、 Always和 Never。 |
關聯
這個專案代表外鍵關聯性的任一端。 如果是一對多關聯性,這將會是一端的EntitySet < T >,以及多端的EntityRef < T >。 針對一對一關聯性,這兩端都是EntityRef < T >。
請注意,關聯兩端不需要有 關聯 專案。 在此情況下,屬性只會在具有專案 (形成單向關聯性) 的端產生。
關聯屬性
屬性 | 類型 | 預設 | 描述 |
---|---|---|---|
名稱 | String | (必要) | 關聯性 (的名稱通常是外鍵條件約束名稱) 。 這在技術上可以是選擇性的,但應該一律由程式碼產生,以避免在相同兩個數據表之間有多個關聯性時產生模棱兩可。 |
成員 | String | 名稱 | 要在此關聯端產生的 CLR 屬性名稱。 |
儲存體 | String |
如果 OneToMany 和 Not IsForeignKey:
_OtherTable 還: _TypeName (OtherTable) |
將儲存此資料行值的私人 CLR 備份欄位名稱。 |
AccessModifier | AccessModifier | 公開 | 要建立之 CLR 屬性的協助工具層級。 有效值為 Public、 Protected、 Internal 和 Private。 |
ThisKey | String | 包含類別內的 IsIdentity 屬性 | 關聯這一端索引鍵的逗號分隔清單。 |
OtherTable | String | 請參閱描述。 | 關聯性另一端的資料表。 一般而言,這可由LINQ to SQL執行時間透過比對關聯性名稱來決定,但單向關聯或匿名關聯則不可能。 |
OtherKey | String | 外部類別中的主鍵 | 關聯另一端索引鍵的逗號分隔清單。 |
IsForeignKey | 布林值 | 否 | 指出這是否為關聯性的「子」端,也就是一對多的多端。 |
RelationshipType | RelationshipType | OneToMany | 指出使用者是否判斷這個關聯相關的資料是否符合一對一資料的準則,或符合一對多的一般案例。 針對一對一,使用者判斷提示主鍵 (「one」) 端的每個資料列,外鍵 (「多」) 端只有一個資料列。 這會導致在 「one」 端產生EntityRef < T >,而不是EntitySet < T >。 有效值為 OneToOne 和 OneToMany。 |
DeleteRule | String | None | 用來將刪除行為新增至此關聯。 例如,「CASCADE」 會將 「ONDELETECASCADE」 新增至 FK 關聯性。 如果設定為 null,則不會新增任何刪除行為。 |
函式
這個專案代表預存程式或資料庫函式。 對於每個 Function 節點, 會在 DataCoNtext 類別中產生方法。
函式屬性
屬性 | 類型 | 預設 | 描述 |
---|---|---|---|
名稱 | String | (必要) | 資料庫中預存程式的名稱。 |
方法 | String | 方法 | 要產生之 CLR 方法的名稱,允許叫用預存程式。 方法的預設名稱具有[dbo]等專案,已移除Name。 |
AccessModifier | AccessModifier | 公開 | 預存程式方法的協助工具層級。 有效值為 Public、 Protected、 Internal 和 Private。 |
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。 函式不能同時具有 Return 和 ElementType。 |
TableFunction
這個專案代表資料表的 CUD 覆寫函式。 LINQ to SQL設計工具允許建立 LINQ TO SQL 的Insert、Update和Delete覆寫方法,並允許將實體屬性名稱對應至預存程式參數名稱。
CUD 函式的方法名稱是固定的,因此TableFunction元素的 DBML 中沒有Method屬性。 例如,針對 Customer 資料表,CUD 方法會命名為 InsertCustomer、 UpdateCustomer和 DeleteCustomer。
資料表函數無法傳回表格式圖形,因此TableFunction元素中沒有ElementType屬性。
TableFunction 屬性
屬性 | 類型 | 預設 | 描述 |
---|---|---|---|
名稱 | String | (必要) | 資料庫中預存程式的名稱。 |
AccessModifier | AccessModifier | 私人 | 預存程式方法的協助工具層級。 有效值為 Public、 Protected、 Internal 和 Private。 |
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 | 位置 | 參數流動的方向。 可以是其中一個 In、 Out和 InOut。 |
傳回
這個專案代表預存程式/函式的傳回型別。
傳回屬性
屬性 | 類型 | 預設 | 描述 |
---|---|---|---|
類型 | String | (必要) | 預存程式/函式結果的 CLR 類型。 |
DbType | String | None | 預存程式/函式結果的 DB 類型。 |
TableFunctionParameter
這個專案代表 CUD 函式的參數。 參數可以傳入和傳出資料。每個參數都會對應至這個 CUD 函式所屬的 Table 資料行。 這個專案中沒有 Type 或 DbType 屬性,因為可以從參數對應的資料行取得類型資訊。
TableFunctionParameter 屬性
屬性 | 類型 | 預設 | 描述 |
---|---|---|---|
名稱 | String | (必要) | CUD 函式參數的資料庫名稱。 |
參數 | String | 名稱 | 方法參數的 CLR 名稱。 |
資料行 | String | 名稱 | 此參數所對應的資料行名稱。 |
方向 | ParameterDirection | 位置 | 參數流動的方向。 可以是其中一個 In、 Out或 InOut。 |
版本 | 版本 | 目前 | 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 | 包含ConnectionString的App.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 () 之前設定。
- 屬於實體身分識別的成員。
- 已變更的成員。
- 參與開放式平行存取檢查的成員。
如果時間戳記或版本號碼資料行用於開放式平行存取檢查,則必須在呼叫 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 提供額外的建構函式來提供 MappingSource。 MapSource的其中一種形式是從 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。
未實作
- <類型 > 。解析
- 列舉可用於資料表中,並對應至整數和字串。 針對後者,會使用 Parse 和 ToString () 方法。
與 .NET 的差異
- 雙精度浮點數的 ToString 輸出會使用 CONVERT (NVARCHAR (30) 、 @x 、2) ,一律會在 SQL 上使用 16 位數和「科學標記法」。例如:「0.000000000000000e+000」 表示 0,因此不會提供與 相同的字串。NET 的 Convert.ToString () 。
System.String
已實作
非靜態方法:
- Length, Substring, Contains, StartsWith, EndWith, IndexOf, Insert, Remove, Replace, Trim, ToLower, ToUpper, LastIndexOf, PadRight, PadLeft, Equals, CompareTo. 除了採用 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、資料庫、資料表資料行或運算式上指定。
到目前為止實作的函式翻譯不會變更定序,也不會在翻譯的運算式上指定不同的定序。 因此,如果預設定序不區分大小寫, CompareTo 或 IndexOf 等函式可以提供與 (區分大小寫) .NET 函式會提供不同的結果。
StartWith (str) /
EndWith (str的方法假設引數) str 是常數或用戶端上評估的運算式。 也就是說,目前無法使用 str的資料行。
System.Math
實作的靜態方法
- 所有簽章:
- Abs、Acos、Asin、Atan、Atan2、BigMul、Ceiling、Cos、Cosh、Exp、Floor、Log、Log10、Max、Min、Pow、Sign、Sinh、Sqrt、Tan、Tanh或Truncate。
未實作
- IEEERemainder。
- DivRem 具有 out 參數,因此您無法在運算式中使用。 常數 Math.PI和Math.E會在用戶端上進行評估,因此不需要翻譯。
與 .NET 的差異
.NET 函式 Math.Round 的翻譯是 SQL 函式 ROUND。 只有在指定指出 MidpointRounding 列舉值的多載時,才支援轉譯。 MidpointRounding.AwayFromZero是 SQL 行為,MidpointRounding.ToEven表示 CLR 行為。
System.Convert
已實作
-
Form To < Type1 > (< Type2 > x) 的方法,其中 Type1,Type2 是下列其中一項:
- bool、 byte、 char、 DateTime、 decimal、 double、 float、 Int16、 Int32、 Int64或 string。
- 行為與轉換相同:
- 針對 ToString (Double) 有特殊程式碼可取得完整精確度。
- 針對轉換Int32/Char,LINQ to SQL會使用 SQL 的UNICODE
/
NCHAR函數。 - 否則,轉譯為 CONVERT。
未實作
ToSByte、 UInt16、 32、 64:這些類型不存在於 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#
Visual Basic
|
GetQueryText (查詢) | 傳回查詢的查詢文字,而不需要執行查詢。 Usage : C# Console.WriteLine(db.GetQueryText(db.Customers));
Visual Basic
|
GetChangeText () | 傳回插入/更新/刪除的 SQL 命令文字,而不需執行它們。 Usage : C# Console.WriteLine(db.GetChangeText());
Visual Basic
|