Share via


Datenabruf und CUD-Operationen in N-Tier-Anwendungen (LINQ to SQL)

Aktualisiert: November 2007

Wenn Sie Entitätsobjekte wie Customers oder Orders über ein Netzwerk an einen Client serialisieren, werden diese Entitäten von ihrem Datenkontext getrennt. Änderungen oder Verknüpfungen mit anderen Objekten werden vom Datenkontext nicht mehr verfolgt. Dies stellt kein Problem dar, solange die Clients die Daten nur lesen. Es ist außerdem relativ einfach, Clients zu ermöglichen, einer Datenbank neue Zeilen hinzuzufügen. Wenn Ihre Anwendung jedoch voraussetzt, dass Clients Daten aktualisieren oder löschen sollen, müssen Sie die Entitäten an einen neuen Datenkontext anfügen, bevor Sie DataContext.SubmitChanges aufrufen. Wenn Sie eine Überprüfung auf vollständige Parallelität mit ursprünglichen Werten verwenden, müssen Sie außerdem eine Möglichkeit schaffen, der Datenbank sowohl die ursprüngliche als auch die geänderte Entität bereitzustellen. Die Attach-Methoden werden bereitgestellt, um es Ihnen zu ermöglichen, Entitäten in einen neuen Datenkontext einzufügen, nachdem sie getrennt wurden.

Auch wenn Sie anstelle von LINQ to SQL-Entitäten Proxyobjekte serialisieren, müssen Sie weiterhin eine Entität auf der Datenzugriffsebene (Data Access Layer, DAL) erstellen und sie an einen neuen System.Data.Linq.DataContext anfügen, damit die Daten an die Datenbank gesendet werden können.

Für LINQ to SQL spielt es keine Rolle, wie Entitäten serialisiert werden. Weitere Informationen dazu, wie Sie O/R-Designer und SQLMetal-Tools zum Generieren serialisierbarer Klassen mithilfe von Windows Communication Foundation (WCF) verwenden, finden Sie unter Gewusst wie: Aktivieren der Serialisierbarkeit von Entitäten (LINQ to SQL).

Hinweis:

Rufen Sie die Attach-Methoden nur für neue oder deserialisierte Entitäten auf. Die einzige Möglichkeit, eine Entität von ihrem ursprünglichen Datenkontext zu trennen, besteht darin, sie zu serialisieren. Wenn Sie versuchen, eine nicht getrennte Entität an einen neuen Datenkontext anzufügen, und diese Entität noch über verzögerte Ladeprogramme aus dem vorherigen Datenkontext verfügt, löst LINQ to SQL eine Ausnahme aus. Eine Entität mit verzögerten Ladenprogrammen aus zwei unterschiedlichen Datenkontexten könnte unerwünschte Ergebnisse hervorbringen, wenn Sie Einfüge-, Aktualisierungs- und Löschvorgänge für diese Entität ausführen. Weitere Informationen zu verzögerten Ladeprogrammen finden Sie unter Verzögertes und unmittelbares Laden (LINQ to SQL).

Abrufen von Daten

Clientmethodenaufruf

In den folgenden Beispielen wird ein Beispielmethodenaufruf der DAL von einem Windows Forms-Client aus veranschaulicht. Die DAL wird in diesem Beispiel als Windows-Dienstbibliothek implementiert:

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.
    }

Implementierung auf der mittleren Ebene

Im folgenden Beispiel wird eine Implementierung der Schnittstellenmethode auf der mittleren Ebene veranschaulicht. Die folgenden beiden Hauptpunkte sind zu beachten:

  • DataContext wird im Methodenbereich deklariert.

  • Die Methode gibt eine IEnumerable-Auflistung der tatsächlichen Ergebnisse zurück. Das Serialisierungsprogramm führt die Abfrage aus, um die Ergebnisse an die Client- oder Präsentationsebene zurückzusenden. Um lokal auf der mittleren Ebene auf die Abfrageergebnisse zuzugreifen, können Sie die Ausführung erzwingen, indem Sie ToList oder ToArray für die Abfragevariable aufrufen. Anschließend können Sie diese Liste oder dieses Array als IEnumerable zurückgeben.

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(); 
}

Eine Instanz eines Datenkontexts sollte eine Lebensdauer von einer "Arbeitseinheit" haben. In einer lose verknüpften Umgebung ist eine Arbeitseinheit normalerweise klein und entspricht vielleicht einer vollständigen Transaktion, einschließlich eines einzelnen Aufrufs von SubmitChanges. Der Datenkontext wird deshalb im Methodenbereich erstellt und freigegeben. Wenn die Arbeitseinheit Aufrufe einer Geschäftsregellogik einschließt, behalten Sie die DataContext-Instanz im Allgemeinen für die gesamte Operation bei. DataContext-Instanzen sind grundsätzlich nicht dafür vorgesehen, über einen längeren Zeitraum für eine beliebige Anzahl von Transaktionen beibehalten zu werden.

Diese Methode gibt Product-Objekte, aber keine Auflistung von Order_Detail-Objekten zurück, die mit den einzelnen Produkten verknüpft sind. Verwenden Sie das DataLoadOptions-Objekt, um dieses Standardverhalten zu ändern. Weitere Informationen finden Sie unter Gewusst wie: Steuern der Anzahl verbundener Daten, die abgerufen werden (LINQ to SQL).

Einfügen von Daten

Um ein neues Objekt einzufügen, ruft die Präsentationsebene nur die relevante Methode für die Schnittstelle der mittleren Ebene auf und übergibt das neue einzufügende Objekt. In einigen Fällen kann es effizienter sein, wenn der Client nur einige Werte übergibt und das Erstellen des vollständigen Objekts der mittleren Ebene überlässt.

Implementierung auf der mittleren Ebene

Auf der mittleren Ebene wird ein neuer DataContext erstellt, das Objekt mithilfe der InsertOnSubmit-Methode an DataContext angefügt und das Objekt beim Aufrufen von SubmitChanges eingefügt. Ausnahmen, Rückrufe und Fehlerbedingungen können wie in jedem beliebigen anderen Webdienstszenario behandelt werden.

' 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();
    }

Löschen von Daten

Um ein vorhandenes Objekt aus der Datenbank zu löschen, ruft die Präsentationsebene die relevante Methode für die Schnittstelle der mittleren Ebene auf und übergibt eine Kopie, in der die ursprünglichen Werte des zu löschenden Objekts enthalten sind.

Löschvorgänge umfassen Überprüfungen auf vollständige Parallelität, und das zu löschende Objekt muss zuerst an den neuen Datenkontext angefügt werden. In diesem Beispiel wird der Boolean-Parameter auf false festgelegt, um anzuzeigen, dass das Objekt über keinen Zeitstempel (RowVersion) verfügt. Wenn die Datenbanktabelle Zeitstempel für jeden Datensatz generiert, sind Überprüfungen auf Parallelität besonders für den Client bedeutend einfacher auszuführen. Übergeben Sie entweder das ursprüngliche oder geänderte Objekt, und legen Sie den Boolean-Parameter auf true fest. Auf der mittleren Ebene ist es grundsätzlich erforderlich, die ChangeConflictException abzufangen. Weitere Informationen zum Behandeln von Konflikten in Zusammenhang mit vollständiger Parallelität finden Sie unter Vollständige Parallelität – Übersicht (LINQ to SQL).

Beim Löschen von Entitäten, die über Fremdschlüsseleinschränkungen für verknüpfte Tabellen verfügen, müssen Sie zuerst alle Objekte in den zugehörigen EntitySet<TEntity>-Auflistungen löschen.

' 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).
    }
}

Aktualisieren von Daten

LINQ to SQL unterstützt Aktualisierungen in folgenden Szenarien mit vollständiger Parallelität:

  • Vollständige Parallelität auf der Grundlage von Zeitstempeln oder RowVersion-Nummern

  • Vollständige Parallelität auf der Grundlage ursprünglicher Werte einer Teilmenge von Entitätseigenschaften

  • Vollständige Parallelität auf der Grundlage der vollständigen ursprünglichen und geänderten Entitäten

Sie können auch Aktualisierungs- oder Löschvorgänge für eine Entität und ihre Beziehungen ausführen, beispielsweise für einen Kunden und eine Auflistung der zugehörigen Bestellobjekte. Wenn Sie auf dem Client Änderungen an einem Diagramm von Entitätsobjekten und deren untergeordneten (EntitySet) Auflistungen vornehmen und für Überprüfungen auf vollständige Parallelität ursprüngliche Werte erforderlich sind, muss der Client diese ursprünglichen Werte für jede Entität und jedes EntitySet<TEntity>-Objekt bereitstellen. Wenn Clients in der Lage sein sollen, eine Reihe verknüpfter Aktualisierungen, Löschungen und Einfügungen in einem einzelnen Methodenaufruf auszuführen, müssen Sie auf dem Client eine Möglichkeit vorsehen, um anzuzeigen, welcher Operationstyp für welche Entität ausgeführt werden soll. Auf der mittleren Ebene müssen Sie anschließend die geeignete Attach-Methode und dann InsertOnSubmit, DeleteAllOnSubmit oder InsertOnSubmit() (ohne Attach, für Einfügungen) für jede Entität aufrufen, bevor Sie SubmitChanges aufrufen. Das Abrufen von Daten aus der Datenbank ist keine zulässige Methode, um ursprüngliche Werte zu erhalten, bevor Sie eine Aktualisierung versuchen.

Weitere Informationen zur vollständigen Parallelität finden Sie unter Vollständige Parallelität – Übersicht (LINQ to SQL). Ausführliche Informationen zum Lösen von Änderungskonflikten bei der vollständigen Parallelität finden Sie unter Gewusst wie: Verwalten von Änderungskonflikten (LINQ to SQL).

In den folgenden Beispielen werden die einzelnen Szenarien veranschaulicht:

Vollständige Parallelität mit Zeitstempeln

' 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).
}

Mit einer Teilmenge ursprünglicher Werte

Bei dieser Vorgehensweise gibt der Client das vollständige serialisierte Objekt zusammen mit den zu ändernden Werten zurück.

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).
        }
    }
}

Mit vollständigen Entitäten

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)/
        } 
    }
}

Um eine Auflistung zu aktualisieren, rufen Sie AttachAll anstelle von Attach auf.

Erwartete Entitätsmember

Wie bereits erwähnt, müssen nur bestimmte Member des Entitätsobjekts festgelegt werden, bevor Sie die Attach-Methoden aufrufen. Entitätsmember, die festgelegt werden müssen, sollten folgenden Kriterien erfüllen:

  • Sie müssen Teil der Identität der Entität sein.

  • Von ihnen wird erwartet, dass sie sich ändern.

  • Es muss sich um einen Zeitstempel handeln oder ihr UpdateCheck-Attribut muss auf einen anderen Wert als Never festgelegt sein.

Wenn eine Tabelle einen Zeitstempel oder eine Versionsnummer für die Prüfung auf vollständige Parallelität verwendet, müssen diese Member daher festgelegt sein, bevor Sie Attach aufrufen. Ein Member gilt als für die Überprüfung auf vollständige Parallelität festgelegt, wenn die IsVersion-Eigenschaft für dieses Spaltenattribut auf true festgelegt ist. Alle angeforderten Aktualisierungen werden nur gesendet, wenn der Wert für die Versionsnummer oder den Zeitstempel in Datenbank identisch ist.

Member werden ebenfalls in der Überprüfung auf vollständige Parallelität verwendet, solange UpdateCheck für den Member nicht auf Never festgelegt wurde. Sofern nicht anders angegeben, lautet der Standardwert Always.

Wenn einer dieser erforderlichen Member fehlt, wird während SubmitChanges ("Zeile nicht gefunden oder geändert") eine ChangeConflictException ausgelöst.

Zustand

Nachdem ein Entitätsobjekt an die DataContext-Instanz angefügt wurde, wird davon ausgegangen, dass sich das Objekt im PossiblyModified-Zustand befindet. Sie können auf drei Weisen erzwingen, dass ein angefügtes Objekt als Objekt im Modified-Zustand betrachtet wird.

  1. Fügen Sie es als unverändert an, und ändern Sie die Felder dann direkt.

  2. Fügen Sie es mit der Attach-Überladung an, die aktuelle und ursprüngliche Objektinstanzen akzeptiert. Dadurch werden dem Änderungsprotokollierer alte und neue Werte zur Verfügung gestellt, sodass er automatisch weiß, welche Felder geändert wurden.

  3. Fügen Sie es mit der Attach-Überladung an, die einen zweiten booleschen Parameter (auf true festgelegt) akzeptiert. Dadurch wird das Objekt vom Änderungsprotokollierer als geändert betrachtet, ohne dass einer der ursprünglichen Werte bereitgestellt werden muss. Bei dieser Vorgehensweise muss dass Objekt über ein Versions-/Timestampfeld verfügen.

Weitere Informationen finden Sie unter Objektzustände und Änderungsverfolgung (LINQ to SQL).

Wenn ein Entitätsobjekt mit derselben Identität wie das angefügte Objekt bereits im ID-Cache enthalten ist, wird eine DuplicateKeyException ausgelöst.

Wenn Sie es mit einem IEnumerable-Satz von Objekten anfügen, wird eine DuplicateKeyException ausgelöst, wenn ein bereits vorhandener Schlüssel vorliegt. Verbleibende Objekte werden nicht angefügt.

Siehe auch

Konzepte

N-Tier- und Remoteanwendungen mit LINQ to SQL

Weitere Ressourcen

Hintergrundinformationen (LINQ to SQL)