Home MVC Storefront

SubSonic: Added Wildcard Methods

With revision 482 I added in the ability to work with string values and "LIKE" queries a little more intuitively.

On a recent post, a commenter (liviu) left this comment:

"...Because Subsonic generated code i found it unexplainable why i cannot write something like:

Select<Product>().Where( Schema.Product.ProductName.StartsWith("a"))

or

Select<Product>().Where(Schema.Product.ProductType == "Cool Engine")."

The good news here is that in the second case it's a matter of me writing more complete samples. You can indeed do this query this way:

IList<Product> products=new Select().From<Product>()
   .Where(Product.ProductTypeColum).IsEqualTo("Cool Engine")
   .ExecuteTypedCollection<Product>();

However liviu is correct - you can't use "StartsWith" or "EndsWith" - until now. I just checked in changeset 481 which allows you to use StartsWith() and EndsWith() as well as ContainsString() - here's my Unit Tests:

 

        [Test]
        public void Select_Using_StartsWith_C_ShouldReturn_9_Records() {


            int records = new Select().From<Product>()
                .Where(Northwind.Product.ProductNameColumn).StartsWith("c")
                .GetRecordCount();
            Assert.AreEqual(9, records);
        }

        [Test]
        public void Select_Using_EndsWith_S_ShouldReturn_9_Records() {


            int records = new Select().From<Product>()
                .Where(Northwind.Product.ProductNameColumn)
                .EndsWith("s").GetRecordCount();
            Assert.AreEqual(9, records);
        }


        [Test]
        public void Select_Using_Contains_Ch_ShouldReturn_14_Records() {


            int records = new Select().From<Product>()
                .Where(Northwind.Product.ProductNameColumn)
                .ContainsString("ch").GetRecordCount();

            Assert.AreEqual(14, records);
        }

 

Many thanks to liviu for the suggestions!

Yitzchok avatar
Yitzchok says:
Thursday, August 21, 2008

Good stuff

But can you please stop hitting the DB for almost every Unit Test :) this just makes running all the test take really long...


Rob Conery avatar
Rob Conery says:
Thursday, August 21, 2008

I wish I could Yitzchok -if we don't construct the SQL and execute it then it sort of defeats the purpose of what we're doing. I know it's not optimal - but I'm not sure I have a choice here :).

Unless you're joking...


James Hall avatar
James Hall says:
Thursday, August 21, 2008

Hey Rob, quick question.

Is there a way in subsonic to do a delete like that?

i.e.

Delete<Product>().Where(Schema.Product.ProductType == "Cool Engine")

Me and a coworker have been working with just linq and the MVC stuff and the closest we could figure out was (in linq):

db.Products.DeleteOnSubmit(from p where p=cool engine select p);

or is there something we may be missing somewhere.

Linq has gotten us quite frustrated, and were possibly thinking of using subsonic.

any info would be super appreciated.


Brendan Kowitz avatar
Brendan Kowitz says:
Thursday, August 21, 2008

Rob,

Just a random thought on the syntax side of things: I was playing around the other week with using simple lambda expressions to add criterion to nhibernate's ICriteria. So I'm guessing that there's probably no reason why a set of extention methods wouldn't let you do the same sort of thing for subsonic, if you reeeeeealy wanted to...Then again, subsonics fluent API already looks intuitive enough. But, check it out if you're interested.


Martin Harris avatar
Martin Harris says:
Friday, August 22, 2008

Does .ContainsString() accommodate Full-Text Search?


Yitzchok avatar
Yitzchok says:
Friday, August 22, 2008

No I am NOT joking.

I would put two categories around tests that "should" be in SubSonic.

1. Integration Tests - This is the tests that can call the DB and this is what you would use if you want to create a new provider you have to make sure that it passes "these tests".

2. Unit Tests - "NO DB CALLS"

This is basically, "Is the right thing happening when you call this method? (Expectation)"

These are just tests that when you do something the correct thing happens.

Like when you call

.Where("ProductName").StartsWith("c")

You can check that the constraint collection has the correct value that you entered you don't have to call the DB for this.

That is also one of the reasons that people use TDD so that you know that the code is testable (so that you don't have to later go check the "Sql generated code" because the Sql is provider specific this can be tested by the Database).


Rob Conery avatar
Rob Conery says:
Friday, August 22, 2008

Thanks Yitzchok :). I hear where you're coming from - it's something I'm doing with the storefront. The thing here is that ultimately it comes down to the generated SQL - and the only way to do that is to run a string-compare, which is sorta nasty.

Ultimately no one's going to care, or find useful, the tests where I say "yes, there is indeed a constraint in the constraint collection". Moreover the string-compare will fail if I change providers - it will get written to accomodate that provider.

These aren't integration tests either - since there is no application/logic set to integrate with an external system. The dealio here is that I'm working with a platform - a data access platform - and with it comes the pain of having to use a live DB since that's what the platform's all about.

What I should be doing here is dropping/recreating the DB for every test run - that will keep the data errors out of it.

Bottom line - the tests are meaningless unless they round trip. SubSonic's not an app -it's a platform and I have to hit the db.


Yitzchok avatar
Yitzchok says:
Friday, August 22, 2008

@Rob

Yes it comes down to the sql generated but the Sql is provider specific so that means that you can't do a string comparison so you have to make a round trip to the DB to validate the Sql generated (but there can be some provider tests (Like SqlServerTests, MySqlTests, etc...) that can compare Sql).

>>Ultimately no one's going to care, or find useful, ...

This is not a correct statement I think

(Then I can put into the SQL in the aspx page ;) no one cares I just need the data shown on the page)

"You" should care, the tests are not only for showing how to use the framework it is to make sure that your system is stable and helps a lot in being maintainable and allows you to know the state of the system after making changes.

I think a test that you added the constraint to the collection and "that it has the correct value" is a good test (Maybe I am wrong but I think it is better than a test calling the DB it is just not needed in this situation) for example just in case something changes in that code you will know where it breaks and this can be done without hitting the DB.

Of course you also need tests that make sure that you correctly parse the SqlQuery to Sql but that doesn’t have anything to do with say the StartsWith("") constraint.

Integration Tests maybe I am using the wrong name but you are working with northwind like a client application for testing.

>>What I should be doing here is dropping/recreating the DB for every test run - that will keep the data errors out of it.

That can surely help with error in the tests

>>Bottom line - the tests are meaningless unless they round trip.

No I don't think so, and would say far far from meaningless, this is not ADO.NET this is an OR/M framework and does a lot more then Generating Sql and connection to the DB and there are parts of the system that can have good tests that don't connect at all to the DB.


Rob Conery avatar
Rob Conery says:
Friday, August 22, 2008

Thanks for the thoughts :) and I think we're talking past each other...

Yes you have a good point RE that granular tests are needed and you should never assume. I agree with you RE the granular tests - but it doesn't change the issue of hitting the DB, which is where you started this from.

I can't get away from hitting the DB - even if I do add the collection test. I need to validate the SQL somehow (and maybe there's a way I don't know of) and moreover need to demonstrate that the query generated will return the expected results.

In other words, ContainsString() can generate valid SQL "SELECT * FROM... WHERE field LIKE '%s %'" but this is not correct - is it (there's a space after the s)? The extra space in there (which actually happened to me) can only be nailed if you hit the DB and ask for records back. I know of no other way to do this and this is, by far, the most critical of any test: does SubSonic behave the way it needs to.

This is a bit different than an application - it's a framework and I need to focus on expected behavior, not necessarily "stability". This is because instability can be introduced (easily) but the user, however if SubSonic behaves the way it should - the user can mitigate the instability (and file a bug with us :).

Anyway - to answer your first point, I need to hit the DB and to be honest with you time is not a concern for me with that :).


Yitzchok avatar
Yitzchok says:
Sunday, August 24, 2008

I know that some tests must call the database but by far almost every test.

"You could test every thing by calling the DB but then you are giving every test 10 places to fail that is not a good test."

--------

Test Type 1.

Example: When interacting with SqlQuery you make sure that the class is doing what it is expected to do within its

context (No Sql/DB Tests) to know that my object is in the right state and that the generator has the right information

to even think about generating correct Sql. Maybe SqlQuery is doing the wrong thing? do you have to go to the DB for

this? that's not his job SqlQuery knows nothing about the DB (at least most of the time except for Execute* and I think

Execute* should be a proxy to another class with methods that take a SqlQuery so that it is easy to access).

Test Type 2.

Example: SqlGenerator this goes off to the DB and should be tested against all the providers (Not DB specific)

(Really this class just generates Sql but that is the providers problem so there is no way to test this with out the DB)

So if a *DBProvider passes all these tests then it should work fine with SubSonic.

Test Type 3.

Example: Sql2005Generator (DB specific) this can do sql compare and access the DB.

Test Type 4.

Can the response from the DB be correctly interpreted (converted to the Strongly Typed Object, etc...).

Now that you have all the parts tested separately now they should just run together with no problems (well should :))

--------

>>ContainsString() can generate valid SQL ... "%s %" but this is not correct

[Test] void SqlQuery_ContainsString_should_not_contain_extra_blanks(){

SqlQuery sql = new Select().From<Product>()

.Where(Northwind.Product.ProductNameColumn).ContainsString("s");

string wildCard = query.Provider.GetWildCard();

Constraint constraint = sql.Constraints[0];

Assert.AreEquals(string.Format("{0}{1}{0}", wildCard , "s"), constraint.ParameterValue);

Assert.AreEquals(Comparison.Like, constraint.Comparison);

...}

Now do you have to call the DB for that?

>>the user can mitigate the instability (and file a bug with us :).

So you are using your users as your testing team :) so why do we need unit tests going back to the days before Unit

Tests ;)

>>I need to hit the DB and to be honest with you time is not a concern for me with that :).

That's if you run all the tests once before you make a check in but if you run all tests whenever you make any changes to the code (Like refactoring) it really takes a long time and you will stop running it only when you really need to.

Sorry for wasting your time :)


Rob Conery avatar
Rob Conery says:
Sunday, August 24, 2008

>>>So you are using your users as your testing team :)<<<

That was uncalled for :p

>>>Sorry for wasting your time :)<<<

Never - you're my favorite :).

As I mention - I'm all for more tests and as I also said - I agree with you on this. To me, however, the only tests I care about are the ones that hit the DB :).

That said - please welcome our new committer (though I'm not sure why you weren't one before). Have at the tests - love them, cuddle them, do your Yitzchok thing :).



Search Me
Subscribe

Index Of MVC Screencasts

You can watch all of the MVC Screencasts up at ASP.NET, and even leave comments if you like.

Popular Posts
 
My Tweets
  • @haacked must.... resist... assimilation...
  • Dinner at the Haacks. How did Phil get such a cute kid? Evidently Phil's in the doghouse though...
  • @shanselman dude turn off twitter and drive! that's gotta be illegal!
  • For D'Arcy and Justice... Scottgu goes Canuck! http://twitpic.com/mfz1
  • Working in ScottGu's office with @shanselman. Wearing an Orange Polo and saying "go ahead" a lot for some reason.
  About Me



Hi! My name is Rob Conery and I work at Microsoft. I am the Creator of SubSonic and was the Chief Architect of the Commerce Starter Kit (a free, Open Source eCommerce platform for .NET)

I live in Kauai, HI with my family, and when my clients aren't looking, I sometimes write things on my blog (giving away secrets of incalculable value).