共用方式為


N-Tier 應用程式中的資料擷取和 CUD 作業 (LINQ to SQL)

更新: November 2007

當您將像是 Customers 或 Orders 等實體物件透過網路序列化到用戶端時,這些實體會與其資料內容中斷連結。資料內容不會再追蹤它們的變更或它們與其他物件的關聯。如果用戶端只讀取資料,這就不成問題。此外,要讓用戶端加入資料列到資料庫,也相對來說簡單。不過,如果您的應用程式要讓用戶端能夠更新或刪除資料,就必須將實體附加到新的資料內容,才能呼叫 DataContext.SubmitChanges。此外,如果您使用開放式並行存取 (Optimistic Concurrency) 來檢查原始值,那麼也需要想辦法將原始實體和修改過的實體提供給資料庫。Attach 方法即是提供來讓您將中斷連結的實體放入新的資料內容。

即使您是序列化 Proxy 物件來取代 LINQ to SQL 實體,仍需要在資料存取層 (DAL) 上建構實體,並將它附加到新的 System.Data.Linq.DataContext,才能將資料提交至資料庫。

LINQ to SQL 完全不管實體如何序列化。如需如何使用物件關聯式設計工具和 SQLMetal 工具來產生可供 Windows Communication Foundation (WCF) 序列化之類別的詳細資訊,請參閱 HOW TO:如何將實體設為可序列化 (LINQ to SQL)

注意事項:

請只在新的或還原序列化的實體上呼叫 Attach 方法。要將實體與其原始資料內容中斷連結的唯一方式就是使其序列化。如果您嘗試將未中斷連結的實體附加到新的資料內容,而該實體仍擁有先前資料內容的延遲載入器,LINQ to SQL 就會擲回例外狀況。若實體擁有來自兩個不同資料內容的延遲載入器,當您在該實體上執行插入、更新和刪除作業時,可能會導致不想要的結果。如需延遲載入器的詳細資訊,請參閱延後和立即載入的比較 (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 集合。序列化程式將會執行查詢,將結果送回用戶端/展示層。若要在中介層本機上存取查詢結果,您可以在查詢變數上呼叫 ToListToArray 來強制執行。然後就可以將該清單或陣列以 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 物件來變更此預設行為。如需詳細資訊,請參閱 HOW TO:控制擷取的相關資料量 (LINQ to SQL)

插入資料

若要插入新物件,展示層會在中介層介面上呼叫相關方法,並傳入要插入的新物件即可。在某些情況下,為提高效率,用戶端可能只會傳入部分值,再由中介層建構完整物件。

中介層實作

在中介層上,新的 DataContext 會建立,物件會使用 InsertOnSubmit 方法附加到 DataContext,當呼叫 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。如需如何處理開放式並行存取衝突的詳細資訊,請參閱開放式並行存取概觀 (LINQ to SQL)

刪除在關聯資料表上具有外部索引鍵條件約束的實體時,您必須先刪除其 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 方法,再呼叫 InsertOnSubmitDeleteAllOnSubmit 或 InsertOnSubmit() (插入不需要 Attach),才能呼叫 SubmitChanges。請不要擷取資料庫的值來當做更新之前取得原始值的方式。

如需開放式並行存取的詳細資訊,請參閱開放式並行存取概觀 (LINQ to SQL)。如需如何解決開放式並行存取變更衝突的詳細資訊,請參閱 HOW TO:管理變更衝突 (LINQ to SQL)

下列範例將示範每個案例:

使用時間戳記的開放式並行存取

' 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 conflicgt 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。

如果遺漏任何一個必要成員,在 SubmitChanges 期間會擲回 ChangeConflictException (「資料列找不到,或者已變更」)。

狀態

實體物件在附加到 DataContext 執行個體之後,會視為處於 PossiblyModified 狀態。有三種方式可將附加物件強制視為 Modified。

  1. 以未修改的形式附加,再執行修改欄位。

  2. 使用接受目前和原始物件執行個體的 Attach 多載附加。這會將新舊值一起提供給變更 Tracker,這樣它就會自動得知哪些欄位已變更。

  3. 使用接受第二個布林值參數 (設為 true) 的 Attach 多載附加。這會指示變更 Tracker 將物件視為已修改,而無須提供任何原始值。在此方式中,物件必須有版本/時間戳記欄位。

如需詳細資訊,請參閱物件狀態和變更追蹤 (LINQ to SQL)

如果實體物件已出現在 ID 快取,而其識別與要附加的物件相同,就會擲回 DuplicateKeyException

當您使用一組 IEnumerable 物件附加時,若有已經存在的索引鍵出現時,會擲回 DuplicateKeyException。其餘的物件將不會附加。

請參閱

概念

使用 LINQ to SQL 的 N-Tier 和遠端應用程式

其他資源

背景資訊 (LINQ to SQL)