Δημοσιεύτηκε στις Τρίτη, 5 Μαΐου 2009 5:43 μμ από το μέλος Markos :: 0 σχόλια

Report Localizer Ver. 0.5

My programs are mainly client Windows Forms programs, aiming at the Greek market. Recently i decided to “globalize” my applications in an effort to expand my activities. Everything went quite smoothly until i tried to localize reports. I was frustrated because apparently there is no easy way to accomplish that. Well, to be honest, i localized some reports in the past and i used parameters to do it. But in that project the number of reports was small, less than ten, so i went for it without much thought. When you have to translate more than 120 reports, you start looking for more efficient ways. So, my quest begun.

 

What is out there…

Perhaps the most complete and generic solution is described in this article. The whole idea is to take advantage of some undocumented properties of the report components, like ValueLocID, ToolTipLocID, and LabelLocID. Those are localization identifiers apparently not used by any service or the ReportViewer control. Yet, this solution made me rather skeptical, because there is no guarantee that this will continue to be the case in the future. Another report localization technique comes from this source. It doesn’t use the LocIDs properties and the translated values are stored in the database rather than in resource files. Finally, one can use the RDLC Localizing Helper Class.

 

Report Localizer

After examining the available alternatives i decided to give a chance to the “Parameters” approach but a visual tool was needed to ease the entire process. I like using visual tools, especially when they are good and free! My favorites are ResEx and the Resource Refactoring Tool. Despite my efforts, i was unable to find anything similar for translating reports. If there is something and i am not mentioning it here, i apologize in advance and i would appreciate it if you would bring it to my knowledge.

Report Localizer is a first attempt to create such a tool. The idea is to deal directly with the rdlc file itself. The first step is to identify those textbox elements that don’t hold expression values. The next step is to generate report parameters, of type string, that will be used to store the localized text version of table headers and other textbox items. Those parameters will be added directly to the <ReportParameters> section of the rdlc file. At the same time, the original text in those textboxes will be replaced by an expression that emits the appropriate parameter value. Next, the new report file will be saved and added to the project, replacing the original. Finally, Report Localizer will generate the necessary c# or vb code for calling the SetParameters() method. When all those steps are completed, Resource Refactoring Tool can be used to export the hard coded strings to a resource file and the ResEx tool to add new locales and translate the string values.

 

Description of the application and a few screens

Report Localizer is a very basic tool. It has has no complex screens, it doesn’t expose many options and it’s not complete yet. Thus the “0.5” of the version number. At the end of this post I will discuss about what can and what cannot be done with it and the type of functionality that needs to be implemented in future releases. Now, let me describe the features of the program.

The user loads an existing rdlc document by selecting the “Open” option in the “File” menu. The xml document is displayed in an WebBrowser control but the content can’t be edited directly by the user. When the document loads for the first time, Report Localizer identifies the TextBoxes that serve as column headers or text labels and adds their definition into a “GoTo” drop down list. The user can navigate through that list to the TextBoxes. Of course, the user can navigate through out the entire document using the “Find/Search” capabilities of the application.

 

Main Screen

 

Defining and adding parameters is done very easily. All the user has to do is to select the option “Generate parameters” in the “Action” menu. Then, a new window appears in which two tables are displayed. The first table has four columns. The first column is the suggested parameter name, the second is the default value, the third is the prompt value and the fourth column exposes an option of whether this parameter should be generated. Always use a prompt value, because if you don’t the parameter will be marked as internal and you won’t be able to access it through code. The second table lists the TextBox items that will be affected by the parameter generation. They may be more than one textboxes for the same parameter, if they happen to share the same text value.

 

Generate Parameters

 

When the parameters are generated, use the drop down list to examine the value of the affected textboxes. Now the report file is ready to be saved. Since this is a very early version of the application, always create a new file and keep the original for back up purposes. Now, the new file can be added to the project and the original report can be excluded. All that remains, is to generate the required c# or vb code. You can define which one of those parameters will be included in the code. This is done with the “Select parameters” option of the “Action” menu. You can make your selection in the new window that appears. Finally, use the appropriate “Generate code” menu command to get your code. This code has to be copied and pasted in to your form or in to your class.

 

Select Parameters

 

Strengths and limitations

Even with this early release a lot of things can be done. First of all, the Report Localizer speeds up the process of parameter generation. Secondly, the code generation feature does its best to take off the burden of writing “dictation code” yourself; and we all know that “dictation code” is both boring and time consuming. But there are a few things you should bear in mind.

The “suggested” parameter names are “generated” from the initial text values of the textboxes. So it’s best to use proper string values as names, when designing your report. If you don’t, you will have to do it in the stage of parameter generation. You should give the same attention when assigning the default values as well, because those values will be used by the program during code generation.

One serious limitation of this release is that it doesn’t recognize subreports. This is a feature that needs to be implemented next. Why is that serious? Because, subreport parameter values cannot be set inside the SubreportProcessing event handler. You can add a datasource to a subreport, but you cannot set the parameter values directly. In order to do that, you have to include them in the parent report, set their values there and finally pass those values to the subreport. The bottom line is that if you use subreports in your projects, an extra amount of work is needed to localize them with the “Parameter” approach. It’s not as tragic as it sounds, but it’s not a fully automated procedure at this stage.

Finally, you cannot localize tooltip messages with this release. I didn’t include them because i never use them. I know that this sounds selfish, but i was trying to cover my own needs when i wrote Report Localizer. I don’t use tooltips because in my opinion all text in a report must be well defined and self-describing. Reports are documents that eventually will be printed. So, if there is something that requires further clarification, i always use a footnote. In future releases this feature could be added as well, but it’s of low priority.

One last thing… Don’t forget about bugs!! Play safe and don’t overwrite your original files with those created by the Report Localizer. At least not with the current release.

 

The attachments…

The first attachment is the msi installer of the application. Report Localizer can be used and distributed freely (reqs .NET 3.5). I would be more than happy if you decide to try my application and i would be twice as happy if you find it useful. I would also appreciate it if you report bug problems back to me or give any other feedback you might think is appropriate. There is also a second attachment which includes a small c# solution with localized reports (VS2008). The original reports are also included so that you can recreate the project yourself. I hope you find it useful as well.


Attachment(s): ReportLocalizerSetup.zip
Δημοσιεύτηκε στις Δευτέρα, 30 Μαρτίου 2009 1:08 πμ από το μέλος Markos :: 2 σχόλια

Hybrid Arithmetic Types. How to increase computational accuracy and range in a DIY approach.

In the process of developing software that has to deal with complex numerical computations, many times you have to think of ways to manipulate your biggest enemy: the double type. Some may disagree, but for a certain category of mathematical problems this type causes more trouble than convenience. Its accuracy may seem sufficient but it is inadequate. Round off errors accumulate and the final result is meaningless. Its range may seem large enough, but we quite often end up with an overflow or an underflow condition. What can be done then? Is there anything else to use in its place?

The decimal type seems to be a promising candidate. Well… it’s not!! Neither its numerical range is wider than that of the double type nor its accuracy is always exploitable. Suppose we have to multiply two numbers that differ greatly in their order of magnitude, for example 2 times srtq[2]e-25; the smaller one will be truncated to such an extent so that their product will be completely inaccurate. OK, the decimal type by itself can’t do much. But, what if it is combined with another type? Say, an int or a long?

Decimal’s 28 digit accuracy makes it a perfect mantissa. The only other thing we need, is an integer exponent and this way we’ve just created our hybrid type (which is of course an object). The tricky part is to force the mantissa’s absolute value to range between 1 (inclusive) and 10 (exclusive) or to be equal to 0. The next step is to define methods for addition, subtraction, multiplication and division or to overload the appropriate operators.

The implementation is as easy as it sounds. The way to perform basic arithmetic operations is already defined and well documented. For addition and subtraction we must first scale the exponents and then add or subtract the mantissae. Multiplication and division is much more straightforward since we only have to multiply (divide) mantissae and add (subtract) the exponents. When everything is finished we must scale the result so that we end up with a new hybrid type that will meet the specifications already mentioned in the previous paragraph.

Are we done yet? Oh, NO!! We also need a library to calculate basic mathematical functions like Exp(x) and Sin(x), etc. The most straightforward approach to do this is to use MacLaurin series. Surely, there are better and more efficient ways to achieve convergence, but for now MacLaurin series will do just fine.

 

The library

I have to confess that this is not the original implementation of the library. My first attempt was made a few years ago while i was learning C#. It had several drawbacks and very bad performance. The reason for this was that at the time i was experimenting with the string approach as well, in order to achieve “user-defined” floating point accuracy. Strings were involved a little more than they should be (a lot more actually) and because of that i didn’t use operator overloading. Anyway, at the time i had no idea where the code would lead me.

This library is different. The code is a lot clearer and more readable. However, i cannot claim it is bug free. Should you find any bugs i would appreciate it a lot if you bring them to my attention. The object that makes all of the above possible is called HybridDecimal and the class that is used to calculate basic mathematical functions is named HybridMath.

Although HybridDecimal is an object, it has to behave like a numeric type. So a lot of overloading was needed to achieve that kind of behavior. It cooperates smoothly with other numeric types in almost every aspect, but assignment. For instance, you can write:

HybridDecimal a = new HybridDecimal(1.3234, 234); a=a * 123;

But you can’t write: a = 1; You should write a = (HybridDecimal)1; instead.

Another aspect you should keep in mind is that HybridDecimal is a strange beast. The type of the exponent is long, so it is practically impossible to suffer from an overflow or underflow condition. I know that because i tested the original library extensively. Whenever a numerical procedure took a wrong path, it never exited. So in this implementation i had to add an OverflowExponent property to simulate the overflow – underflow condition.

Please try it and let me know of what you think.


Attachment(s): HybridDecimal.zip
Δημοσιεύτηκε στις Τετάρτη, 5 Νοεμβρίου 2008 8:42 μμ από το μέλος Markos :: 0 σχόλια

Μερικές σκέψεις πάνω στο θέμα της αποστολής Ιεραρχικών Μεταβολών (Hierarchical Changes) σε ADO.NET Databases

 

Εισαγωγή


Η ενημέρωση των μεταβολών που πραγματοποιούνται στα στοιχεία συνδεδεμένων πινάκων θεωρείται ως “advanced updating scenario”. Αδιαμφισβήτητα το ADO.NET είναι μια σπουδαία τεχνολογία, αλλά όπως είναι ήδη γνωστό, ταλαιπωρεί τους χρήστες όσον αφορά την αποστολή και αποθήκευση hierarchical data changes. Τα άρθρα και τα videos που έχουν κατά καιρούς δημοσιευτεί σε διάφορα blogs και fora, δεν μου παρέχουν μία αίσθηση πληρότητας και θεωρώ ότι το θέμα δεν έχει καθόλου, μα καθόλου, εξαντληθεί.

Ένας από τους λόγους που χρησιμοποιούνται οι βάσεις δεδομένων για την αποθήκευση της πληροφορίας, είναι η δυνατότητα διασύνδεσης των πινάκων για τη διατήρηση της σχεσιακής ακεραιότητας (referential integrity) των εγγραφών. Έτσι, λοιπόν, πιστεύω ότι δικαιούμαι να ζητάω από το ADO.NET πιο αποτελεσματική διαχείριση της πολυπλοκότητας της αποστολής των hierarchical changes προς τη βάση, ελαχιστοποιώντας την παρέμβαση του developer.

Το παρόν post φιλοδοξεί να επαναφέρει στο προσκήνιο ένα θέμα που φαίνεται να έχει ξεχαστεί λίγο και να δώσει το έναυσμα για νέες τοποθετήσεις και συζητήσεις. Όλα όσα πρόκειται ν' αναφερθούν, αφορούν στο ADO.NET 2.0 και στο Visual Studio 2005.



Θεωρητική περιγραφή


Με βάση την τεχνολογία ADO.NET η επιτυχής αποστολή και ενημέρωση των εγγραφών, όσον αφορά συνδεδεμένους πίνακες, σε μία βάση δεδομένων εξαρτάται από δύο παράγοντες: Ο ένας είναι η σειρά αποστολής των προσθηκών, μεταβολών και διαγραφών και ο άλλος είναι το SourceVersion των παραμέτρων στα Insert-, Update- και Delete- Commands των DataAdapters.

Όταν έχουμε συνδεδεμένους πίνακες (parent – child relationship), με cascade actions για update & delete, τόσο στη Βάση όσο και στο DataSet, το SourceVersion των παραμέτρων παίζει καθοριστικό ρόλο. Οι περισσότεροι πιστεύω ότι κάνουμε χρήση του DataSource Wizard για να προσθέσουμε πίνακες στον DataSet Designer. Για το λόγο αυτό, ας εξετάσουμε πρώτα τι συμβαίνει με τους TableAdapters που δημιουργεί ο Wizard μόλις προσθέσει τους πίνακες στον Designer.

Ο Wizard δημιουργεί τα Select-, Delete-, Insert- και Update- Commands τα οποία είναι υπεύθυνα για την ανάγνωση των δεδομένων από τη βάση, αλλά και την επιτυχημένη αποστολή των μεταβολών των εγγραφών που επεξεργάζεται ο χρήστης. Σ' αυτά τα Commands υπάρχουν παράμετροι. Οι παράμετροι αυτοί έχουν, συνήθως, το ίδιο όνομα με τα πεδία του πίνακα (βεβαίως, προηγείται πάντοτε ο χαρακτήρας @). Το ενδιαφέρον, τώρα, πρέπει να εστιαστεί σ' εκείνες τις παραμέτρους που έχουν πρόθεμα @Original_ και οι οποίες συναντώνται μόνο στα Delete- και Update- Commands. Κάπου εδώ τώρα, αρχίζει και γίνεται σημαντική η τιμή του SourceVersion αυτών των παραμέτρων.

Όταν αλλάζει η τιμή των πεδίων των primary keys στο parent table, αυτές οι αλλαγές γίνονται cascade στο child table (αναφέρομαι στο DataSet). Συνεπώς, όταν αποθηκεύσουμε αυτές της αλλαγές, εξαιτίας του cascade που προκαλεί η αλλαγή των τιμών των πεδίων στο parent table της βάσης, οι αλλαγές αυτές προωθούνται και στο child table της βάσης. Άρα, στο child του DataSet οι original τιμές των πεδίων του relation δε συμφωνούν με τις αντίστοιχες τιμές που έχει το child table στη βάση ύστερα από την αποθήκευση των μεταβολών στο parent. Όταν, λοιπόν, προσπαθήσουμε να αποθηκεύσουμε τις αλλαγές του child από το DataSet στη βάση, θα πάρουμε ένα ωραιότατο exception.

Για να το αποφύγουμε τη δυσάρεστη αυτή εξέλιξη, θα πρέπει το SourceVersion των παραμέτρων των πεδίων του relation στο child, για το UpdateCommand, να γίνει current. Αυτό δεν ισχύει για τις παραμέτρους του DeleteCommand μιας και οι διαγραφές αφορούν πάντα στις original τιμές. Στον πίνακα 1 που ακολουθεί, παρατίθενται οι τιμές του SourceVersion των παραμέτρων με πρόθεμα @Original_ για το UpdateCommand για συνδεδεμένους πίνακες. Η λογική που αναπτύχθηκε στην παρούσα παράγραφο για δύο πίνακες επεκτείνεται σε n-πίνακες.


Πίνακας 1. Τιμές που πρέπει να έχει το SourceVersion των παραμέτρων με πρόθεμα @Original_ του UpdateCommand για επιτυχημένη αποστολή ιεραρχικών μεταβολών (hierarchical changes) σε συνδεδεμένους πίνακες


Πίνακας

Πεδία Primary Keys

SourceVersion

Father

PK0

Original

Child(1)

PK1

PK0

Current

PK1'

Original

Child(2)

PK2

PK1 = PK0,1'

Current

PK2'

Original

|

|

|

|

Child(n)

PKn

PK(n-1) = PK0,1',...,(n-1)'

Current

PKn'

Original


Σημ.: Όπου PΚi τα πεδία που αποτελούν το PK του πίνακα-i, και PΚi' τα νέα πεδία του PK του πίνακα-i που προστίθενται σε εκείνα που 'κληρονομούνται' από το αμέσως προηγούμενο parent table.


Εντάξει με το SourceVersion των UpdateCommands. Με ποια σειρά, όμως, θα στείλουμε τις αλλαγές στη βάση; Για να απαντήσουμε σ' αυτό το ερώτημα θα χρησιμοποιήσουμε την “εις άτοπον απαγωγή”.

Ας εξετάσουμε τι θα συμβεί αν στείλουμε πρώτα τις προσθήκες. Υπάρχει περίπτωση αποτυχίας; Η απάντηση είναι ΝΑΙ! Αν κάπου στην ιεραρχία, δηλαδή σε κάποιο parent table, αλλάξει η τιμή των πεδίων του PK και στη συνέχεια προστεθούν εγγραφές στο child (φυσικά, με τις νέες τιμές για το PK), οι προσθήκες στο child ΔΕ θα αποθηκευτούν γιατί οι νέες τιμές του PK για το parent table δεν υπάρχουν ακόμα στη βάση.

Ωραία, ας στείλουμε τότε πρώτα τις μεταβολές. ΕΝΣΤΑΣΗ! Κι εδώ μπορεί να υπάρξει αποτυχία. Αν μεταβάλουμε την τιμή των πεδίων του PK στο parent και στη συνέχεια διαγράψουμε εγγραφές στο child, πως θα περάσουν οι διαγραφές στη βάση; Ας θυμηθούμε, το SourceVersion των DeleteCommands είναι original.

Ας ξεμπερδεύουμε, λοιπόν, πρώτα με τις διαγραφές. Έτσι, κι αλλιώς με SourceVersion original δεν πρόκειται ν' αποτύχουμε. Καλά μέχρι εδώ, αλλά με ποια φορά θα ξεκινήσουμε να διαγράφουμε; Μα φυσικά, από το τελευταίο child προς το αρχικό parent. Δηλαδή:

Φορά διαγραφών: Child(n) -> Child(n-1) ->-> Child(1) -> Parent


Ωραία... Πάμε τώρα να κάνουμε τις προσθήκες. Πάλι ένσταση! Ο λόγος αποτυχίας κατά την αποστολή των προσθηκών εξακολουθεί να υφίσταται. Άρα πρέπει να στείλουμε τις μεταβολές πριν τις προσθήκες. Με ποια φορά; Μα φυσικά αντίθετη από εκείνη με την οποία στείλαμε τις διαγραφές. Δηλαδή από το αρχικό parent προς το τελευταίο child:


Φορά μεταβολών: Parent -> Child(1) ->-> Child(n-1) -> Child(n)


Για το τέλος, όπως είναι πια προφανές, μας έμειναν οι προσθήκες οι οποίες κι αυτές θα γίνουν με τη φορά που έγιναν και οι μεταβολές. Δηλαδή:


Φορά προσθηκών: Parent -> Child(1) ->-> Child(n-1) -> Child(n)


Κάπου εδώ τελειώνει η θεωρητική περιγραφή. Για να δούμε, όμως, με ένα παράδειγμα πως υλοποιούνται όλα όσα περιγράφηκαν παραπάνω.



Από τη θεωρία στην πράξη


Έφτασε, λοιπόν, η στιγμή της υλοποίησης ενός παραδείγματος το οποίο θα επιβεβαιώνει όλα όσα αναφέρθηκαν στην προηγούμενη ενότητα και από πρακτικής απόψεως. Για να είναι πειστικό αυτό το παράδειγμα θα πρέπει να περιλαμβάνει τουλάχιστον τρεις πίνακες (father, child, grandchild). Όχι ότι υπάρχει κάποια διαφορά σε σχέση με το συνηθισμένο master – detail, αλλά έτσι για αλλαγή. Επίσης, καλόν είναι και τα δεδομένα να έχουν μια δόση αληθοφάνειας, μιας και οι τεχνητές “υλοποιήσεις” συνήθως είναι βαρετές.

Το παράδειγμα που επέλεξα βασίζεται στην ταξινομική των ειδών με βάση το σύστημα του Λινναίου. Στο σύστημα αυτό όλα τα είδη έχουν ονοματεπώνυμο. Συνοπτικά, οι κύριες ταξινομικές βαθμίδες των ζωντανών οργανισμών είναι Βασίλειο (πχ. Ζώα), Φύλο (πχ. Αρθρόποδα), Κλάση (πχ. Έντομα), Τάξη (πχ. Δίπτερα), Οικογένεια (πχ. Muscidae), Γένος (πχ. Musca) και Είδος (πχ. Musca domesticaοικιακή μύγα).

Ας υποθέσουμε, λοιπόν, ότι θέλουμε να φτιάξουμε μια βάση δεδομένων για φυτικά είδη που μας ενδιαφέρουν και επιθυμούμε να ιεραρχήσουμε τα είδη αυτά βάσει της ταξινομικής τους κατάταξης. Στο παράδειγμά μας θα αδιαφορήσουμε για τις ανώτερες βαθμίδες και θα επικεντρωθούμε στις τρεις τελευταίες (οικογένεια, γένος και είδος). Αμέσως, αμέσως έχουμε τρεις πίνακες με φανερή σχέση father – child – grandchild. Η βάση δεδομένων είναι απλή και τα relationships ξεκάθαρα. Για περισσότερες πληροφορίες ρίξτε μια ματιά στο συνημμένο.

Δυστυχώς, παρόλο που η υλοποίηση φαντάζει straightforward, το Visual Studio διαμαρτυρήθηκε(!?). Όταν επιχείρησα ν' αλλάξω το SourceVersion των παραμέτρων με πρόθεμα @Original_ στα UpdateCommands των πινάκων των γενών και των ειδών από τον Designer, σύμφωνα με τη θεωρητική περιγραφή, δε γινόταν build το project. Για κάποιο ανεξήγητο λόγο ο Wizard δημιουργούσε μεθόδους Update με λιγότερες παραμέτρους από εκείνες που πρέπει και το compilation αποτύγχανε. Προς στιγμή, νόμισα ότι κάτι συμβαίνει με το CommandText και γι' αυτό ζήτησα από το VS να κάνει generate τις αντίστοιχες Stored Procedures. Όπως ήταν λογικό, το πρόβλημα δε διορθώθηκε. Η μαϊμουδιά της διαγραφής των προβληματικών μεθόδων απευθείας στον generated κώδικα (μη φωνάζετε, ώρες – ώρες συμπεριφέρεται σα να το ζητάει ο οργανισμός του), κάνει compile, αλλά πετάει concurrency violation exception κατά την αποθήκευση των αλλαγών. Το γρήγορο fix, δεύτερη μαϊμουδιά, είναι να τεθεί στην ContinueUpdateOnError property του DataAdapter η τιμή true και το project παίζει. Δε ξέρω αν πρόκειται για κάποιο bug του VS2005. Θα ήταν, όμως, ενδιαφέρον εάν κάποιος αναγνώστης μπορεί να αναπαράγει το σφάλμα και να ενημερώσει για την ύπαρξή του. Ακόμη και εκείνοι που χρησιμοποιούν το VS2008 ας ενημερώσουν για το αν εμφανίζεται αντίστοιχο πρόβλημα. Πάντως, δεν υπάρχει λόγος ανησυχίας μιας και όλα μπορούν να υλοποιηθούν με τρόπο ορθόδοξο, χωρίς μαϊμουδιές.

Η λύση είναι απλή. Δεν πειράζουμε καθόλου την τιμή του SourceVersion των παραμέτρων από τον Designer και όλη τη δουλειά την κάνουμε προγραμματιστικά. Απλά, στο Load_Event της φόρμας που εμφανίζει τα δεδομένα, φροντίζουμε να καλέσουμε τις αντίστοιχες μεθόδους για να αλλάξουμε το SourceVersion (βλπ. κώδικα στο συνημμένο). Εκεί που χρησιμοποιώ methods εσείς χρησιμοποιήστε properties. Δεν είναι όμορφο να περνά ένα και μοναδικό enum σαν παράμετρος μιας μεθόδου. Ο κώδικας εμπεριέχει και κάποιο βασικό validation, αλλά όχι σπουδαία πράγματα. Υπάρχει μόνο για να μην crash-άρει η εφαρμογή. Παίξτε με τον κώδικα για να δείτε πως αντιδρά σ' αυτές τις μεταβολές το DataSet κατά την αποθήκευση.



Σχολιασμός


Η υλοποίηση που περιγράφηκε στην προηγούμενη ενότητα δίνει αρκετό υλικό για σχολιασμό, κυρίως όσον αφορά στην ίδια την τεχνολογία του ADO.NET. Ένα θέμα που με απασχολεί είναι το γεγονός ότι όταν γίνονται cascade οι αλλαγές των πεδίων του PK από το parent στο child, αλλάζει το RowVersion των DataRows στο child, ακόμα κι αν δεν υπάρχουν αλλαγές στα δεδομένα (πέραν εκείνων που αφορούν στα πεδία του PK). Αυτό έχει ως αποτέλεσμα να ζητάει το ADO.NET να γίνουν update στη βάση αλλαγές που ουσιαστικά δεν υπάρχουν και που η ίδια η βάση κάνει cascade από μόνη της από το parent στο child. Εύκολα υλοποιήσιμο workaround δε φαίνεται να υπάρχει, καθώς η AcceptChanges() αφορά σ' ολόκληρο το DataRow και όχι σε μεμονωμένα πεδία. Συνεπώς, δε μπορεί να κληθεί αβασάνιστα, επειδή υπάρχει το ενδεχόμενο οι τιμές κάποιων πεδίων των child rows να έχουν αλλάξει από το χρήστη.

Για τις διαγραφές είναι δυνατό να σκεφτεί κανείς ένα workaround. Απλά καλούμε την AcceptChanges() για διαγραφές που αφορούν σε children rows που έγιναν cascade όταν διαγράφηκε η parent row. Απαιτείται, όμως, σωστός σχεδιασμός και προσεγμένη υλοποίηση, μιας και αντιστρέφεται η σειρά αποστολής των διαγραφών στη βάση (από τα parents πια, στα children). Επίσης, ιδιαίτερη μέριμνα πρέπει να ληφθεί για την περίπτωση αποτυχίας της διαγραφής του parent record.

Υπάρχει, όμως, ακόμα ένα ζήτημα. Τελικά, πόσο επικίνδυνο είναι ο χρήστης να έχει πρόσβαση στις τιμές των PKs σ' όλα τα επίπεδα της ιεραρχίας; Η απάντηση είναι ΠΟΛΥ ΕΠΙΚΙΝΔΥΝΟ! Ο κώδικας του παραδείγματος που είναι υπεύθυνος για την αποστολή των μεταβολών στη βάση μπορεί να χαρακτηριστεί ως οδοστρωτήρας. Ακόμα κι αν οι τιμές των πεδίων των PKs είναι προστατευμένες, όπως στα walkthroughs του MSDN, αυτός ο ισοπεδωτικός τρόπος αποστολής των αλλαγών μπορεί να δημιουργήσει προβλήματα, εκτός κι αν αναφερόμαστε σε καθαρά single user εφαρμογές.

Όταν θέλουμε ν' αποθηκεύσουμε αλλαγές σε relational data, μάλλον πρέπει να σκεφτόμαστε με τη φιλοσοφία του object. Αν στο παράδειγμά μας θεωρήσουμε ότι το βασικό object είναι η οικογένεια, τότε για κάθε μία ξεχωριστά θα πρέπει να γίνει επιτυχημένη αποστολή των αλλαγών τόσο στην ίδια όσο και στα children μέσω ενός transaction. Αυτό σημαίνει ότι για κάθε μία οικογένεια θα πρέπει να αποσταλούν με τη σωστή σειρά οι διαγραφές, οι μεταβολές και οι προσθήκες μέσω ενός transaction με scope τη συγκεκριμένη οικογένεια. Έτσι, λοιπόν, ή θα πετύχουν όλες οι μεταβολές ή καμία. Μετά μπορούμε να προχωρήσουμε στην επόμενη οικογένεια κλπ. Σ' αυτή την περίπτωση πρέπει να ληφθεί μέριμνα για την ενημέρωση του DataSet όταν κάποιο(-α) transaction αποτύχει. Το DataSet είναι disconnected και δε γνωρίζει τίποτε για το “φόνο” και πρέπει να ενημερωθεί για το rollback των μεταβολών.

Συγκεκριμένη υλοποίηση κώδικα που θα επιδεικνύει την παραπάνω προσέγγιση δεν έχω προς το παρόν να σας δώσω. Ελπίζω να βρήκατε ενδιαφέροντα τα όσα αναφέρονται στην παρούσα δημοσίευση και θα χαρώ πολύ να διαβάσω τις δικές σας σκέψεις και τοποθετήσεις, μιας και το θέμα των hierarchical updates έχει πολλές ιδιαιτερότητες. Ζητώ προκαταβολικά συγνώμη για τα όποια λάθη και παραλείψεις μου.

 


Καταχώρηση στις κατηγορίες:
Attachment(s): Taxonomy.zip