Hanalei, Hawaii Tuesday, February 09, 2010

SubSonic: Version 2.1 (Pakala) Preview: The New Query Tool

Eric and I have been working just about non-stop on our bug list as well as some very cool innovations that he and I have cooked up over the last six months. We've had to wait a bit for the dust to settle in the .

Eric and I have been working just about non-stop on our bug list as well as some very cool innovations that he and I have cooked up over the last six months. We've had to wait a bit for the dust to settle in the .NET world (not to mention our personal lives), but I think the wait has been worth it. Over the holidays I was able to create the querying API I've been thinking on for the last year, and it has come together nicely. I'd like to show you what I've come up with, and ask for your comments and help before we release it with version 2.1 of SubSonic.

 

The Next Rev
This is going to be a long post, so I'm going to be as brief here as I can. We have an issue with how to rev SubSonic with respect to loving both .NET 2.0 and .NET 3.0. I've noodled on this a lot, and I think our current plan is the smartest:

Rev 2.1 (Pakala) will be the last .NET 2.0 version of SubSonic. We will support bugs, as always, and will release support patches, but this is the last major rev for .NET 2.0.

I'll talk more about 3.0 stuff in a later post - for now I want to focus on the upcoming good stuff. One thing you might note is that I've decided to go ahead and finish what I started with the new Query tool. I had some inspirations over the holidays (and lots of boring hours to fill) and I fiendishly created (what I think) is our coolest feature yet: a very LINQy Query Tool for .NET 2.0.

 

The New Query Tool: Query2
I was pretty bummed out after writing my post on which tool to use for your Data Access using MVC. Don't get me wrong - I really love everybody, but I'm a selfish brat and I really want SubSonic to be world class and at the top of the list when it comes to ... well everything :). So over the holidays I completely geeked out (more like OCD overdosed on caffiene and chocolate) and finished up this project that's been sitting around for far too long.

My "story" - or focus - was simple:

  • Create a "LINQy" experience for .NET 2.0, using every trick I could think of to create a fluent API
  • Create a query tool where you would be able to do 95% of all the queries you'd need, in a readable, discoverable way (which is up from 80% with our initial release)
  • Make Jeff Atwood praise me :):) and SubSonic for not having any "Object Noise" and creating something that's very, very close to SQL (and in which you can even "just use sql").

That's it really - I'm selfish and I want Jeff Atwood to love me. That's the secret sauce in SubSonic.

Being serious for a second - I get asked about LINQ a whole bunch. We have a plan for LINQ - specifically LINQ to Sql - and as I mention above I'll get into that in a later post. For now please know that this query tool is for .NET 2.0 (though it will indeedy be usable in 3.0). I'm not into competing with Microsoft (and never have been) - they (we) can do the heavy lifting (create the platform) - I'll just make it pretty.

 

Query Factories
With Pakala, I've updated the code generation bits so that you have a factory built off of your Provider. So if we're using Northwind (and our namespace is Northwind), you can use:

  • Northwind.DB.Select
  • Northwind.DB.Insert
  • Northwind.DB.Update
  • Northwind.DB.Delete
  • Northwind.DB.Query (an adhoc query - I'll explain more below)

 

The Code, Please
Joins, Inserts, Updates, Typed Lists, Collections, INs, Nested Statements, Parametered Literals - It's all in there. Rather than babble - here's the code that shows what you can do. I've tried to make this very readable - hopefully you can tell what's going on:

Northwind.ProductCollection products = 
    Northwind.DB.Select().From("Products")
    .Where("categoryID").IsEqualTo(5)
    .And("productid").IsGreaterThan(50)
    .ExecuteAsCollection<Northwind.ProductCollection>();

 

A Joined Collection:

Northwind.CustomerCollection customersByCategory = new Select()
    .From(Northwind.Customer.Schema)
    .InnerJoin(Northwind.Order.Schema)
    .InnerJoin(Northwind.OrderDetail.OrderIDColumn, Northwind.Order.OrderIDColumn)
    .InnerJoin(Northwind.Product.ProductIDColumn, Northwind.OrderDetail.ProductIDColumn)
    .Where("CategoryID").IsEqualTo(5)
    .ExecuteAsCollection<Northwind.CustomerCollection>();

You can use any type of JOIN you want here - Inner, Outer, Right/Left Inner/Outer, Cross, Unequal - it's up to you.

And now (this is where we get sorta LINQy) - you can pass in any "type" of object you want, and have it returned in a typed list. So let's say we make a condensed class:

class TestProduct {
    private int _id;
    public int ProductID {
        get { return _id; }
        set { _id = value; }
    }
    private string _name;
    public string ProductName {
        get { return _name; }
        set { _name = value; }
    }
    private decimal _price;
    public decimal UnitPrice {
        get { return _price; }
        set { _price = value; }
    }
}

You can use this class in a Query expression:

List<TestProduct> result = new 
    Select("productid", "productname", "unitprice")
    .From(Northwind.Product.Schema)
    .ExecuteTypedList<TestProduct>();

 

Constraint Expressions
If you have some complicated expressions, you can add those in too:

Northwind.ProductCollection products = new Select(Northwind.Product.Schema)
    .WhereExpression("categoryID").IsEqualTo(5).And("productid").IsGreaterThan(10)
    .OrExpression("categoryID").IsEqualTo(2).And("productID").IsBetweenAnd(2, 5)
    .ExecuteAsCollection<Northwind.ProductCollection>();

Anything following "WhereExpression" (or Or/AndExpression) will be wrapped in parentheses. You can close the expression by using "CloseExpression()", or it will be closed for you if another is started (as with OrExpression above) or if the query ends.

 

Aggregates
You can use any Aggregate function you want as well:

double result = new 
    Select(Aggregate.Sum("UnitPrice*Quantity", "ProductSales"))
    .From(Northwind.OrderDetail.Schema)
    .ExecuteScalar<double>();

I should mention here that you can "new up" the query (using new Select()...) or you can use Northwind.DB.Select if you like the factory approach - it's up to you.

 

Paging
IDataReader rdr = Northwind.DB.Select("ProductId", "ProductName", "CategoryName")
    .From("Products")
    .InnerJoin(Northwind.Category.Schema)
    .Paged(1, 20)
    .ExecuteReader();

I should mention here that we're taking into account which DB you're using, and if it's SQL 2005, we'll use ROW_COUNT to generate the paged bits (which is something people have asked for a lot).

 

Params - I <3 The Params Keyword
I've gone nuts with the "params" keyword - which allows you flexibility when sending values in as an argument. You can see this in action with our Select() constructor (see above):

Select("productid", "productname", "unitprice")

I've added it the In() and NotIn() constraint methods as well:

int records = new Select().From(Northwind.Product.Schema)
    .Where("productid").In(1, 2, 3, 4, 5)
    .GetRecordCount();

Another fun way to use In() is with a nested Select() - this is recursive by the way, so go nuts if you like:

int records = new Select(Northwind.Product.Schema)
    .Where("productid")
    .In(
        new Select("productid").From(Northwind.Product.Schema)
        .Where("categoryid").IsEqualTo(5)
        )
    .GetRecordCount();

 

Insert uses params and nested Select() statements as well:

int recordsAffected = new Insert().Into(Northwind.Category.Schema)
    .Values("Test", "TestDescription", DBNull.Value)
    .Execute();
int recordsAffected = new Insert().Into(Northwind.Category.Schema)
    .Select(new Select("CategoryName", "Description", "Picture").From(Northwind.Category.Schema))
    .Execute();

You may have noticed that you can use one of three things in a Select() constructor - a column list, a table schema, or nothing at all. If you pass in the schema you can save yourself from typing "From(...)" but you lose a little readability. If you pass in nothing then you'll need to pass in a from - either way, with the latter two, a fully qualified column list will be built for you.

Updates are pretty simple as well:

int recordsAffected = new Update(Northwind.Product.Schema)
    .Set("UnitPrice").EqualTo(100)
    .Where("productid").IsEqualTo(1).Execute();

We've been asked a few times to support SQL Expressions here - in other words allowing for "UPDATE table set column=column+1", and you can now do that using SetExpression:

int records = new Update(Northwind.Product.Schema)
    .SetExpression("UnitPrice").EqualTo("UnitPrice * 3")
    .Where("productid").IsEqualTo(1)
    .Execute();

Deletes work much the same way - and hopefully you're seeing a pattern here:

int recordsAffected = Northwind.DB.Delete()
    .From(Northwind.Region.Schema)
    .Where("regiondescription").Like("test%")
    .Execute();

 

Doing It All At Once With InlineQuery
One thing we didn't have, necessarily, is a "back door" in SubSonic so you could let yourself out after coding yourself into a corner :). This can happen with any ORM tool and it's one major reason I've embraced Views and Stored Procs for the more complex stuff. But sometimes (if you're Jeff Atwood) you might not want to deal with our API - just get your data.

For you, I made InlineQuery. It will execute your query happily, and parameterize it too so you don't get yourself SQL-injected:

Northwind.ProductCollection products=
   new InlineQuery()
    .ExecuteAsCollection<Northwind.ProductCollection>
    ("SELECT productID from products WHERE productid=@productid", 1);

SubSonic will see "@productid" and build a parameterized statement for it on the fly, then execute the results into a collection for you. Embedding SQL is almost never optimal - but if you need it, we got ya covered.

In some cases this can work in your favor, however. Each of the methods you've seen (Select, Update, Insert, Delete) has the ability to return their SQL by using "BuildSqlStatement()". You can use this for batch operations - like multiple inserts:

StringBuilder sb = new StringBuilder();
//insert a test product

sb.AppendLine(new Insert().Into(Northwind.Region.Schema).ValueExpression("'test1'").BuildSqlStatement());
sb.AppendLine(new Insert().Into(Northwind.Region.Schema).ValueExpression("'test2'").BuildSqlStatement());
sb.AppendLine(new Insert().Into(Northwind.Region.Schema).ValueExpression("'test3'").BuildSqlStatement());
sb.AppendLine(new Insert().Into(Northwind.Region.Schema).ValueExpression("'test4'").BuildSqlStatement());
sb.AppendLine(new Insert().Into(Northwind.Region.Schema).ValueExpression("'test5'").BuildSqlStatement());
sb.AppendLine(new Insert().Into(Northwind.Region.Schema).ValueExpression("'test6'").BuildSqlStatement());
string sql = sb.ToString();

//insert the values
new InlineQuery().Execute(sql);

There are a lot of ways that these things can work together - and hopefully you're starting to see the "interop" pattern I'm going for here.

 

Transactions
Transaction work as they always have with SubSonic, but I've added a quick way to do it so you don't have to keep writing all that using{} code:

List<Insert> queries = new List<Insert>();
queries.Add(new Insert().Into(Northwind.Region.Schema).Values("test1"));
queries.Add(new Insert().Into(Northwind.Region.Schema).Values("test2"));
queries.Add(new Insert().Into(Northwind.Region.Schema).Values("test3"));
queries.Add(new Insert().Into(Northwind.Region.Schema).Values("test4"));
queries.Add(new Insert().Into(Northwind.Region.Schema).Values("test5"));
queries.Add(new Insert().Into(Northwind.Region.Schema).Values("test6"));
queries.Add(new Insert().Into(Northwind.Region.Schema).Values("test7"));

//execute in a transaction
SqlQuery.ExecuteTransaction(queries);

 

When?
Well, that's up to you in large part. I've touched on perhaps 60% of the functionality we now have with this tool in this post, and that means I need to test it to the nines before it goes out the door. I have about 44 unit tests (if there's a chance you can have negative code coverage... well I'm close) - but need a whole bunch more before I'm happy with it. If you'd like to help push this out the door, you can:

Patches are the life blood of our project - and I'd be extremely happy to have yours and/or any documentation you'd like to help with. If you're into it - leave a message on our forums and I'll make sure to sign you up!


Matt - January 11, 2008 - Very cool Rob. Very cool.
Allan - January 11, 2008 - Sweetness! Great stuff Rob. Can't wait. will be checking out SVN soon for a first-hand look.
adminjew - January 11, 2008 - This looks great I think I will have to stay up a few nights to play with this goodness:) Thanks a bunch Eric & Rob
John S. - January 11, 2008 - Awesome. Simply, awesome. Still dying to hear about your plans with the MVC templates. I've got a couple projects build around them now so here's hoping I can do all this sweet new query stuff in a separate controller.
josh - January 11, 2008 - interesting.. this is going straight to the top of my list of things to try. (the query stuff)
Shawn Oster - January 11, 2008 - "Isn't she lovely... Isn't she beautiful..."

Figured a little Stevie Wonder was in order for this announcement :)

I haven't cracked open the source yet but I'm assuming the old syntax is still around as far as querying directly off the collections? Something like:

new ProductCollection().Where("categoryID", 1).Load();

Other than that the first thing that jumped out at me looking at your sample code is if there is anyway to shorten the queries from:

new Select(Northwind.Product.Schema)

to just:

new Select(Northwind.Product)

Get it? Perhaps some type-checking magic and always looking down into the Schema property if it's there? I just had an odd flash of Ruby in my head for something like this, but that really won't help us here :) Anyway, since I actually have had to query the actual schema of a table vs. it's data (when creating a db modeling tool) it doesn't read as well for me to see Schema there.

OK, last bits before I head into the forums, seems there are some missing dangling Rob bits hanging out in the project, like RobTests and RobsSite and something called SubSonic.Documenter. I only noticed because 2008 complained when it was converting the project.
Elmar - January 11, 2008 - Thanks Rob. Very appreciated! This is great news and the query tool is cooler then I could ever dream of. Is "query tool" still the right word for all of this?
Denny Ferrassoli - January 11, 2008 - Awesome job! I really like what you've done with the Query tool. One question, since I can't take a look at the source just yet... Is ExecuteTypedList() available as a Stored Procedure method? Thanks!
Rob Conery - January 11, 2008 - @Shawn: ha! This is one of those things that makes Eric hate me - I screwed up the .sln file and the project's you're talking about are silly little things I have here locally :). Ignore em. If you look at Select.From() - I believe this is what you're asking for - I toyed with this (it's still in the source) but the weirdness here is readability. What do you think? I can pass a type (since that's what Northwind.Product is) but if you do that... well that's generics! @Elmar: You can call it... "Tim" :)
Anders - January 11, 2008 - Do you have any plans to support operator overloading queries as NHibernate Query Generator does?
ab - January 11, 2008 - Hi Rob, Excellent work. Must say I am new to SubSonic and am loving it. May thanks for the time and effort you and others have put in to such a great product. I have a question and hope you have the time to answer it. Is it possible to have transactions wrapping multiple calls that return ids for relationally linked objects. e.g. Insert customer object returns new id of customer. Insert customerproducts with previously returned id of customer. Currently have to implement this in a procedure and wrap a transaction around this. If there is a database change then you have to go through all procedures checking that any changes may have a problem. Is it possible not to have a transaction property that can be set on each object and then called to use on the next object and then commited. e.g. (without rollbacks etc...) SqlTransaction tran = new SqlTransaction(); tran = conn.BeginTransaction(); Customer cust = new Customer(); cust.Surname = 'NewSurname'; cust.Transaction = tran; cust.Save(); CustomerProduct custProd = new CustomerProduct(); custProd.ProdId = 1; custProd.Transaction = tran; custProd.Save(); tran.Commit(); I have implemented this in my own automatic generation but not sure how complicated it would be in SubSonic as I haven't looked at the engine. My apologies if you have answered this before elsewhere.
ab - January 11, 2008 - Missed out: custProd.CustId = cust.Id;
Sergey - January 11, 2008 - @Rob Conery

Fantastic update! I'm doing quite a big project using SubSonic and it rocks! Thanx for JOINs, it's really improvement #1 for me.

Are you going to support not only In operator, but NotIn as well? It's really quite important for real world applications.

Thanx for your bits one more time!
Phil - January 11, 2008 - Nice work Rob! (Nice Monty Python reference too) I was looking at your examples and noticed this: List result = ... .ExecuteTypedList(); Have you considered returning Collection (or ReadOnlyCollection) instead of List? I believe FxCop recommends the former for public APIs. So this could read like this: Collection result = ... .ExecuteCollection(); though I guess it could get confusing when there also code like the following from one of your other examples: Northwind.ProductCollection products = ... .ExecuteAsCollection();
mike - January 11, 2008 - Why not make the method generic? Is that possible? Select().etc.etc It looks very nice, I have some feedback that I hope you will appreciate: 1. CloseExpression(): seems like a code smell to me. 2. Paged(1, 20): awesome! 3. Where("productid").In(1, 2, 3, 4, 5): more awesome! 4. SetExpression: cool! 5. BuildSqlStatement(): What about ToString()? 6. SqlQuery.ExecuteTransaction(queries): Very nice! All in all, very good work, congratulations on coming this far and good luck with the release. Thank you!
tuan - January 11, 2008 - Hi Rob, what an awesome job you guys has done, have been checking out your blog everyday for this new release. I am actually building my web application using Subsonic with the MVC template, so I wonder what the this new version will do with the MVC template, will it take me a long time for upgrading from 2.03 to this 2.1 version. Great thanks for you guys again, will try the best to do the unit test.
Parag kantharia - January 11, 2008 - Hi Rob, Great News. I am specially more interested in 3.0 stuff. You had blogged in November about the following things shaping up. I also downloaded the movie and watched it several times. * Migrations * MVC Scaffold Generation * Core site design what are the fresh news with it. Can you kindly blog more on this, also waiting to hear more on the book about Subsonic and MVC. Thanks Parag Kantharia
Joe - January 11, 2008 - WOW! I have explored SubSonic in the past but was always turned off by the query mechanisms. I think have been spoiled by LLBLGen's robust object based queries. Will definitely give SubSonic another try. Awesome! Joe
mikedopp - January 11, 2008 - Rob Keep up the good work. Thanks for all you do. !mike
Mike Griffin - January 11, 2008 - Hmmm, that looks just like our API. I guess we should take it as a compliment, our users Love it.

Mike Griffin
EntitySpaces LLC
http://www.entityspaces.net
Chris - January 11, 2008 - All my database are belong to u!!!

Love it man... lov et...
Chris - January 11, 2008 - In downloading the bits I noticed that SubStage is using Krypton. Do I need to download or installing anything? Is it free? What is SubStage?

-Chris
Rob Conery - January 11, 2008 - @Mike Griffin: I am guessing we have the same goal in mind: SQL Readability. For what it's worth I don't know from Entity Spaces :). @Chris: Krypton/SubStage is a little Eric Kemp project that he'll update you on :) @mike: Yah, CloseExpression() is something I tried to get around, but really can't. I've tried to make it as implicit as I can, but there has to be a way to tag on a ")" and that's what I came up with. I am toying with generics right now but really don't like the readability: Select() seems a little wonky - but it's simple enough to add and I spose I can let y'all decide that. @Sergey: Yes, In and NotIn are "In there" @ab: yes - the transaction question you have is possible - take a look at our Transaction docs (see link above). @Denny: We have some StoredProc love coming - and yes this will be possible.
Jon Galloway - January 11, 2008 - Really cool, Rob. I'm McLovin it.

I agree with Shawn, I think Select(Northwind.Product) is more readable than Select(Northwind.Product.Schema) and it's pretty obvious what it's doing.
ESICO - January 11, 2008 - @rob, glad you think generics aint the most readable stuff.

btw. from what i'm seeing now this new query tool (as do E Spaces) beats linq qua readability and thats a really really good thing.
que0x - January 11, 2008 - thanks for the post ! i started to lose hope about subsonic future lately
Ibleif - January 11, 2008 - This is very cool news.

The new query tool with simple JOIN support will kick most of my Stored Procedures back to hell! :-)

I have a couple of sites running smoothly on the 2.0.3 release, and would of course like to update/upgrade to the 2.1 release when ready.
Like tuan asks: Will this be possible at all? ... and do you even consider this when creating the new 2.1 version ?
Rob Conery - January 11, 2008 - @Ibleif: All the current bits will remain - this is essentially an "add on".
Chris Brandsma - January 11, 2008 - Im in ur tests maken em better... OK, I downloaded the code, already had it on Tortoise speed-dial anyway. I installed Northwind and Pubs on my SQL Server I disabled SubStage -- I'd also like a clue about that one, but there is nothing there right now, so I don't care as much right now. Created a SubSonic user to access Northwind. I tried to run the scripts in SubSonic.Tests\DBScripts, but the data ones no go. And over half the tests fail. Products.DateCreated doesn't exist in database. Am thinking you need a Nant setup script to help get things started. Maybe something like what Jeffery Palarmo did for his CodeCampServer project. Just a thought.
Geri Langlois - January 11, 2008 - Hey Rob,
This is great stuff -- I can't wait to try it.

Quick question -- I know that you said that this is an "add-on" so I assume it's non-breaking. I'm also hoping that everything (old and new) will play nice inside a using statement like this --
using (SubSonic.SharedDbConnectionScope scope = new SubSonic.SharedDbConnectionScope(conn))

Please tell me it is so...
Rob Conery - January 11, 2008 - @Geri: it is so :)
JC Grubbs - January 12, 2008 - I can't wait to check out the repo and build this motha. I actually like it a lot better than the LINQ syntax, much cleaner and I like that it's not language extension just good ol' fashion object oriented goodness. Keep up the good work ;)
Zack Owens - January 12, 2008 - Ahh... this will make migrations so easy to use :)
Jim Hollander - January 13, 2008 - This is awesome stuff!
OmegaSupreme - January 13, 2008 - Ubelievably cool, amazing work. Can't wait to get my grubby mits on it :D
Robert - January 14, 2008 - Awesome. Love support for natural joins.
mike - January 14, 2008 - Angle brackets are not displaying, so for all of you wondering about my proposal, subsitute square brackets with angle brackets to get generics: select[MyType]()
Rob Conery - January 14, 2008 - Hey Mike- I'm adding that in right now. Sometimes generics aren't the most readable things but it's type-safe so I think it's a good thing. I already have it in From() but I'll add everywhere I can.
Willie Tilton - January 17, 2008 - Hey Rob, was excited to try it out, got the latest source and tried: return new Select() .From(Data.Views.MyView) .Where(Data.MyView.Columns.CompletedBy).IsNotEqualTo("") .And(Data.MyView.Columns.TableId) .In( new Select(Data.MyTable.Columns.TableId) .From(Tables.MyTable) .Where(Data.MyTable.Columns.UserId).IsEqualTo(userId) ) .ExecuteAsCollection(); and got error: Object must implement IConvertible, which seems specific to the In nested Select object. Maybe because it's a view?
Rob Conery - January 18, 2008 - Hmmm - IConvertible happens when you pass in a param as a string and it's supposed to be an int (for example). What's userID?
Charles Nurse - January 19, 2008 - Looks Good Rob I started looking at SubSonic because it was a Data Layer that was "flexible" because of the Query class - and now it looks like you've extended that to the Max
Shalalai - January 20, 2008 -
Jordan Brough - January 22, 2008 - With the new query tool, how would one create a query like this? -- SELECT SUM(Cost) FROM Products WHERE ProductType = 'Album' ? My 'WHERE' gets converted into a 'HAVING' and fails. My equivalent SubSonic Code: int sum = new Select(Aggregate.Sum("Cost")) .From(Northwind.OrderDetail.Schema) .Where("ProductType").IsEqualTo("Album")) .ExecuteScalar() Am I missing something?
Willie Tilton - January 28, 2008 - It's a GUID. Still happening...maybe I need to throw in strings? It's probably me, I'm just not sure how to use it and am too lazy to inspect the source :P
Willie Tilton - January 31, 2008 - That was it, I'm passing in a Guid into the IsEqualTo method and it fires off the IConvertable exception. If I do a ToString() on the Guid it works fine.
ReTox - February 4, 2008 - "I should mention here that we're taking into account which DB you're using, and if it's SQL 2005, we'll use ROW_COUNT to generate the paged bits (which is something people have asked for a lot)."

SQLServer 2000 also has a support for ROWCOUNT:

CREATE PROCEDURE ListLatestMembers
@top int
AS
SET ROWCOUNT @top
select * from user
order by joined desc
-- never forget to set it back to 0!
SET ROWCOUNT 0
GO
SubC - March 17, 2008 - It's March. Will we see 2.1 in May? June? Thanks!
AlexCode - March 17, 2008 - Hi, this is great stuff Rob. Just one question, will "old" 2.0.x code be supported or some of it will blow? Thanks! Alex
linmesss - March 23, 2008 - I'am Using Sqlite, And Find Paging Not Working So well With This The New Query Tool. Deal To The Source I Suggesst Using the Follow Code insteted of SqlLiteGenerator To Fix It. /* * SubSonic - http://subsonicproject.com * * The contents of this file are subject to the Mozilla Public * License Version 1.1 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. */ namespace SubSonic { /// /// /// public class SqlLiteGenerator : ANSISqlGenerator { /// /// Initializes a new instance of the class. /// /// The query. public SqlLiteGenerator(SqlQuery query) : base(query) { } public override string BuildPagedSelectStatement() { string sql; //build the command string sql = GenerateCommandLine(); sql = GenerateFromList(); sql = GenerateJoins(); if (query.Aggregates.Count > 0) sql = GenerateGroupBy() System.Environment.NewLine; sql = GenerateConstraints(); sql = GenerateOrderBy(); return sql string.Format(" Limit {0} Offset {1} ", query.PageSize, (query.CurrentPage - 1) * query.PageSize); } } }
Ahsan - May 1, 2009 - It seems Parameterized InlineQuery works only for SQL Server. Is that correct? I was trying to utilize InlineQuery in order to take advantage of functions such SUBSTR
Jitendra - October 25, 2009 - Hi Rob,

I have a situation where I have two columns in a table linked to same primary key in other table, I am stuck with joins here.

Is it possible to join same table twice?? if so, how?
Gecko