Concurrency και conflict resolution στο ADO.NET – 2ο μέρος


Στο δεύτερο μέρος του άρθρου σχετικά με το concurrency και το conflict resolution στο ADO.NET, θα δούμε τι μπορούμε να κάνουμε για να διορθώσουμε τα προβληματικά Updates.

Τα περιεχόμενα του DataRow

Συνηθίζουμε να σκεφτόμαστε το DataTable ως ένα collection από DataRows, κάτι σαν έναν πίνακα, ωστόσο αυτή είναι μια λανθασμένη αντίληψη. Κάθε DataRow έχει πολλαπλές εκδόσεις, ανάλογα σε ποια φάση της ζωής του βρίσκεται. Όταν διαβάζουμε τα περιεχόμενα του DataRow, μπορούμε να προσδιορίσουμε ποια έκδοση από αυτά θέλουμε, χρησιμοποιώντας το DataRowVersion enumerator. Αυτό μπορεί να πάρει τιμές: Current, Default, Original και Proposed. Βέβαια, όλες οι εκδόσεις δεν είναι διαθέσιμες ανά πάσα στιγμή. Ένα DataRow που έχουμε κάνει AddNew (δηλαδή αντιστοιχεί σε Insert) θα έχει Current έκδοση αλλά όχι Original. Ένα DataRow που έχουμε μαρκάρει ως Deleted, θα έχει Original version αλλά όχι Current version. Και φυσικά ένα DataRow του οποίου έχουμε αλλάξει διάφορα πεδία, θα έχει και τα δύο, το Original που δείχνει τι τιμές είχαν τα πεδία αρχικά και την Current που έχει τις αλλαγές. Τώρα λοιπόν, ξέρετε την απάντηση στην ερώτηση «Που ξέρει τις τιμές @Original_CompanyName και @Original_Phone ώστε να τις περάσει ως παραμέτρους;»

Ας αλλάξουμε τον προηγούμενο κώδικα λίγο:

If dr.HasErrors Then
    Console.WriteLine("Row with ID {0} has error: {1}", dr("ShipperID"), dr.RowError)
    Console.WriteLine(“Current CompanyName: {0}”, dr("CompanyName")
    Console.WriteLine(“Original CompanyName: {0}”, dr("CompanyName", DataRowVersion.Original)
End If

Αν πρόκειται όμως να ζητήσουμε από τον χρήστη τι θα γίνει με τις «προβληματικές» εγγραφές, θα πρέπει να τον πληροφορήσουμε τι τιμές έχει τώρα η βάση.

Μπορούμε να χρησιμοποιήσουμε ένα δεύτερο DataSet το οποίο γεμίζουμε με τις προβληματικές εγγραφές. Τις τραβάμε μία-μία από τη βάση χρησιμοποιώντας κάθε φορά το κλειδί της εγγραφής. Αν δεν βρεθεί καν, πάει να πει ότι κάποιος την έχει διαγράψει, οπότε βάζουμε το κατάλληλο μήνυμα στο RowError property. Γι αυτήν τη δουλειά, μπορούμε να χρησιμοποιήσουμε το RowUpdated event του οποίου το event argument μας δίνει ό,τι πληροφορίες χρειαζόμαστε για να δουλέψουμε.

    Private Sub SqlDataAdapter1_RowUpdated(ByVal sender As System.Object, _                                           ByVal e As System.Data.SqlClient.SqlRowUpdatedEventArgs) _ Handles SqlDataAdapter1.RowUpdated
        Dim intRowCount As Integer
        If TypeOf (e.Errors) Is DBConcurrencyException AndAlso _                                                       e.Status = UpdateStatus.ErrorsOccurred Then
            HelperAdapter.SelectCommand.Parameters(0).Value = e.Row("ShipperID")
            intRowCount = HelperAdapter.Fill(HelperDataSet)
            If intRowCount = 1 Then
                e.Row.RowError = "Someone else have changed this record"
            Else
                e.Row.RowError = "This record no longer exists"
            End If
            e.Status = UpdateStatus.Continue
        End If
    End Sub

Προηγουμένως, κάνουμε είτε μέσω του IDE, είτε προγραμματιστικά copy το DataSet11 και το DataAdapter1 σε δύο νέα, HelperDataSet και HelperDataAdapter αντίστοιχα.

Ας δούμε τώρα πως μπορούμε να αυτοματοποιήσουμε τη διαδικασία λίγο παραπάνω. Θα πρέπει να γυρίσουμε λίγο πίσω… Ο λόγος για τον οποίο αποτυγχάνει το update ενός DataRow είναι ότι τα Original values του DataRow είναι διαφορετικά από αυτά που υπάρχουν εκείνη τη στιγμή στη βάση. Άρα, αν με κάποιον τρόπο μπορούσαμε να «φρεσκάρουμε» τα DataRows με τις τρέχουσες τιμές των πεδίων της κάθε εγγραφής, χωρίς να χάσουμε τα Current values, κατόπιν τα Update θα πετύχαινε!

Ο τρόπος για να το κάνουμε αυτό είναι η Merge μέθοδος του DataSet. Αυτή τυπικά χρησιμοποιείται για να συγχωνεύσουμε τα περιεχόμενα από δύο DataSets. Το κόλπο έγκειται στο ότι αν η Merge μέθοδος βρει δύο ίδια DataTables, τότε για κάθε DataRow που έχει ίδιο κλειδί μεταξύ των δύο DataTables, θα συγχωνεύσει τα περιεχόμενα των δύο DataRow σε ένα! Μάλιστα, η μέθοδος είναι overloaded και μπορεί να δεχθεί μια παράμετρο για να δηλώσουμε αν θέλουμε (True) ή όχι (False, default) να διατηρηθούν τα περιεχόμενα, έτσι ώστε να κάνουμε overwrite μόνο τα Original values και όχι τα Current. Κατόπιν, αν αμέσως κάνουμε Update το DataSet (όσο πιο μικρό το παράθυρο μεταξύ του Merge και του Update, τόσο μικρότερες οι πιθανότητες να δημιουργηθούν conflicts εκ νέου) τότετο Update θα πετύχει.

Μια τελευταία περίπτωση που δεν μπορεί να αντιμετωπιστεί με τον παραπάνω τρόπο είναι να θέλουμε να κάνουμε Update ενώ η εγγραφή έχει διαγραφεί από τη βάση. Αν είναι «λογικά» σωστό να γίνει κάτι τέτοιο, μπορούμε να κάνουμε το εξής: Κάνουμε Remove το DataRow από το Rows collection του DataSet και κατόπιν το προσθέτουμε ξανά. Αυτό θα έχει ως αποτέλεσμα το RowState property του DataRow να γίνει «Added» οπότε με το επόμενο Update του DataSet θα τρέξει ένα Insert statement.

Επίλογος

Ο χειρισμός των conflicts στο ADO.NET απαιτεί από τον προγραμματιστή να γράψει περισσότερο κώδικα, εκεί που παλιότερα στο ADO απλά έκανε error handling όταν ο χρήστης προσπαθούσε να αλλάξει μια κλειδωμένη εγγραφή. Ωστόσο, αυτό είναι το τίμημα για να υλοποιήσουμε σενάρια με μεγαλύτερη ευελιξία ως προς τη λογική τους αλλά και να κατασκευάσουμε εφαρμογές που γίνονται ευκολότερα scale και μπορούν να συμπεριφερθούν πιο άνετα κάτω από μεγαλύτερο φορτίο.



Σχολιάστε

Εισάγετε τα παρακάτω στοιχεία ή επιλέξτε ένα εικονίδιο για να συνδεθείτε:

Λογότυπο WordPress.com

Σχολιάζετε χρησιμοποιώντας τον λογαριασμό WordPress.com. Αποσύνδεση / Αλλαγή )

Φωτογραφία Twitter

Σχολιάζετε χρησιμοποιώντας τον λογαριασμό Twitter. Αποσύνδεση / Αλλαγή )

Φωτογραφία Facebook

Σχολιάζετε χρησιμοποιώντας τον λογαριασμό Facebook. Αποσύνδεση / Αλλαγή )

Φωτογραφία Google+

Σχολιάζετε χρησιμοποιώντας τον λογαριασμό Google+. Αποσύνδεση / Αλλαγή )

Σύνδεση με %s