Automated Database Deployments

I am using the VSTS Database Editon to manage my schema in development as well.  For deployments I am using a combination of VSTS Database Edition generate scripts, manually created data migration scripts and nant. 

Steps for generating scripts for deployment:

  • Create a new database with the schema from the last production release.
  • Do a Schema Compare in VSTS DB Edition with the project as the source and the new database as the target.  Export to the Editor so that you can get the sql script to run but don’t execute the script yet.  This will create you the upgrade script.
  • Do a Schema Compare in VSTS DB Edition with the new database as the source and project as the target.  Export to the Editor so that you can get the sql script to run but don’t execute the script yet.  This will create you the back out script.
  • Scan thru the generate scripts and make any additions/changes to handle the data migration.  Unfortunately it is hard to auto generate a script to manage the data side of the upgrade.
  • To test the scripts, restore a copy of the production database and run the upgrade/back out scripts against it.

Nant Script used for Upgrade Deployment:

    <!—List of upgrade scripts -->
   <property name="releases" value="3.0.1,3.0.2,3.0.3,3.0.4,3.0.5,3.0.6,3.0.7" /> 

   <!—Reverse list of upgrade scripts used for back out -->
   <property name="releases.reverse" value="3.0.7,3.0.6,3.0.5,3.0.4,3.0.3,3.0.2,3.0.1" /> 

   <!—Current production release.  Could get from DB.  Used to determine how many back out scripts to run -->
   <property name="current.version" value="3.0.7" /> 

   <!— Release package number.  Used to determine when to start running the upgrade scripts.
   <property name="current.release" value="3.0.8" /> 

 

<!—Loop thru the releases, run all once you either hit the current release or what is in prod original.  -->
<!-- Files are name v.X.X.X.AlterScript.sql and vX.X.X.DataAlterScript.sql -->
<property name="runmorepappscripts" value="false" />
<foreach item="String" in="${releases}" delim="," property="count">
    <if test="${property::get-value('count') == property::get-value(current.release')}">
                    <property name="runmorepappscripts" value="true" />
    </if>
    <if test="${property::get-value('runmorepappscripts') == 'true'}"> 
          <property name="filename" value="${path::combine(release.database.dir, 'v' + count + '.AlterScript.sql')}" />
          <if test="${file::exists(path::combine(release.database.dir, filename))}">
                    <echo message="Running ${filename} script against ${database.name} database  on ${database.server}" />
                    <sql 
                        connstring=""
                        transaction="true"
                        delimiter="GO"
                        delimstyle="Line"
                        batch="false" 
                        print="true" 
                        source="${path::combine(release.database.dir, filename)}"
                        verbose="${verbose}" />
                    <echo message="Completed ${filename} script against ${database.name} database  on ${database.server}" />
          </if>
          <property name="filename" value="${path::combine(release.database.dir, 'v' + count + '.DataAlterScript.sql')}" />
          <if test="${file::exists(path::combine(release.database.dir, filename))}">
                    <echo message="Running ${filename} script against ${database.name} database  on ${database.server}" />
                    <sql
                                    connstring="" 
                                    transaction="true"
                                    delimiter="GO" 
                                    delimstyle="Line" 
                                    batch="false" 
                                    print="true" 
                                    source="${path::combine(release.database.dir, filename)}" verbose="${verbose}" />
                    <echo message="Completed ${filename} script against ${database.name} database  on ${database.server}" />
          </if>
    </if>
    <if test="${property::get-value('count') == property::get-value(current.version')}">
                    <property name="runmorepappscripts" value="true" />
    </if> 
</foreach> 
 
Nant Script for backout
 
<!—Loop thru the releases reverse, run all until you hit what was original in prod.  Files are name v.X.X.X.AlterScript.sql and vX.X.X.DataAlterScript.sql --> 

<property name="runmorepappscripts" value="true" /> 

<foreach item="String" in="${releases.reverse}" delim="," property="count">
                <if test="${property::get-value('count') == property::get-value(‘current.version')}">
                                <property name="runmorepappscripts" value="false" />
                </if>
                <if test="${property::get-value('runmorepappscripts') == 'true'}"> 

                                <property name="filename" value="${path::combine(release.database.dir, 'v' + count + '.BackoutAlterScript.sql')}" />
                                <if test="${file::exists(path::combine(release.database.dir, filename))}">
                                                <echo message="Running ${filename} script against ${database.name} database  on ${database.server}" />
                                                <sql connstring="" transaction="false" delimiter="GO" delimstyle="Line" batch="false" print="true" source="${path::combine(release.database.dir, filename)}" verbose="${verbose}" />
                                                <echo message="Completed ${filename} script against ${fuzion.database.name} database  on ${fuzion.database.server}" />
                                </if>
                                <property name="filename" value="${path::combine(release.database.dir, 'v' + count + '.BackoutDataAlterScript.sql')}" />
                                <if test="${file::exists(path::combine(release.database.dir, filename))}">
                                                <echo message="Running ${filename} script against ${database.name} database  on ${database.server}" />
                                                <sql connstring="" transaction="false" delimiter="GO" delimstyle="Line" batch="false" print="true" source="${path::combine(release.database.dir, filename)}" verbose="${verbose}" />
                                                <echo message="Completed ${filename} script against ${database.name} database  on ${database.server}" />
                                </if>
                </if>
</foreach>

Lack of complaints does not equal success : How To Be A Good Product Manager: Product management tips

Great post on lack of complaints not equaling success.  I have seen lots of developers think this way only to be proven wrong later when they are struggling with user adoption.

Taken from: Lack of complaints does not equal success : How To Be A Good Product Manager: Product management tips

If you want to be a bad product manager, assume that lack of complaints means your product is successful. There are lots of customers using your product, so when you add a new feature or make a change and don’t hear complaints, that must mean that everything is working fine. If something was really unusable or broken or didn’t meet your customers’ needs, they would let you know. It’s much easier to just make a change or add something to the product and wait to hear feedback than to do a whole bunch of research and testing first — that’s just a waste of time, right?

If you want to be a good product manager, proactively seek out feedback rather than wait for complaints. Lack of complaints does not mean that you have a fantastic product — it just means that you are not getting any complaints.

Waiting for customers to complain is problematic for several reasons:

  1. Not all customers complain. Think about all of the products you use on a daily basis, and the problems you encounter with all of them. There may be a confusing button on your cell phone, a strange error message on your online banking site, or a slippery grip on a kitchen gadget. How many times have you taken it upon yourself to contact the organization responsible for that product? Despite the multitude of different ways to complain — from the traditional methods like contacting the company directly, to more modern methods of voicing your frustration on Twitter or a product review site — most customers do not make the effort to send this feedback directly or indirectly to the company. A product manager simply hoping to hear from customers with problems is only going to hear about a fraction of the problems from only a fraction of the customers. For every customer who vocally complains, there are likely tens or hundreds or thousands of others who are silent.
  2. Lack of complaints may mean lack of customers or users. While we would like to think that lack of feedback means lack of problems, it is often that lack of feedback means lack of experience on which to provide feedback. When a product manager adds a new feature to a product and does not hear any complaints about the feature, he may assume that the feature is a success and the fact that customer service has not received any complaints is because it is working smoothly. Unfortunately, it could be just as likely that no one is using the new feature, and thus no one has any experience about which to complain. If there are a small number of customers using the new feature, relying on their complaints alone may provide very skewed feedback.
  3. By the time someone complains, it is usually too late. While the previous two points are worth noting, this is truly the most important reason to not simply wait for complaints. For physical products, changes to a product after it is in the market can be extremely expensive and time consuming to rectify. From a purely financial standpoint, it is the responsibility of a product manager to attempt to produce the best product and thus avoid costly changes. However, even for web-based products which can be changed very quickly and cheaply, waiting for customers to complain is backwards approach to product development. Sure, it may be gratifying on the surface to say that you are able to respond quickly to problems that customers raise, but wouldn’t it be better to prevent these problems in the first place? Would you rather buy a car from a company who listens to your complaints and reacts when your car has problems, or would you rather buy a car from a company who produces a car which will not cause you problems and will not cause you to have to complain?

Ultimately, no matter how hard an organization tries to address problems and meet needs, people will complain, and product managers can benefit from listening to and understanding those complaints. However, when a legitimate complaint is lodged, rather than just reacting to it, product managers should ask, “How did we not know about this earlier?” Is the complaint related to something that the team should have known about? Would a better understanding of the customer needs have helped prevent it? Would better design or more usability testing have uncovered the underlying problem? Did a defect make its way into the final product? Did we know about the problem and just hope that no one would notice? How did it come to this — that a customer had to complain in order for us to realize something was not right?

Complaints are a valuable source of information which can be used to help improve your product, though they are only one source and should be used carefully. Product managers need to be proactive at gathering feedback from customers and prospects, though activities like usability testing, Win/Loss analysis, site visits, observational interviews, and other types of qualitative and quantitative research. Rather than just waiting for complaints and responding to them, product managers need to be focused on preventing them from occurring and getting to the root cause when they do appear.

Jeremy's Other Laws of Continuous Integration

Good laws of continuous integration from Jeremy Miller.  The first law is pretty funny but oh so true.

Taken from: Jeremy's Other Laws of Continuous Integration

1.) If you check in very often and/or first, you can make merge conflicts be someone else's problem

2.) Check in as often as you come to a stopping point.  The more frequently you check in, the less troubling your merge conflicts will be.  For the most part, I think you can make merge conflicts almost entirely go away after the first couple weeks of the project.  We had a mild conflict today (that spawned my tongue in cheek remark about checking in first) that was caused by one pair renaming a testing utility method to make it more consistent with our other testing methods, while my pair was still using the existing method name.  Both pairs knew it was coming so it was no harm, no foul

3.) Communicate with your team members anytime you think you might be doing something that would cause a merge conflict.  Yet one more reason why a colocated team beats a distributed team

4.) Find ways to divide your work into smaller chunks.  If you can't check in new code for days at a time because you aren't at a clean point, you may have some serious problems with your design.

5.) If you can't check in your code for a long time ( > 1/2 a day let's say), be sure to update your version of the source code with the updates from the rest of the team to make your eventual checkin easier

6.) Rotate your build notification sounds.

7.) And in the "do as I say not as I do" category, you might learn how to use a merging tool (then come teach me).


GridView doing double postback when using ImageButton for Delete

I just spent a bit of time trying to figure out why my GridView was randomly doing a double postback when deleting rows.  When I hooked up the debugger and put a break point on in the Page_Load, I would see that it was being called twice.  It turns out that there is a bug with the GridView that causes this behavior.  If you use either a LinkButton or regular Button, the GridView works as expected.  There is also a workaround @ http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=105123


ActiveRecord Codesmith Template Updated

I have made a bunch of changes to the ActiveRecord template.  Below is the list of all of the changes. 

Codesmith.zip (18.14 kb)

Changes:

Lookup table Enum and SQL Generation

I have always used enumerations for all of my lookup tables so that I don't have to hard code the lookup ID value in code.  Until now I had generated these by hand but I finally got around to creating the ability to generate the enum and the sql insert statement.  The template expects an integer primary key and a single string based column.  You can have other values in the table but the template will ignore them. 

The SQL script can either be generated with each table in it's own or as a single file.  The template checks to see if the primary key is an identity column and adds the identity_insert tag.  As well the generate script checks to see if the lookup value based on it's primary key is already in the table before trying to add it.  This allows you to run the script multiple times wthout any issues.

Views as ActiveRecord Classes

I added the ability to specify views to be used to generate ActiveRecord classes.  In order to properly generate the class you have to add an extended property to the view called PrimaryKey and specify the column that is the primary key. 

Self-Referencing Tables

I changed the code to look for self-referencing tables and append List to the end of the property.

Ability to not generate Tests

Added a flag to turn off the test code generation.  All the test classes are is a placeholder anyway which so folks didn't want.

Output Structure

I changed the output structure to better organize the code based on the type of database entity (enum, table, view, sql, etc)

PropertyNames structure for use with nHibernate Expression classes

One thing that I have always disliked about using the nHibernate expression classes was that the properties are string values and cannot be compile time checked.  This unfortuantely leads to only finding errors at runtime for the expression queries.  To solve this problem, I implement the suggestions from the Summer of Nhibernate Screencast (http://www.summerofnhibernate.com ) to create a static class for each ActiveRecord class with the name of each property as a string.  I also add a string value for all of the BelongsTo relationships to get return the RelationshipName.ID since you need that in many instances.  As well I created an ActiveRecordTableNames.cs file that has a listing of all of the tables names.

 


SEVDNUG Presentation: ActiveRecord

On Thursday I gave an quick overview of Castle ActiveRecord (http://www.castleproject.org ) as part of the Southeast Valley .NET User Group (http://www.sevdnug.org ).  I thought the presentation went fairly well.  It is hard to really dig into too much code in 15 minutes, so I mainly concentrated on the features of ActiveRecord.  Below is the links to the code that present.  I used nHibernate 1.2 and ActiveRecord RC3. 

AR Code Demo.zip (1.13 mb)

  • ARCodeDemo.DataObjects: ActiveRecord classes
  • ARCodeDemo.Tests: nUnit Tests
  • ARCodeDemo.Web: Sample Web Application. 
  • Dependencies:  the required dll's to reference for the projects.

Database.zip (3.65 mb)

  • Database used for the sample code
  • Need to just re-attached the database
  • App.Config/Web.Config expects a user of SEVDNUG_User with password of SEVDNUG_User

Book Review: Microsoft AJAX Library Essentials: Client-side ASP.NET AJAX 1.0 Explained

This is a book that should be in your library if you are going to be doing development with ASP.NET AJAX.

Typically when you start learning ASP.NET Ajax all of the material centers around UpdatePanels and leaves out anything to do with the client-side scripts.  This book will make you realize how much functionality you are missing by only using UpdatePanels.

Also, this book not only covers learning the ASP.NET AJAX client scripts but how to do object oriented javascript development. 


nHibernate: Great screencast series

Stepen Bohlen is running a great screencast series called Summer of NHibernate .  So far he has 6 sessions posted.  If you are new to NHibernate it is a great way to get up to speed quickly. 


Visual Studio Tips and Tricks

I am big fan of using keystrokes over using the mouse.  The two blogs below have some great post on visual studio shortcuts.

 

Sara Ford's Weblog : Daily post of a Visual Studio Tip.

Chris Craft's Blog : Currently running a 31 days of Visual Studio Tips and Tricks


Latest ActiveRecord Codesmith Template

I have made some changes to the Codesmith template that I created for ActiveRecord.  One of the major changes made was for nullable database fields to use the c# nullable type (e.g. int?, DateTime?).

 Download: ActiveRecord.zip (13.15 kb)