Hanalei, Hawaii 9/2/2010
438 Posts and Counting

SubSonic: Migrate Me

Wednesday, October 03, 2007 -

Just last night I ran my first "Green Light" unit tests on what I think is going to be a pretty groovy new feature: Migrations. Usually I don't like to talk much about functionality until it's 90%-ish, but I figured that it might be good to let everyone know the what/how/why now so I can take all your lovely comments and see if I can pop them in before it gets released.

For those who don't know, Migrations allow you to build out your DB schema using code. It's a Rails Thing, and if you're not familiar with them you can watch one of my webcasts where I use them (right around the 3 minute mark), or just read up on them here.

The concept is that you version the building of your DB into code files, called "Migrations". Using code, you can build and change your DB as needed, and keep a reference of this in your app's codebase. Since you're using code to build out the schema, you can do all kinds of neat things like add data, ping services, send emails, etc whenever you change around the schema. This puts the DB work into your app and out of the designer, which in some cases can be neat.

Code Sample
Currently, all migrations are built off of an abstract base class called, as you might guess, SubSonic.Migration. Each migration must inherit from this base class, and must also implement two methods: Up (versions your DB upward) and Down(versions it down). Here's an example:

    public class Migration001:Migration {

        public override void Up() {
            TableSchema.TableCollection tables = new TableSchema.TableCollection();

            TableSchema.Table t = new TableSchema.Table("Test1");
            t.AddColumn("Name", System.Data.DbType.String);
            t.AddColumn("Description", System.Data.DbType.String,5000);
            t.AddColumn("DateEntered", System.Data.DbType.DateTime,0, false, "getdate()");
            AddSubSonicStateColumns(t);
            
            tables.Add(t);

            t = new TableSchema.Table("Test2");
            t.AddColumn("Name", System.Data.DbType.String);
            t.AddColumn("Friend", System.Data.DbType.String);
            t.AddColumn("FriendInt", System.Data.DbType.Int32, 0,false,"50");
            AddSubSonicStateColumns(t);
            tables.Add(t);

            Create(tables);

        }

        public override void Down() {
            Drop(new string[] { "Test1", "Test2" });
        }
    }
  • Notice that the name of the Migration is MigrationNUMBER - this isn't important from SubSonic's point of view, but in general naming like this will help you. I'll get more into our "convention" on this later in the post.
  • Also notice we're using TableSchema.Table, a good old friend, to specify the schema for each table. We're also using a TableSchema.TableCollection to hold each spec. This syntax might change as I want to make this code as razor-stupid-simple as I can, and would appreciate any thoughts on a more "fluent" and "discoverable" method signature.
  • We have 5 overrides for AddColumn which allow you to quickly add a column spec to your schema as needed. There really shouldn't be a SQL schema you can't create here (but I'm sure I'll eat these words).
  • Notice as well that I didn't create a Primary Key field - this goes to the core of what SubSonic tries to do for you (remove repetition). You and I both know that your table needs a PK, and most of the time that column follows the pattern TableNameID. So if you don't specify a PK - we add it for you (if you haven't defined one already). If you want to use your own - you can! You can use whatever name and data type you want by using AddPrimaryKeyColumn().
  • We also added some sugar the mix with "AddSubSonicStateColumns()" - this does what you'd think it would, adding CreatedBy, CreatedOn, ModifiedBy, ModifiedOn to your table.
  • Finally - notice that the second column added is of type "String" and I've set the length to 5000. This means something to SubSonic since the max length on nvarchar (our default string type) is 4000. Using the value 5000 will auto-set the data type to nvarchar MAX (or ntext using Sql 2000). If you want to be explicit about this, you can by using the "AddLongText()" method.

More Code
You can also add/remove columns, and alter their settings (without dropping and losing data). This next bit of code shows not only how to alter data, but also why you'd use migrations in the first place - it's a road map of how your DB has changed over time:

    public class Migration002:Migration {
        public override void Up() {
            //alter the description field - change to 1200
            TableSchema.TableColumn col = DataService.GetSchema("Test1", "Northwind").GetColumn("Description");
            col.MaxLength = 1200;

            //use AlterColumn to run an Alter statement - we don't want to save the schema
            //since that would overwrite what we're trying to do
            AlterColumn(col);

            //add a new column to Products
            DataService.GetSchema("Products", "Northwind").AddColumn("MyNewColumn",System.Data.DbType.DateTime);
            
            //add another one and set the default, as well as non-nullable
            DataService.GetSchema("Products", "Northwind").AddColumn("MaxInventory", System.Data.DbType.Int32,0,false,"100");

        }

        public override void Down() {
            
            //the Down() method reverses what Up() did
            //reset Description to 9000
            TableSchema.TableColumn col = DataService.GetSchema("Test1", "Northwind").GetColumn("Description");
            col.MaxLength = 9000;
            AlterColumn(col);

            //remove MyNewColumn and MaxInventory
            RemoveColumn("Northwind", "Products", "MyNewColumn");
            RemoveColumn("Northwind", "Products", "MaxInventory");


        }
    }

How It All Works
There's a lot going on under the hood here, as you can imagine. To keep track of it all, we create a new table in your DB (we have to) called "SubSonic_Schema" and it has one field - "version". We set this to know which version the DB is, and it allows SubSonic to know which direction to take the Migration (up or down).

The tool that actually runs the migration is our command line tool, SubCommander. This tool not only runs migrations, it will also create the migration code file (with naming and everything) that's needed to run the migration itself (you can do this by hand if you like):

sonic.exe migration /create

To run the migration:

sonic.exe migrate

To run to a specific version:

sonic.exe migration /version 1

When you run a migration (as opposed to creating one), SubCommander will look for a folder called "Migrations" in the same directory that it's executing in. If you're running it in VS (like you do with the generators), this would be the root of your project.

All the files get pulled in, and the name of the migration file is analyzed - this part is CRUCIAL. We need to use some method, non-code based, to determine which version the migration represents. For now I've come up with:

VERSION_Descriptor.cs (or vb)

so a sample migration code file would be "001_InitialSchema.cs". The numeric part is for us, the descriptor part is for you. I'm still very open on this, but this is how Rails does it and if someone else already thought this stuff out...

Each code file is pulled in and SubSonic takes a look at the version (the first 3 characters) and decides which to use. Once that's determined, then the source is pulled from the code file, handed off to our compiler (the same bits that our generator uses), and (using Reflection) the Up() or Down() method gets invoked in the appropriate migration file. If your current version is 2 (for instance) and you have migration files 001_x through 009_x, Up() will be called on files 003_x through 009_x.

Once your DB is version 9, you can version back down to 3 by using the command

sonic.exe migrate /version 3

This will run the Down() method in code files 009_x through 004_x.

The cool thing about this process is that you can call your class and namespace whatever you like; it's the naming of the file that's important. Would love some comments on this since naming is the biggest issue we have and people have some whacky naming conventions. I'd like to be open on this - but in general I think I'll default to some stronger adherence to convention.

Finally - each provider is responsible for implementing the methods needed to create/alter/remove columns as well as create/alter/drop tables. So far I've extended only the SQL Server provider (and by extension the EntLib ones) - I'm hoping to get to the MySQL and others before we release this.

Everyday Use
To rewind a bit - the goal here is to give you a way to version your DB as you work, and give you a way to go backwards if needed. Also, this is a great way for teams to review how a DB was schemed and built, with comments and so forth that can be reviewed during a code-review cycle.

When you need to change your DB, you just "migrate" it to the next version - this is the thinking here. Rather than just a quick tweak in the designer, you track what you do in code.

Many people have asked "why not just let Subversion track your changes as you make them to one code file" and the answer is that this method doesn't allow you to go backwards, unless you Revert, which isn't the best option.

Left To Do
There's a lot left to do - specifically I want to make sure this is all transactional. I have some ideas for this - but the thing I really need to find out (which I don't know) is how well the providers we use (MySQL, SqlLite, etc) support DB Management transactions - in other words data transactions are pretty straightforward; I'm not so sure about schema changes.

Also, I want to be able to "Reverse Engineer" an existing DB into a migration file. This isn't very hard to do, but doing it right might take some time to figure out and I'd love your comments on this.

When? Did You Say When? What Was That?
I have no timeline just yet, though if you want to play around with the bits they will be committed tonight/tomorrow (I hope). I want to include this feature as part of our next major release, which will also include our REST Handler and our new query tool - so it will be a few months until we can get this all tightened up.

I'm doing what I can to crack this stuff out and Eric's getting married in October (oops... not sure if I'm supposed to tell people that) so he's sort of out of the loop for a bit. I know he'd love any presents you want to send him :).

I'm going to lean on the commit team to see if they can free up some time (Phil? Jon? Scott?) - is it OK to call out your dev team like this? Who cares... it's Open Source and we make the rules as we go don't we?

And let me state this clearly:

I'm always working the bug list, so YES I WILL fix the outstanding issues :):):):)

Lemme hear it! What do y'all want to see aside from bug fixes and an earlier release?

Related


Gravatar
Zack Owens - Wednesday, October 03, 2007 - With the Migrations as they are now, can you jam data in as you change the table schema?

(Does Rails allow you to add data while doing a Migration??)


But VERY nice... I shall go play arround with it.
Gravatar
Kevin Williams - Wednesday, October 03, 2007 - I have not seen much interest in migrations from the .NET community since I helped Marc-Andre with his Migrator (http://code.macournoyer.com/). I hope someone gets a migration tool into the mainstream consciousness of .NET developers, because it certainly is one of the most empowering aspects of Rails.

@Zack - Yes, Rails allows you to add data in your migrations. Once a table is created, you can use that model to add records.
Gravatar
Steven Harman - Wednesday, October 03, 2007 - Flippin' Awesome! I've wanted this for a long time. This has great potential for OSS projects, like Subtext, that rely so heavily on the database. We (Subtext) have a pretty solid mechanism in place right now, using versioned (in both SVN and naming convention) that allows us to see how the schema changed from version to version - but it's all in SQL. So our ability to do *really cool* things during the upgrade is kind of limited. Anyhow, thanks for getting the ball rolling Rob. Maybe I'll finally force myself to sit down and contribute to SubSonic since it's done so much for me. :)
Gravatar
EtienneT - Wednesday, October 03, 2007 - Niice! Really nice Rob, I wanted this feature for a long time :). One of the cool idea of Marc-Andre's Migrator (http://code.macournoyer.com/) project was that you could add an attribute to each of your migration class with a version #. This way when your code is compiled into your project or a library, by reflection you can actually know which migration to run. I don't know if you talked about it, but it would be really nice to have a way to run migrations in code. Using sonic.exe can be nice, but beeing able to do it automagically in code would be even nicer. What would be cool would be to be able to have a kind of Migratior object that would take an Assembly as a parameter, then you can just call a method Migrate from this object and it would migrate to the most recent version or something like that by using the migrations class in the assembly you provided. Good stuff!!!
Gravatar
J. Philip - Wednesday, October 03, 2007 - @kevin The new project for Marc André 's migrator is: http://code.google.com/p/migratordotnet/ svn: svn checkout http://migratordotnet.googlecode.com/svn/trunk/ migratordotnet The project is now owned by Nick Hems @Rob migratordotnet uses attributes of the class to control migration numbers: For example: [Migration(1)] Class CreateClients() : Migration { instead of: public class Migration002:Migration {
Gravatar
OmegaSupreme - Wednesday, October 03, 2007 - Super-cool, now all I need is for SubSonic to make me a cup of tea :)
Gravatar
Rob Conery - Wednesday, October 03, 2007 - I haven't seen the attribute bits, but the main difference here (for me) is that I don't want to execute the code from within the app - I want to be able to execute it outside - like a script. Using attributes I'd have to pull in the assembly of the project (which is problematic with Web Projects) and analyze the classes, etc. This way all I need to do is look at the file name :). In addition, it sort of enforces good, recognizable naming :). Also, I'm NOT using class names (as I mention above) - it's the name of the class file.
Gravatar
EtienneT - Wednesday, October 03, 2007 - I agree that it's nice to be able to use the .cs files as script file. This is a cool feature. But as a lazy developer, I like to do as less step as possible when I deploy an application. I think it would be nicer for me to just deploy my application let the code do the migration when my application starts. It can apply the migrations if it wants because it knows which migration it needs. I would not have to run sonic.exe /migrate or something like that. Everything would be embedded in my application.
Gravatar
Shawn Oster - Wednesday, October 03, 2007 - Dashing out the door so I haven't had time to read all the the other comments but I see a chance for a side utility that can take a rails-style migration and code-gen it into a subsonic migration class.

Rails migrations are pretty lean and mean and to the point, this looks to be a lot of hand jiving so anything that can help reduce that is always nice. Sorry if I missed something that makes this idea stupid, I just skimmed and dashed.
Gravatar
Rob Conery - Wednesday, October 03, 2007 - @Etienne - migrations are a development feature, not an integration one. Having said that, you could easily setup a Global.asax App_Start method to do just what you're talking about... @Shawn - the code here is pretty much exactly what you do in Rails, with one or two extra lines to handle typing. I do agree that slim and mean are the goals ... hopefully will find some ways to do that.
Gravatar
Stuart Allen - Wednesday, October 03, 2007 - This totally rocks. MS should be paying you to keep .NET steaming along...
Gravatar
denni - Wednesday, October 03, 2007 - Yet another reason why SubSonic won't die to LINQ to SQL.
Gravatar
J. Philip - Thursday, October 04, 2007 - @Rob, Using attributes I'd have to pull in the assembly of the project... You can compile the migrations in a separate assembly and execute them from your command line tool or from the application if one wants to for a self-installing application. You can distribute the Migration assembly or not. Ruby uses file names because it is the Ruby way as an interpreted language. With the attributes, you don't care about the class names either, only use reflection to find the classes having the Migration attribute. There are examples of providers with transaction for MySql and PosgreSql in migratordotnet.
Gravatar
Rob Conery - Thursday, October 04, 2007 - Thanks J - I'm not sure what you get with attributes over naming. I'll be honest and tell you I don't like having to tell a developer to do anything to get migrations to work, other than working up a directory full of code. Demanding they use an extra assembly also alienates the Express crowd. Also - how do I know which assembly to use? I've thought this through to a pretty good level and my goal is to create the smallest footprint possible, however i'm still listening. Speak to me in terms of developer value...
Gravatar
Jitesh - Thursday, October 04, 2007 - Thanks Rob! I was hoping for this feature in Subsonic. Subsonic Rocks!
Gravatar
Ed - Thursday, October 04, 2007 - From a deployment perspective, can't people just use sonic.exe /migrate: from within a nant script? Another attribute idea, can't we have an optional attribute for build number, so the migration file can be mapped to a build of your app, persisting in SubSonic_Schema? The syntax looks pretty self explanatory to me, I guess you could adopt a more fluent interface if required, this maybe useful longer term to infrastructure engineers who may wish use migrations but are only familiar with PowerScript.
Gravatar
Bill McKnight - Thursday, October 04, 2007 - What I would like to see is some sort of wholesale replacement approach for the ObjectDataSource, something you've rightly criticized. If we are are going to abandon the ease of the ODS Wizard, which helps us with not only Gridviews, but also Detailviews and Formviews, then we need something--probably code generated like the controller classes. Is this feasible, in your opinion?
Gravatar
Atilla - Thursday, October 04, 2007 - Hi Rob,

You are doing a great job with Subsonic. I would like to thank you for your efforts to provide a great tool.

I would like to know, if you have a future in Subsonic, where you need to make/create a TempTable to have some sort of data.

I have this data, where user selects a rostercode from the Dropdownlist, than selects a date to view all the employees time sheet for a week of data.

The problem I am having is that If there is no data in the for requested date/dates, I get no results.
I am joining two tables. Because of this, I can't create a report.

I come up with a solution but I would like to have a short one. So, in Subsonic, is there a future where I can create a tmpTable to create the data to retrieve it for the reporting.

If you can let me know that would be great!

Thanks,
Atilla
Gravatar
Denny Ferrassoli - Thursday, October 04, 2007 - Sweet... Absolutely sweet. Can't wait for the next SubSonic release and Migration is an awesome benefit.

Great work!
Gravatar
Yanin - Thursday, October 04, 2007 - Thanks Rob! I think this is awesome. I can see how this can help a lot of people. I would love to see some more examples including handling data changes. P.S If you could e-mail me so that I can e-mail you back, or contact me via msn (contact-at-yanin.net) that would be really awesome :) Thanks again, Yanin
Gravatar
macournoyer - Friday, October 05, 2007 - Hey, Glad to see the migration concept getting some momentum in the .NET community. But wtf w/ using .cs file as script ?? Don't you guys know about Boo or why not just switch to the real thing w/ Ruby ? And why not reuse the one Nick is supporting now? http://code.google.com/p/migratordotnet/ ?
Gravatar
Rob Conery - Friday, October 05, 2007 - I know about Boo and I know about Ruby - WTF with the WTF? Do you know how to get Boo and Ruby to run in the .NET environment? Do you think it's smart to use a language that 1% of the .NET community know and understand? I'm sure Nick's is lovely - but I have a particular goal in mind here and I'm also not in a position to simply add another OSS project into our codebase.
Gravatar
Ryan - Friday, October 05, 2007 - I've been thinking about this for a little while too. I've been looking at using .Net 3.5 though.. Using the sample from the Rails wiki at (http://wiki.rubyonrails.org/rails/pages/UnderstandingMigrations) I've been thinking of a syntax closer to this: public class Migration001 : Migration { public override void Up() { this.CreateTable("Users", new Table { new List { new Column {Name="Name",Type=DataType.@string}, new Column {Name="Login",Type=DataType.@string,Null=false}, new Column {Name="Password",Type=DataType.@string, Limit=32, Null=false}, new Column {Name="Email",Type=DataType.@string} } } ); } public override void Down() { /* drop_table :users */ this.DropTable("Users"); } }
Gravatar
Rob Conery - Friday, October 05, 2007 - Yep that's absolutely doable - the thing I need to do, however, is make sure that I have some way to execute all the changes in a transaction. In my original code sample I used "Create" on a table collection to do this - but that doesn't take into account anything you do after create. I need an event, like "OnUpped" or something :).
Gravatar
J. Philip - Friday, October 05, 2007 - Rob, Quote : Do you know how to get Boo and Ruby to run in the .NET environment? Boo is a native .NET language and IronRuby is well on its way. I understand your position not using them form the bulk of the .NET community, but using .cs files as script files seems to be far away from MS best practices. Meta data is a strength of .NET, why not using it. If someone wants to write a boo or IronRuby script against your migration assembly, they can access that meta data.
Gravatar
Ryan - Friday, October 05, 2007 - @J.Phillip - If we were going the metadata route, why not create migrations in xml. They could be serialized to objects in any .Net language, and it's easy to read, modify and generate xml. They could be easily supported by any language on any platform. @Rob - Wouldn't the code that reads these migrations load all of these dll's through reflection and before each Up method, it would create a transaction? Assuming the CreateTable and AlterTable methods were objects instead: And for the events, couldn't there be a base object the CreateTable and AlterTable objects would inherit from that could provide the eventing, and that eventing could be called from the method the calling code would be executing?
Gravatar
Rob Conery - Friday, October 05, 2007 - @J Phillip - R U Serious? Really? Do "Best Practices" exist for this kind of thing? Did you mean to use the words "Best Practices" and "Boo" in the same sentence? Doesn't ASP do the same thing I'm doing here? Again - talk to me in terms of value. "Best Practices", Boo, IronRuby, and "why not" don't add up to much if you can't give me an argument as to what value I'm offering. In order to read the meta data I'd need to read in the compiled code which means loading a DLL which means you telling me the name/location of said DLL as well as the namespace/class names. Why? If I can suck in the C#, parse it, and execute it, why do I need to throw DLLs around? @Ryan - no, I don't load any DLLs and that's the point. To do that I'd need to know where they are and what they're called. I need to invoke the Up() method, which uses the SubSonic.Migration as the base, and then somehow make sure that whatever happens in there happens in a transaction. Some things to figure out for sure...
Gravatar
Yvan aka Vanof - Friday, October 05, 2007 - Hey Rob,

I am a great fan of subsonic. So far i've only be impressed at what could be done with it.
Up to now, your're reading SQL's metadata/schema to provide us with a "middle tier" model.
Now with migrations that same tier will kinda be able to change/update the only source of info it has.

What's the big picture (integration plan) about it?

I know the scope migrations is pretty big but...
Aren't you forgeting anything?

Table have constraints, triggers, indexes, relations. And the there's view's and procedures related to thoses.
All the objects above are working together to provide the best bang for buck!

Where will the migrations stand by this?
Gravatar
Ryan - Friday, October 05, 2007 - @Yvan - Rails migrations have a means to execute raw SQL against the database as well. I'm sure Rob has thought of the cases you've described.
Gravatar
Ryan D. Hatch - Wednesday, October 10, 2007 - Great job on getting Migrations in .NET! I have one big issue with this - We all use the Database Diagram tool to get a visual representation of our database & relationships. If we alter our database through code, ** Does our Database Diagram become out of date? ** If we a Migration adds a new table and defines a relationship - is it viewable in our Database Diagram? Our Database Diagram need to change seamlessly with our Migrations. Please advise - Thank you! Ryan
Gravatar
Vanof - Wednesday, October 10, 2007 - Rob,

I have a quick question about the way Subsonic sends sql select commands.

After using SQL profiler, i've noticed that most of the queries are ending as exec sql commands.

Doesn't this affects performance / tuning / stats of SQL Server?

Also i noticed that if i bind a collection of objects representing a table that has foreign keys, each of the rows send another select cmd to db.

Is there a better approach in subsonic to limit cmds issued too db? I tried the Lazy stuff without looking at the code and it had no effect :-(

Yvan aka Vanof.
Gravatar
Cash - Monday, October 15, 2007 - Rob,

Good stuff. Here's a few questions:

What if I need to make a new, non-null column that will require a migration script?

From a Developers perspective, I want to see all of the values of a lookup table in one place, not spread out over a half dozen migrations. Is it possible to have them managed in a single place?

How can I Add a new Lookup value, run a process that will change "some" of dependent values to this new value?

How do you manage Stored Procedures, Views, and Functions? As a developer, I want to see only the latest version, edit it, and count on it being deployed with the next release. I don't want to search back through Migrations to find the latest version.

Cash Foley
Gravatar
Vanof - Wednesday, October 17, 2007 - Hey Rob,

Where are you up to with Migrations?

Have you decided on how you'll work the transactions?

I still think that we need some way to be able to start the migration from Subsonic instead of SubCommander. This way we could trigger the migration using an embedded logic.


Really looking forward to this.

Vanof.
Gravatar
Ryan D. Hatch - Tuesday, October 30, 2007 - Hi Rob, Yes - I also would like to be able to trigger my Migrations from within my WinForms application. My users are running an embedded database - and my software downloads updates automatically. I want my application to Migrate their database on-the-fly when they start my application - without any console commands. Would it then make sense to move the Migrate() methods into the SubSonic project, and call this method from SubCommander (which would still offer the command-line)? Please let me know your thoughts. Thank you! Ryan D. Hatch
Gravatar
flipdoubt - Saturday, November 03, 2007 - Does SubSonic's Migrations do anything with creating, dropping, and altering stored procedures?
Gravatar
.Net Migrations - Sunday, February 03, 2008 - [...] use Subsonic that in someways it is still stronger than LinQ. One of these ways is that it supports migrations. I love LinQ, and I think is a unique and powerful tool for C#, but I sure hope that Subsonic [...]
Gravatar
.Net Database Migrations - Sunday, February 03, 2008 - [...] use Subsonic that in someways it is still stronger than LinQ. One of these ways is that it supports migrations. I love LinQ, and I think is a unique and powerful tool for C#, but I sure hope that Subsonic [...]
Gravatar
Shawn - Saturday, May 30, 2009 - I am admitidly new to SubSonic, and Migrations but am def. digging it. Question: Could you not create a T4 Template that is used by SubSonic to gen up your v1 migration from an existing schema? Anyone done this?