-
Στο χθεσινό event είχαμε μια ενδιαφέρουσα συζήτηση σχετικά με τα Insert/Update/Delete commands που παράγονται κατά το configuration ενός TableAdapter και απ’ ότι είδα, ενώ τα ORMs μπαίνουν όλο και περισσότερο στην καθημερινότητά μας, υπάρχει ακόμα ανάγκη κατανόηση στον τρόπο που δουλεύουν τα DataSets, οπότε back to basics…
Λοιπόν, όταν κατασκευάζουμε ένα DataSet μπορούμε να ακολουθήσουμε την τεχνική drag / drop του πίνακα από το Server Explorer παράθυρο ή να κάνουμε δεξί κλικ στο designer και να επιλέξουμε το wizard “Add Table Adapter”. Ας πάμε με τον δεύτερο τρόπο. Επιλέγουμε ένα Northwind connection και κατόπιν “Use SQL Statements” και στο query γράφουμε:
SELECT ShipperID, CompanyName, Phone
FROM Shippers
Πατάμε “Advanced Options” και βγάζουμε το check από τα “Use optimistic concurrency” και “Refresh the data table”. Πατάμε Next και Finish κι έχουμε φτιάξει το πρώτο DataTable που ονομάζεται “Shippers”. Ξανακάνουμε την ίδια διαδικασία χωρίς να πειράξουμε το πρώτο DataTable ώστε να φτιάξουμε ένα δεύτερο Shippers (αυτή τη φορά θα ονομαστεί “Shippers1”) χωρίς όμως να βγάλουμε τα check από τα “Use optimistic concurrency” και “Refresh the data table”. Τι έχουμε κάνει λοιπόν… Έχουμε φτιάξει δύο DataTables, το πρώτο έχει “pessimistic concurrency” και το δεύτερο “optimistic concurrency”. Τι σημαίνει αυτό: Το ADO.NET μέσω του DataSet δουλεύει σε disconnected mode. Πράγμα που σημαίνει ότι συνδεόμαστε στη βάση, παίρνουμε τα data, τα επεξεργαζόμαστε και κατόπιν τα ξαναρίχνουμε πίσω. Κατά το στάδιο της επεξεργασίας, τα data δεν έχουν κλειδωθεί στη βάση καθώς εμείς έχουμε αποσυνδεθεί, οπότε υπάρχει το ενδεχόμενο κάποιος άλλος να έχει κάνει το ίδιο πράγμα με εμάς. Έτσι για παράδειγμα υπάρχει η περίπτωση να επιχειρήσω να κάνω update σε data που υπήρχαν μεν όταν τα διάβασα αλλά πλέον έχουν σβηστεί από άλλον χρήστη. Ας δούμε λοιπόν πως χειρίζονται αυτό το θέμα τα δύο concurrency μοντέλα. Με το pessimistic concurrency υποθέτουμε ότι δεν υπάρχουν μεγάλες πιθανότητες κάποιος άλλος να αλλάξει τα data που έχω πάρει εγώ. Όταν λοιπόν επιχειρήσω να κάνω update μέσω του TableAdapter, για κάθε γραμμή από το DataTable που έχω αλλάξει, προσθέσει ή αφαιρέσει, στέλνει αντίστοιχα ένα Update, Insert ή Delete statement. Τα statements αυτά φαίνονται παρακάτω:
UPDATE Shippers
SET CompanyName = @CompanyName, Phone = @Phone
WHERE (ShipperID = @Original_ShipperID)
INSERT INTO [Shippers] ([CompanyName], [Phone])
VALUES (@CompanyName, @Phone)
DELETE FROM [Shippers]
WHERE (([ShipperID] = @Original_ShipperID))
Παρατηρούμε ότι για να πετύχει το update και το delete θα πρέπει απλά να βρεθεί η εγγραφή για την οποία επιχειρώ να κάνω το operation. Δηλαδή
WHERE (([ShipperID] = @Original_ShipperID))
Με το optimistic concurrency υποθέτουμε υπάρχουν μεγάλες πιθανότητες κάποιος άλλος να αλλάξει τα data που έχω πάρει εγώ, οπότε θέλω να είμαι σίγουρος ότι δεν θα υπάρξει μπέρδεμα και γι αυτό πρέπει να βεβαιωθώ ότι πριν κάνω κάτι τα data δεν έχουν πειραχθεί. Έτσι τα statements έχουν ως εξής:
UPDATE [Shippers]
SET [CompanyName] = @CompanyName, [Phone] = @Phone
WHERE (([ShipperID] = @Original_ShipperID) AND ([CompanyName] = @Original_CompanyName) AND ((@IsNull_Phone = 1 AND [Phone] IS NULL) OR ([Phone] = @Original_Phone)));
SELECT ShipperID, CompanyName, Phone FROM Shippers WHERE (ShipperID = @ShipperID)
INSERT INTO [Shippers] ([CompanyName], [Phone])
VALUES (@CompanyName, @Phone);
SELECT ShipperID, CompanyName, Phone
FROM Shippers
WHERE (ShipperID = SCOPE_IDENTITY())
DELETE FROM [Shippers]
WHERE (([ShipperID] = @Original_ShipperID) AND ([CompanyName] = @Original_CompanyName) AND ((@IsNull_Phone = 1 AND [Phone] IS NULL) OR ([Phone] = @Original_Phone)))
Κατ’ αρχήν παρατηρούμε ότι τα statements για Insert και Update είναι διπλά καθώς ακολουθεί ένα SELECT. Αυτό οφείλεται στην ενεργοποίηση της επιλογής “Refresh the data table” και θα δούμε παρακάτω που χρησιμεύει. Ερχόμαστε στο ενδιαφέρον κομμάτι: Για να πετύχει το Update και το Delete δεν αρκεί πλέον να βρεθεί η εγγραφή βάσει του @Original_ShipperID. Πρέπει να διασφαλίσουμε ότι όλα τα πεδία είναι όπως τα είχαμε διαβάσει και γι αυτό ελέγχονται ένα προς ένα. Το πρόβλημα είναι ότι ο έλεγχος πρέπει να λάβει υπόψη το NULL (αν δέχεται κάποιο πεδίο). Αν δεν είχαμε αυτό το θέμα, το WHERE θα ήταν κάπως έτσι:
WHERE (([ShipperID] = @Original_ShipperID) AND ([CompanyName] = @Original_CompanyName) AND ([Phone] = @Original_Phone)))
Όταν όμως έχουμε null, όπως στην περίπτωση του Phone πεδίου, το παραπάνω WHERE δεν δουλεύει. Γι αυτό ο code generator παράγει το παρακάτω:
WHERE (([ShipperID] = @Original_ShipperID) AND ([CompanyName] = @Original_CompanyName) AND ((@IsNull_Phone = 1 AND [Phone] IS NULL) OR ([Phone] = @Original_Phone)))
Ουσιαστικά, η προσθήκη είναι το
(@IsNull_Phone = 1 AND [Phone] IS NULL)
Βλέπουμε ότι το παραπάνω expression γίνεται πάντα evaluate σε true ή false και ποτέ κάποιο τμήμα του δεν γίνεται evaluate σε null όπως υποστήριξε κάποιος στο event ότι συμβαίνει.
Αυτός είναι ο τρόπος που πιάνουμε τα προβλήματα στο concurrency. Για κάθε αποτυχημένο query έχουμε κι από ένα ConcurrencyException οπότε από εκεί και πέρα χρειάζεται να πάρουμε μια απόφαση για το τι θα κάνουμε. Εδώ μας βοηθάει αυτό το SELECT που ακολουθεί καθώς μας επιστρέφει τις τρέχουσες τιμές της εγγραφής κι έτσι μπορούμε να υλοποιήσουμε οποιοδήποτε μοντέλο business logic mitigation.
To θέμα του concurrency είναι πολύ σημαντικό και χρειάζεται εξοικείωση με τις τεχνικές για την αντιμετώπισή του. Τα DataSets δίνουν μια καλή ευκαιρία για να μάθει κανείς τις τεχνικές καθώς τα ίδια concepts υπάρχουν ακόμα κι αν χρησιμοποιεί κανείς κάποιο ORM.
-
Εχθές είχα παρουσίαση στη Microsoft Hellas για το event "The Next Web Now". Θα το θέσω απλά: Ήταν από τις χειρότερες που έχω κάνει ποτέ. Γενικά με κυνηγάει η κατάρα του demo. Κάνω preview τα demos μου αρκετές φορές πριν κάθε παρουσίαση αλλά πάντοτε κάτι πάει στραβά στο τέλος. Στο event "Το .ΝΕΤ Framework από το A ως το Ω" η μέθοδος GetProducts επέστρεφε μυστηριωδώς Suppliers! Ακόμα δεν έχω καταλάβει πως έγινε αυτό, ελπίζω στο retake που θα γίνει την επόμενη εβδομάδα να μην εμφανιστεί το ίδιο πρόβλημα.
Χθες πάντως, πήγε ακόμα πιο στραβά η παρουσίαση. Ειδικά, το demo του Expression Blend είχε θέμα το customization ενός button με χρήση του Expression Design και η συνεργασία του Expression Blend με το Visual Studio 2008. E λοιπόν, είχα τρέξει το σενάριο πολλές φορές σπίτι και όλα μια χαρά. Στο live demo όμως δεν πήγαν. Το σενάριο είχε ως εξής:
- Κάνουμε copy το default template του button σε ένα νέο που θα πειράξουμε
- Από το νέο, κάνουμε cut το ContentPresenter (ώστε να το κρατήσουμε και να το κάνουμε paste αργότερα)
- Διαγράφουμε το Chrome
- Βάζουμε ένα Grid πάνω στο template
- Βάζουμε ένα Ellipse πάνω στο Grid
- Κάνουμε paste το ContentPresenter
και έχουμε ένα basic νέο button.
E, το 6o βήμα δεν έπαιζε με τίποτα... Αργότερα, όταν το δοκίμασα σπίτι για να δω τι πήγε στραβά, ανακάλυψα ότι αυτό που ξεχνούσα ήταν να κάνω διπλό-click στο Grid ώστε να αλλάξω το edit mode από το επίπεδο του template στο επίπεδο του Grid. Αυτό το απλό πραγματάκι αρκούσε για να καταστρέψει την παρουσίαση...
Μιας και το πρώτο μέρος της παρουσίασης καταγράφηκε σε video, θα κάνω ένα cast από το session του Expression Blend και άλλο ένα από το session του Expression Web (μιας και αυτό το demo δεν πήγε όπως θα ήθελα) ώστε να τα δείτε σωστά.
-
To ωραίο με την TechEd είναι ότι κάθε μέρα, όλο και θα παρακολουθήσεις κάποιο session που θα σε πωρώσει και θα σου ανοίξει την όρεξη για να αρχίσεις το ψάξιμο. Αυτό συνέβη και σήμερα. Όχι με το πρώτο session ("Building Location-Aware Applications in SQL Server 2008: Introducing the Spatial Data Type") παρότι ήταν πολύ διαφωτιστικό ως προς τη νέα δυνατότητα που θα έχουμε στον SQL Server 2008 να αποθηκεύουμε και να χειριζόμαστε γεωγραφικά δεδομένα. Ιδιαίτερα μου άρεσε ο χειρισμός (ο οποίος γίνεται μέσω ένος managed DLL που μπορεί κανείς να ενσωματώσει στις εφαρμογές του) όπου μπορεί κανείς εύκολα να πει "φέρε μου όλους τους δρόμους που τέμνουν το τετράγωνο οδός Α, οδός Β, οδός Γ και οδός Δ.
Στο επόμενο session ("Microsoft SQL Server 2005 Compact Edition in Action") είδαμε θέματα που σχετίζονται με το τον SQL Server 2005 CE όπως ενσωμάτωση στην εφαρμογή, διαχείριση και deployment. Φαίνεται ότι ο SQL Server 2005 CE μπορεί να αντικαταστείσει το Jet Engine ως local datastore και μάλιστα αν συνδυαστεί με Sync Services να αποτελέσει μια πολύ καλή λύση.
Άλλο ένα session που είχε μεγάλο ενδιαφέρον, ήταν το "Entity Framework: Application Patterns" μέσα από το οποίο είδαμε τα σενάρια χρήσης του Entity Framework, δηλαδή πως μπορεί να χρησιμοποιηθεί για λύσεις client-server, για εφαρμογές web αλλά και πως μπορεί να συνδυαστεί με το WCF.
Κανένα από τα παραπάνω sessions όμως δεν με πώρωσε όσο το τελευταίο "Developing More Intelligent Applications using Data Mining". Ο Rafal Lukawiecki, παρότι έχει πολύ περίεργη άρθρωση λόγου, είναι απίθανος παρουσιαστής και το θέμα του, δεν ήταν το security (στο οποίο ειδικεύεται ) αλλά το πώς με χρήση data mining μπορεί μια εφαρμογή να ανακαλύψει patterns χρήσης και να προσαρμοστεί ανάλογα. Τα παραδείγματα πολλά. Μπορεί για παράδειγμα να καταλάβει τι προσπαθεί να κάνει ο χρήστης και να προβλέψει την επόμενη ενέργειά του (βλ. wizards, favorites, κλπ), να καταλάβει ότι δέχεται επίθεση από κακόβουλο χρήστη, να καταλάβει ότι μια web παραγγελία θα αποτύχει, να φτιαχτεί ο uber error handling μηχανισμός χωρίς ατελείωτα Try...Catch. Όλα τα παραπάνω, με τον παραδοσιακό τρόπο προγραμματισμού, απαιτούν να έχει φανταστεί ο προγραμματιστής εκ των προτέρων όλα τα πιθανά σενάρια και να έχει ενσωματώσει την ανάλογη λογική στην εφαρμογή, πράγμα που είναι εξαιρετικά δύσκολο. Στο παράδειγμα που μας έδειξε, μια εφαρμογή έκανε data entry validation μόνο με ένα IF ενώ το ωραίο ήταν ότι τα business rules τα ανακάλυπτε μόνης! Περισσότερα για το πως δουλεύει κάτι τέτοιο είναι πολύ δύσκολο να σας τα εξηγήσω από τον περιορισμένο χώρο εδώ, ωστόσο αυτό το session θα μπορούσε άνετα να παρουσιαστεί σε ένα DevDays ή σε ένα community event. Πραγματικά, είναι το μέλον τόσο στο "user experience", όσο και στο πως μπορούν να υλοποιηθούν έξυπνες self adjusting εφαρμογές.
-
Μου τρέχουν τα σάλια... Σήμερα ήταν η μέρα του Sync Framework. Με δύο sessions ("Introduction to Microsoft Sync Framework – Synchronization Framework for Enabling Roaming, Offline, and Collaboration Across Devices, Services & Apps" και "Optimizing Online, Enabling Offline with SQL Server Compact and Sync Services for ADO.NET") έγινε εμφανές πόσο σπουδαίο είναι αυτό το πραγματάκι που εμφανίζεται γα πρώτη φορά. Πρόκειται για υπηρεσίες συχρονισμού μεταξύ οποιουδήποτε τύπου δεδομένων. Αρκεί να υπάρχει ο αντίστοιχος provider. Στο πρώτο demo είδαμε να συγχρονίζονται custom contacts με Vista contacts με Outlook contacts. Είδαμε συγχρονισμό αρχείων (τσεκάρετε το Sync Toy 2.0) και συγχρονισμό database data. Τα ωραία είναι πολλά: Το framework είναι υπεύθυνο για όλη τη διαχείριση του synchronization (πχ δεν ανησυχείς τι θα γίνει αν δεν ολοκληρωθεί σωστά). Έχει full support για conflict resolution. Μπορεί να ενσωματωθεί στις εφαρμογές ξεκινώντας από basic υλοποίηση και σταδιακά να προστεθούν περίπλοκα features. Το ίδιο μπορεί να γίνει για να περάσει η υλοποίηση σε n-tier μοντέλο. Έχει visual desginer για το VS 2008. Ο οποίος μάλιστα συνεργάζεται με τα WCF projects. Τα κακά είναι λίγα: Προς το παρόν, σε επίπεδο βάσης, ως client υποστηρίζεται μόνο ο SQL Server Compact. Αν αυτό δεν μας ικανοποιεί, περιμένουμε μέχρι να βγει ο SQL Server 2008 που θα έχει build-in υποστήριξη για changes tracking (μέχρι τώρα απαιτούνται μικρές τροποποιήσεις στο schema της βάσης) σε όλες τις εκδόσεις και ως sync client και ως sync server.
Κατά τ'άλλα παρακολουθήσαμε τη συνέχεια του "Building a Complete Web Application Using ASP.NET "Orcas" and Microsoft Visual Studio 2008" όπου είδαμε Intellisence σε Javascript, την υποστήριξη AJAX καθώς και τον νέο IIS 7 και πως αυτός συνεργάζεται με το ASP.NET.
Επίσης, στο session "Microsoft Visual Studio Team Foundation Server (Part 1 of 2): Applying Work Item Tracking and Version Control to Application Lifecycle Management" είδαμε το TFS τόσο από τη σκοπιά του developer καθώς και τη σκοπιά του lead developer.
Τέλος, στο session "Entity Framework Introduction" είδαμε σε δράση το Entity Framework, τον designer και πως μερικές γραμμές LINQ to Entities κώδικα μετατρέπονται - μαγικά - σε ένα σεντόνι-query.
-
Η πρώτη μέρα ήταν αρκετά δύσκολη καθώς σε λίγο κλείνω 24 ώρες ξύπνιος αφού πήγαμε κατευθείαν από το αεροδρόμιο στο συνεδριακό κέντρο.
Ξεκινήσαμε με το keynote από τον S. Somasegar. Ο άνθρωπος ως Vise President του Developer Division, ίσως να είναι μεγάλο κεφάλι. Ως παρουσιαστής όμως... μας προκάλεσε πονοκέφαλο με την δραματικά κακή προφορά των αγγλικών του. Κατά τ'άλλα, το keynote ήταν αρκετά ανάλαφρο με πιο ενδιαφέρον σημείο, όταν ανακοίνωσαν ότι θα ανοίξει ο κώδικας του IDE, όπου το ανάλογο demo ήταν το πως μπορεί κανείς να χρησιμοποιήσει το VS για να γράψει ...extensions για το World Of Warcraft! Άντε κι έχω αφήσει τον μάγο μου στο level 42 εδώ και 2 χρόνια...
Το επόμενο session που παρακολούθησα, "Building a Complete Web Application Using ASP.NET "Orcas" and Microsoft Visual Studio 2008, part 1" ήταν εξαιρετικά ενδιαφέρον. Είδαμε το νέο designer του Visual Studio (αυτόν του Expression Web) με την πλήρη υποστήριξη CSS, είδαμε (άλλη μια φορά LINQ) και είδαμε nested master pages να γίνονται preview χωρίς κανένα πρόβλημα.
To κλου της ημέρας ήταν το session "Agile Development with Team System" όπου ο Roy Osherove ανέπτυξε από το τι είναι Agile Development και ποιές είναι οι μεθοδολογίες Scrum και Extreme Programming, μέχρι πως μπορεί να χρησιμοποιηθεί ο Team System με το MSF Agile και το Scrum for Team System. Φυσικά, ο Roy πιστός στη συνήθειά του έκλεισε με κιθάρα διασκευάζοντας "Every class you make, every build you break, every mock you fake, I'll be watching you" κατά το Every breath you take - Police. Ρόλο κλειδί σε αυτό το session έπαιξε εξέχον μέλος του dotNETZone.gr που ανέλαβε να αλλάζει τα PowerPoint slides την ώρα του τραγουδιού. Video taken: Priceless. Όσοι έρθουν στο επόμενο community event θα έχουν η μοναδική ευκαιρία να το δουν 
-
Τον τελευταίο καιρό έχω παρατηρήσει ότι υπάρχει μια διάχυτη αίσθηση ότι η VB.NET αρχίζει να εγκαταλείπεται και πλέον οτιδήποτε "σοβαρό" γίνεται σε C# (μιλώντας πάντοτε για τον managed κόσμο). Εξάλλου, κι εμείς οι VB.NET MVPs "γκρινιάζουμε" για την έλλειψη samples σε VB.NET για διάφορα νέα SDKs και τις edge τεχολογίες που εμφανίζονται. Πρόσφατα, το VB product group μας έδωσε κάποια στοιχεία σχετικά με το momentum της VB.NET:
- Visual Basic is the #1 .NET language (as reported by Forrester Research)
- Visual Basic is most downloaded and registered Express Edition
- Visual Basic Express is ranked #1
- Visual C++ Express is ranked #2, by a difference of 20% from #1
(όχι δεν είναι τυπογραφικό :))
- Visual Basic has the most trafficked MSDN language dev center and blog
- VB Team blog is in the top 1% in readership of all MS bloggers
Και όλα αυτά πριν εμφανιστούν τα νέα dynamic χαρακτηριστικά...
-
Ένα από τα νέα χαρακτηριστικά της VB.NET είναι η υποστήριξη XML Literals. Πρόκειται για τη δυνατότητα της ενσωματώσης XML μέσα στον κώδικα. Για παράδειγμα, μπορεί να πει κάποιος
Dim countriesWithCapital As XElement = _
<Countries>
<Country Name='SomeCountry' Density='123456'>
<Capital>
<Name>City Name Here</Name>
<Longitude>123.45</Longitude>
<Latitude>123.45</Latitude>
</Capital>
</Country>
</Countries>
Αυτό δεν είναι και τόσο εντυπωσιακό βέβαια. Λίγο πιο εντυπωσιακό είναι ότι μπορεί κανείς να εισάγει expressions a la ASP χρησιμοποιώντας το <%= %> συντακτικό, πχ
Dim countriesWithCapital As XElement = _
<Countries>
<Country Name='<%=CountryName%> Density=<%=Density%>>
<Capital>
<Name><%=Name%></Name>
<Longitude><%=Longitude%></Longitude>
<Latitude><%=Latitude%></Latitude>
</Capital>
</Country>
</Countries> Αυτό από μόνο του και πάλι δεν είναι τρομερά εντυπωσιακό, ωστόσο αν συνδυαστεί με ένα LINQ query τότε, έχουμε κάτι πολύ εντυπωσιακό:
Dim countriesWithCapital As XElement = _
<Countries>
<%= From country In Countries, city In Capitals _
Where country.Name = city.Country _
Select <Country Name=<%= country.Name %>
Density=<%= country.Population / country.Area %>>
<Capital>
<Name><%= city.Name %></Name>
<Longitude><%= city.Longitude %></Longitude>
<Latitude><%= city.Latitude %></Latitude>
</Capital>
</Country> _
%>
</Countries>
Έχουμε ξαναπεί ότι το παραπάνω κάνει τη VB.NET dynamic language, όπερ σημαίνει ότι πλέον μπορούμε να κάνουμε ένα σωρό πολύ εντυπωσιακά κόλπα όπως φαίνεται σε αυτό το απίθανο post της Beth Massi: http://blogs.msdn.com/bethmassi/archive/2007/10/23/avoid-underscores-in-your-multiline-strings.aspx
Θεωρώ ότι τα XML Literals δίνουν ένα πολύ δυνατό code deneration εργαλείο. ΟΚ, υπάρχουν λύσεις όπως το CodeSmith ωστόσο μόνο το γεγονός ότι σε όλα τα παραπάνω έχουμε Intellisence αφήνει τα υπόλοιπα πολύ πίσω. Επιπρόσθετα, εφόσον πρόκειται για κώδικα .NET τότε μπορούμε και μιλάμε για τη δυνατότητα inheritance μέσα στο code pattern, πράγμα πολύ σημαντικό για περίπλοκα code gen σενάρια.
-
Να και μία ανακοίνωση που έχει βγει από τις 10 Ιουλίου αλλά πρόσεξα τώρα στο PressPass. Η Microsoft πρόκειται να κυκλοφορίσει μια σειρά προϊόντων υπό την ονομασία Software Licensing and Protection Services (SLP Services) που θα έχουν ως σκοπό να βοηθήσουν τους developers και τους ISVs να προστατέψουν τις εφαρμογές τους, να κάνουν track τις άδειες χρήσης και τις εκδόσεις της κάθε εφαρμογής και τέλος να ενσωματώσουν όλα τα παραπάνω στα backend συστήματά τους.
Όλα ξεκινούν από την εξαγορά από τη Microsoft της Secured Dimensions, εταιρείας που εξειδικεύεται στη προστασία .ΝΕΤ κώδικα. Έτσι λοιπόν, αναμένεται η κυκλοφορία:
- Του Code Protector SDK, ενός toolkit που θα διατίθεται δωρεάν από το Microsoft Download Center και το οποίο θα επιτρέπει την προστασία του κώδικα και τη δημιουργία "licensable entities" που θα ελέγχονται με άδειες χρήσης. Σύμφωνα με την ανακοίνωση, μια έκδοση του παραπάνω θα παρέχεται και με το Visual Studio 2008.
- Του Software Licensing and Protection Server έτσι ώστε οι ISVs να μπορούν να διαχειρίζονται άδειες (machine-based, time-based, feature based subscriptions, trials, κλπ).
- Του SLP Online Service. Όπως τα παραπάνω αλλά ως service από τη Microsoft. Μάλιστα, από το MSDN shipment του Οκτωβρίου, τα Premium subscriptions θα δίνουν από μία δωρεάν συνδρομή στην basic έκδοση του SLP Online Service.
Όλα τα παραπάνω είναι καλά νέα για όσους θέλουν να δώσουν εφαρμογές για μαζική χρήση και ψάχνουν για κάποια λύση licensing. Μένει να δούμε κόστη και να συγκρίνουμε αυτή τη λύση σε σχέση με τις άλλες που κυκλοφορούν.
-
Ο ερχομός του SQL Server 2008 συνεπάγεται το τέλος της υποστήριξης του SQL server 2000. To Mainstream Support (που ουσιαστικά είναι τα Security Updates και τα Non Security Hotfixes) θα λήξει στις 4 Αυγούστου 2008 για τις εκδόσεις SQL Server 2000 64-bit Edition, SQL Server 2000 Developer, SQL Server 2000 Enterprise, SQL Server 2000 Standard, SQL Server 2000 CE, και SQL Server 2000 Workgroup Edition. Το Extended Support θα συνεχιστεί ως το 2013 ωστόσο ο SQL Server 2000 θα πάψει να πωλείται από τους περισσότερους vendors από τον Δεκέμβριο του 2007. Εξάλλου, καμία έκδοση του SQL Server 2000 δεν υποστηρίζεται στα Windows Vista.
Ήδη τις προάλλες μου έλεγε κάποιος γνωστός ότι αισθάνεται ότι ο SQL Server 2008 βγαίνει πολύ νωρίς και ότι ακόμη δεν έχει περάσει σε SQL Server 2005 καθώς οι εφαρμογές του δουλεύουν μια χαρά με 2000, δεν "απαιτούν" κανένα feature του 2005 και δεν υπάρχει λόγος να περάσει από τη διαδικασία του upgrade.
Δεν έχει μεγάλο άδικο... Ουσιαστικά αυτοί που θα τραβήξουν τις υπάρχουσες εγκαταστάσεις είναι οι vendors που θα παρουσιάσουν προϊόντα που απαιτούν 2005. Διαφορετικά, θα πρέπει το προϊόν να έχει τουλάχιστον ένα killer feature που θα μπορεί να χρησιμοποιηθεί χωρίς να χρειάζεται να αλλάξουν οι εφαρμογές. Τέτοια features είναι αυτά που συνήθως ανήκουν στην κατηγορία του administration (ή όπως λέει η Microsoft, στο key-area "Mission-critical platform"). Όχι ότι ο SQL Server 2005 δεν έχει τέτοια features. Απλά δεν είναι killer για την πλειοψηφία των εγκαταστάσεων που απλά τρέχουν ένα ERP ή CRM.
-
Στο Visual Basic Team Blog έχει ξεκινήσει μια νέα σειρά από posts με τίτλο "LINQ Cookbook". Αποτελούνται από "recipes", η κάθε μία εκ τω οποίων δείχνει το τι μπορεί να κάνει κανείς με το LINQ. Μέχρι στιγμής έχουμε φτάσει στην 5η συνταγή και μάλιστα οι συγγραφείς είναι ανοιχτοί για παραγγελίες.
Enjoy!
-
Βρήκα σε αυτή (http://blogs.msdn.com/jfoscoding/attachment/765135.ashx) τη διεύθυσνη ένα πολύ ενδιαφέρον Word doc το οποίο παρουσιάζει το WPF και απευθύνεται σε WinForm developers. Αρκετό υλικό και μπόλικα links στο πνεύμα του "στα Windows Forms έχουμε αυτό, στο WPF πως γίνεται;"
Enjoy!
-
Πέτυχα αυτήν την ερώτηση στο MSDN "Jasper" forum Is Jasper useable from c#?
H μικρή απάντηση είναι
The short answer is – We designed Jasper specifically for CLR languages with late-bound facilities such as VB and IronPython. C# doesn’t currently support late-bound calls and hence the answer would be no though some aspects of Jasper may still be applicable.
Για περισσότερα δείτε στο forum.
Δεν το αναφέρω για να κομπάσω ως VB.NET fan (και σαφώς δεν θέλω να ξεκινήσω άλλο ένα VB vs C# debate), αλλά με κάνει να σκέφτομαι ότι η επερχόμενη έκδοση της VB.NET έχει την προοπτική να αποτελέσει ένα εργαλείο που θα προσφέρει το καλύτερο από τους δύο κόσμους. Να έχει χαρακτηριστικά από τον κόσμο της SmallTalk, ECMA Script, Lisp, Ruby, Perl, Python κλπ αλλά και αυτό που είχαμε αναφέρει παλιότερα "Static Typing Where Possible, Dynamic Typing When Needed" (βλ ref)
Δηλαδή, να περιμένω τη μέρα που εμείς οι VB developers θα δούμε τη VB.NET ως cross platform, cross browser, server side και client side γλώσσα?! 
-
σου έχω keyboard shortcuts!
Στο MVP Summit, η ομάδα της VB μοίρασε δωράκια και ένα από αυτά ήταν ένα ωραίο poster με όλα τα keyboard shortcuts. Το δικό μου ακόμα δεν το έχω αναρτήσει στον τοίχο γιατί, όπως και να το κάνουμε, ένα τέτοιο δώρο έχει πολύ αρνητικό SAF (spouce acceptance factor). Ωστόσο του έχω ρίξει μια ματιά και έχω βρει μερικά "σωτήρια" keyboard shortcuts όπως το CTRL+TAB και το F7/Shift-F7.
Πλέον μπορούν όλοι να προμηθευτούν αυτά τα posters σε ηλεκτρονική μορφή από το Microsoft Download Center. Υπάρχουν τρία, ένα για Visual Basic 2005, ένα για Visual C# 2005 και ένα για Visual C++ 2005.
Enjoy!
-
Ο τίτλος δεν έχει σχέση με τον Ολυμπιακό και τον Παναθηναϊκό. Το κόκκινο και το πράσινο χρησιμοποιούνται για να χαρακτηρίσουν τον ρόλο που παίζει κάθε assembly στο οικοδόμημα του .NET Framework.
Κάνοντας μια μικρή αναδρομή, ξεκινήσαμε από το .ΝΕΤ Framework 1.0, μετά ήρθε το .ΝΕΤ Framework 1.1 και κατόπιν το .ΝΕΤ Framework 2.0. Το 1.1 αντικατέστησε το 1.0 ενώ το 2.0 μπορεί να τρέχει παράλληλα με το 1.1. Μετέπειτα, με τα Windows Vista, ήρθε το 3.0 που ουσιαστικά είναι επιπρόσθετα assemblies τα οποία συμπληρώνουν αυτά του 2.0, καθώς τo 3.0 χρειάζεται το 2.0 και δεν είναι "αυτόνομο". Σε λίγο (ή σε πολύ... ποιός να ξέρει...), όταν το Visual Studio "Orcas" βγει σε παραγωγή, θα έρθει το .ΝΕΤ Framework 3.5 το οποίο όμως θα κάθεται πάνω στο 3.0.
Και εδώ είναι που προκύπτει ένα πρόβλημα... Καθώς το 3.5 θα βασίζεται πάνω στο 3.0 είναι αναγκαίο να υπάρχει ο μέγιστος βαθμός συμβατότητας ώστε οι εφαρμογές που φτιάχτηκαν πάνω στο 3.0 να μην αντιμετωπίσουν προβλήματα όταν τρέξουν στο 3.5. Αυτό είναι πολύ καλό γιατί θα γλυτώσει πολύ κόσμο και πολλές εταιρείες από διάφορα προβλήματα support. Όμως συνεπάγεται και κάποιες δυσάρεστες συνέπειες. Δεν μπορούμε να περιμένουμε Service Packs μέχρι να κυκλοφορήσει το "Orcas" καθώς η ομάδα του "Orcas" δουλεύει πάνω σε συγκεκριμένη έκδοση .ΝΕΤ Framework, την 3.0 των Windows Vista.
Η ομάδα του Soma Somasegar έχει καθορίσει ένα μοντέλο που το έχει ονομάσει "Red and Green". Σύμφωνα με αυτό, ότι assembly έχει γίνει ήδη release (WPF, WCF, WF και .NET Framework 2.0) χαρακτηρίζεται ως "Red" ενώ όλες οι νέες προσθήκες από assemblies ως "Green". Στόχος τους είναι να πειράζουν όσο το δυνατόν λιγότερο τα Red assemblies και τα κύρια νέα χαρακτηριστικά να εισαχθούν ως "Green" assemblies ώστε να εξασφαλισθεί η συμβατότητα των γραμμένων εφαρμογών. Ο χρόνος θα δείξει αν θα καταφέρουν να το πετύχουν. Εσείς στο μεταξύ, μπορείτε πλέον να αντιληφθείτε πότε και αν θα διορθωθεί εκείνο το bug που υποβάλατε στο http://support.microsoft.com/ και σας έχει βγάλει την πίστη...
Για περισσότερα περί του "Red and Green", ρίξτε μια ματιά σε αυτό εδώ: http://blogs.msdn.com/somasegar/archive/2006/05/18/601354.aspx
-
Πρόσφατα, χρειάστηκε να υλοποιήσω μια φόρμα που σίγουρα θα την έχετε συναντήσει αρκετές φορές. Η φόρμα έχει δύο listbox. Ο χρήστης επιλέγει items από την αριστερή και τα ρίχνει στην δεξιά μέσω drag'n'drop ή μέσω buttons που υπάρχουν ανάμεσα στα δύο listbox.
Στην δική μου περίπτωση, ουσιαστικά πρόκειται για τα ίδια data και απλώς τα items που εμφανίζονται στην δεξιά φόρμα είναι το υποσύνολο όλων αυτών που υπάρχουν στην αριστερή, με όλη όλη διαφορά ένα flag-πεδίο που γίνεται set.
Σκέφτηκα λοιπόν, ότι η λύση είναι απλή. Θα βάλω δύο BindingSource components πάνω στο ίδιο DataSource με τη διαφορά ότι το δεύτερο BindingSource θα έχει το κατάλληλο φίλτρο στο Filter property ώστε να παίρνω μόνο τα "selected" data. Κατόπιν, το μόνο που χρειάζεται είναι να κάνω bind το κάθε ListBox στο ανάλογο BindingSource.
Σωστά;
Not so fast…
Όλα τα παραπάνω είναι ωραία και καλά όμως όταν τα data βρίσκονται σε DataTable. Όταν βρίσκονται σε BindingList όπως στην περίπτωσή μου, τότε υπάρχει ένα σοβαρό πρόβλημα…
Κάθε φορά που ορίζουμε ένα BindingSource πάνω σε ένα DataTable, ουσιαστικά το BindingSource παίζει πάνω στο DefaultView property του DataTable. To DefaultView property είναι τύπου DataView και το DataView είναι αυτό που ευθύνεται για το DataBinding και κάνει δύο δουλειές που μας ενδιαφέρουν: Ταξινομεί και φιλτράρει τα data του DataTable. Μπορούμε να ορίσουμε πολλαπλά DataViews πάνω σε ένα DataTable:
Dim newView As New DataView(Me.NorthwindDataSet.Products)
και να κάνουμε bind οποιοδήποτε control πάνω σε αυτό το νέο DataView.
Το πρόβλημα λοιπόν που λέγαμε είναι ότι το DataView είναι φτιαγμένο μόνο για DataTables. Δεν μπορούμε να ορίσουμε DataView για ένα BindingList! Αυτό το πρόβλημα με απασχολούσε εδώ και καιρό ωστόσο δεν το αποφάσιζα να υλοποιήσω το δικό μου DataView γιατί απαιτείται αρκετή δουλειά και χρόνο για ένα τέτοιο εγχείρημα. Θα πρέπει να υλοποιηθούν μια σειρά από interfaces με κυριότερο το IBindingList.
Έτσι λοιπόν, όταν δούλευα στην φόρμα που σας περιέγραψα, πήρα απόφαση να το φτιάξω το ρημάδι το Custom-View. Ψάχνοντας όμως για documentation, προς μεγάλη μου έκπληξη, βρήκα έναν καλό άνθρωπο, ονόματι Jesse Johnston που το έχει υλοποιήσει! Το έχει ονομάσει ObjectListView και μπορείτε να το βρείτε εδώ: http://www.teamjohnston.net/Downloads.aspx. Επιπρόσθετα, στο blog του έχει όλες τις λεπτομέρειες για την κατασκευή του.
Το χάρηκα πολύ γιατί με αυτό το component είναι εφικτό το advanced data binding σε object collections, η μη-αλφαριθμητική ταξινόμησή των objects όπως επίσης και η δυνατότητα πολύπλοκου filtering καθώς υποστηρίζει filter predicates.
Enjoy!
-
Ένα χαρακτηριστικό της VB 9 είναι ο νέος τρόπος που μπορεί κανείς να κάνει initialize objects. Παλιότερα, για να φτιάξουμε ένα object θα έπρεπε να πούμε κάτι σαν το παρακάτω:
Dim emp As New Employee()
With emp
.Name = "John Smith"
.DepartmentID = 123
.Salary = 1500
End With
Τώρα πλέον μπορούμε (απλούστερα) να πούμε:
Dim emp2 As Employee = New Employee With {.Name = "Ann", .DepartmentID = 123, .Salary = 1500}
Σε περίπτωση που δεν συμπεριλάβουμε όλα τα properties στα οποία θέλουμε να δώσουμε τιμές, τότε όσα παραλείψουμε θα αποκτήσουν τη default τιμή. Μπορούμε ακόμα να πούμε και:
Dim emp2 As Employee = New Employee With {}
Για να μπορούμε να χρησιμοποιήσουμε το παραπάνω συντακτικό, θα πρέπει η κλάση να έχει default constructor. Μάλιστα, πάντοτε χρησιμοποιείται αυτός, ακόμα κι αν υπάρχει parameterized constructor που "ταιριάζει" περισσότερο με τον object initializer που χρησιμοποιούμε.
Πακέτο με τον νέο initalizer πηγαίνει κι άλλο ένα χαρακτηριστικό που ονομάζεται Type Inference. Μπορούμε να παραλείψουμε το As clause και ο compiler θα καταλάβει τον τύπο της μεταβλητής βάσει του ορίσματος.
Dim intSomething = 1
Dim strSomething = "John Smith"
Τα παραπάνω μπορεί να φέρνουν στο νου άσχημες αναμνήσεις από VB 6 και late binding ωστόσο τώρα έχουμε strong types και όχι generic objects. Αυτό θα το διαπιστώσετε καθώς για το intSomething και strSomething θα έχετε κανονικό IntelliSense! Παράλληλα, το Option Infer Off μπορεί να χρησιμοποιηθεί για να ακυρώσουμε αυτήν την συμπεριφορά.
Έτσι, το προηγούμενο παράδειγμα μπορεί να ξαναγραφτεί ως εξής:
Dim emp3 = New Employee With {.Name = "Ann", .DepartmentID = 123, .Salary = 1500}
Ενώ μπορούμε να φτιάξουμε εύκολα ένα array:
Dim emps() = { _
New Employee With {.Name = "Ann", .DepartmentID = 123, .Salary = 1500}, _
New Employee With {.Name = "John", .DepartmentID = 125, .Salary = 1300}, _
New Employee With {.Name = "Paul", .DepartmentID = 128, .Salary = 1600}}
Τέλος, υπάρχουν και τα anonymous types:
Dim emp4 = New With {.Name = "Rick", .DepartmentID = 130, .Salary = 1280}
Όταν ο compiler συναντήσει κάτι τέτοιο, τότε δημιουργεί ένα νέο object με τύπο που καθορίζεται on the fly και έχει ως properties αυτά που έχουμε καθορίσει στον initializer, δεν χρειάζεται δηλαδή να καθορίσουμε class definition.
Δοκιμάστε το παρακάτω πείραμα:
Dim emp1 = New With {.Name = "Ann", .DepartmentID = 123, .Salary = 1500}
Dim emp2 = New With {.Name = "John", .DepartmentID = 125, .Salary = 1300}
Dim emp3 = New With {.Name = "Paul", .DepartmentID = 128, .Salary = 1600, .HireDate = #1/1/2001#}
Dim emp4 = New With {.Name = "Paul", .DepartmentID = 128, .HireDate = #1/1/2001#, .Salary = 1600}
Console.WriteLine(emp1.GetType)
Console.WriteLine(emp2.GetType)
Console.WriteLine(emp3.GetType)
Console.WriteLine(emp4.Equals(emp3))
Tα Anonymous Types χρησιμοποιούται περισσότερο ως temporary objects. Δεν έχουν methods και δεν μπορεί να χρησιμοποιηθεί το type definion που έχουν σε delegates κλπ.
-
Όλοι γνωρίζετε για το SETI@home, το distributed computing project ανάλυσης δεδομένων για εξωγήινη νοημοσύνη. Το project αποτελείται από 1 εκατομμύριο internet connected "εθελοντές" υπολογιστές που αναλύουν ένα μικρό δείγμα δεδομένων ο καθένας, από αυτά που συλλέγουν τα ραδιοτηλεσκόπια του Arecibo.
Αν και προς το παρόν δεν έχει βρεθεί κανένας εξωγήινος πολιτισμός, τουλάχιστον βρέθηκε με τη βοήθειά του ένα κλεμμένο laptop!
Ένας εθελοντής, ο James Melin, έτρεχε το SETI@home στους 7 οικιακούς υπολογιστές του, ένας εκ των οποίων ήταν το laptop της γυναίκας του που κλάπηκε. Ο Melin, έλεγξε τη database του SETI και ανακάλυψε ότι αυτός που έκλεψε το laptop είχε στείλει data στο SETI. Κατέγραψε την IP, την έστειλε στην αστυνομία και μετά από μερικές μέρες είχε το laptop πίσω!
Απίθανο ε!
Περισσότερα εδώ.
-
Πολλές φορές χρειάζεται να σπάσουμε την κανονικοποίηση ενός πίνακα και να συμπεριλάβουμε computed columns. H θεωρία λέει ότι η τιμή κάθε computed column υπολογίζεται on-the-fly κάθε φορά που το χρησιμοποιεί κάποιο query και στη βάση δεν αποθηκεύεται τίποτα, έχουμε δηλαδή ένα virtual πεδίο. Όταν όμως η φόρμουλα του computed column είναι περίπλοκη (πχ μπορεί να περιέχει CASE) τότε ενδεχομένως να θέλαμε να αφαιρέσουμε λίγο φορτίο από τη CPU σε βάρος λίγου (ή πολύ) χώρου στο δίσκο ο οποίος θα μπορούσε να φιλοξενεί τις υπολογισμένες τιμές του πεδίου.
Mια τεχνική που χρησιμοποιούσαμε μέχρι σήμερα για να κάνουμε persist τις computed τιμές, ήταν να βάζουμε index πάνω στο computed πεδίο ώστε να έχουμε έτοιμες τις τιμές που θέλουμε να διαβάσουμε. Το πρόβλημα είναι ότι η συντήρηση του index έχει overhead γιατί το index δεν είναι και τόσο κατάλληλο γι αυτή τη δουλειά. Ένα UPDATE στα πεδία που απαρτίζουν το computed column ενδέχεται να έχει ως αποτέλεσμα πολύ περισσότερα write ops απ' όσα χρειάζονται για ένα αντίστοιχο απλό update ενός τυπικού πεδίου (όταν αυτό δεν έχει index), καθώς ενδεχομένως να χρειαστεί να γίνει και restructure του index.
Στον SQL Server 2005 υπάρχει η δυνατότητα να δηλώσουμε ένα computed column ως persistent πράγμα που σημαίνει ότι ο server θα διαχειρίζεται διαφανώς τις τιμές του computed πεδίου. Όταν τις διαβάζουμε, θα τις διαβάζουμε από τον δίσκο και όταν αλλάζουν τα πεδία που απαρτίζουν το computed column (στα Inserts και Updates) θα τo ενημερώνει αυτόματα. Ας δούμε ένα παράδειγμα:
Αρχικά, θα φτιάξουμε δύο πίνακες, έναν με persistent και ένα με μη-persistent computed column.
CREATE TABLE dbo.Product (
ProductID INT,
ProductName NVARCHAR(50),
SellStartDate DATETIME NOT NULL,
SellEndDate DATETIME NOT NULL,
SellDuration AS DATEDIFF(dd, SellStartDate, SellEndDate) )
GO
CREATE TABLE dbo.ProductPCC (
ProductID INT,
ProductName NVARCHAR(50),
SellStartDate DATETIME NOT NULL,
SellEndDate DATETIME NOT NULL,
SellDuration AS DATEDIFF(dd, SellStartDate, SellEndDate) PERSISTED )
GO
Το computed column είναι πολύ απλό, ίσα-ίσα για το proof-of-point. H μόνη διαφορά στους δύο πίνακες είναι το PERSISTED keyword στο πεδίο SellDuration. Κατόπιν θα γεμίζουμε τους δύο αυτούς πίνακες με δοκιμαστικά data.
DECLARE @i INT
DECLARE @SellStartDate DATETIME
DECLARE @SellEndDate DATETIME
SET @i = 1
WHILE @i < 1000
BEGIN
SET @SellStartDate = CAST(( 365.2422 * 105 ) * RAND() AS DATETIME)
SET @SellEndDate = DATEADD(d, RAND() * 10000, @SellStartDate)
INSERT INTO Product
VALUES ( @i,
'test' + CAST(@i AS VARCHAR),
@SellStartDate,
@SellEndDate )
INSERT INTO ProductPCC
VALUES ( @i,
'test' + CAST(@i AS VARCHAR),
@SellStartDate,
@SellEndDate )
SET @i = @i + 1
END
GO
1000 εγγραφές με τυχαία SellStartDate και SellEndDate. Τώρα είμαστε έτοιμοι να δώσουμε τα query μας και να δούμε πως συμπεριφέρονται. Προσοχή! Επειδή μετά από κάθε SELECT θα χρησιμοποιούμε δύο Data Management Views για να μετρήσουμε την απόδοσή του, θα πρέπει να καθαρίζουμε την cache του Query Engine ώστε να έχουμε αξιόπιστα αποτελέσματα. Δίνουμε λοιπόν
DBCC DROPCLEANBUFFERS
DBCC FREEPROCCACHE
GO
Και τρέχουμε το πρώτο query. Αυτό δεν χρησιμοποιεί καθόλου computed columns και δουλεύει με την παραδοσιακή τεχνική.
SELECT Product.ProductID,
Product.ProductName,
Product.SellStartDate,
Product.SellEndDate,
Product.SellDuration,
DATEDIFF(dd, SellStartDate, SellEndDate) SellDuration
FROM Product
Τώρα θα χρησιμοποιήσουμε τα δύο Data Management Views. Τα Data Management Views είναι κάποια system views που παρέχουν διάφορες πληροφορίες και είναι εξαιρετικά χρήσιμα. Αποτελούν το νέο τυποποιημένο τρόπο να κάνουμε διάφορες δουλίτσες, εκεί που χρησιμοποιούσαμε μέχρι σήμερα DBCCs, εξωτερικά εργαλεία, κλπ.
Το Sys.Dm_Exec_Query_Stats μας παρέχει στατιστικά για τα queries που έχουν εκτελεστεί. Είναι πολύ χρήσιμο καθώς μας γλυτώνει από την ανάγκη να χρησιμοποιήσουμε τον Profiler. Το sys.dm_exec_sql_text είναι βοηθητικό, για να πάρουμε το κείμενο του κάθε query. Τρέχουμε το παρακάτω λοιπόν:
SELECT s2.text,
total_worker_time,
total_physical_reads,
total_logical_reads,
total_elapsed_time
FROM Sys.Dm_Exec_Query_Stats
CROSS APPLY sys.dm_exec_sql_text(sql_handle) AS s2
και παίρνουμε
total_worker_time |
total_physical_reads |
total_logical_reads |
total_elapsed_time |
112742 |
3 |
9 |
126750 |
Ξανακάνουμε το ίδιο (μην ξεχάσετε τα DBCC), για το επόμενο query που χρησιμοποιεί το computed column:
GO
SELECT Product.ProductID,
Product.ProductName,
Product.SellStartDate,
Product.SellEndDate,
Product.SellDuration
FROM Product
GO
To query αυτό μας δίνει
total_worker_time |
total_physical_reads |
total_logical_reads |
total_elapsed_time |
73125 |
3 |
9 |
74515 |
Και τέλος, άλλη μια από τα ίδια για το persisted computed column
SELECT ProductPCC.ProductID,
ProductPCC.ProductName,
ProductPCC.SellStartDate,
ProductPCC.SellEndDate,
ProductPCC.SellDuration
FROM ProductPCC
GO
To DMV query μας δίνει τα παρακάτω αποτελέσματα:
total_worker_time |
total_physical_reads |
total_logical_reads |
total_elapsed_time |
58632 |
6 |
9 |
61130 |
Συγκεντρωτικά, έχουμε τα παρακάτω αποτελέσματα (τα δικά σας θα διαφέρουν):
|
total_worker_time |
total_physical_reads |
total_logical_reads |
total_elapsed_time |
Query 1 |
112742 |
3 |
9 |
126750 |
Query 2 |
73125 |
3 |
9 |
74515 |
Query 3 |
58632 |
6 |
9 |
61130 |
Γενικά, φαίνεται ότι τα persistent computed columns έχουν πολύ καλύτερο performance, φυσικά με trade-off τον αποθηκευτικό χώρο.
Μπορείτε να συνεχίσετε τα πειράματα. Δοκιμάστε να βάλετε ένα index πάνω στο computed πεδίο και στους δύο πίνακες για να δείτε πως θα επηρεαστούν τα αποτελέσματα. Επιπρόσθετα, πριν αποφασίσετε να τα χρησιμοποιήσετε, θα πρέπει να εξετάσετε τι γίνεται με τα write ops από πλευράς performance. Γενικά, τα write ops δημιουργούν overhead οπότε ένας πίνακας με πολλά write ops ενδέχεται να μην είναι κατάλληλος για persistent computed columns.
-
Σε προηγούμενο post Efficient schema changes είχα αναφέρει το πόσο χρονοβόρα μπορεί να γίνει μια διαδικασία αλλαγής του schema ενός πίνακα, όταν αυτός είναι γεμάτος data. Ας δούμε μια περίπτωση. Το σενάριο είναι το εξής: Έχουμε έναν πίνακα με ένα πεδίο σταθερού μεγέθους το οποίο θέλουμε να μειώσουμε ώστε να χωρέσουν περισσότερες εγγραφές στα data και index pages.
Εκ πρώτης όψεως δεν φαίνεται δύσκολο κάτι τέτοιο. Μπορούμε να γράψουμε ένα ALTER TABLE … ALTER COLUMN … statement και να ξεμπερδέψουμε. Σπάνια όμως θα παίξει κάτι τέτοιο (δείτε στα Books On Line τους περιορισμούς). Και αν παίξει, αυτό που θα γίνει θα είναι να κλειδώσει ολόκληρο τον πίνακα όσο γίνεται η διαδικασία, όπερ σημαίνει ότι ως λύση δεν είναι και τόσο ελκυστική.
Εναλλακτικά, μπορεί να γίνει αυτό που κάνει το Management Studio/Enterprise Manager όταν κάνουμε αυτή τη δουλειά μέσω του UI. Δημιουργία ενός νέου temp πίνακα με το νέο schema, μετά INSERT INTO από τον παλιό στο νέο, drop του παλιού, rename του νέου με το παλιό όνομα. Ρίξτε μια ματιά στο INSERT INTO που παράγεται όταν ζητήσουμε αλλαγή του πεδίου Country σε nvarchar(5) από nvarchar(15) (δεν είναι σταθερού μήκους και ούτε indexed αλλά δεν πειράζει, ως παράδειγμα κάνει)
IF EXISTS(SELECT * FROM dbo.Customers)
EXEC('INSERT INTO dbo.Tmp_Customers (CustomerID, CompanyName, ContactName, ContactTitle, Address, City, Region, PostalCode, Country, Phone, Fax, ModifiedDate)
SELECT CustomerID, CompanyName, ContactName, ContactTitle, Address, City, Region, PostalCode, CONVERT(nvarchar(5), Country), Phone, Fax, ModifiedDate FROM dbo.Customers WITH (HOLDLOCK TABLOCKX)')
GO
Χμμμμ… WITH (HOLDLOCK TABLOCKX) (!!!) Μαζί με τη διαχείριση των constraints (drop τα παλιά, create νέα στον temp πίνακα, κλπ) όλα αυτά σε ένα transaction. Όχι πολύ efficient αλλά ευτυχώς που παράγεται αυτό το script αυτόματα!
Ας δούμε μια άλλη προσέγγιση στο πρόβλημα:
Προσθέτουμε στον υπάρχον πίνακα ένα νέο column με την αλλαγή που θέλουμε, κάνουμε UPDATE (αντί INSERT INTO) με τις τιμές από το παλιό πεδίο, κάνουμε drop το παλιό πεδίο και rename το νέο με το παλιό όνομα. Το πλεονέκτημα είναι ότι δεν χρειάζεται table lock πράγμα που σημαίνει ότι αυξάνεται το availability του πίνακα ενώ με το κολπάκι που θα σας δείξω για το update, μπορεί να τρέξει η διαδικασία σε ένα σύνολο από πολλά μικρά transactions.
USE Northwind
ALTER TABLE Customers
ADD tmpCountry NVARCHAR(5) NULL
SET NOCOUNT ON
SET ROWCOUNT 500
DECLARE @rows INT
SELECT @rows = 1
WHILE @rows > 0
BEGIN
BEGIN TRAN
UPDATE Customers
SET tmpCountry = CONVERT(NVARCHAR(5), Country)
WHERE tmpCountry IS NULL
SET @rows = @@ROWCOUNT
COMMIT
END
ALTER TABLE Customers DROP COLUMN Country
EXEC sp_rename 'Customers.tmpCountry', 'Country', 'COLUMN'
To κόλπο είναι ότι όταν γίνεται ADD το πεδίο tmpCountry, αυτόματα μπαίνει η τιμή NULL σε όλες τις εγγραφές. Κατόπιν, το loop τρέχει όσο βρίσκει εγγραφές με null τιμές για να τους περάσει μέσω του UPDATE τη νέα τιμή. Αυτή η δουλειά γίνεται κάθε φορά σε batches των 500 εγγραφών, δηλαδή δεν χρειάζεται να κλειδώνεται ολόκληρος ο πίνακας. Αν θέλουμε, μπορούμε να προστατέψουμε τις εγγραφές που έχουν γίνει ήδη update από τυχόν updates που έρχονται από τις εφαρμογές που χρησιμοποιούν τη βάση βάζοντας προσωρινά ένα update trigger για το πεδίο Country. Αυτό το update trigger θα κάνει propagate τις αλλαγές στο νέο πεδίο του πίνακα. Το μόνο που χρειάζεται πλέον είναι να κάνουμε το switch των constraints…
Υ.Γ. Τα παραπάνω, βασίζονται σε υλικό που άντλησα από εδώ, πηγή μεγάλης βοήθειας όταν παιδευόμουν...
-
Το βασικότερο βήμα κατά την εκτέλεση ενός query στον SQL Server είναι η δημιουργία του execution plan, δηλαδή ο καθορισμός του τρόπου που θα γίνουν access τα δεδομένα (χονδρικά, τι indexes θα χρησιμοποιηθούν και πως). Η δημιουργία του execution plan είναι μια περίπλοκη διαδικασία που υλοποιείται από το query engine λαμβάνοντας υπόψη διάφορους παράγοντες με έναν μηχανισμό που ονομάζεται cost-based query optimization. Ο query optimizer βελτιώνεται διαρκώς όχι μόνο από έκδοση σε έκδοση στον SQL Server αλλά ακόμη και από SP σε SP καθώς το development team του SQL Server έχει ειδικές συμφωνίες με μεγάλους πελάτες που παρέχουν πραγματικά δεδομένα και σχετικά traces για το πώς αυτά χρησιμοποιούνται.
Ήδη από την έκδοση 2000 του SQL Server, ο query optimizer είναι πολύ αποτελεσματικός και σχεδόν πάντοτε προτείνει το βέλτιστο execution plan. Εντούτοις, πολλοί developers, για διάφορους λόγους, θεωρούν ότι πρέπει να υποχρεώσουν τον SQL Server να εκτελέσει κάποιο query με συγκεκριμένο τρόπο, επιβάλλοντας δηλαδή συγκεκριμένα indexes ή συγκεκριμένα είδη joins – τα λεγόμενα "HINTS". Όπως αναφέρουν και τα Books On Line, τα query hints θα πρέπει να χρησιμοποιούνται μόνο εφόσον υπάρχει πολύ καλή και δικαιολογημένη αιτία και έχοντας υπόψη ότι ανά πάσα στιγμή ενδέχεται να χρειαστεί να σταματήσει η χρήση τους καθώς η αλλαγές στη φύση των δεδομένων μπορεί να έχει καταστήσει το query hint τελείως ακατάλληλο. Το πρόβλημα είναι ότι πολλές φορές, οι developers γράφουν εφαρμογές που χρησιμοποιούν query hints και πλέον είναι εξαιρετικά δύσκολο να αλλάξει το query.
Στον SQL Server 2005 υπάρχει τρόπος να αλλάξει ένα query (ως προς τα hints) χωρίς να πειραχθεί η εφαρμογή που το στέλνει, δηλαδή να γίνει override κατά κάποιο τρόπο. Αυτό μπορεί να γίνει με τα Plan Guides. Κάθε query που θέλουμε να κάνουμε override, αποθηκεύεται σε έναν εσωτερικό system table μαζί με το αντίστοιχο υποκατάστατό του. Η διαδικασία αυτή γίνεται μέσω δύο stored procedures, της sp_create_plan_guide και sp_control_plan_guide ενώ τα αποθηκευμένα Plan Guides φαίνονται σε ένα system catalog view που ονομάζεται sys.plan_guides.
Για παράδειγμα, δημιουργούμε ένα Plan Guide:
sp_create_plan_guide
@name = N'PlanGuide1',
@stmt = N'SELECT COUNT(*) AS Total
FROM Sales.SalesOrderHeader h, Sales.SalesOrderDetail d
WHERE h.SalesOrderID = d.SalesOrderID and h.OrderDate
BETWEEN ''1/1/2000'' AND ''1/1/2005''
',
@type = N'SQL',
@module_or_batch = NULL,
@params = NULL,
@hints = N'OPTION (MERGE JOIN)'
GO
Χρειάζεται προσοχή ώστε το SQL statement να είναι ακριβώς όπως το λαμβάνει ο server. Γι αυτό χρησιμοποιούμε τον Profiler τόσο για το statement (@stmt) όσο και για τις παραμέτρους (@params). Κατόπιν, μπορούμε να το ενεργοποιήσουμε, να το απενεργοποιήσουμε, να το κάνουμε drop, κλπ με την sp_control_plan_guide:
sp_control_plan_guide N'DROP', N'PlanGuide1'
Τα Plan Guides έχουν πολλές εφαρμογές. Πέρα από το παράδειγμα που ανέφερα, μπορούν να χρησιμοποιηθούν πολύ έξυπνα μαζί με τα OPTIMIZE FOR ή RECOMPILE query hints καθώς και για να αλλάξει συμπεριφορά σε queries που παίζουν με parallel execution plans. Πρακτικά, μπορούν να χρησιμοποιηθούν σε όλες τις εκδόσεις του SQL Server 2005 εκτός από την Express και τη Workgroup.
Πολλά περισσότερα για τα παραπάνω μπορείτε να βρείτε στο http://www.microsoft.com/technet/prodtechnol/sql/2005/frcqupln.mspx όπου υπάρχει και ένα πολύ καλό technical article σε word format για να κάνετε download.
-
Σήμερα έδωσα το exam 70-431 καθώς χρειάζεται να κάνω upgrade το MCDBA certification μου. Γενικά δεν ήταν δύσκολο, ωστόσο αφορμή για αυτό το post ήταν μια ερώτηση που μου έκανε εντύπωση. Έλεγε ότι υπάρχει ένας πίνακας με προϊόντα και θέλουμε να προσθέσουμε ένα νέο πεδίο. Ποιος είναι ο ποιο efficient τρόπος για να γίνει αυτό; Και είχε τέσσερις διαφορετικές λύσεις. Αυτό που σκέφτηκα αμέσως είναι ότι αυτή η ερώτηση είναι ασαφής γιατί δεν προσδιορίζει τι μέγεθος έχει ο πίνακας. Αν έχει για παράδειγμα 1.000, 10.000 ή 1.000.000 εγγραφές.
Πέρυσι είχα αναλάβει ένα performance tuning για έναν SQL Server που είχε έναν αντίστοιχο πίνακα με προϊόντα μόνο που είχε μερικά εκατομμύρια εγγραφές. Σε τέτοιες περιπτώσεις τα queries που δοκιμάζεις στον πίνακα θέλουν προσοχή γιατί αργούν να ολοκληρωθούν. Μια αλλαγή στα indexes απαιτεί και αυτή αρκετή ώρα για να ολοκληρωθεί. Γι αυτόν τον λόγο πρέπει όλα να γίνονται προσεκτικά ώστε να μην χάνεται τσάμπα χρόνος. Το ίδιο ισχύει και όταν συμβαίνουν αλλαγές στο schema. Για παράδειγμα, μπορεί να απαιτηθεί να γίνει προσθήκη ενός πεδίου. Κάτι τέτοιο μπορεί να γίνει με πολλούς τρόπους, όπως:
- Πεδίο NULL (με ή χωρίς DEFAULT)
- Πεδίο NOT NULL και DEFAULT
- Πεδίο με NULL και DEFAULT με WITH VALUES option
- Πεδίο με NULL και DEFAULT με WITH VALUES option και με CHECK constraint
Καθένας από τους παραπάνω τρόπους έχει διαφορετική επίδραση στον πίνακα και τελείως διαφορετικό χρόνο εκτέλεσης, πράγμα που δεν φαίνεται σε πίνακες με μερικές χιλιάδες εγγραφές αλλά γίνεται πολύ αισθητό όταν υπάρχουν εκατομμύρια.
Το σενάριο αυτό λοιπόν, μου θύμισε όταν κατά τη διάρκεια εκείνου του performance tuning χρειάστηκε να μειώσω το μέγεθος ενός πεδίου. Ένα απλό ALTER COLUMN δεν ήταν καθόλου efficient καθώς θα κλείδωνε τον πίνακα για αρκετή ώρα μέχρι να ενημερώσει τις νέες εγγραφές. Έτσι, σκέφτηκα έναν τρόπο για να γίνει πιο efficiently η διαδικασία. Υπομονή μέχρι το επόμενο post...
-
Ένα νέο χαρακτηριστικό στον SQL Server 2005 είναι τα Schemas. To Schema είναι κάτι σαν το namespace στο .ΝΕΤ Framework. Κάθε database object δημιουργείται κάτω από ένα schema και το πλήρες reference στο object (το "fully qualified name" που λέμε) γίνεται χρησιμοποιώντας όνομα του τύπου server.database.schema.object (πχ SERVER01.AdventureWorks.Production.Product)
Το παραπάνω μοιάζει με το fully qualified name που έχουμε στον SQL Server 7 & 2000 ωστόσο η ομοιότητα είναι μόνο οπτική. Στον 7 & 2000, το schema προσδιορίζεται από τον δημιουργό του object, υπάρχει δηλαδή άμεση συνάφεια μεταξύ user και schema. Στον 2005, τα schemas είναι ανεξάρτητα από τους users. Αυτή η οργάνωση έχει διάφορα πλεονεκτήματα όπως:
- Λιγότερα προβλήματα με τα object ownerships
- Απλουστευμένο μοντέλο security καθώς μπορούν να εκχωρηθούν δικαιώματα είτε σε schema level, είτε σε object level
- Ευκολότερη διαχείριση καθώς αν διαγραφεί ένας χρήστης δεν αφήνει ορφανά objects
Διαδικασία Name Resolution
Προκειμένου να καταλάβει ο SQL Server σε ποιο object αναφερόμαστε σε κάθε statement, χρησιμοποιεί μια διαδικασία που ονομάζεται name resolution. Για παράδειγμα, ας υποθέσουμε ότι η βάση περιέχει δύο objects Sales.Product και Production.Product. Αν δεν προσδιορίσουμε το fully qualified name και πούμε "SELECT * FROM Products" τότε ο Server θα εξετάσει αν ο χρήστης που έδωσε το query έχει default schema και αν υπάρχει πίνακας Products σε αυτό. Αν δεν υπάρχει default schema θα ψάξει το object στο dbo schema. Μπορούμε να κατασκευάσουμε ένα schema με την εντολή CREATE SCHEMA:
CREATE SCHEMA Production
Ενώ μπορούμε να δώσουμε default schema σε κάποιον χρήστη με την εντολή ALTER USER:
ALTER USER Manos WITH DEFAULT_SCHEMA = Production
-
Ένα από τα βασικά aspects της διαχείρισης σε μία βάση του SQL Server είναι η συντήρηση των Indexes των πινάκων. Το πρόβλημα προκύπτει από το γεγονός ότι σε πολλές περιπτώσεις το OLTP σύστημα χρησιμοποιείται και για reporting σκοπούς, πράγμα που σημαίνει ότι πρέπει να υπάρχουν αρκετά indexes. Τα indexes είναι καλά για να επιστρέφουν πληροφορίες αλλά αποτελούν πρόβλημα όταν γίνονται αρκετά write operations γιατί παρουσιάζεται το φαινόμενο του fragmentation. Έτσι λοιπόν, χρειάζεται ανά τακτά χρονικά διαστήματα να κάνουμε reindex ή defrag τα indexes. Για περισσότερες πληροφορίες, ρίξτε μια ματιά σε αυτό το εξαιρετικό άρθρο: SQL Server Index Fragmentation and Its Resolution και μετά συνεχίστε εδώ.
Ωραία, τώρα που ξέρετε το πως ανιχνεύουμε το fragmentation το επόμενο πρόβλημα που πρέπει να λύσουμε είναι το πως αυτοματοποιούμε τη διαδικασία. Το ζητούμενο είναι να μην χρειάζεται να δώσουμε χειροκίνητα όλα αυτά τα DBCC SHOWCONTIG και να ξεκινήσουμε κατόπιν τα reindex/index defrag.
Στον SQL Server 2005, υπάρχει ένα dynamic management function που ονομάζεται sys.dm_db_index_physical_stats. Όταν κάνουμε SELECT σε αυτό το function, επιστρέφονται παρόμοια δεδομένα με αυτά που επιστρέφει η DBCC SHOWCONTIG WITH TABLERESULTS, NO_INFOMSGS, δηλαδή το output είναι σε πινακοειδή μορφή, κατάλληλο για να το ρίξουμε σε κάποιον πίνακα.
Στο msdn, στα παραδείγματα χρήσης του sys.dm_db_index_physical_stats έχει το παράδειγμα D το οποίο είναι ένα πάρα πολύ καλό ολοκληρωμένο script που τρέχει την sys.dm_db_index_physical_stats και κατόπιν κάνει generate και execute όλα τα απαραίτητα index rebuild/index defrag βάσει των αποτελεσμάτων της. Μερικά σημεία που αξίζει να παρατηρήσουμε:
Το βασικό query είναι αυτό:
SELECT
object_id AS objectid,
index_id AS indexid,
partition_number AS partitionnum,
avg_fragmentation_in_percent AS frag
INTO #work_to_do
FROM sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL , NULL, 'LIMITED')
WHERE avg_fragmentation_in_percent > 10.0 AND index_id > 0;
Και μας επιστρέφει όλα τα cluster indexes με πάνω από 10% logical fragmentation. Κατόπιν, χτίζει έναν cursor πάνω στα προηγούμενα αποτελέσματα και χρησιμοποιεί για κάθε εγγραφή του #work_to_do ένα IF βάσει του οποίου αποφασίζει βάσει του πόσο fragmented είναι το index αν θα κάνει defrag ή rebuild. Μπορείτε να αλλάξετε το παραπάνω ώστε να γράφει τα αποτελέσματα σε έναν μόνιμο πίνακα ο οποίος θα έχει ένα extra πεδίο ημερομηνίας που θα παίρνει default τιμή με GETDATE() ώστε να έχετε ένα lineage του fragmentation των indexes σε περίπτωση που θέλετε να κάνετε πιο advanced troubleshooting.
IF @frag < 30.0
SET @command = N'ALTER INDEX ' + @indexname + N' ON ' + @schemaname + N'.' + @objectname + N' REORGANIZE';
IF @frag >= 30.0
SET @command = N'ALTER INDEX ' + @indexname + N' ON ' + @schemaname + N'.' + @objectname + N' REBUILD';
IF @partitioncount > 1
SET @command = @command + N' PARTITION=' + CAST(@partitionnum AS nvarchar(10));
EXEC (@command);
PRINT N'Executed: ' + @command;
Το μόνο που έχετε να κάνετε είναι να το προσαρμόσετε στις ανάγκες σας (αυτά που λέγαμε περί reporting vs OLTP) και κατόπιν να προγραμματίσετε ένα job που θα το εκτελεί ανά τακτά χρονικά διαστήματα!
Στον SQL Server 2000 δεν υπάρχει η sys.dm_db_index_physical_stats οπότε θα πρέπει να χρησιμοποιήσουμε τη DBCC SHOWCONTIG και να αλλάξουμε λίγο τo script…
Αρχικά θα φτιάξουμε τον πίνακα που θα κρατήσει τα αποτελέσματα της DBCC SHOWCONTIG.
CREATE TABLE SHOWCONTIG (
ObjectName CHAR(255),
ObjectId INT,
IndexName CHAR(255),
IndexId INT,
Lvl INT,
CountPages INT,
CountRows INT,
MinRecSize INT,
MaxRecSize INT,
AvgRecSize INT,
ForRecCount INT,
Extents INT,
ExtentSwitches INT,
AvgFreeBytes INT,
AvgPageDensity INT,
ScanDensity DECIMAL,
BestCount INT,
ActualCount INT,
LogicalFrag DECIMAL,
ExtentFrag DECIMAL,
DateOfCount DATETIME DEFAULT ( GETDATE() ) )
Κατόπιν, χρειαζόμαστε μια SP που να κάνει ό,τι περίπου κάνει το query που είδαμε παραπάνω:
CREATE PROCEDURE FillShowContigTable AS
declare @RETURN_VALUE int
DECLARE @command nvarchar(2000)
SET @command = 'INSERT INTO SHOWCONTIG (
ObjectName,
ObjectId,
IndexName,
IndexId,
Lvl,
CountPages,
CountRows,
MinRecSize,
MaxRecSize,
AvgRecSize,
ForRecCount,
Extents,
ExtentSwitches,
AvgFreeBytes,
AvgPageDensity,
ScanDensity,
BestCount,
ActualCount,
LogicalFrag,
ExtentFrag )
EXEC (''DBCC SHOWCONTIG ("?") WITH ALL_INDEXES, TABLERESULTS, NO_INFOMSGS'')'
exec @RETURN_VALUE = sp_MSforeachtable @command1 = @command
Το κόλπο εδώ είναι η undocumented sp_Msforeachtable που θα εκτελέσει το INSERT INTO μία φορά για κάθε πίνακα της βάσης.
Οπότε το script του MSDNμετατρέπεται ως εξής (έχω κάνει την υλοποίηση που λέγαμε, με το DATETIME πεδίο):
-- Ensure a USE <databasename> statement has been executed first.
SET NOCOUNT ON;
DECLARE @objectid int;
DECLARE @indexid int;
DECLARE @partitioncount bigint;
DECLARE @schemaname nvarchar(130);
DECLARE @objectname nvarchar(130);
DECLARE @indexname nvarchar(130);
DECLARE @partitionnum bigint;
DECLARE @indexes bigint;
DECLARE @frag float;
DECLARE @command nvarchar(4000);
EXEC FillShowContigTable
-- Declare the cursor for the list of indexes to be processed.
DECLARE indexes CURSOR FOR SELECT ObjectID [object_id], IndexID index_id, LogicalFrag frag FROM SHOWCONTIG
WHERE DATEDIFF(day, DateOfCount, getdate()) = 0;
-- Open the cursor.
OPEN indexes;
-- Loop through the indexes.
WHILE (1=1)
BEGIN;
FETCH NEXT
FROM indexes
INTO @objectid, @indexid, @frag;
IF @@FETCH_STATUS < 0 BREAK;
SELECT @objectname = QUOTENAME(o.name), @schemaname = QUOTENAME(s.name)
FROM sys.objects AS o
JOIN sys.schemas as s ON s.schema_id = o.schema_id
WHERE o.object_id = @objectid;
SELECT @indexname = QUOTENAME(name)
FROM sys.indexes
WHERE object_id = @objectid AND index_id = @indexid;
-- 30 is an arbitrary decision point at which to switch between reorganizing and rebuilding.
IF @frag < 30.0
SET @command = 'DBCC DBREINDEX (''' + @schemaname + N'.' + @objectname + ''', ' + @indexname + ',80)';
IF @frag >= 30.0
SET @command = 'DBCC INDEXDEFRAG (' + DB_NAME() + ', ''' + @schemaname + N'.' + @objectname + ''', ' + @indexname + ')';
EXEC (@command);
PRINT N'Executed: ' + @command;
END;
-- Close and deallocate the cursor.
CLOSE indexes;
DEALLOCATE indexes;
Οι αλλαγές που χρειάστηκαν να γίνουν είναι:
- Αλλαγή του SELECT ώστε να παίζει πάνω στον πίνακα SHOWCONTIG και να φιλτράρει ως προς την ημερομηνία για να παίρνει μόνο τις μετρήσεις της ίδιας μέρας
- Αφαίρεση των σχετικών με partitions γιατί στον SQL Server 2000 δεν υποστηρίζονται partiotioned indexes.
- Αλλαγή των ALTER INDEX … REORGANIZE και ALTER INDEX … REBUILD με τα αντίστοιχα DBCC.
Το script αυτό αποτελεί μια καλή βάση για να ξεκινήσετε και να το βελτιώσετε ώστε να γίνει πιο ευέλικτο ως προς το πότε θα κάνει reindex/defrag κάποιον πίνακα με το να χρησιμοποιεί διαφορετικές ρυθμίσεις (αποθηκευμένες σε κάποιο "settings" πίνακα) για κάθε πίνακα ανάλογα με τη χρήση του όπως επίσης και για το fillfactor ή να προστεθεί και ο έλεγχος για physical fragmentation.
-
Ένα από τα σπουδαία βοηθήματα στο IDE του Visual Studio 2005 είναι οι Debugger Visualizers. Οι Debugger Visualizers παρέχουν οπτική αναπαράσταση κατά το debugging για μεταβλητές των οποίων είναι δύσκολο να δούμε τα περιεχόμενα. Για παράδειγμα, μπορεί κάποιος να δει τα XML περιεχόμενα από ένα XML string σε ένα νέο παράθυρο που επιτρέπει να εμφανιστεί ολόκληρο το string και όχι ένα τμήμα του όπως όταν κάνουμε hover με το ποντίκι.
Το Visual Studio 2005 έρχεται με μερικούς έτοιμους visualizers ωστόσο μας επιτρέπει να δημιουργήσουμε εύκολα και δικούς μας. Ας δούμε πώς…
Ο στόχος είναι να φτιάξουμε ένα visualizer ο οποίος να μας δείχνει τα περιεχόμενα μεταβλητών τύπου System.Drawing.Bitmap (ας πούμε φτιάχνουμε μια εφαρμογή που κάνει επεξεργασία σε images).
Στη C# υπάρχει έτοιμο template με κώδικα, ωστόσο στη VB.NET έχουμε λίγο παραπάνω κόπο. Αρχικά φτιάχνουμε ένα Class Library project το ονομάζουμε CustomVisualizer και αλλάζουμε το Class1.vb σε BitmapVisualizer.vb. Κατόπιν, προσθέτουμε μια φόρμα που την ονομάζουμε Viewer. Επίσης, προσθέτουμε στα References το Microsoft.VisualStudio.DebuggerVisualizers dll.
Ας ξεκινήσουμε από τα εύκολα, τη φόρμα. Αλλάζουμε το FormBorder Style σε FixedToolWindow και προσθέτουμε ένα PictureBox του οποίου θέτουμε Dock = Fill.
Προσθέτουμε τον παρακάτω constructor στην φόρμα:
Sub New(ByVal BebuggerImage As System.Drawing.Bitmap)
' This call is required by the Windows Form Designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Me.ClientSize = New System.Drawing.Size(BebuggerImage.Size.Width, BebuggerImage.Size.Height)
PictureBox1.Image = BebuggerImage
End Sub
Πρόκειται για έναν constructor μέσω του οποίου θα κάνουμε πάσα στην φόρμα το object που θέλουμε να δούμε.
Πλέον ήρθε η ώρα για τον Visualizer. Αρχικά προσθέτουμε το παρακάτω attribute:
<Assembly: DebuggerVisualizer(GetType(BitmapVisualizer), _
GetType(Microsoft.VisualStudio.DebuggerVisualizers.VisualizerObjectSource), _
Description:="Bitmap Visualizer", Target:=GetType(System.Drawing.Bitmap))>
Η αρχιτεκτονική του Debugger Visualizer αποτελείται από δύο τμήματα. Το πρώτο είναι το "debugger" που τρέχει στο debugger του VS και η δουλειά του είναι να υλοποιεί το UI του visualizer. Το δεύτερο είναι το "debuggee" που τρέχει στο process που κάνουμε debug και η δουλειά του είναι να στέλνει στο "debugger" τα data που θέλουμε να δούμε με την φόρμα που φτιάξαμε. Ουσιαστικά, με το παραπάνω Attribute συνδέουμε το "debugger" τμήμα με το "debuggee". Στην παράμετρο Description δίνουμε το όνομα που θέλουμε να βλέπουμε για τον visualizer στο IDE του VS. Στο Target του λέμε τι τύπο θέλουμε να κάνουμε visualize.
O κώδικας της κλάσης BitmapVisualizer είναι πολύ απλός:
Inherits Microsoft.VisualStudio.DebuggerVisualizers.DialogDebuggerVisualizer
Protected Overrides Sub Show(ByVal windowService As Microsoft.VisualStudio.DebuggerVisualizers.IDialogVisualizerService, ByVal objectProvider As Microsoft.VisualStudio.DebuggerVisualizers.IVisualizerObjectProvider)
Dim img = CType(objectProvider.GetObject, System.Drawing.Bitmap)
Using v As Viewer = New Viewer(img)
windowService.ShowDialog(v)
End Using
End Sub
Κάνουμε δύο δουλειές. Η πρώτη είναι να διαβάσουμε τα data του object που θέλουμε να δούμε. Αυτήν την πληροφορία μας τη δίνει ο objectProvider με τη μέθοδο GetData και εμείς το μόνο που έχουμε να κάνουμε είναι να τη μετατρέψουμε στον κατάλληλο τύπο, στην περίπτωσή μας System.Drawing.Bitmap. Η δεύτερη είναι να εμφανίσουμε τη φόρμα που φτιάξαμε δίνοντας ως παράμετρο τα data που μόλις μετατρέψαμε. Εδώ, δεν μπορούμε να εμφανίσουμε τη φόρμα όπως κάνουμε συνήθως στις Windows Forms εφαρμογές. Είπαμε ότι αυτό είναι δουλειά του "debugger" τμήματος και η πρόσβαση σε αυτό γίνεται μέσω του windowService.
Αυτό ήταν! Κάνουμε compile το Project και τοποθετούμε το dll στο My Documents\Visual Studio\Visualizers ή στο C:\Program Files\Microsoft Visual Studio 8\Common7\Packages\Debugger\Visualizers
Για να δοκιμάσουμε το αποτέλεσμα, δημιουργούμε ένα νέο Project με κώδικα σαν τον παρακάτω:
Dim img As System.Drawing.Bitmap
img = System.Drawing.Bitmap.FromFile("Soap Bubbles.bmp")
PictureBox1.Image = img
Βάζουμε ένα break-point στο PictureBox1.Image = … και όταν μπει σε debug mode και φτάσει στο statement, κάνουμε hover το ποντίκι πάνω από τo object img και με κλικ στον μεγεθυντικό φακό, εμφανίζεται η φόρμα μας με τα περιεχόμενα του object!
Τέλος, ο objectProvider μας επιτρέπει όχι μόνο να δείξουμε τα data αλλά να τα επιστρέψουμε πίσω αλλαγμένα μέσω της ReplaceObject. Επίσης, στην περίπτωση που έχουμε δικούς μας τύπους, μπορούμε να τους κάνουμε serialize και deserialize μιας και υπάρχουν αντίστοιχα οι GetData και ReplaceData που παίρνουν ως παράμετρο stream.
-
Ένα μικρό κομματάκι T-SQL για γρήγορη δημιουργία CSV λίστας.
Με αυτόν τον τρόπο, γλυτώνουμε από το iteration μέσα στο resultset όπου κι αν το κάνουμε.
DECLARE @authors varchar(400)
SELECT @authors = ''
SELECT @authors = @authors + ', ' + au_lname + ' ' + au_fname
FROM authors
SELECT @authors = SUBSTRING(@authors, 3, 400)
PRINT @authors
Δεν θυμάμαι που το βρήκα την πρώτη φορά για να αποδώσω τα εύσημα