Friday, November 07, 2008 -
I've been working a lot over the last few months on our next rev of SubSonic, and I think I have something that's good enough to issue as a preview. Please understand that this is a preview in every sense of the word, and may shatter into a zillion pieces and make you want to pour your Red Bull over my head. This is bleeding edge edge crazy talk. It's everything bad that wakes you up screaming at night, and quite possibly is the coolest thing I've ever made... or somewhere in between. Just know that if you download and play with it, you're in for a ride... maybe. Or maybe it will shine like a million suns at the dawn of man...
SubSonic 3.0 Is...
A complete rewrite. The culmination of the things I've learned over the last year as I've (once again)
Changed Everything and shared some wonderful Koolaid with my Alt.NET buddies :). Seriously - I've ripped everything out and redone it, with a focus on:
(Not in that order - but generally I've placed an emphasis on simplicity). I've installed interfaces everywhere, and focused on making everything lightweight, testable, and injectable. I hope. We'll see.
Overall I've had exactly 5 false starts. I literally have 6 folders on my hard drive right now, each named "SubSonic 3.0 [X]" where "X" is the iteration number. Sometimes it's a swear word - but I won't go there now. Linq is hard - no really - just ask Ayende. It's not a matter of smarts - it's just really hard to try to fit Linq in... trust me. I was hoping originally that I could just create an Expression parser and build stuff using our core bits... but umm... that didn't happen.
One of the blogger people I've been following as I've been working through all of this is Matt Warren, the Zeus of Linq To Sql. He has a great series of posts on how to implement IQueryable, and when he got to post number 9 I began to think I should just use his code wholesale (yes, I know it's cheap of me). He's now on post number 11, and I asked him a while back if I could just steal all of his code - and this is why Matt Warren is my personal Santa Claus - he said "yes - it's all under MS-PL anyway. Just forward the license".
So there it is - the SubSonic.Linq namespace is 99.999999% Matt Warren's code. I've tweaked it a touch to work with our core Query stuff, but everything else is him and his large brain. Thank him for this and have a read on what you get with this provider.
My philosophy while developing SubSonic 3.0 is this:
There it is. And I want it to be stupid simple.
SubSonic 3.0 Is Not...
I think this is as important as what I want to do. So, what I don't want to do is:
Onward! To Some Code!
The Setup
To use SubSonic 3.0 you'll need to do exactly 3 things (assuming you're using .NET 3.5, which is required):
No providers, no funkiness. That's it!
Generated Code
There are fiveT4 templates as of right now (this will probably change), and they are:
There's a lot more you can do here - and that leads me to the next thought...
This Aint No ORM Disco
The "Big Guy" ORMs (NHibernate, EF, Linq To Sql) keep track of an object's state and interacts with the DB in what's known as a "Unit of Work". This can be great sometimes, a PITA others. I've realized that ORMs are a slippery slope in this regard, and no one can really agree on what they should really do, when, and why.
So I've decided to focus one step below and try to make data access and manipulation silly simple. That way, in the future, maybe I (or you!) can work up some cool ORM templates to sit on top of the core SubSonic bits, and we can all share and have a party.
Just know that my focus, for right now, is to make working with the DB bloody crazy simple. The more complicated stuff can come later (eager loading, unit of work, etc) in the form of T4 templates.
Enough Already - Show Me Some Code!
The meat of the system is worked up by Provider.tt. This is a "wrapper" for your database and allows you to work with IQueryable and SubSonic at the same time:
namespace Northwind{ public partial class DB{ public IDataProvider DataProvider = new DbDataProvider("Northwind"); public DbQueryProvider provider; public Query<CustomerDemographics> CustomerDemographics; public Query<Region> Region; public Query<Employees> Employees; public Query<Categories> Categories; public Query<Customers> Customers; public Query<Shippers> Shippers; public Query<Suppliers> Suppliers; public Query<EmployeeTerritories> EmployeeTerritories; public Query<Order_Details> Order_Details; public Query<CustomerCustomerDemo> CustomerCustomerDemo; public Query<Territories> Territories; public Query<Orders> Orders; public Query<Products> Products; public Select Select() { return new Select(DataProvider); } public Insert Insert() { return new Insert(DataProvider); } public Update<T> Update<T>() where T:new(){ return new Update<T>(DataProvider); } public Delete Delete(){ return new Delete(DataProvider); }
There's more that's generated here - but I'll get to that in a second. To use IQueryable, you do what you normally do with Linq To Sql:
Northwind.DB db = new Northwind.DB(); var products = from p in db.Products where p.CategoryID == 5 select p; foreach (var p in products) { Console.WriteLine(p.ProductName); }
Which produces what you might think (using delayed execution):
Nothing really extraordinary about this - we've seen it before :). Most things are covered using the Linq provider that Matt started, but I'm sure there are some things missing - the good news there is that you have SubSonic's query tool to back you up if you get stuck (which is a lot of fun since you're not locked into one way of doing things now):
var products2 = db.Select.From<Northwind.Products>()
.Where(Northwind.ProductsTable.CategoryID)
.IsEqualTo(5)
.ExecuteTypedList<Northwind.Products>();
Note that you can also just use a string in the Where() statement.
And if you really get stuck, we've got your back so you can avoid all of the "Proprietary Object Noise" (with apologies to Jeff Atwood):
var products3 = new CodingHorror("SELECT * FROM Products WHERE CategoryID=@id", 5) .ExecuteTypedList<Northwind.Products>(); foreach (var p in products2) { Console.WriteLine(p.ProductName); }
I Like Lambdas
I've tried to rethink everything in terms of how the bits are put together so we can flex the changes in the language. To that end I've included lambdas where appropriate to add some type-safety etc. I know some people don't like em, but they're optional, as always.
db.Insert.Into<Northwind.Region>(x => x.RegionID, x => x.RegionDescription)
.Values(6, "Hawaii").Execute();
object avg = db.Avg<Northwind.Products>(x => x.ProductID);
The latter example here is why I love lambdas so much - they can (if implemented properly) "terse up" your code nicely. You could use Linq on db.Products for this as well - but I like the terseness of this one line :).
All of our aggregates are now reachable from the db wrapper class:
db.Avg<Northwind.Products>(x => x.ProductID);
db.Sum<Northwind.Products>(x => x.ProductID);
db.Count<Northwind.Products>(x => x.ProductID);
db.Min<Northwind.Products>(x => x.ProductID);
db.Max<Northwind.Products>(x => x.ProductID);
db.Variance<Northwind.Products>(x => x.ProductID);
db.StandardDeviation<Northwind.Products>(x => x.ProductID);
The best part is that if you don't like any of this, just change your template! It's all right there for you to tweak and alter to fit your project, which is a major goal of mine here: I want to give you the power to use SubSonic as you please. And if you do it better than us (which I'm sure you will) - perhaps you'll share. My focus is on the engine - you guys can hop it up with chrome and neon.
The biggest support issue we've had is with regards to our generated stuff. Specifically how we name objects. Our code generation is now completely transparent so if you don't like it - you can change the name of that table to be what you like!
Using System.Data.Common
At the core of the flexibility we now have is our use of System.Data.Common and the DataFactory pattern. If you don't know what this is, have a read here. The idea behind System.Data.Common is that we shouldn't have to rewrite all of our data access stuff if we happen to change database platforms. To that end the DataFactory pattern was created so that ADO data provider developers could write against a common API, and you could benefit.
We've used that to our advantage with SubSonic 3.0. So if you need to switch your DB provider, for instance, you can change from this:
<connectionStrings> <add name="Northwind"
connectionString="server=.\SQLExpress;Integrated Security=true;database=northwind;" providerName="System.Data.SqlClient"/> </connectionStrings>
To this (provided you have the DLL in your /bin):
<connectionStrings> <add name="Northwind"
connectionString="server=localhost;user id=root;pwd=HAHA?;database=northwind;" providerName="MySql.Data.MySqlClient"/> </connectionStrings>
There is no other setting - no "provider tweaks", no jiggity dances and prayers. The only assumption here is that you have the same DB on your target platform (which I do - I've ported Northwind to MySQL) and run:
Most database providers support ADO.NET 2.0 and the Data Factory. Some off the top of my head that I know of are MySQL, Oracle, and SqlLite and I'm sure there are others - I just don't know.
You can download SubSonic 3.0, Preview 1 here.
We're still actively working on it and I'm sure you'll encounter bugs, etc. Feel free to send me an email (see the README) and please try to let me know what you were doing in the context of Northwind, so I can repro the issue.
As always, please be patient.
Epilogue: SubSonic.Sugar is now SubSonic.Extensions.
Wanna have some fun? Have a look at the SubSonic.Extension methods that I've ported from the Sugar class and turned into applicable extensions. To use them, you'll need to make sure you reference the appropriate namespace:
using SubSonic.Extensions.Strings; using SubSonic.Extensions.Dates; using SubSonic.Extensions.Files; using SubSonic.Extensions.Linq; using SubSonic.Extensions.Numbers; using SubSonic.Extensions.Objects using SubSonic.Extensions.Validation;
In there is some fun, and some damn handy utilities. Things like Date math, file creation and text loading, string pluralization, regex validation and matching (Validation's "Is" functions rock - things like "IsValidEmail" and "IsCreditCard" are super groovy).
I mention them last as they are, literally, sugar utilities to help you through your day, and help us with SubSonic. They're not the main point at all.
though, is why in the world are you specifying 2 unique columns and running
a FK on the non-PK string? 99% of my brain can't see why you'd do that - but
1% of it is intensely interested :)
I think there is a problem with Foreign Keys. I have the main table with an integer identity primary key and a unique constraint on a string column (I know, it's a bad design ...).
If another table has a foreign key pointing to the unique string column, SubSonic generates wrong code:
public IQueryable
get{
XXX.DB _db =DB.CreateDB();
return from items in _db.MainTable
where items.ID_COLUMN == _CODE_COLUMN
select items;
}
}
"Drop in the T4 templates, edit the connection string in "Connection.tt", and watch it generate your action."
there is more detail in the README.txt as well. You just need to drop the TT files into your project - they're magic.
DataTable SortDataTable(DataTable tbl, string sort) {
tbl.DefaultView.Sort = sort;
return tbl.DefaultView.ToTable();
}
DataTable GetTables(){
string[] excludeTables= new string[]{"sysdiagrams","SubSonicSchemaInfo"};
DataTable tables = GetConnection().GetSchema("Tables");
return SortDataTable(tables, "TABLE_NAME");
}
Try file/new web site (Shift + Alt + N).... in this case t4 templates doesnt work.
When I tried file/ new project/ web application all the stuff works fine! :)
I need to learn about the differences about "web site" and "web applications".... I always use the first one to create my projects.
Thanks Rob!
Added a reference to SubSonic.dll.
Added a reference to System.Data and System.Core.
Drop the 5 T4 templates (*.TT) into my App_Code folder.
Changed the connection string and ProviderName in Connection.tt to point to the Northwind DB.
So what's next? I did build the project but nothing happened, I don't see any generated code, I cant even do the below:
Northwind.DB db = new Northwind.DB();
??? I am missing something?
1) Are there any CS files generated? They'll be right under the TT file
2) If not - try putting them in the root of the project, or anywhere other than App_Code
3) Is Northwind installed in your DB cause you'll need that.
I think you should mention it in the post that anybody using AttachDbFilename in the connection string will have empty classes if he is not using the Database attribute in the connection string. Database attribute needs to be set exactly the same as the name of the Provider in Connection.tt to solve empty class generation issue.
string GetPK(string table){
DbConnection connection = GetConnection();
string[] restrictions = new string[4] { connection.Database, null, table, null };
DataTable tbl= connection.GetSchema("IndexColumns",restrictions);
string pk="";
foreach(DataRow dr in tbl.Rows){
string index=dr["constraint_name"].ToString();
if(index.StartsWith("PK_")){
pk=dr["COLUMN_NAME"].ToString();
break;
}
}
return pk;
}
DataTable GetColumns(string table){
DbConnection connection = GetConnection();
string[] restrictions = new string[4] { connection.Database, null, table, null };
return connection.GetSchema("Columns",restrictions);
}
DataTable GetSPParams(string spName){
DbConnection connection = GetConnection();
string[] restrictions = new string[4] { connection.Database, null, spName, null };
return connection.GetSchema("ProcedureParameters", restrictions);
}
- No Change events
- No databinding
- no broken rules collection
- no, no this is not good
1) I had to right click each t4 template and choose 'Run custom tool' to generate the C# code
2) The columns from my test database were generated as fields rather than properties so DataBind didn't work with a GridView
looking at but we'll wrap up 2.1.1 very soon.
help if you're game :)
if you can imagine that.
What if we have multiple databases that share the same schema or portion thereof. How are we going to support this scenario with SubSonic 3.0?
For people with a large code base of subsonic generated code (queries both in old and new query tool), can we expect to be able to port it to 3.0 with a lot of refactoring and modifying the templates or it'll just be impossible?
In Console or ClassLibs projects the t4 templates works fine even Subsonic's template or any other that I created.
Does that mean I need VS2008 in order to develop with Subsonic or do I only have to have the framework installed?
ummm...
Thanks for the heads up. I missed that detail in Rob's example.
Jesse
Do you think it'll be possible to modify the templates to be backward compatible with existing code if we want to? Could you elaborate a little bit more on the backward compatibility point in a future post please?
Thanks Rob, great work as always!
Also very nice to hear about the 2.1.1 update !
And man... did I made the right decision. Thanks a lot for this wonderful product Rob. Its a life saver. And believe me, even if I have to rewrite all my BLL, I will shift to this new version.
Thanks again.
Yogesh.
However your brilliant syntax eludes me when I try to delete , or update a row. Can I get an example? I was expecting to be able to call update or delete by passing in the table object: db.Products.Update(product).
Line 93 should be:
return new Select(DataProvider, new Aggregate(colName, AggregateFunction.Count)).From(tableName).ExecuteScalar();
instead of:
return new Select(DataProvider, new Aggregate(colName, AggregateFunction.Avg)).From(tableName).ExecuteScalar();
Can't we add a no parameter Count<> method which just returns the count of the query, like:
public object Count
{
return new Select(DataProvider, new Aggregate("*", "RecordCount", AggregateFunction.Count))
.From(typeof(T).Name).ExecuteScalar();
}
public class Users { }
insted of
public class Users
{
public String Username;
public String Password;
}
:(. If you're not using VS Express, I was able to get this to work here by
file/new/web application and dropping the TT files into the root.
var products = from p in db.Products
where p.Category.Name == 'My lovely category'
select p;
because i don't / can't name me provider ( subnus ) the same as my namespace ( Subnus.MVC.Data )
if i do visual studio crash
Great job, thanks for sharing this.
For some reasons (please don't ask why) I have a table named "user". So the queries fail with a "syntax error near by the keyword user". Adding brackets (eg. "[user]") could be a solution.
Thomas
the release is sooner than you've promised.... your last day at Microsoft or what? I see you've even written an epilogue blog entry not to get fired straight away...boss praising and such goodies =)
congratulation to 1st public release of LINQSonic... I am a little dizzled about the provider and whole factory model though...
does it mean I need to get ADO.NET providers myself to support e.g. postgre database?
or will they ship with subsonic?
thanks
I am not a native english speaker but does it mean you don't want us to completely depend on this "extra" stuff?
e.g. to use it just when necessary...
or was it humour? ;)
I guess it makes sense to use the existing functionality in VS2008. But it was nice to be able to use Subsonic 2.x with the free versions.
Rob, you've made my ORM-y choice for any future projects really easy to make.
:D
columns.Rows == 0
but
columns.Columns.Count == 18
Jesse
npgSql
http://pgfoundry.org/projects/npgsql/
so I assume that all that is needed for me is to implement IDataProvider myself, which shouldn't be a nuclear science, and I am free to use SubSonic 3.0 on postgre databases, or not? =D
only other provider that I found was a commercial one from DevArt:
http://www.devart.com/pgsqlnet/
but I see no reason to pay for it (and there's some bad posts on forums about their "knowledge of developing" sqlite provider that Robert Simpson put big effort to) but that is just my oppinion, it might be super-performant ;)
the pros, like myself, don't use intellisense at all.
quick look to library is sufficient to know _all_ what is required (sometimes we don't even need Rutz reflector, we guess many things just from the hex view of a dll)
... but it makes sense for you I see ;)
However this was my point above - anyone can craft a system on top of SubSonic 3 using the T4 templates :). So if you want to take a swing at it that would rock!
I know this is still extremely early, but are you anticipating compatibility with mono 2.0+? I've recently started using mono for asp.net and 2.1 works great. I have a feeling once LINQ becomes available in a stable mono release, then it might be a non-issue. Thoughts?
I think you might have a lot of leverage vs EF if they truly deprecate L2S like they're talking.
All's well with work :)
If your DB won't work, you can implement IDataProvider (I think) which is all of 11 methods (nice and light). That's the hope, anyway.
in this case I'm trying to make the LINQ experience a bit more transparent - allowing you to work up the code as you like with the T4 templates.
Notepad FTW!
It's all up to you.
Any help ?
I'm using T4 templates
http://blog.wekeroad.com/blog/make-visual-studio-generate-your-repository/
I have the Team Version and tt files only works in Console or ClassLibrary projects.
When I create tt files in WebApps nothing happens, even out of App_Code directory.
(In the Professional Version the same thing happens)
I was wondering if you could discuss the situation between subsonic 2.x and subsonic 3 in terms of moving forward, will subsonic 2.x stuff be worked upon for example I know there are many key outstanding bugs and time is limited between you guys and now with subsonic 3 being worked upon what do you envisage will happen to subsonic 2.x etc
Thanks for your time looking forward to using Subsonic 3.0
But I still don't know how to generate the code
It was not enough having it in my connection.tt
It would be nice only setting it once.
said - I'll see what I can do here.
resolved quickly and easily by changing your templates. In your case, you
can to this:
tableName=tableName.Replace("user","[user]");
That will fix :)
db.Delete.From
I'll fix this up :). Looks like "Totem Code"
Anyway - I'm still puzzled as to why it all of sudden started to work. I've seen this happen and I'm wondering about locks...
Now that I do have your attention... How do I use the aggregates with "criterias" on?
db.Count
Google gave me 2 quick choices, official one straight from the oven I haven studied:
http://www.oracle.com/technology/tech/windows/odpnet/index.html
and the one I mentioned already, DevArt's developed provider, which I would like to sleep with straight away - so much I like their marketing info, Entity framework support and free ironing of my whole laundry during weekends:
http://www.devart.com/oranet/
which one of those is better is upon you (maybe some 3rd I haven't found), which one is better for subsonic 3.0 is upon your own tests =)
finally, you need to change providerName value in connectionStrings to your choosen provider and pray a lot...
I think it would be sufficient for me, sinners like you needs to try and pray a little harder though
the Provider.tt file, you will see where I'm generating this stuff. All you
need to do is to change out the return value from object to SqlQuery, and
then instead of returning ExecuteScalar, you return the Select that is
built.
I'll make that change...
I toyed with the template and it appears that Connection.GetColumns isn't returning any records. I'm using version 5.2.3 of the MySql Connector. I'm gonna start debugging the template but, off the top of your head, do you know what might be happening?
Thanks.
DataTable GetColumns(string table){
DbConnection connection = GetConnection();
string[] restrictions = new string[4] { connection.Database, null, table, null };
return connection.GetSchema("Columns",restrictions);
}
Check again, I think it will solve the issue.
playing with subsonic, mysql and c#. error message brought all fun to dead halt.
when i tried;
user.update(2, "jm","jackass", "marcus", 1);
assert.areequal(1, ulevel);
my test failed!! i was trying to test the update method by changing ulevel from 3 to 1.
the error message was from the mysql server alleging that subsonic sent a wrong
update query.
is there a way to update another way? by the way, is there no way for me to change
which dotnet connector i am using? (to a ore recent version).
what is / where is your email?
thnx
This is awesome news! I used Subsonic 2.0.3 on a previous project of mine and it was easy to use and set up, on top of which it doesn't create code bloat like Nettiers.
Looking forward to putting it through it's paces. :-)
string[] restrictions = new string[4] {null, ProviderName, table, null};
I also noticed that the column name is different when looking for the Primary Key. Instead of "constraint_name" it's "index_name".
I'm really excited about the new changes Rob, great work!
I can upload the updates if anyone is interested.
this - that's really weird that the order is different!
btw, thanks for putting this out! I really like the direction you are going here and I'm interested to see what kind of ORMs pop-up that will run on subsonic as a result.
1. I'm using this in an ASP.NET MVC Web application and none of the T4 templates run unless I right-click ea. one and select "Run Custom Tool." This gets to be a pain after awhile ... so is there any way to correct this so they automagically run when the project is saved and/or compiled?
2. There is no code genereated by the Connection.tt ... is that correct?
3. Is there any reason that the classes generated by Classes.tt are not partial?
4. Why do we have to specify the connection string in Connection.tt if we already have done so in the web.config?
Thanks again.
1) Add them to your project
2) Change the text
3) Right-click run.
In terms of "pain" - try writing that code yourself ;).
Connection.tt doesn't generate code, no.
Partials are coming next...
The reason you need to specify a connection string is that the App domain
that T4 runs in is completely separate than that of your project.
How difficult would it be to update your MVC addin to work against preview 1 here? I didn't see the source available for the former so I'm not sure how much work would be involved ... but being a Rails guy who likes Subsonic and what I see in the mvc addin ... that is exactly what i'm lookin for.
thanks
I'm guessing something like your mvc addin would be a better spot to include code to execute the templates for a given db ... or even just a particular model ... along with all the other code gen it does for your mvc app.
http://blog.wekeroad.com/blog/subsonic-3-alpha-is-ready/
Also there is a mailing list for 3.0 Alpha issue (link on the article).
Finally - if you can me a SQL Script to repro your DB I'll do my best to fix
the issue (send using the group list).
I use a MySQL DB and I added MySql.Data.dll in my /bin directory
I added the code to switch DB provider in my Web.config
This is the error :
Error 2 Running transformation: System.InvalidOperationException: The ConnectionString property has not been initialized.
at System.Data.SqlClient.SqlConnection.PermissionDemand()
at System.Data.SqlClient.SqlConnectionFactory.PermissionDemand(DbConnection outerConnection)
at System.Data.ProviderBase.DbConnectionClosed.OpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory)
at System.Data.SqlClient.SqlConnection.Open()
Why it invoke System.Data.SqlClient.SqlConnection and not MySql.Data.MySQLConnection ???
using System.Data.Common is in my page
thanks