Friday, July 4, 2014

New Feature and Refactoring Freedom: Determining Code Coverage in Sails.js

Happy Independence Day!  To celebrate the USA's birthday, I offer a quick post on exercising your freedom as a developer to implement new features or refactor code without flying blind or worrying what you may have missed or broken.  I'm of course talking about some code coverage in Sails.js.

In my prior post on Web API development in Sails.js, we utilized Grunt and Mocha as the primary tools for our unit tests.  However, what we did not review was how to target what you missed in your unit tests.  After all, what good are running unit tests if you don't know where your gaps lie?


Monday, June 30, 2014

How to Unit Test a Sails.js Model Without Lifting Sails

The Problem

As a quick follow-up to the previous post about creating a traditional MVC-style Web API using Sails.js, I did get some questions around how to test models in Sails, especially those that have instance methods and override their Waterline lifecycle callbacks, such as afterCreate and afterUpdate.  This post is for you guys!


Friday, June 20, 2014

Growing Up and Growing Large: Modern JavaScript Web Development Using Sails.js and AngularJS (Part 2 of 3)

Sails.js: A Server-side Solution

One of the early criticisms (and also strengths) of JavaScript, and open source architectures in general, was the conundrum of "too many options".  After all, it was scientifically proven that people (yes, even software developers) prefer solutions over individual components.  Part of growing up is learning from past mistakes, and the JavaScript community has responded in kind, particularly the folks at Giant Squid with Sails.js.

Sails.js attempts to reel in the noise in choosing components for a server-side JavaScript framework.  It is a proper MVC framework, inspired by Ruby on Rails, that gives a developer everything you could possibly need: an MVC pattern, ORM, multiple adapters for data stores, Socket.io support, intelligent routing, and much more, all in a carefully curated solution so that a developer can focus on making great software products versus worrying about getting components to play nicely with each other.


Sunday, June 15, 2014

Seeding a Sails.js Application's MongoDB Store Standalone Using Node.js and Waterline

As I was constructing the Sails.js application for the forthcoming Part 2 of my 3-part MEAN-stack series, I realized that I needed an easy way to seed the MongoDB instance on which my Sails app would run.  In this particular example, I wanted to seed three collections (Movie, Person, and MoviePerson) in MongoDB with data defined in .json files that matched the Models for these collections I'd created in Sails.

So, how do we go about doing this in a standalone fashion using Node.js and Sails' excellent Waterline ORM?

Thursday, June 12, 2014

Growing Up and Growing Large: Modern JavaScript Web Development Using Sails.js and AngularJS (Part 1 of 3)

The JavaScript Revolution

I will be the first to admit: had you told me that JavaScript would become the de-facto standard for web development a few years ago, I would've laughed in your face and requested that you take some breathalyzer measurements.  How could a language reserved for some ugly client-side DOM manipulation and the general clutter of the view-side of MVC architectures become anything close to enterprise-grade?  Heck, JavaScript was even an afterthought, built in 10 days by Brendan Eich to give browsers some whizbang on the client-side.

Well, the numbers don't lie: JavaScript development has grown up and grown large...but this ain't your mid-to-late 2000's JavaScript, folks.  We are talking highly scalable, testable, and standards-compliant multi-platform development.  We are talking a language that is very well suited for using NoSQL data stores.  We are talking a language that empowers developers to react and implement quickly.  Modern JavaScript is to past JavaScript as a jetliner is to the Wright Brothers plane.  It is time to take it seriously and for practitioners of classical languages to bone up on this quirky but highly effective interpreted language and its frameworks.

Lean and MEAN

PHP had the LAMP stack (Linux/Apache/MySQL/PHP).  JavaScript has the MEAN stack (MongoDB/Express/AngularJS/Node.js).  Like with the LAMP stack, each letter of the acronym represents important components.

"M": MongoDB

MongoDB is the data store of choice for JavaScript-based frameworks.  It is, indeed, a NoSQL database: a schemaless document store that, out of the gate, supports REST and JSON, as well as scale-out capabilities (sharding) that you can expect from a NoSQL platform.

"E": Express

Express is a Node.js-based framework (Node.js is, incidentally enough, also part of the overall stack) that represents server-side JavaScript.  Yes, server-side like ASP .Net MVC and Spring MVC.  This is the Server API portion of a MEAN stack application, the keeper of domain objects via RESTful endpoints.

Sails.js is an excellent MVC framework built on top of Express, and is the framework we will use for this thought exercise.  For .Net folks, think of Express as IIS and Sails.js as ASP .Net MVC.  For Java folks, think of Express as JBoss and Sails.js as Spring MVC.  You could technically build a "traditional" server-side web application on Express without the use of Sails, if you so desired.

"A": AngularJS

AngularJS is a client-side single page application (SPA) framework heavily sponsored by Google that aims to "extend" HTML to represent the dynamic nature of web applications and provide a slick, seamless user experience.

"N": Node.js

While technically a subset of the "E" part of MEAN, Node.js does so much more than just the server-side operations.  Node.js helps manage JavaScript library dependencies for the Server API as well as AngularJS (using NPM...think Maven for Java or NuGet for .Net).  It can also help streamline the "build" workflow of a MEAN stack application by running unit tests (in conjunction with a task runner, like Grunt or Gulp).  Node.js is essentially the glue that binds all the elements together.

A Typical MEAN Stack Web Application

A "typical" use case of a web application

As the diagram above indicates, MEAN stack applications give your typically nice, segregated separation of concerns.  

Sails.js on the server-side exposes RESTful endpoints for external applications to interact with the domain of the application.  The domain is persisted on MongoDB and you'll have "POJSO's" (Plain-old JavaScript Objects) for Sails.js to interact with the persistence layer.  In case you're wondering, yes, you could easily swap out MongoDB with MySQL or PostgreSQL if you're still unsure about NoSQL platforms...but I'd recommend against torturing yourself in that fashion.  ;)

AngularJS on the client-side focuses on a rich user experience, shuttling any of its data needs to the Sails.js Server API.  Likewise, if you were to build, say, a native Android or iOS application, they would interact directly with the Sails.js Server API to get at your application's domain object model.  True multi-platform development!

While the representation above isn't anything new in software, doing a full MEAN stack offers many benefits:
  1. JavaScript end-to-end.  No context-switching for developers when they cross the boundary from client to server-side.  (Read: faster development, real collaboration between client and server)
  2. Built to scale out.  AngularJS is a SPA loaded on the client.  Sails.js runs on Express and Node.js which was designed to scale out.  Likewise for MongoDB.
  3. JavaScript has grown up.  This ain't your daddy's JavaScript.  Want complex collection or object manipulation?  npm install underscore (or lodash).  Need an ORM, Socket.io support, native RESTful/JSON support?  Comes out of the box with Sails.js and AngularJS.  Dependency Injection?  Built into AngularJS, and use require on Sails.js.  Unit testing and mocking: do you prefer jasmine or mocha?

Okay, So Let's Build Something!

In Part 2 of this 3 part series, we're going to get started with a bottom-up approach.  Using Sails.js and MongoDB, we will create a domain model and persistence layer for a movie application called the AgileMovieDB.

Sunday, March 17, 2013

Orlando Code Camp 2013: SQL 2012 BI

Many thanks to everyone who attended my Orlando Code Camp 2013 session on SQL Server 2012 BI.  There is great potential for the Tabular Modeling of SSAS, and I hope you're excited about using it for your BI needs!

Here are a few links that I promised that will help you get started quickly in using the entire SQL Server 2012/SharePoint Server 2010/PowerPivot and PowerView stack:
For those with Subversion who want to get at the artifacts from the session yesterday, perform a checkout on https://edg.sourcerepo.com/edg/OrlandoCodeCamp2013 to get the Visual Studio solution and the backup of the NFLDW database we used as our source for SSAS Tabular.

Finally, feel free to contact me on Twitter (@grales) or e-mail (eric.v.nograles@gmail.com) if you have any questions/issues or wanted to bounce some ideas around SSAS tabular and its applications in corporate BI.

Thanks again to the Orlando .Net User Group for the opportunity to speak at this fun event!  Hopefully, I'll be seeing you all again next year!


The AgileThought family thanks everyone for attending our sessions at Orlando Code Camp 2013!

Tuesday, January 29, 2013

Packaging Existing SQLite Databases With Your Google Android Application

Many examples of Android applications on the Internet assume that the apps you develop will, by default, create a new, blank MySQL database at its first run-time.  However, there aren't many examples of a situation where one would create a separate MySQL database which would then be used by the Android application.  A potential solution, borrowed from databases on other platforms that use Continuous Integration, would be to embed seeding SQL statements in the application to run on creates or upgrades, but this may be prohibitive for some developers in terms of practicality and time, in addition to the fact that Android does some backend wizardry with SQLite databases to have them work properly with its SQLiteOpener class.  So, how would one embed an existing SQLite database to the application's assets folder and use it at runtime?  Stay tuned for the solution after the jump!


Saturday, September 15, 2012

Using Unity for Dependency Injection With WCF Services

The Dependency Injection (DI) pattern of software development offers many benefits in the area of separation of concerns.  The loose-coupling nature of this pattern allows for truly atomic unit tests and (theoretically) more effective development.    

While the DI pattern is well documented in web UI technologies that espouse separation of concerns (such as MVC), the use of this pattern in the less glamorous area of application integration using web services is a little leaner on the volume of documentation.  Being that application integration apps such as WCF Services have a tendency to perform some elaborate transportation and transformation logic, having the benefits of the DI pattern greatly improves the effectiveness of the development of these applications.

So, in terms of WCF Services, how exactly do we achieve the DI pattern?  Thanks to the lightweight Unity library, we can offer the following DI benefits for a WCF Service:

  • File-less activation of services (no more pesky .svc files to maintain)
  • Loosely coupled development
  • Rapid and agile development thanks to unit testing
Coding commences after the jump!

Sunday, June 17, 2012

Business Intelligence Shootout: Microsoft SQL Server 2008 BI vs. Pentaho BI Enterprise Edition (Part 2: SQL Server Analysis Services)

The BI Platform That They Already Own
So, now that we've seen some of the basic capabilities of Pentaho Analysis Services (aka Mondrian), let's check the other side of the ring where SQL Server Analysis Services (SSAS) sits.  The entire SQL Server BI stack is a very interesting case.  Starting with SQL Server 2005, Microsoft began packaging their entire BI suite with a Standard Edition license, and I mean the whole shebang.  SSIS, SSAS, SSRS, SSMS, and BIDS...the whole gang to satisfy all data analytic needs.  Apparently, this was not emphasized enough in the literature for Microsoft SQL Server, because more often than not, this can be news to IT folks in enterprises.  Usually, it's good news, as the company may have already made a sizeable investment in SQL Server, and the icing on the cake is a world-class BI platform.

Now, for those companies who haven't already made an investment in SQL Server, the barrier to entry for SQL Server BI may be the price of a license.  After all, the bulk of the license fee pays for the RDBMS, one would argue.  However, this does not diminish the value of the SQL Server BI stack.  This platform has come a long way since Analysis Services was first revealed in SQL Server 2000, and we will take a deep dive into the capabilities of the latest features within SQL Server 2008 R2 Business Intelligence.

Monday, February 20, 2012

Business Intelligence Shootout: Microsoft SQL Server 2008 BI vs. Pentaho BI Enterprise Edition (Part 1: Pentaho Analysis Services)

A Tale of Two Analytic Engines
In this first head-to-head comparison, we pit Microsoft SQL Server Analysis Services (SSAS) 2008 against Pentaho BI's Analysis Services.  As mentioned in the introductory post, in-memory analytic engines aren't exactly new hat.  In fact, the inspiration for these engines came from very simple spreadsheet applications, hence the origins of Essbase's name -- "Extended Spread Sheet Database."  Despite their age, the benefits of analytic engines remain the same: data retrieval and ad-hoc analysis at lightning speeds.  Because most of the data is persisted in RAM, the speed at which you retrieve the data is only limited by your network speed and your user interface's rendering.  With that in mind, we have two analytic engines here that drew inspiration from similar roots, but go about their implementations differently.  

Microsoft SQL Server Analysis Services has actually been around since SQL Server 7, thanks to Big Redmond's acquisition of Panorama Software.  It has only been a recent development, starting with SQL Server 2005, that Microsoft has made a serious push into the BI space, literally offering its entire BI stack for free with a SQL Server 2005 (and then later, 2008) Standard Edition license.  Microsoft innovated the now ubiquitous Multi-Dimension Expression (MDX) query, and has made strides in usability, deeply integrating the SQL Server BI platform to all of its core enterprise offerings, Microsoft Office and Microsoft Sharepoint, as well as its well renowned integrated development environment, Visual Studio.

Pentaho Analysis Services, aka Mondrian in the open source world, is a relative newcomer to the BI marketplace.  Started by industry veterans from the defunct Arbor Software (where Essbase was incubated and released) at the turn of the 21st century, this Java-based analytic platform began its roots as an open source platform, along with the other components of Pentaho BI.  The goal of its founders was to create a powerful, flexible, cohesive, scalable, and cost-effective platform, meeting or exceeding the capabilities of its commercial conglomerate counterparts.  Who better to architect and develop such a solution but some of the very pioneers of the BI movement?  PAS is the core of the Pentaho BI stack, offering seemingly the same capabilities as other analytic engines in the market.


With the history of both analytic engines in mind, let's take a deep dive at both, using AdventureWorks as the star schema base.  For the impatient, I have published all the artifacts produced in this blog post on my source control system, which grants everyone read access.  If you have a Subversion client, point to https://edg.sourcerepo.com/edg/PentahoAdventureWorks




Friday, February 3, 2012

Business Intelligence Shootout: Microsoft SQL Server 2008 BI vs. Pentaho BI Enterprise Edition (Introduction)

Business Intelligence: A History in Review
Business Intelligence (BI) has become a hot topic in IT and business in recent years.  In the past, BI seemed more like a gimmick, with disconnected pieces of software made by various purveyors that seemingly required a significant upfront investment from both IT and business resources, from both a fiscal and training standpoint.


You might have a tremendous analytic engine from someone the likes of Arbor Software with their Essbase platform, but to effectively syphon the data out into a presentable, maintainable format, you'd also have to invest in some sort of enterprise reporting tool like Arcplan.  Furthermore, to grant users the ability to perform ad-hoc slice and dice analysis, you'd need an analytic software package pushed by a company like Hyperion.  The overall experience of BI, much like its roots, seemed disjointed, with no common vision, and as a result, was expensive to learn and maintain and did not impact the business world the way it had hoped.


The days of disjointed BI have thankfully past, and we are in an era of computing where these tools have become nearly as slick as their web and desktop enterprise application counterparts.  Speed of deployment, ease of use, and cost efficiency are the key elements in modern BI.  Users want their data faster, more flexible, and robust enough to truly gain that holy grail of turning business intelligence into business INSIGHT. 


The Geekstantialism BI Shootout!
In this eight-part series, we will bring two up-and-coming BI platforms front and center, and objectively review the strengths and weaknesses of each, as well as make some recommendations on their optimal effectiveness.


In one corner stands a slick platform backed by the billions of the Microsoft Corporation -- SQL Server 2008 BI.


In the other corner, a young upstart Java-based open source BI platform architected by some of the brightest minds in BI since its inception -- Pentaho Business Analytics.


The Parameters
In order to gain the best insight of the strengths and weaknesses of both platforms when stacked up against each other, I've put together the following parameters/requirements that both must follow in this thought exercise...

Pentaho System Setup
  • Ubuntu Linux Server 11.10
  • Apache Tomcat Server
  • MySQL database backend
SQL Server 2008 Setup
  • Windows Server 2008 Enterprise Edition
  • IIS
  • SQL Server 2008 backend

Data Source
The base data warehouse for both will be the AdventureWorksDW2008 database offered by Microsoft.  Obviously, as Pentaho BI runs off of MySQL out of the box, I will be converting said SQL Server database to MySQL, keeping it structurally the same.


Analytic Engine
Using the design tools for both platforms, cubes will be built against all fact tables in the AdventureWorksDW2008 database.  All dimensions will be created.  Dimensional hierarchies will be created where applicable.  Simple calculated members will be created.  Data mining options will be briefly evaluated.


Ad-hoc Analysis
The following ad-hoc slice, dice, and drillthrough will be performed against both platforms using the respective front-ends:

  1. Investigation of Internet Sales by Product, Customer, and Sales Territory
  2. Investigation of Reseller Sales by Order Date, Employee, and Promotion
Reporting
Both platforms will be evaluated in producing the following reports using provided design tools and persisting them to their respective report servers for distribution:
  1. Call Center Report by Date Shift
  2. Category of Items Purchased by Customer and Date.
Integration Engine
Both integration engines will be evaluated by using the design tools provided and will perform the following:
  1. Refresh DimProduct with new data (CSV source)
  2. Refresh DimCustomer with new data (CSV source)
  3. Rebuild the respective cubes and dimensions on the respective platform
The Final Verdict
After going through the components of both BI platforms, I will outline the overall strengths and weaknesses of both, and make some educated recommendations on the optimal implementation of either platform.  The goal here isn't necessarily to determine which is truly "better" (as that can be a relative term, depending on the circumstances), but instead to gain a better understanding of what scenarios would call for what tool.  After all, that's the whole fun in technology!


My Qualifications
Finally, if you haven't done so already, you must be asking yourself what makes me qualified to make any sort of judgement call on BI platforms?  Professionally, business intelligence has been my specialty area for the past 6 years.  Having been involved as the lead developer for multiple BI platforms on projects of varying sizes in finance and investments, I have practical experience on BI implementations and the demands of its users.


I do have a depth of knowledge with the Microsoft product stack, as that has also been my professional specialty for about 9 years now (specifically Visual Studio [C#, VB, ASP .Net], SQL Server, and the SQL Server BI stack).


I also have a depth of knowledge in the open source and Java, always experimenting with various flavors of Linux (from RedHat to Mandrake to Debian and now Ubuntu).  For fun, I like to develop Google Android apps.  


This will be my first deep dive into the Pentaho platform.  I hope to gain and document insight into this well-regarded platform and see how it stacks up to the SQL Server BI stack that I know well.  Furthermore, I've always had a soft spot for open source initiatives, but found many times that they're a little too rough around the edges for risk-averse enterprises.  Pentaho looks a bit more promising.


Pentaho claims to be able to lure people away from "big commercial BI" with its platform, and I want to put that to the test.  Is it really a BI platform I can confidently recommend as a viable, and even superior, option when scoping out projects?  Time will tell, and I'm excited to find out!   




Stay tuned for Part 1: Pentaho Analysis Services!

Thursday, January 19, 2012

Sebastien Lorien's Fast CSV Reader: Standing the Test of Time

A couple of years ago, when I was working on some data integration projects and didn't have the luxury of SSIS or Informatica, I had to write some custom .Net components to handle CSV sources flowing into OLEDB destinations.  Thinking about what libraries are available in standard .Net (2.0 at the time) -- String parsers, RegEx handlers, StreamReaders, etc. -- one would think it would relatively be a cinch.  However, I wanted to have some of the niceties afforded to ETL engines like SSIS and Informatica.  Specifically, fully qualified text fields, handling of escaped characters, custom delimeter characters, and missing field actions.


Instead of trying to reinvent the wheel, I scoured CodeProject for some inspiration as a starting point.  Never did I realize that an entire CSV parsing library was written so well, that I ended up using the entire project out of the box for my CSV handling needs.  This is the case with Sebastien Lorien's Fast CSV Reader.


Sebastien did a tremendous job in parsing CSV's the way a good integration utility (such as SSIS or Informatica) would.  You name it, this library's got it: handling missing required fields, handling malformed CSV rows, field headers, the works.  I believe the only thing that it didn't do (at the time I used it) was identifying exactly which field was not in an expected data format.  That may have changed over the years, however, so I'll have to bring it into a test project and see what she can do now, about 3 years later.


Oh, and one other thing.  The Fast CSV Reader is incredibly memory efficient.  I can corroborate the numbers reported on the Code Project site for this code, she runs lean and mean.  I seem to recall running a relatively large CSV file (several hundred megabytes) using the reader, and it ran quickly and I didn't have any memory issues.  For that, if I ever meet Sebastien in real life, I would definitely give him a Geek High Five.


Anyway, it appears that this project is still actively supported by Sebastien, so if you are writing custom .Net utilities to handle CSV's (or any character delimited file), I'd highly recommend either using Fast CSV Reader or using its code as a starting point for your implementation.

Saturday, January 14, 2012

Google Chrome: What's With All those Processes?

Full disclosure: I love Google Chrome.  Ever since its release, I have not used another browser, be it my old beloved Firefox, Opera, and Internet Explorer.  However, one thing always kind of piqued my curiosity about my fast, relatively lightweight browsing friend, Chrome: why the heck does it fire off x processes (10 on this particular instance) of Chrome.exe at launch?

Hmm...Google Chrome phone home while I'm not looking, perhaps?
Upon some further digging, you can check out what's going on behind the scenes with Chrome by clicking on Tools --> View background pages



And this little window pops up...


Ah-ha...there is the answer to our question.  In this example, I had 10 processes of Chrome.exe fire off...to correspond with my extensions AND the tabs I currently have open in Chrome.

So, in case you were ever nervous about those excess processes that Chrome spawns, not to worry, it looks like they're there only to help enhance your web browsing experience.

At least, that's what Skynet wants us to think.  

Sunday, January 8, 2012

Excel Page Breaks in SQL Server Reporting Services 2008 by Number of Rows and Preserving Column Headers

In your adventures or misadventures with SQL Server Reporting Services 2008 (SSRS), you may be asked to produce exceptionally large tabular reports to be exported to Excel either interactively or on a subscription.  While one would assume this to be a non-issue with SSRS, we then realize that SSRS 2008 unfortunately still exports to an Excel 2003 format, and thus is at the mercy of the dreaded 65536 rows per sheet limitation.


In other words, if you produce a simple tabular report that outputs, say, 100,000 rows of data, and want to export it to Excel, you will get a pretty nasty message from SSRS resembling an unhandled ASP .Net exception, which informs you that Excel sheets (at least in the 2003 version) have a 65536 row limitation.  Drat!


While the solution would be to have SSRS just export to Excel 2007 or 2010 formats and dump the results to one sheet like a proper modern application, Microsoft felt the need to still tightly couple the output to the "lowest common denominator" of Excel 2003, which, to this day, is probably still the most widely used version of Excel (and MS Office).


So, that kind of leaves us developers out on an island initially.  Of course, the next best option is to have SSRS create Page Breaks every 65536 rows, but how are we supposed to do that?  Furthermore, how do we have the column headers for the tabular report repeat on each page?  Through some digging in various forums and MSDN, the solution to this little conundrum is documented below.


  • Database Source: AdventureWorks2008 Database
  • Database Table: HumanResources.Employees
  • Objectives:
    • Produce an Excel output from SSRS 2008 which produces a new sheet every 100 rows
    • Ensure that the column headers repeat on the new pages
    • Freeze the column headers when browsing the report directly on the web browser
So, let's get started by creating a new report which will point to the HumanResources.Employees table of the AdventureWorks2008 database, and throw all of the data elements to the Details section of the report.


Step 1: Standard SQL query to obtain data

Step 2: Move columns to the Details
Step 3: Easy peasy lemon squeezy

Page Breaks Every Specified Number of Rows

So, now we have our standard tabular report.  Let's fulfill our first objective, having a new sheet produced every 100 rows.  This is accomplished by creating a new parent group to the details and specifying a Group Expression for this new parent group.

Step 1: Meet the Parent
Step 2: Use the following syntax to group your details by number of rows --
=Ceiling(RowNumber(Nothing)/[number of rows]).  
Example above breaks the groups up by 100 rows.
Hit OK twice and you will notice a new group (called Group1) created as the parent of your details.  We need to further modify this group to create our Page Breaks, so go ahead and double click on that newly created parent group to bring up the Group Properties window.  We will now instruct this report definition to break at the end of the group, as well as remove unnecessary sorting on the group and the new "Group1" column created on our report design.

Step 3: Enable page breaks
Step 4: Click on the "Sort by" and then click the Delete button.  Press OK twice to save Group Properties.

Step 5: Delete the new "Group1" column that's been thrown onto the design.  Delete the columns ONLY.

So, now we have a report definition that page breaks every 100 rows.  Brilliant!  We're done, right?  Well, not necessarily.  Suppose our client wants to see the column headers on each new page, as well as have the column headers freeze themselves when they browse the report on their browser interactively...


Repeating Column Headers in Excel and Freezing Interactive Column Headers


The first thing we need to do is to go into Advanced Mode for our Row Groups.  This is accomplished by clicking the small black down arrow to the far right of the "Column Groups" label.


Step 1: Click on Advanced Mode
Now that we've got Advanced Mode up, click on the first item on the Row Groups labelled "(Static)" and bring up the Properties window if it's not already on your toolbox.


Step 2: Set FixedData to True (freezes column headings in interactive mode), then set KeepsTogether and RepeatOnNewPage to True and KeepWithGroup to After (publishes column heading on each page when output to Excel).
Congratulations, you have now created an SSRS tabular report that breaks every 100 rows and reproduces the column header on each new sheet, as well as freezes the column headers when scrolling down interactively!








In Summary...


Do I wish that there was more apparent solution to this seemingly trivial issue?  I absolutely do.  I'm not sure why this design limitation was overlooked in the development process of SSRS 2008.  It would seem to me that the proper solution would be that the SSRS Excel output algorithm would be loosely coupled from SSRS itself and be dependent upon the version of Excel one installs on the server on which SSRS is running?


Anyway, whatever the reason, at least there is a solution out there, albeit a bit of a roundabout one.

Thursday, December 29, 2011

Shrinking Transaction Logs in SQL Server 2005/2008 Using T-SQL

A tripping point of database development in SQL Server 2005 and 2008 tends to be the management of the transaction logs.  When you get into a fairly complicated transactional database design, developers don't often worry too much about the transaction logs -- that is, until, the number of unused pages grows exponentially to the point where the transaction log hits its disk space limitation (if it has one), or worse yet, peg most of the physical disk space available, not to mention give your database an enormous performance hindrance.  


These tasks ought to be taken care of by a DBA (routine transaction log backups/truncations, etc), but developers may not have the luxury of DBA assistance in their test environments.

You can, of course, use SQL Server Management Studio, to assist with shrinking the transaction logs to free up disk space, but there may be times when you need to shrink the transaction as part of your T-SQL stored procedure processes.  Sample code follows:


CREATE PROCEDURE [dbo].[usp_ShrinkTransactionLog]
as

Checkpoint
DBCC OPENTRAN(SampleDatabase)

DECLARE @wk_fileid INT
SELECT @wk_fileid = fileid  
FROM sysfiles
WHERE [name] = 'SampleDatabase_log'

DBCC SHRINKFILE (@wk_fileid)

GO


The stored procedure above calls DBCC OPENTRAN to give you information on any open transactions, and is optional.  It's more of a nice-to-have to see what's still ongoing in the database.  


The real meat of the stored procedure is grabbing the internal FileId of your database's log and then issuing a DBCC SHRINKFILE against it, essentially performing the same task as the Tasks --> Shrink --> Files on SSMS -- the code above does not specify a target size, so it will try to shrink to your transaction log's configured default size.


Note that, if your database is on a Full Recovery model, this sproc may not free up all the disk space that you'd want (with DBCC SHRINKFILE potentially coming back with a message saying it couldn't free up all the space requested).  This is because the Full Recovery model, by design, requires that you have regular backups of your logs.  Thus, it will keep all transaction logs in the virtual logs until they are backed up.  The act of backing up your transaction log will truncate it and free up even more space.  So, we can modify the above stored procedure to take this into account:


CREATE procedure [dbo].[usp_BackupAndShrinkTransactionLog]
as

Checkpoint
DBCC opentran(SampleDatabase)
BACKUP LOG SampleDatabase

DECLARE @wk_fileid INT
SELECT @wk_fileid = fileid  
FROM sysfiles
WHERE [name] = 'SampleDatabase_log'

DBCC SHRINKFILE (@wk_fileid)

GO


Note that the stored procedure code above assumes that you or your DBA have configured a default Backup Destination for your database.  You can modify the BACKUP LOG statement to dump the transaction log to a specific named Backup Device by adding " TO SampleBackupDevice_Log1" (BACKUP LOG(Sample Database) TO SampleBackupDevice_Log1)


Using either of these stored procedures as part of your T-SQL routines will help in keeping your transaction log sizes manageable.  Of course, do consult with your DBA's if you end up moving your test database to a production environment, as I'm sure they will wonder why the log files backups happen more frequently than they designed.  :-)


Additional info from MSDN: Shrinking the Transaction Log

Thursday, September 22, 2011

Using Synergy Over a Non-Split Tunnel VPN

As with any good geek, I have multiple computers running at home. Going by the "DRY" (Don't Repeat Yourself) principle of software development, I preferred not to use multiple sets of keyboards and mice to control these machines. Enter Synergy.

Synergy is a clever piece of open source software. It uses the basic client-server paradigm to allow you to share one computer's keyboard and mouse across multiple computers over the network. The idea here is that the "server" computer has the keyboard and mouse physically connected to it, and the "client" machines simply connect to the "server" to get access to its keyboard and mouse. Simple, yet elegant.

I wanted to take this concept a step further. For my work machine, we have a non-split tunnel VPN. In lay terms, this means that, when I initiate a VPN connection to my work network, I lose all local connectivity on my laptop. In other words, my work laptop is no longer considered "local" to the other computers on my LAN. This is a bummer, because now I WOULDN'T be able to share my one set of keyboard/mouse between my personal laptop and my work laptop.

Through some port forwarding trickery, I was able to get Synergy to run on my personal laptop as well as on my work laptop whilst I was on VPN. How did I achieve this?

  1. Establish a port forwarding rule from my router to my local machine for HTTP (which is TCP Port 80). In other words, if a machine from outside my network browses to the WAN address of my router from a web browser, it will redirect that traffic to my local machine.
  2. Configure the Synergy "server" on my personal machine to run on Port 80.
  3. (Optional) If you have IIS running, set your Default Website to run on another port (say, 81) or just stop it outright.
  4. On the client machine (my work laptop while on VPN), the host name is the WAN address of my router. Go to Advanced Options and set the port to 80.
  5. Start the Synergy server on my personal laptop. Start the Synergy client on my work laptop while on VPN. Presto.
So, let me explain my approach above. By default, Synergy runs on TCP port 24800, which is all fine and good for my local network (I can do whatever I please with regards to my router firewall, port forwarding, etc). However, that is not kosher for my work's firewall. In fact, my work's firewall blocks all outgoing traffic to "non-common" ports...we're extra stingy at my work, so the only "common" port defined is HTTP (port 80), since, well, that's kind of the backbone of the Internet, and they don't want to block out all Internet traffic.

TCP Port 80 is the only non-blocked TCP port I could use to connect Synergy from my work network (via non-split tunnel VPN) to my personal network, hence the setup above.

Of course, this little setup only works if it's not vital for you to actually publish web content on Port 80 for your local network...it personally isn't for me (that's what my web hosts are for!). If your work network's firewall rules are less stingy than mine, you can of course apply the same approach to any TCP port that isn't blocked.

Now, my only concern is that they don't outright block my local network's IP. I haven't WireSharked Synergy so I don't know how verbose the language is when publishing out the X and Y coordinates of your mouse, as well as action buttons from the mouse or keyboard (I can't imagine it to be TOO verbose), so hopefully it will not generate an exorbitant amount of traffic to warrant blocking.

Friday, March 18, 2011

Breaking Down Database Query Results in Chunks of 65536 Rows for Excel 2003 Using Office Interop and VB .Net (Memory Efficient Edition)

So, my prior post had a solution for breaking down database query results from Excel into Worksheets of 65536 rows each. As previously mentioned in the post, it is a memory hog, and probably will blow through your assembly's allocation. I came up with a more memory-friendly solution to this issue. In essence, in this approach, I swap out memory processing for I/O processing.

This solution takes the following approach:
  1. Dump out contents of database query to Excel 2003 XML format using a SqlDataReader, broken by x rows (65536 rows in this case, in the spirit of Excel 2003's limitations)
  2. Open said Excel 2003 XML file using Excel Interop
  3. Do any post-processing formatting and niceties to your spreadsheet
  4. Save the file as a normal Excel Workbook
  5. Delete the source XML file (which does indeed get gigantic)
The result is a process which consumes, at most, 40-50 MB in memory (as opposed to the hundreds of MB in the prior approach) with performance close to the prior approach's in-memory + Excel Interop approach.

Code follows.

Excel XML Header (as referenced in the code below)



<?xml version="1.0"?>
<?mso-application progid="Excel.Sheet"?>
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:html="http://www.w3.org/TR/REC-html40">
<DocumentProperties xmlns="urn:schemas-microsoft-com:office:office">
<Author>enograles</Author>
<LastAuthor>enograles</LastAuthor>
<Created>2011-03-18T16:24:37Z</Created>
<Company></Company>
<Version>12.00</Version>
</DocumentProperties>
<ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel">
<WindowHeight>11895</WindowHeight>
<WindowWidth>19020</WindowWidth>
<WindowTopX>120</WindowTopX>
<WindowTopY>105</WindowTopY>
<ProtectStructure>False</ProtectStructure>
<ProtectWindows>False</ProtectWindows>
</ExcelWorkbook>
<Styles>
<Style ss:ID="sDate">
<NumberFormat ss:Format="Short Date"/>
</Style>
</Styles>



Implementation Code



Public Shared Function ExportToExcelXML(ByVal conn As SqlConnection, ByVal sql As String, _
Optional ByVal path As String = Nothing) As String

Dim excel As Application

Try
Dim destination As System.IO.DirectoryInfo
Dim workbookFullPathSource As String
Dim dtResult As New System.Data.DataTable
Dim textWriter As System.IO.TextWriter
Dim pageCount As Integer = 1
Dim rowCount As Integer = 0
Dim worksheetWritten As Boolean

' First grab the result set
Dim cmdResult As New SqlCommand(sql, conn)
Dim rdr As SqlDataReader = cmdResult.ExecuteReader

' Generate a new Guid for the Workbook name.
Dim workbook_name As String = System.Guid.NewGuid().ToString()

' First validate the destination
If String.IsNullOrEmpty(path) = False Then
If System.IO.Directory.Exists(path) Then
destination = New System.IO.DirectoryInfo(path)
Else
' Attempt to create it
destination = System.IO.Directory.CreateDirectory(path)
End If
Else
' Just drop it in the executing assembly's root folder if no path is specified
destination = New System.IO.DirectoryInfo(System.Reflection.Assembly.GetExecutingAssembly.Location.Substring(0, System.Reflection.Assembly.GetExecutingAssembly.Location.LastIndexOf("\")))

End If

' Construct the full path
workbookFullPathSource = destination.FullName & "\" & workbook_name & ".xml"

' Instantiate the writer
textWriter = New System.IO.StreamWriter(workbookFullPathSource, False)

' Write the header
WriteExcelXMLHeader(textWriter)

' Iterate through the results
If rdr.HasRows Then
While rdr.Read

' Iterate the sheet if we've reached the limit
If rowCount = 65536 Then
worksheetWritten = False
pageCount += 1
rowCount = 0 ' Reset the counter to 0
End If

' Define a sheet and write the headers
If worksheetWritten = False Then
If pageCount <> 1 Then
textWriter.WriteLine(" </Table>")
textWriter.WriteLine(" </Worksheet>")
End If

textWriter.WriteLine(" <Worksheet ss:Name=""Page " & pageCount & """>")
textWriter.WriteLine(" <Table ss:ExpandedColumnCount=""" & rdr.FieldCount & """>")
textWriter.WriteLine(" <Row ss:AutoFitHeight=""0"">")

' The headers
For i As Integer = 0 To rdr.FieldCount - 1
textWriter.WriteLine(" <Cell><Data ss:Type=""String"">" & rdr.GetName(i) & "</Data></Cell>")
Next

textWriter.WriteLine(" </Row>")

' Yes, the header counts as a row
worksheetWritten = True
rowCount += 1
End If

' Write the actual data
textWriter.WriteLine(" <Row ss:AutoFitHeight=""0"">")
For i As Integer = 0 To rdr.FieldCount - 1
Dim dataType As String
Dim dataContents As String = rdr.Item(i).ToString()

If TypeOf (rdr.Item(i)) Is String Then
dataType = "String"
ElseIf TypeOf (rdr.Item(i)) Is DateTime Then
dataType = "String"
dataContents = CType(rdr.Item(i), DateTime).ToString("MM/dd/yyyy")
ElseIf IsNumeric(rdr.Item(i)) Then
dataType = "Number"
Else
dataType = "String"
End If

' The data with the proper type
textWriter.WriteLine(" <Cell><Data ss:Type=""" & dataType & """>" & dataContents & "</Data></Cell>")
Next

' Terminate the row
textWriter.WriteLine(" </Row>")

' Iterate row counter
rowCount += 1
End While
Else
textWriter.WriteLine(" <Worksheet ss:Name=""Page " & pageCount & """>")
textWriter.WriteLine(" <Table ss:ExpandedColumnCount=""" & rdr.FieldCount & """>")
textWriter.WriteLine(" <Row ss:AutoFitHeight=""0"">")
textWriter.WriteLine(" <Cell><Data ss:Type=""String"">No Results Found from Query:" & cmdResult.CommandText & "</Data></Cell>")
textWriter.WriteLine(" </Row>")
End If

' Close out the writer
textWriter.WriteLine(" </Table>")
textWriter.WriteLine(" </Worksheet>")
textWriter.WriteLine("</Workbook>")
textWriter.Close()

' Process in Excel Interop for formatting and saving to a proper Excel document
excel = New Application()
excel.DisplayAlerts = False

Dim wbSource As Workbook = excel.Workbooks.Open(workbookFullPathSource)

' For all sheets, autofit columns, bold first rows, freeze panes on all worksheets
For i As Integer = 1 To wbSource.Worksheets.Count
Dim ws As Worksheet = CType(wbSource.Worksheets(i), Worksheet)
ws.Select()
CType(ws.Cells(1, 1), Range).EntireRow.Font.Bold = True
CType(ws.Cells(2, 1), Range).Select()
excel.ActiveWindow.FreezePanes = True
ws.Columns.AutoFit()
Next

' Select the first Worksheet
CType(wbSource.Worksheets(1), Worksheet).Select()

' Save as a workbook, exit out of Excel
wbSource.SaveAs(destination.FullName & "\" & workbook_name & ".xls", FileFormat:=Microsoft.Office.Interop.Excel.XlFileFormat.xlWorkbookNormal)
wbSource.Close()
excel.Quit()

' Delete the XML source
'System.IO.File.Delete(workbookFullPathSource)

Return workbook_name & ".xls" 'workbook_full_path

Catch ex As Exception
Throw ex
Finally
If excel Is Nothing = False Then
excel.Quit()
System.Runtime.InteropServices.Marshal.ReleaseComObject(excel)
excel = Nothing
GC.Collect()
End If
End Try
End Function

''' <summary>
''' This routine will write an Excel XML header before defining worksheets
''' </summary>
''' <param name="textWriter"></param>
''' <remarks></remarks>
Private Shared Sub WriteExcelXMLHeader(ByVal textWriter As System.IO.TextWriter)
textWriter.WriteLine(My.Resources.resMain.ExcelXMLHeader)
End Sub


Thursday, March 10, 2011

Breaking Down Database Query Results in Chunks of 65536 Rows for Excel 2003 Using Office Interop and VB .Net

March 10, 2011. Still hard to believe the date. Even harder to believe is how enterprises hold onto older versions of MS Office -- in our case, MS Office 2003. Yes, Excel 2007/2010 and the Ribbon UI brings a bit of a learning curve, but having worked extensively with 2003 and 2007/2010, I can honestly say that I am faster and way more productive on the new Ribbon UI. I have drank the Kool-Aid, I love the newer versions of Office.

The new version of Office give us some niceties not afforded to the 2003 version. Some neat niceties (the Excel file being a glorified Zip file, with XML standards), and some practical niceties... in the case of the latter, the elimination of that pesky 65536 row limitation. Alas, with my enterprise still on 2003, it is a limitation we developers need to live with. We have various utilities in .Net that export results out to Excel spreadsheets, because let's face it, as an ad-hoc BI tool, Excel is pretty darn good at analyzing volumes of data.

With that in mind, when you have a requirement for large amounts of data (think in the hundreds of thousands of rows) to be published out to an Excel 2003 Workbook, it poses a little bit of a challenge. Specifically, how do you break up your results into Pages of Worksheets (65536 rows in each page) that the end-users can manipulate to their hearts' contentment? A couple of issues you run into: (a) How to break down your results in chunks of 65536 rows and (b) How to insert them blocks at a time without blowing through the Range.Value2's memory limitation (which, as far as I can tell, is not even published)?

I came up with a routine that facilitates this functionality. Just a fair word of warning, it's a bit of a hack, using standard ADO .Net objects and clever pasting of arrays to the Range.Value2 property of the Excel Interop Model while gratuitously calling the Garbage Collector so we don't blow through our memory allocation (because it uses up a bunch to begin with in persisting the results to memory as an ADO .Net DataTable).


Not my neatest of routines, but it gets the job done. An obvious improvement would be to break down the work in multiple subroutines. Another improvement could be the utilization of memory -- I've only tested this routine for the upper bounds of our data requirements (about 900,000 points of data?), so for larger data requirements, you may need to tune it a bit so it will not blow through the assembly's memory allocation. Of course, if you come up with a clever way to clean the code up a bit, I'd be happy to hear about it!

Again, I reiterate my HACK ALERT statement:


  
Public Shared Function ExportToExcel(ByVal conn As SqlConnection, ByVal sql As String, _
Optional ByVal path As String = Nothing) As String

Dim excel As Application


Try
Dim destination As System.IO.DirectoryInfo
Dim workbook_full_path As String
Dim dtResult As New System.Data.DataTable
Dim lstSheets As New Generic.List(Of String)

' First grab the result set
Dim cmdResult As New SqlCommand(sql, conn)
Dim rdr As SqlDataReader = cmdResult.ExecuteReader
dtResult.Load(rdr)

' How many sheets will we need?
Dim sheet_count As Integer = SheetCount(dtResult.Rows.Count)

' Generate a new Guid for the Workbook name.
Dim workbook_name As String = System.Guid.NewGuid().ToString()

' First validate the destination
If String.IsNullOrEmpty(path) = False Then
If System.IO.Directory.Exists(path) Then
destination = New System.IO.DirectoryInfo(path)
Else
' Attempt to create it
destination = System.IO.Directory.CreateDirectory(path)
End If
Else
' Just drop it in the executing assembly's root folder if no path is specified
destination = New System.IO.DirectoryInfo(System.Reflection.Assembly.GetExecutingAssembly.Location.Substring(0, System.Reflection.Assembly.GetExecutingAssembly.Location.LastIndexOf("\")))

End If

' Construct the full path
workbook_full_path = destination.FullName & "\" & workbook_name & ".xls"

' Create Excel objects
excel = New Application
excel.DisplayAlerts = False
Dim wb As Workbook = excel.Workbooks.Add()
Dim results_processed As Integer = 0

' Tracking dictionary so we can order the sheets
Dim dicSheets As New Generic.Dictionary(Of Integer, Worksheet)

' Only do the Excel processing if we have results
' Create the right number of sheets
' And drop data into each sheet
If sheet_count > 0 Then
For i As Integer = 1 To sheet_count

' Create a sheet
Dim ws As Worksheet

' Put in order
If i > 1 Then
ws = wb.Worksheets.Add(Type.Missing, dicSheets(i - 1))
Else
ws = wb.Worksheets.Add
End If

dicSheets.Add(i, ws)

Dim results_outstanding As Integer = dtResult.Rows.Count - results_processed

ws.Name = "Page " & i.ToString()
lstSheets.Add(ws.Name)

' Start and end positions of the DataTable. Conditions for if we have
' more than 65536 results, we need to know the breakdown for where we
' should start and end (ordinally) on the table based on what Sheet we're on
Dim dt_start_row As Integer
Dim dt_end_row As Integer
Dim sheet_end_row As Integer

' Data Table Bounds
' Positionally on the DataTable, where are we extracting data?
If i > 1 Then
dt_start_row = (i - 1) * 65536 ' Minus one because the first sheet is rows 1-65536
Else
dt_start_row = 1
End If

If results_outstanding < 65536 Then
dt_end_row = dtResult.Rows.Count
sheet_end_row = results_outstanding + 1 ' We need it to be inclusive
Else
dt_end_row = i * 65535
sheet_end_row = 65535
End If

' Create a two dimensional array
' First dimension = rows
' Second dimension = columns
' Add + 1 to rows because the first row is always the column headers
Dim result(sheet_end_row + 1, dtResult.Columns.Count) As Object

' Publish the row header
For col As Integer = 0 To dtResult.Columns.Count - 1
result(0, col) = dtResult.Columns(col).ColumnName
Next

Dim sheet_row_count As Integer = 1

' Propagate the data down in the array
' subtract one since the DataTable is zero based
For result_data_row As Integer = dt_start_row - 1 To dt_end_row - 1

For result_data_col As Integer = 0 To dtResult.Columns.Count - 1

Dim value As Object = dtResult.Rows(result_data_row)(result_data_col)

' DateTimes come up weird on Excel
If TypeOf (value) Is DateTime Then
result(sheet_row_count, result_data_col) = value.ToString() 'dtResult.Rows(result_data_row)(result_data_col).ToString()
Else
result(sheet_row_count, result_data_col) = value 'dtResult.Rows(result_data_row)(result_data_col)
End If
Next

' Iterate the sheet's row counter (not the data table's row counter)
sheet_row_count += 1
Next

' Arbitrary number of rows to drop at a time so Excel's rng.Value2 doesn't exception out. Needs further refining, obviously
If sheet_row_count >= 65535 AndAlso dtResult.Columns.Count > 30 Then

' Drop in Chunks of 700 rows at a time
' We can change this later as we performance tune
Dim row_chunks As Integer = 700
Dim excel_sheet_max As Integer = 65536

For array_row As Integer = 0 To excel_sheet_max - 1 Step (row_chunks - 1)

Dim number_of_rows As Integer

' If we are at the very end, specify as such
' as it might not be a full 700 rows
If array_row + (row_chunks - 1) > excel_sheet_max Then
number_of_rows = excel_sheet_max - array_row
Else
number_of_rows = row_chunks
End If


Dim result_segment(number_of_rows, dtResult.Columns.Count) As Object
Dim rngBegin As Range = ws.Cells(array_row + 1, 1)
Dim rngEnd As Range = ws.Cells(array_row + number_of_rows, dtResult.Columns.Count)
Dim rngAll As Range = ws.Range(rngBegin.Address & ":" & rngEnd.Address)

' Copy the 700 rows of elements to the segment
Try
Array.Copy(result, array_row * (dtResult.Columns.Count + 1), result_segment, 0, number_of_rows * (dtResult.Columns.Count + 1))
rngAll.Value2 = result_segment
Catch e As Exception

End Try

Array.Clear(result_segment, result_segment.GetLowerBound(0), result_segment.Length)
Array.Clear(result_segment, result_segment.GetLowerBound(1), result_segment.Length)
result_segment = Nothing
Next

Else
Dim rngBegin As Range = ws.Cells(1, 1)
Dim rngEnd As Range = ws.Cells(sheet_end_row + 1, dtResult.Columns.Count)
Dim rngAll As Range = ws.Range(rngBegin.Address & ":" & rngEnd.Address)

rngAll.Value2 = result
End If



' Formatting -- bold and freeze header columns, autofit columns
CType(ws.Cells(1, 1), Range).EntireRow.Font.Bold = True
CType(ws.Cells(2, 1), Range).Select()
excel.ActiveWindow.FreezePanes = True
ws.Columns.AutoFit()

' Blank out the array
Array.Clear(result, result.GetLowerBound(0), result.Length)
Array.Clear(result, result.GetLowerBound(1), result.Length)
result = Nothing
GC.Collect()

' Keep track of what we've processed
results_processed += sheet_end_row
Next


' Cleanse sheets - remove Worksheets that aren't named "Page x"
Dim lstRemoveSheets As New Generic.List(Of String)
For Each objWS As Object In wb.Worksheets
Dim ws As Worksheet = CType(objWS, Worksheet)
If lstSheets.Contains(ws.Name) = False Then
lstRemoveSheets.Add(ws.Name)
End If
Next

For Each sheet_to_delete As String In lstRemoveSheets
CType(wb.Worksheets(sheet_to_delete), Worksheet).Delete()
Next
End If

' Select the first cell of the sheet

Dim wsFirst As Worksheet '= dicSheets(1)

' Indicate we received no results
If sheet_count = 0 Then
wsFirst = wb.Worksheets(1)
CType(wsFirst.Cells(1, 1), Range).Value2 = "No results found."
Else
wsFirst = dicSheets(1)
End If

wsFirst.Select()
CType(wsFirst.Cells(1, 1), Range).Select()

' Finally return the workbook name
wb.SaveAs(workbook_full_path)
wb.Close()

' Dispose the dtResult
dtResult.Dispose()
dtResult = Nothing

Return workbook_name & ".xls" 'workbook_full_path

Catch ex As Exception
Throw New Exception("Error encountered when exporting to Excel: " & ex.Message & " " & ex.StackTrace)
'Return Nothing ' A nothing returned implies unsuccessful creation
Finally
If excel Is Nothing = False Then
excel.Quit()
System.Runtime.InteropServices.Marshal.ReleaseComObject(excel)
excel = Nothing
GC.Collect()
End If
End Try
End Function

Saturday, February 26, 2011

Greekstentialism


Kalisera from Athens, Greece! Another day, another data integration project with one of our field offices...and thankfully, this one happens to be in one of the cities I've been meaning to visit since I was a child. Growing up a product of the US public school system, you'd often hear stories about ancient Greek civilizations and their culture. This country is indeed the birthplace of democracy and great minds such as Socrates, and of course, being a general math geek that I am, Pythagoras. Heck, ancient Greek reading was even required in high school, as we read Homer's Iliad and Odyssey in our English classes (the latter being one of my favorite books of all time). All things considered, despite having fallen on hard times recently, I believe Greece to still be one of the most fascinating countries in the world. The picture to the left is me seated out front of the Parthenon in the Acropolis, a truly amazing structure that I'd rank up with the Egyptian pyramids as a "must see before time wears it down to nothing."

Anyway, geeky tourist fascination aside, I've learned much about the modern Greek culture since I've been here. Some tips for my fellow Americans who may have never visited Greece before and are planning on coming by in the near future:
  • The Greeks LOVE cheese. Not just feta either. Cheese is practically on every dish. For those who are lactose-intolerant, bring all appropriate OTC treatments.
  • Eat lunch at 12-1 pm? Most Greeks will look at you strangely. Lunch here typically begins around 2:30pm/3:00pm.
  • Eat dinner at 6-7 pm? Again, most Greeks will look at you strangely. Actually, you won't see them looking at you strangely because none of them will be out. Dinner starts around 9:00pm/9:30pm.
  • Common Greek phrases can be found here complete with phonetic pronunciation.
  • Want to eat some amazing seafood while driving by some of the biggest villas in Greece? Take a trip a little north of Athens to a place called Kiffisias.
  • Have ouzo on the rocks when having seafood, then finish off with mastica as a dessert apertif.
Word is I might be headed back around these parts sooner rather than later, so I will be sure to post any more tips as I discover them.

Sunday, February 13, 2011

Environmental Geekstentialism

With petrol prices still climbing and a need for a highly efficient car for my fiance, I decided it was time to finally bid adieu to my Honda S2000. I had quite a terrific time with that car, but its impracticality wore my patience a little thin over the years. I would still recommend it for anyone who wants a reasonably priced convertible sports car without sacrificing the fun -- I dare say you won't find a better 6-speed shifter this side of a Porsche 911. I'll still have my 2003 Subaru WRX project car to keep my speed needs at bay.

That being said, as a result of our vehicular needs, we have joined the ranks of the hybrid owners of the world and got ourselves a 2010 Honda Insight. I must admit, I was a little leery with hybrid technology at first; the first generation Insight was the S2000's environmental cousin (i.e. completely impractical with anything else aside from fuel efficiency) and the prior and current gen Priuses never really did it for me with regards to a driving experience...not to mention its steadily climbing price.

With that in mind, I was able to hunt down a certified 2010 Honda Insight that struck our fancy with its price point. In test driving the car, I found myself to be a bit surprised -- the handling of the car is, dare I say it, actually fun. Its handling characteristics reminded me of my cousin's Honda Fit, with which I'm sure the Insight shared some of its components. The steering is very responsive, definitely much more responsive than the Priuses I've test driven, and it feels pretty nimble on its feet in normal and highway traffic.

Don't get me wrong, speed and acceleration-wise, the Insight is the polar opposite of the S2000, but my fiance was more concerned with reliability and fuel efficiency. In both aspects, I'd say Honda excels. Reliability? It's a Honda. I know it's become the company's unofficial motto, but their cars simply are highly reliable. I still remember my parents' old 1989 Civic. My family piled on about 200,000 miles and 15 years on that car. Engine never blew, only had to replace the usual wear and tear items, and overall was a great little car. Ditto my S2000 -- not a problem with it mechanically in my 3 years of ownership (and, um, enthusiastic driving).

Fuel efficiency...now this is where the Insight really impresses the geek in me. Most of the new hybrids have a similar system, but I believe the Insight's execution of it is a little better on the user-side. The car has a built in ECO Mode which maximizes the car's fuel efficiency to go along with the brilliant ECO Guide user interface on the dash. The ECO Guide is what I'm impressed with the most. The speedometer (mounted at the top of the dash, a la the new Civics) has an ambient light that glows green when you're driving in a fuel efficient manner and dark blue when you're driving like a maniac. To help coach you into better fuel efficiency, there is also a UI on the dash called the Eco Drive Bar which shows you efficiency ranges as you accelerate and decelerate. I think this system, at least for me, works a bit better than Ford's pretty "leaves" UI. The ambient lighting on the dash gives enough of a high level overview of your fuel efficiency while the Eco Drive Bar can show a driver specifically how his inputs affect his fuel efficiency. So, even a lead footed speed addict such as myself can be trained into maximizing his fuel efficiency. Not bad at all.

So, what's the verdict on the fuel efficiency? The sticker says 40 city/43 highway. I drove from the hills of Manayunk to Abington, taking all local hilly roads, and I easily beat the city average and got closer to 45 mpg. I'd imagine that reaching 50+ mpg on the highway is easily attained. The Prius still beats those numbers by about 10-20% with its slightly more advanced hybrid system, but again, I felt the driving experience on the Insight was slightly more fun than that of its Toyota competitor...not to mention I'm kind of biased to Honda products.

While the car enthusiast in me may mourn the loss of one of the best production sports cars ever built, the Eco Geek in me will rejoice in (maybe) once a month fill-ups, lower insurance premiums, and a more positive environment impact. That will certainly help.

...and I think my WRX is about due for a new turbo and intercooler anyway ;-)