多層式架構應用程式中的資料擷取和 CUD 作業 (LINQ to SQL)
當您將像是 Customers 或 Orders 等實體物件透過網路序列化到用戶端時,這些實體會與其資料內容中斷連結。 資料內容不會再追蹤它們的變更或它們與其他物件的關聯。 如果用戶端只讀取資料,這就不成問題。 此外,要讓用戶端加入資料列到資料庫,也相對來說簡單。 不過,如果您的應用程式要讓用戶端能夠更新或刪除資料,就必須將實體附加到新的資料內容,才能呼叫 DataContext.SubmitChanges。 此外,如果您使用開放式並行存取 (Optimistic Concurrency) 來檢查原始值,那麼也需要想辦法將原始實體和修改過的實體提供給資料庫。 Attach
方法即是提供來讓您將中斷連結的實體放入新的資料內容。
即使將 Proxy 物件序列化以取代 LINQ to SQL 實體,您仍然需要在資料存取層 (DAL) 中建構實體,並將其附加至新的 System.Data.Linq.DataContext,才能將資料提交至資料庫。
LINQ to SQL 完全不管實體如何序列化。 如需深入了解如何使用物件關聯式設計工具和 SQLMetal 工具,以產生可使用 Windows Communication Foundation 進行序列化的類別,請參閱操作說明:使實體可序列化。
注意
請只在新的或還原序列化的實體上呼叫 Attach
方法。 要將實體與其原始資料內容中斷連結的唯一方式就是使其序列化。 如果您嘗試將未中斷連結的實體附加到新的資料內容,而該實體仍擁有先前資料內容的延遲載入器,LINQ to SQL 就會擲回例外狀況。 若實體擁有來自兩個不同資料內容的延遲載入器,當您在該實體上執行插入、更新和刪除作業時,可能會導致不想要的結果。 如需延遲載入器的詳細資訊,請參閱延遲與立即載入。
擷取資料
用戶端方法呼叫
下列範例示範 Windows Form 用戶端對 DAL 的範例方法呼叫。 在這個範例中,DAL 實作為 Windows 服務庫:
Private Function GetProdsByCat_Click(ByVal sender As Object, ByVal e _
As EventArgs)
' Create the WCF client proxy.
Dim proxy As New NorthwindServiceReference.Service1Client
' Call the method on the service.
Dim products As NorthwindServiceReference.Product() = _
proxy.GetProductsByCategory(1)
' If the database uses original values for concurrency checks,
' the client needs to store them and pass them back to the
' middle tier along with the new values when updating data.
For Each v As NorthwindClient1.NorthwindServiceReference.Product _
In products
' Persist to a List(Of Product) declared at class scope.
' Additional change-tracking logic is the responsibility
' of the presentation tier and/or middle tier.
originalProducts.Add(v)
Next
' (Not shown) Bind the products list to a control
' and/or perform whatever processing is necessary.
End Function
private void GetProdsByCat_Click(object sender, EventArgs e)
{
// Create the WCF client proxy.
NorthwindServiceReference.Service1Client proxy =
new NorthwindClient.NorthwindServiceReference.Service1Client();
// Call the method on the service.
NorthwindServiceReference.Product[] products =
proxy.GetProductsByCategory(1);
// If the database uses original values for concurrency checks,
// the client needs to store them and pass them back to the
// middle tier along with the new values when updating data.
foreach (var v in products)
{
// Persist to a list<Product> declared at class scope.
// Additional change-tracking logic is the responsibility
// of the presentation tier and/or middle tier.
originalProducts.Add(v);
}
// (Not shown) Bind the products list to a control
// and/or perform whatever processing is necessary.
}
中介層實作
下列範例顯示中介層 (Middle Tier) 上的介面方法實作。 下列是需要注意的兩個重點:
- DataContext 是在方法範圍內宣告。
- 此方法會傳回實際結果的 IEnumerable 集合。 序列化程式將會執行查詢,將結果送回用戶端/展示層。 若要在中介層本機上存取查詢結果,您可以在查詢變數上呼叫
ToList
或ToArray
來強制執行。 然後就可以將該清單或陣列以IEnumerable
傳回。
Public Function GetProductsByCategory(ByVal categoryID As Integer) _
As IEnumerable(Of Product)
Dim db As New NorthwindClasses1DataContext(connectionString)
Dim productQuery = _
From prod In db.Products _
Where prod.CategoryID = categoryID _
Select prod
Return productQuery.AsEnumerable()
End Function
public IEnumerable<Product> GetProductsByCategory(int categoryID)
{
NorthwindClasses1DataContext db =
new NorthwindClasses1DataContext(connectionString);
IEnumerable<Product> productQuery =
from prod in db.Products
where prod.CategoryID == categoryID
select prod;
return productQuery.AsEnumerable();
}
資料內容的執行個體應具有「工作單元」的存留期 (Lifetime)。在鬆散結合的環境中,工作單元通常很小,可能是一個開放式異動,其中包含對 SubmitChanges
的單一呼叫。 因此,資料內容會在方法範圍內建立和處置 (Dispose)。 如果工作單元包含對商務規則邏輯的呼叫,那麼通常會需要在整個作業期間保留 DataContext
執行個體。 無論在哪種情況下,DataContext
執行個體都不適合跨任意數目的交易而長期保持作用中。
這個方法將傳回 Product 物件,而不是與每個 Project 相關聯之 Order_Detail 物件的集合。 請使用 DataLoadOptions 物件來變更此預設行為。 如需詳細資訊,請參閱操作說明:控制擷取多少相關資料。
插入資料
若要插入新物件,展示層會在中介層介面上呼叫相關方法,並傳入要插入的新物件即可。 在某些情況下,為提高效率,用戶端可能只會傳入部分值,再由中介層建構完整物件。
中介層實作
在中介層上,新的 DataContext 會建立,物件會使用 DataContext 方法附加到 InsertOnSubmit,當呼叫 SubmitChanges 時,物件即會插入。 例外狀況、回呼 (Callback) 和錯誤狀況的處理方式就如同在任何其他 Web 服務案例中處理一樣。
' No call to Attach is necessary for inserts.
Public Sub InsertOrder(ByVal o As Order)
Dim db As New NorthwindClasses1DataContext(connectionString)
db.Orders.InsertOnSubmit(o)
' Exception handling not shown.
db.SubmitChanges()
End Sub
// No call to Attach is necessary for inserts.
public void InsertOrder(Order o)
{
NorthwindClasses1DataContext db = new NorthwindClasses1DataContext(connectionString);
db.Orders.InsertOnSubmit(o);
// Exception handling not shown.
db.SubmitChanges();
}
刪除資料
若要刪除資料庫中的現有物件,展示層會在中介層介面上呼叫相關方法,然後針對要刪除的物件,傳入包含其原始值的複本。
刪除作業包含開放式並行存取檢查,而要刪除的物件必須先附加到新的資料內容。 在這個範例中,Boolean
參數會設定為 false
,表示物件沒有時間戳記 (RowVersion)。 如果資料庫資料表會為每個資料錄產生時間戳記,那麼開放式並行存取檢查會簡單許多,對用戶端來說尤然。 這只要傳入原始或修改過的物件,再將 Boolean
參數設定為 true
即可。 無論在哪種情況下,在中介層上通常需要攔截 ChangeConflictException。 如需如何處理開放式並行衝突的詳細資訊,請參閱開放式同步存取:概觀。
刪除在關聯資料表上具有外部索引鍵條件約束的實體時,您必須先刪除其 EntitySet<TEntity> 集合中的所有物件。
' Attach is necessary for deletes.
Public Sub DeleteOrder(ByVal order As Order)
Dim db As New NorthwindClasses1DataContext(connectionString)
db.Orders.Attach(order, False)
' This will throw an exception if the order has order details.
db.Orders.DeleteOnSubmit(order)
Try
' ConflictMode is an optional parameter.
db.SubmitChanges(ConflictMode.ContinueOnConflict)
Catch ex As ChangeConflictException
' Get conflict information, and take actions
' that are appropriate for your application.
' See MSDN Article "How to: Manage Change
' Conflicts (LINQ to SQL).
End Try
End Sub
// Attach is necessary for deletes.
public void DeleteOrder(Order order)
{
NorthwindClasses1DataContext db = new NorthwindClasses1DataContext(connectionString);
db.Orders.Attach(order, false);
// This will throw an exception if the order has order details.
db.Orders.DeleteOnSubmit(order);
try
{
// ConflictMode is an optional parameter.
db.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException e)
{
// Get conflict information, and take actions
// that are appropriate for your application.
// See MSDN Article How to: Manage Change Conflicts (LINQ to SQL).
}
}
更新資料
LINQ to SQL 支援在下列牽涉到開放式並行存取的案例中更新:
- 以時間戳記或 RowVersion 號碼為基礎的開放式並行存取。
- 以實體屬性子集的原始值為基礎的開放式並行存取。
- 以完整原始和修改過的實體為基礎的開放式並行存取。
您也可以對實體連同其關聯一起執行更新或刪除,例如 Customer 及其關聯 Order 物件的集合。 當您在用戶端上修改實體物件及子 (EntitySet
) 集合的圖形,而開放式並行存取需要原始值時,用戶端必須提供每個實體和 EntitySet<TEntity> 物件的原始值。 如果要讓用戶端能夠在單一方法呼叫中執行一組相關的更新、刪除和插入,您必須提供用戶端方式來指出要對每個實體執行何種作業。 接著在中介層上,您必須為每個實體呼叫適當的 Attach 方法,再呼叫 InsertOnSubmit、DeleteAllOnSubmit 或 InsertOnSubmit (插入不需要 Attach
),才能呼叫 SubmitChanges。 請不要擷取資料庫的值來當做更新之前取得原始值的方式。
如需開放式同步存取的詳細資訊,請參閱開放式同步存取:概觀。 如需解決開放式同步存取變更衝突的詳細資訊,請參閱操作說明:管理變更衝突。
下列範例將示範每個案例:
使用時間戳記的開放式並行存取
' Assume that "customer" has been sent by client.
' Attach with "true" to say this is a modified entity
' and it can be checked for optimistic concurrency
' because it has a column that is marked with the
' "RowVersion" attribute.
db.Customers.Attach(customer, True)
Try
' Optional: Specify a ConflictMode value
' in call to SubmitChanges.
db.SubmitChanges()
Catch ex As ChangeConflictException
' Handle conflict based on options provided.
' See MSDN article "How to: Manage Change
' Conflicts (LINQ to SQL)".
End Try
// Assume that "customer" has been sent by client.
// Attach with "true" to say this is a modified entity
// and it can be checked for optimistic concurrency because
// it has a column that is marked with "RowVersion" attribute
db.Customers.Attach(customer, true)
try
{
// Optional: Specify a ConflictMode value
// in call to SubmitChanges.
db.SubmitChanges();
}
catch(ChangeConflictException e)
{
// Handle conflict based on options provided
// See MSDN article How to: Manage Change Conflicts (LINQ to SQL).
}
使用原始值子集
在這種方式中,用戶端會傳回已序列化的完整物件以及要修改的值。
Public Sub UpdateProductInventory(ByVal p As Product, ByVal _
unitsInStock As Short?, ByVal unitsOnOrder As Short?)
Using db As New NorthwindClasses1DataContext(connectionString)
' p is the original unmodified product
' that was obtained from the database.
' The client kept a copy and returns it now.
db.Products.Attach(p, False)
' Now that the original values are in the data context,
' apply the changes.
p.UnitsInStock = unitsInStock
p.UnitsOnOrder = unitsOnOrder
Try
' Optional: Specify a ConflictMode value
' in call to SubmitChanges.
db.SubmitChanges()
Catch ex As Exception
' Handle conflict based on options provided.
' See MSDN article "How to: Manage Change Conflicts
' (LINQ to SQL)".
End Try
End Using
End Sub
public void UpdateProductInventory(Product p, short? unitsInStock, short? unitsOnOrder)
{
using (NorthwindClasses1DataContext db = new NorthwindClasses1DataContext(connectionString))
{
// p is the original unmodified product
// that was obtained from the database.
// The client kept a copy and returns it now.
db.Products.Attach(p, false);
// Now that the original values are in the data context, apply the changes.
p.UnitsInStock = unitsInStock;
p.UnitsOnOrder = unitsOnOrder;
try
{
// Optional: Specify a ConflictMode value
// in call to SubmitChanges.
db.SubmitChanges();
}
catch (ChangeConflictException e)
{
// Handle conflict based on provided options.
// See MSDN article How to: Manage Change Conflicts
// (LINQ to SQL).
}
}
}
使用完整實體
Public Sub UpdateProductInfo(ByVal newProd As Product, ByVal _
originalProd As Product)
Using db As New NorthwindClasses1DataContext(connectionString)
db.Products.Attach(newProd, originalProd)
Try
' Optional: Specify a ConflictMode value
' in call to SubmitChanges.
db.SubmitChanges()
Catch ex As Exception
' Handle potential change conflict in whatever way
' is appropriate for your application.
' For more information, see the MSDN article
' "How to: Manage Change Conflicts (LINQ to
' SQL)".
End Try
End Using
End Sub
public void UpdateProductInfo(Product newProd, Product originalProd)
{
using (NorthwindClasses1DataContext db = new
NorthwindClasses1DataContext(connectionString))
{
db.Products.Attach(newProd, originalProd);
try
{
// Optional: Specify a ConflictMode value
// in call to SubmitChanges.
db.SubmitChanges();
}
catch (ChangeConflictException e)
{
// Handle potential change conflict in whatever way
// is appropriate for your application.
// For more information, see the MSDN article
// How to: Manage Change Conflicts (LINQ to SQL)/
}
}
}
若要更新集合,請呼叫 AttachAll,而非 Attach
。
必要的實體成員
如前所述,在呼叫 Attach
方法之前,只需要設定實體物件的某些成員。 需要設定的實體成員必須符合下列條件:
- 屬於實體識別的一部分。
- 必須修改。
- 本身是時間戳記或將其 UpdateCheck 屬性 (Attribute) 設為
Never
以外的值。
如果資料表使用時間戳記或版本號碼進行開放式並行存取檢查,則您必須設定這些成員,才能呼叫 Attach。 當 IsVersion 屬性 (Property) 在該 Column 屬性 (Attribute) 上設定為 true 時,成員就會專供開放式並行存取檢查使用。 只有在版本號碼或時間戳記值在資料庫上相同時,才會提交任何要求的變更。
只要成員沒有將 UpdateCheck 設定為 Never
,該成員也會用於開放式並行存取檢查。 如果沒有指定其他值,預設值會是 Always
。
如果遺漏任何一個必要成員,在 ChangeConflictException 期間會擲回 SubmitChanges (「資料列找不到,或者已變更」)。
州/省
實體物件在附加到 DataContext 執行個體之後,會視為處於 PossiblyModified
狀態。 有三種方式可將附加物件強制視為 Modified
。
以未修改的形式附加,再執行修改欄位。
使用接受目前和原始物件執行個體的 Attach 多載附加。 這會將新舊值一起提供給變更 Tracker,這樣它就會自動得知哪些欄位已變更。
使用接受第二個布林值參數 (設為 true) 的 Attach 多載附加。 這會指示變更 Tracker 將物件視為已修改,而無須提供任何原始值。 在此方式中,物件必須有版本/時間戳記欄位。
如需詳細資訊,請參閱物件狀態和變更追蹤。
如果實體物件已出現在 ID 快取,而其識別與要附加的物件相同,就會擲回 DuplicateKeyException。
當您使用一組 IEnumerable
物件附加時,若有已經存在的索引鍵出現時,會擲回 DuplicateKeyException。 其餘的物件將不會附加。