Showing posts with label sql server. Show all posts
Showing posts with label sql server. Show all posts

Wednesday, November 18, 2009

LINQ To SQL Profiler And How To Swing It

[warning: technical post]

My friend showed me a cool profiler the other day by the smart chaps at Hibernating Rhinos which lets you see what is going on behind the scenes of the magic black box known as "LINQ To SQL" (L2S). I've been using LINQ to SQL for almost 2 years now and while it's far from perfect it makes my job (life!) much easier. Unfortunately by hiding the inner workings away from the developer by the time you come to look at the performance of your website (or desktop app) you're pretty much left in the dark. That's where L2SProf comes in. It hooks into your datacontext and gives you a great view of what's happening back there...


Now, the set up is pretty straightforward with L2SProf and involves adding a reference to a single DLL into your app (web or otherwise) then copying and pasting a single line of code into your startup method (e.g. Application_Start in global.asax for a website). Once you've done that you restart your application, crack open the included L2SProf.exe and voila, your LINQ to SQL datacontext's secrets are no longer secret... unless you're me. I got a big fat nothing.

After trying to sort it out with my friend and failing I jumped onto the support forums and posted a quick message asking for help. There was a bit of to-ing and fro-ing over email with not much accomplished. Then Oren Eini (AKA Ayende Rahien) popped onto the scene (then Skype) to give me some help.

First we tried writing the profiler data to a text file (a slight modification to that single line of code I mentioned earlier) and while the log file was created, nothing was written to it. Next we created a brand new web application from scratch and everything worked with the profiler showing my single datacontext call and its corresponding SQL code. Oren looked over the web.config from my original website and decided the problem wasn't in there. After fiddling around some more we came across a little bit of code I'd put in place to stop Visual Studio from trying to read my connection string from the data model project's app.config. This was our "aha" moment.

Normally you'd either have your DBML file (LINQ to SQL datacontext) in your website or if it's out in another project in your solution, you'd let Visual Studio control the database connections by dropping them into the app.config, that's not how I had it set up. This is the method I had in place in my DataContext.cs...

public MyDataContext() : base(ConfigurationManager.ConnectionStrings["MyConnectionString"].ConnectionString)
{
OnCreated();
}


I'll let Oren explain what was going on here...

"The problem with doing it this way is that you are forcing L2S to build a new model every time that you create a data context, that is quite expensive, from a perf point of view. L2S have to use reflection to get the values each and every time. A side affect of that is that it also kills the way L2S Prof works. Here is how to make it both performance and work with L2S Prof:"

public MyDataContext() : base(ConfigurationManager.ConnectionStrings["MyConnectionString"].ConnectionString, mappingSource)
{
OnCreated();
}


Once we did this everything fell into place, data access information pouring out of my website. So there you go, once again proving that if you don't do what Visual Studio wants you to do you can get into trouble.

Thanks a lot to Oren for his help and patience in helping me solve this problem. Now I need to go see how badly my website is performing.

Wednesday, October 1, 2008

SQL Server 2008 Full Text Search Weirdness

Sorry to the non-tech readers but I just had to post this one.

After hearing Greg Low's session at Tech.Ed on Full-Text Searches in SQL Server 2008 I thought I'd upgrade from 2005 to take advantages of the new features. The biggest difference between 2008 and 2005's FTS is that the indexing etc. is now done in the database instead of palming it off to the operating system (2005 used Windows Search for FTS).

So after a switch to SQL Server 2008 CTP on new hardware running Windows Server 2008 I found that one of my searches that ran well under 1 second in 2005 was taking over 10 seconds in 2008. As you may have guessed I was a little flabergasted.

Basically the query is searching for cars (models) that have the search string in their name or the search string is in the car's manufacturer name. Using "lambo" as a test it should return all the Lamborghinis in the database.

Original Inner Join Query

SELECT * FROM Model WITH (NOLOCK)

INNER JOIN Manufacturer WITH (NOLOCK) ON Model.ManufacturerId = Manufacturer.ManufacturerId

WHERE CONTAINS(Model.*, '"*lambo*"') OR CONTAINS(Manufacturer.*, '"*lambo*"')

ORDER BY Model.StartOfProduction DESC, Model.ModelName


Now before you get carried away with those "SELECT *"s in there, this is early days and I'm using LINQ to SQL.

Anyway, my first thought was that the FTS indexes were out of whack after coming across from 2005. I remembered Greg saying something about 2008 not rebuilding the indexes on import unless you explicitly told it to. I set the server to rebuild indexes on import and reimported the database. The query was down to a scorching 5 seconds or so. Still far from an improvement over the >1 seconds on 2005.

A few emails to and fro with Greg, an upgrade to the hardware (more RAM) and to SQL Server 2008 RTM and the very same query was now taking over 20 seconds! And it was in this darkest hour that I stepped back and looked at the problem from afar and realised I should just stop bashing my head against the wall.

What I needed from the query was a list of cars that either have a matching name or a matching manufacturer name. So instead of using an inner join with a Contains() I used Union which allows you to take the results from multiple queries and output them as one table.

New Union Query

SELECT ModelId ... (explicit list of columns)

FROM Model WITH (NOLOCK)

WHERE CONTAINS(Model.*, '"*lambo*"')

UNION

SELECT ModelId ... (explicit list of columns again)

FROM Model WITH (NOLOCK)

INNER JOIN Manufacturer WITH (NOLOCK) ON Model.ManufacturerId = Manufacturer.ManufacturerId

WHERE CONTAINS(Manufacturer.*, '"*lambo*"')

ORDER BY Model.StartOfProduction DESC, Model.ModelName


It's a little more complex than the original query but now it's back to running at under 1 second. *phew*

Big thanks to Greg for putting up with my nattering and for passing this on to the SQL Server product group to see if they can figure out what's going on with the inner join query.

THE END

Saturday, September 6, 2008

Tech.Ed Summary

Well, Tech.Ed Australia 2008 has come and gone leaving me with a bursting brain and a bursting belly.  Instead of going into detail about what is currently at the front of my mind I'm going to list a few bullet points in no particular order.  I may expand on these at a later date...
  • RFID is a good idea, if it works
  • The best thing you can do for UX is to chuck everything out that you don't need (i.e. the KISS principle) and to START with the design.
  • Empty drink fridges for most of Thursday are NOT cool
  • I'd like to see something ELSE running on the Microsoft Surface other than the photo app
  • Tips of the Show: Ctrl-. and Copy/Paste References in Visual Studio 2008
  • Full Text Search in SQL Server 2008 kicks ass
  • Scott Hanselmann is an absolute pisser and knows his stuff/audience
  • The Windows Mobility team are insane
  • Dr Neil tells it like it is (and is a bit of a stirrer)
  • I still prefer LINQ to SQL over LINQ to Entities
  • I can't decide if Adam Cogan was acting stoned
All up, it was well worth the time and expense (thanks J&A!) and I can't wait to get in to work on Monday to start fiddling with stuff.  Chucking my database on 2008 and the site on IIS7 (bai bai Cassini!). *fingers crossed*