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

SubSonic: 2.1 (Pakala) Preview, Part 2

Wednesday, January 16, 2008 -

We're working full guns on the next release of SubSonic, fixing bugs implementing literally hundreds of the features you've asked for. I wrote previously about our new Query tool, and we've introduced some changes to it to make it even more usable and readable. We've also implemented the Repository Pattern, which a lot of people favor over Active Record. In this post I'll talk about that and a few other things that we've been working on. No, I don't know when :).

 

Query2 Updates
A lot of people have asked to be able to use the new Query tool without having to identify the schema for each table in the query. To that end I've put in some generics overrides, so now you can write your query this way:

    ProductCollection p = Northwind.DB.Select()
        .From<Northwind.Product>()
        .InnerJoin<Northwind.Category>()
        .Where("CategoryName").Like("c%")
        .ExecuteAsCollection<Northwind.ProductCollection>();

We've also added the ability to return typed results from Stored Procedures. We don't generate the classes for you (yet), but if you declare a class for holding the results:

    /// <summary>
    /// Class for holding SP Results for CustOrderHist
    /// </summary>
    class CustomerOrder {
        private string productName;

        public string ProductName {
            get { return productName; }
            set { productName = value; }
        }
        private int total;

        public int Total {
            get { return total; }
            set { total = value; }
        }

    }

You can execute your SP like this:

    List<CustomerOrder> orders = Northwind.SPs.CustOrderHist("ALFKI")
        .ExecuteTypedList<CustomerOrder>();

 

The New Repository Pattern
SubSonic has always used ActiveRecord, and many people have found that to be a bit heavy for use in .NET applications. To address this, I created a set of alternate templates a while back (that I called MVC, which in retrospect was pretty confusing I'm sure) which reworked the generated classes and created "dumb classes" more or less (classes that aren't database-aware).

With 2.1 we're introducing an alternative that not only addresses the ActiveRecord "issue", but also easily allows you to create your own base classes without having to create a whole new set of templates. If you want to use the Repository Pattern, you can by simply specifying the "RepositoryRecord" base class to your provider (note that you can put in any base class here now - including your own - and ActiveRecord is the default):

    <add name="NorthwindRepository" type="SubSonic.SqlDataProvider, SubSonic" 
         connectionStringName="Northwind" 
         generatedNamespace="NorthwindRepository" 
         tableBaseClass="RepositoryRecord"/>

 

This will add Repository methods(Get, Save, Delete, Destroy) to the DB class in the namespace "NorthwindRepository" (this is just my test namespace). Each table-based object (as opposed to View) is now blissfully ignorant of the database and is not allows to Fetch() or Save().

Using the Repository-based classes is as you'd expect. Here's my unit test to verify CRUD operations:

    //Get a record
    Product p = NorthwindRepository.DB.Get<NorthwindRepository.Product>(1);
    Assert.IsTrue(p.ProductName == "Chai");

    p.UnitPrice = 200;
    NorthwindRepository.DB.Save(p);

    //pull it back out and test
    p = NorthwindRepository.DB.Get<NorthwindRepository.Product>(1);
    Assert.IsTrue(p.UnitPrice==200);

    //add a new product
    p = new Product();
    p.ProductName = "Test Product";
    p.SupplierID = 1;
    p.CategoryID = 1;
    p.QuantityPerUnit = "0";
    p.UnitPrice = 0;
    p.UnitsInStock = 0;
    p.UnitsOnOrder = 0;
    p.ReorderLevel = 0;
    p.Discontinued = false;
    p.DateCreated = DateTime.Today;

    //save
    DB.Save(p);
    int newID = p.ProductID;
    Assert.IsTrue(newID > 0);

    //pull the new record back out
    p = NorthwindRepository.DB.Get<NorthwindRepository.Product>(newID);
    Assert.IsTrue(p.ProductName == "Test Product");

    //delete it - this sets Deleted to "true"
    NorthwindRepository.DB.Delete(p);
    p = NorthwindRepository.DB.Get<NorthwindRepository.Product>(newID);
    Assert.IsTrue(p.Deleted == true);


    //destroy it
    NorthwindRepository.DB.Destroy(p);
    p = NorthwindRepository.DB.Get<NorthwindRepository.Product>(newID);
    Assert.IsTrue(p.IsLoaded==false);

    //now destroy all test data
    NorthwindRepository.DB.Destroy<NorthwindRepository.Product>("ProductName", "Test Product");

It's worth noting here that I've gone to great lengths to keep from introducing breaking changes - even in reworking our core classes. It's not as tidy as I'd like in there, but we'll clean this all up with 3.0 :).

In addition to the CRUD methods above, the DB class acts as a query factory (as you can see in the first example with generics), and you can ask it for every type of Select/Insert/Update/Delete query that you want. These methods get generated no matter which base class you choose - ActiveRecord or Repository.

 

Rev 274
All of the above items are checked in, including a silly bug that Shawn Oster somehow snuck in (who gave that guy commit!). Someday Shawn you'll have to tell me what a "Neep" is.

I could really use some help testing this, and moreover "doing the VB thing" with our templates to make sure they all synch properly.

 

That Sneaky Eric
Eric has been massively geeking out (and ignoring his new wife) with his new Pet Project called "SubStage" - though that name may change (I'm thinking SubSonicFunk after his "canofunk" alias?). I won't steal his thunder - but I will say that it's a pretty damn nice tool and will help many, many people who've had a hard time with docs and conventions. I keep prodding him to throw up a preview post... if he doesn't I might just spill the beans in the coming week...

Related


Gravatar
Steven Harman - Wednesday, January 16, 2008 - Rob, will there be a testing story for the Repositories? That is... how would I go about mocking them out for unit tests so as to avoid hitting the db? It might be nice if they were interface based, or at least all of the public methods (like Get, Update, Delete, Select, etc...) were virtual so I could partially mock them with a tool like Rhino.Mocks. Thoughts?
Gravatar
alberto - Wednesday, January 16, 2008 - Man, don't you have a CI server to find those bugs for you? No more "works for me"...
Gravatar
Rob Conery - Wednesday, January 16, 2008 - @Steve: if you like TDD so much why don't you just marry it :p.. Good idea :), I'll crank up an interface toot sweet (can't make em virtual cause they're static).
Gravatar
Rob Conery - Wednesday, January 16, 2008 - @Alberto - I lost the login you gave me... lemme know when it's set up and I'll make sure we're rolling.
Gravatar
Shawn Oster - Wednesday, January 16, 2008 - *laugh* hopefully everyone understands your playful sarcasm about Neep, since I obviously *didn't* put it in there since I *don't* have commit access! Anyway, whether it was you or Eric that introduced the bug I'm glad the Neep is gone! Now maybe people can start running the entire test-suite and getting it beefy.

I had a small shudder of horror when I saw the Repository pattern, it brought back flashes of when NetTiers turned into the bloated, confusing mess it is today by trying to please everyone, all of the time. Mean-spirited man that I am I tell the people that want the Repository pattern to use one of the many other ORM tools that already support it, no reason to reinvent the wheel... or so I thought, seems you've reinvented it but in a nice, non-obtrusive way.

Can you tell I've been burned by frameworks that turned into pure muck, spiraling out of control to support every whiz-bang under the sun? I switched to SubSonic simply because it was clean, *simple* and was only trying to do a few things but very, very well. Well, everything is still looking fairly clean so SubSonic - 1, Mucky Framework of Death - 0.

Oh, love the generic overrides for Queries, much cleaner and greater sex appeal!
Gravatar
Rob Conery - Wednesday, January 16, 2008 - @Shawn - oh sure, duck the blame... whatever. In terms of muck and bloat - I hear ya. The good thing here is that it took me 4 hours to implement it, and forced me to break out responsibilities like I should have done long ago. I still have to test it a lot tho. The Repository isn't so bad - it's nice and clean and keeps "Product.Save()" out of your Code Behind :).
Gravatar
Zack Owens - Wednesday, January 16, 2008 - Yo Rob, do you just need the templates converted from the C# to VB? If so, I'm all over it :)
Gravatar
Trevor - Thursday, January 17, 2008 - Rob, I'm not sure I'm getting the Repository pattern. Excuse my ignorance, but can you give me a broad conceptual difference between that and the active record? I'm currently using Subsonic activerecord. I'm not sure what this (Repository) pattern buys me. Thanks, Trevor
Gravatar
Anthony Kilhoffer - Friday, January 18, 2008 - Ah, come on, Rob. It could take Eric quite a while to unveil his new project. Why don't you just go ahead and let the cat out of the bag? Well? :-)
Gravatar
Anthony Kilhoffer - Friday, January 18, 2008 - @Rob....you mentioned above, in a comment, creating an interface for the repositories to facilitate mocking. Well, you said you couldnt mark the methods as virtual because they were static. How are you going to include them in an interface if they're static? Just curious. I could be totally confused here and it wouldnt surprise me.
Gravatar
ESICO - Friday, January 18, 2008 - I have to say that that repositry pattern is much less readable. I'm glad its not enabled by default ! :-)
Gravatar
Rob Conery - Friday, January 18, 2008 - @Anthony: I'm thinking about ways I can use IoC (this is Harman's idea) or ... ? I want to make these things easily testable and it's not easy...
Gravatar
alberto - Friday, January 18, 2008 - @Rob, FYI, I wasn't criticizing your work, I was (am) just *really* surprised you don't have one.

What's the reason for not using it? Not having a machine available? Not having the time for setting it up? You don't believe in that?
Gravatar
Marco - Saturday, January 19, 2008 - Hi Rob! I've a little feature request. >You have the config options includeTableList, excludeTableList, includeProcedureList, excludeProcedureList, ... These are very useful, but it would be very nice to have these informations not directly in web.config or app.config (for Win Forms and class library projects), but instead in separate files, as it is possible with Website projects. Thanks. Marco
Gravatar
Rob Conery - Saturday, January 19, 2008 - @alberto: it takes some time to get any CI server running and then time to admin it. I know I need one - but have been able to deal (up to this point) without it. If you'd like to volunteer, I'd love you for it. @marco: Providers are set in the application's config file - if you want you can set it to be SubSonic.config and then set the src attribute on SubSonicService but this would still need to be defined in your application's config. This is true for WinForms and Class Lib projects. Class Library's don't contain configs - they are components and not executables.
Gravatar
alberto - Monday, January 21, 2008 - Well. I fighting to gain some spare time, so if I'm successful, I may give it a try. I'll let you know if it comes to an end.
Gravatar
Jeff - Tuesday, January 22, 2008 - Hi Rob I'm quite keen to use the 2.1 release of SubSonic for a project, and am wondering what your roadmap for release is? It seems like quite an upgrade over the current stable release.
Gravatar
Roger - Tuesday, January 22, 2008 - I'll second that on 2.1 (or 3.0, or whatever). I've been trying to use Subsonic 2.0.3 on a new(ish) development machine with VS 2005 and .net 3.5 on it. Lots of problems are cropping up with reading web.config and app.config files: - ERROR: Trying to execute generate Error Message: System.InvalidCastException: Unable to cast object of type 'System.Configuration.DefaultSection' to type 'SubSonic.SubSonicSection'. at SubCommander.Program.SetProvider(String appConfigPath) in C:\SubSonic 2.0.3\src\SubCommander\Program.cs:line 396 It looks to be a problem with changes in the Web.configuration class hierarchy in 3/3.5. I've tried throwing .Net 2.0/1.1 and the 2.0 AJAX frameworks at it, without much improvement. (just different errors) I think this is only going to get worse as .Net moves forward and subSonic remains in 2.0-world. I managed to get it to work in the end, but only by putting everything on the command line and avoiding any config files. Not ideal.
Gravatar
Rob Conery - Tuesday, January 22, 2008 - @Roger - I use SubSonic routinely in both VS 2008 and VS 2005 and the problem you're describing doesn't make sense.

Send me an email - I think your web.config is goofed. It should "just work" with the same settings etc.
Gravatar
developerfood - Tuesday, January 29, 2008 - I'm getting the same issue as Roger, tons of command line issues. Was there any resolution to this?
Gravatar
Quinten - Monday, February 04, 2008 - I'm having the same problem as well Just to make sure I did a clean install of XP, VS2005, VS2008 and only SubSonic 2.1 Beta 1. Any ideas? Cheers Q
Gravatar
Jesse Gavin - Monday, February 11, 2008 - I have the same problem right now as well. I am using subsonic.exe to generate the files to a Class Library. It has been working fine up until I opened the project in VS 2008. Here is my config: Here is the error: ERROR: Trying to execute generate Error Message: System.InvalidCastException: Unable to cast object of type 'System.Configuration.DefaultSection' to type 'SubSonic.SubSonicSection'. at SubCommander.Program.SetProvider(String appConfigPath) in C:\SubSonic 2.0.3\src\SubCommander\Program.cs:line 396 at SubCommander.Program.SetProvider() in C:\SubSonic 2.0.3\src\SubCommander\Program.cs:line 266 at SubCommander.Program.GenerateTables() in C:\SubSonic 2.0.3\src\SubCommander\Program.cs:line 808 at SubCommander.Program.Main(String[] args) in C:\SubSonic 2.0.3\src\SubCommander\Program.cs:line 68
Gravatar
Jesse Gavin - Monday, February 11, 2008 - Ok, I found the solution on the SubSonic forums. As it turns out the configSections node of the App.Config file has to be the 1st child node. I had an appSettings node before the configSections node. When I swapped the order, it worked like a charm. Answer found here. http://forums.subsonicproject.com/forums/p/2046/8349.aspx#8349
Gravatar
Dotnetshadow - Saturday, March 15, 2008 - Hi there, With regards to the RepositoryPattern shouldn't the CRUD methods be internal or something? As it stands now the web app still has access to your DB layer. I thought it would be good if the CRUD methods were internal that way you can implement a controller that can only call the DB.XXX methods? Or is the simple solution just overwrite the modifiers in the template? What are your thoughts on this? Am I thinking differently? Regards Dotnetshadow