Mar 9 2010

Moving off of TFS

Category: zvolkov @ 12:49

TFS issues:

  • Does not always get the latest version correctly
  • Merge gets confused : “Nothing to merge”
  • Files get out of sync, TFS said "Nothing to check in"
  • Workspaces get corrupted
  • Auto-merge is unreliable
  • The built-in merge tool is so bad it might as well not be included
  • Slow
  • The UI is annoying and esthetically displeasing
  • Limited search functionality
  • Integrated Work Item Tracking is terrible
  • Makes the controlled files read-only
  • UI is not optimized for pure-keyboard use.  We’re talking about programmers here!

 SVN issues:

  • No native integration with TFS Build Server
  • Auto-merge is still somewhat unreliable in more complex scenarios

 GIT / Mercurial issues:

  • No native integration with TFS Build Server
  • GUI tools are not very polished yet
  • Learning curve (aka paradigm shift)

With Mercurial and GIT there’s no “server” component. Instead, every developer gets a copy of every repository he works on. Still, it is often convenient to designate one location as the master copy.

Mercurial:

GIT:

SVN:

Microsoft CodePlex supports Mercurial as of Jan 2010. Google supports Mercurial on its Google Code. Google chose Mercurial after conducting extensive analysis of the differences between Mercurial and Git. Both Microsoft and Google mention Mercurial's superior Windows compatibility.

There are hosted solutions like http://www.codebasehq.com/ or http://www.activestate.com/firefly/plans/ that support integrated work item tracking

Seriously though, Subversion is the way to go for most non-distributed development teams. Subversion is tried and true. Mercurial is nice for the federated open-source projects with lots of people developing in parallel but its overhead and the learning curve is just not worth the risk/overhead IMHO.

Still not sure about build server integration though…

Tags:

Feb 25 2010

Optimize SP recompilation in SQL Server 2008

Category: zvolkov @ 07:40

In SQL Server 2000, SP recompilation was a subject of fear. No matter what one did one couldn't get it right. Since major causes of recompilation such as DDL, temp tables, and cursors are ever present in big SQL-based systems, recompilation was pretty much unavoidable. The good news is, with SQL 2005 statement-level recompilation feature most of these problems disappeared or at least became less prominent. Still, based on my experience, by following a few basic rules you can further minimize statement-level recompilation and make your heavily optimized SPs even more polished:

  1. Create all temp tables in one place, as early in the processing as possible, but not in the same SP that uses those tables. Ideally, your outermost SP should create all temp tables and execute a second level SP which will do all the processing. This way the queries in the second SP won’t be subjected to “deferred compilation”.
  2. Create all indexes on temp tables right after you create the tables, do not wait until the tables are populated with data. While deferring index creation until the data is inserted may make the index creation faster, it also causes recompilations of subsequent query plans due to “schema change”.
  3. Do not drop the temp tables when you done with them. They will be dropped automatically when the outermost proc that created them finishes. Dropping them explicitly, confuses SQL Server into thinking that these tables are not the same between executions of the proc, causing it to recompile the query plan due to “temp table change”.
  4. If outermost SP is called multiple times from your application, make sure it’s called on a new SQL Connection every time. Once again, calling it on the same connection confuses SQL Server into thinking you’re trying to create a new temp table with the same name, so it recompiles the query plan due to “temp table change”.

Not only these conclusions are unintuitive, they directly contradict some of the things I see in the MSDN whitepaper: Plan Caching in SQL Server 2008. However if you think I pulled them from [behind] my butt, you're wrong :). I used the SQL Profiler. Try it and see for yourself:

  1. Start SQL Management studio
  2. From Tools menu, start SQL Profiler
  3. In SQL Profiler, start a new trace
  4. Connect to your server
  5. Switch to the Events Tab
  6. Remove all default events by right-clicking on them and choosing Deselect Event Category
  7. Check the “Show All Events” checkbox
  8. In the TSQL category, add SQL:StmtRecompile
  9. If you want to determine the statement that causes the recompile, in Stored Procedure category, add SP:StmtStarting and SP:StmtCompleted
  10. The columns you will need are TextData and EventSubClass (check Show All Columns checkbox to see the latter)
  11. You can leave the trace without filters, but if you tracing a shared SQL server you may want to filter out other users. This can be done in Column Filters.

Or, if you shy of the profiler you can try the amazing SQLTRACE stored proc by Lee Tudor. Seeing is believing!

Tags:

Feb 8 2010

Red Gate's SQL Prompt

Category: zvolkov @ 14:09

SQL Prompt ($195, 14 days free trial) is an add-on for SQL Management Studio (and Visual Studio too) similar in its idea to SQL Assistant, which I covered last time.

While definitely targeting the same auditory of hard-core SQL developers, SQL Prompt arguably delivers less bang for its higher buck. While SQL Assistant can, with some degree of wishfull thinking, be called "Resharper for SQL", this honorary title does not feel appropriate for SQL Prompt. While a cursory look will find the same core features as in SQL Assistant -- better Intellisense, code auto-formating and customizable snippets, what strikes is complete absence of in-editor enhancements. Here's a list of features I particularly missed:

  • commenting out selected block of code by pressing Ctrl+/. 
  • jumping to matching )s and BEGIN/END statements by pressing Ctrl+Alt+B
  • auto-highlighting all occurances of the word the text cursor is on
  • interactive syntax helper (Ctrl+F1)
  • real-time syntax validation status, in the editor as well as in the scrollbar area

In simple language, what I actually want is just that -- Resharper for SQL -- complete with "Find Usages" and "Go To Declaration".

The good news is, SQL Prompt does feel faster, and its fewer features have subtly more refined polish. By that I mean little quirks of SQL Assistant like swallowing some space bar hits or displaying intellisense menu when not needed, seem less prominent with SQL Prompt.

The bottom line: if I were you I would not buy either of these. Let's wait until one of the companies reads this blog. Or, who knows, maybe one day JetBrains will come up with Resharper for SQL?

Tags:

Jan 26 2010

SQL Assistant: Resharper for SQL Management Studio

Category: zvolkov @ 13:49

SQL Assistant ($149, 14-days free trial) is an extension for SQL Management Studio that adds number of productivity enhancements, sort of like Resharper does for Visual Studio. The most visible features are: much enhanced intellisense complete with column selector (use right arrow to choose multiple columns), and on-the-fly syntax checking (activated after 3 seconds of idle).  SQL Assistant also adds an item to SQL Management Studio menu, with whole bunch of actions you can invoke from there. The most useful for me are:

  • Ctrl+Mouse-hover on table name or procedure to see details. Ctrl+Click for even more details.
  • Ctrl+F11 to reformat SQL code
  • Ctrl+/ to block-comment selected text, Ctrl+\ to uncomment
  • Ctrl+F1 for inline T-SQL syntax helper

Another great feature is Visual Bookmarks. Most code editors have bookmarks activated by keyboard shortcuts which very little people use. With SQL Assistant you can set and navigate bookmarks by clicking on the bookmark column near the scrollbar. This is similar in spirit to setting break points in Visual Studio, but on the right of the editor window.

There’s also bunch of handy and not-so-handy snippets to choose from. These are similar in spirit to Resharper’s live templates. I suggest you actually invest some time to familiarize with them, and fine-tune to actually suit your needs. My favorites out-of-the-boxers are:

  • lj and rj for LEFT JOIN and RIGHT JOIN (I edited mine to remove the word OUTER)
  • The non-invasive Enter-activated ones like begin, while, if, case, begin try, and /*
  • The famous cfetch – just try it!
  • is and iv for INSERT SELECT and INSERT VALUES, complete with all column names
  • I've also added ij for INNER JOIN

Now, there are several configuration tricks you need to know if you want to have smooth experience:

1) During installation, it will ask whether you’re SQL Admin vs. Application Developer vs. User. Be sure to choose SQL Admin.

2) Once you installed it, it will run as a tray app. Right click on the tray icon, go to Options.

  • On Targets tab
    • Uncheck everything except MS Management Studio. While SQL Assistant can also integrate with VS.NET, Notepad++ and even Notepad, the integration is kinda suckish so you don’t want that.
    • In MS Management Studio, set the following settings
      • SQL Reference = MSSQL T-SQL 2008
      • Options\Jump to matching bracket = No
      • Options\List Item Selection Key = Enter, Tab
  • On DB Options tab
    • Under SQL Server, set Show Keys and Indexed Columns to Yes
  • On Code Snippets tab
    • Under T-SQL Snippets, set Default Key Char to Space

If you're as annoyed as I am by the way native Intellisense works in SQL Management Studio, you should be releived. Go and try SQL Assistant, I think you will like it.

UPDATE: another similar product is Sql Prompt, by Red Gate. I've just installed the trial on my system and will share my experiences in a few weeks.

Tags:

Dec 2 2009

Creating MSMQ queues and setting permissions using Powershell

Category: zvolkov @ 11:08

Back in September, I posted on using Powershell Community Extensions to manage MSMQ queues. Community Extensions are cool in how they provide you with simple comandlets to get and create the queues but they lack advanced features that native System.Messaging.MessageQueue class posesses. Here's an example of managing MSMQ queues using System.Messaging from Powershell, complete with queue listing, checking for existance, deleting, creating, and setting permissions. This works on Powershell 1.0 and does not require the Community Extensions installed. As always, make sure you have enabled script execution (set-executionpolicy unrestricted). Enjoy!

#thanks to SteveC http://stackoverflow.com/questions/765954/setting-permissions-on-a-msmq-queue-in-script/915127#915127
#thanks to Scott Saad http://stackoverflow.com/questions/1309461/automated-msmq-setup-with-powershell/1312688#1312688
#thanks to dahlbyk http://stackoverflow.com/questions/1048954/equivalent-to-cs-using-keyword-in-powershell/1049010#1049010

echo "Loading System.Messaging..."
[Reflection.Assembly]::LoadWithPartialName( "System.Messaging" )
$msmq = [System.Messaging.MessageQueue]

$queueList = ( "breadline", "milkline", "paycheckline")

echo ""
echo "(Re)creating the queues and setting permissions to Everyone/FullControl:"
foreach($qName in $queueList){
		$qName = ".\private$\" + $qName
		if($msmq::Exists($qName)){
			echo ("    " + $qName + " already exists and will be deleted and recreated")
			$msmq::Delete($qName)
		}
		$q = $msmq::Create( $qName )
		$q.UseJournalQueue = $TRUE
		$q.MaximumJournalSize = 1024 #kilobytes
		$q.SetPermissions("Everyone", [System.Messaging.MessageQueueAccessRights]::FullControl, [System.Messaging.AccessControlEntryType]::Set)
	}
echo "All queues processed."
echo ""

echo "Listing existing private queues:"
echo ""
foreach($q in $msmq::GetPrivateQueuesByMachine(".")){
	echo ("    " + $q.QueueName)
}

echo ""
echo "Done."

Tags:

Nov 25 2009

NHibernate 2.1.2 released

Category: zvolkov @ 11:26

Silently, the NH community released version 2.1.2 of NHibernate and its side-kick projects.

The NHibernate and NHibernate LINQ binaries are available on Source Forge and NHContrib 2.1.2 can be downloaded from NHContrib section.

The changes since 2.1.1 seem very minor, however, if you missed version 2.1.1 now may be a good time to upgrade, as 2.1.1 fixed many important bugs. See release notes for more details and complete list of changes. I did not check all the side-kick projects for changes, only the ones I use. What I see is one bug fixed in NH Linq in 2.1.2 (plus ~20 fixed in 2.1.1) and one change to SysCache2 provider adding support for defining a command timeout.

UPDATE: There's also a matching version of NHibernate Lambda Extensions for Criteria API released.

Please note that if you use any third party libraries referencing NH 2.1.0 (e.g. Spring.NET 1.3) they may start failing during assembly binding resolution. If that happens, you can use the binding redirect like so:

<runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="NHibernate"
                          publicKeyToken="aa95f207798dfdb4"
                          culture="neutral" />
        <bindingRedirect oldVersion="2.1.0.4000"
                         newVersion="2.1.2.4000" />
      </dependentAssembly>
    </assemblyBinding>
</runtime>

Tags:

Nov 10 2009

Back to the future: return of Norton Commander

Category: zvolkov @ 10:19

While I used Norton Commander 3.0 and 4.0 a lot, by the time of 5.x I was off NC and on its clones: Volkov Commander (VC) and DOS Navigator (DN). I think it was DN that first showed me how productive an integrated environment can be. Although late NC and its clones supported mouse (which was a funny name back then) the screen resolution in text mode was too low to support rich point-and-click UI. Keyboard shortcuts were the name of the game, shortcuts and context-sensitive shortcut-activated plugins (right-click menus were not yet invented)

When Windows 3.1 came out its mouse-based interface was still too cumbersome, and I remember occasionally switching back to NC for a super fast session of file management. It was Netscape Navigator that forced the permanent switch to point-and-click mentality. Even though I hated Windows Explorer, for some reason I blindly trusted Microsoft's vision of desktop productivity. It was not until yesterday that last remnants of that early hypnosis fell away. I realized I don't like using the mouse. For web browsing -- yes. For graphic editing -- perhaps. For desktop productivity -- not anymore.

The comeback started with Resharper (a plugin for Visual Studio) and Find and Run Robot (aka FARR)  (a keyboard-based program launcher). After a few months of using these, and learning a few Windows shortcuts along the way (Win+E, Ctrl+Shift+Esc) switching to mouse felt increasingly painful. I realized the point-and-click mentality is by definition lazy and leads to passive way of thinking. Instead of making the computer do what I want, I have to go through a series of approximations: Minimize all windows, click on My Computer, what was I going to do?

Not so with Norton Commander. My hands still remember the shortcuts. Alt+F1-C to open the C drive. Press * to select all files. F8 followed by Enter to delete. Now we're talking!

  • While there are many NC clones out there, the most actively supported one is FAR Manager - get yourself version 1.75 in 32-bit flavor (64-bit is not compatible with many plugins, specifically the 7-zip plugin I will talk about next. Note that 32-bit version of FAR works perfectly fine on 64-bit Windows). Extract into Program Files (x86)\Far and create windows shortcuts as needed (I use FARR launcher for ultimate mouseless experience).
  • Next you will need 7-zip plugin, it will let you Enter into .RAR and .ZIP files (and many others). Go to 7-zip.org and get 32-bit version. Also get the plugin for FAR Manager (a separate download on the same page). Follow instructions in far7z.txt for native 7-Zip FAR Plugin (don't bother with MultiArc). Do copy 7z.dll from where you installed 7-zip over to the plugin folder.
  • Although the default color theme brings back good memories, you may find it too bright comparing to most modern programs. If that is the case, FAR comes with a choice of alternative schemes, the one I like is this greyish scheme; copy the .reg file to Far\Addons\Colors\Custom Highlighting\ and import it using import_colors.bat (Far should not be running)
  • Go to console window properties (Alt+Space -> Properties and Alt+Space -> Defaults) and change the font to 10x18
  • In FAR, go to Menu (F9) -> Options -> Panel Settings and uncheck "Show hidden and system files".
    • Also, check "Select Folders" -- now you can invert file selection using * button on the numerical keyboard.
    • Also, check Show Scrollbar.
    • Now, go to Menu -> Options -> Interface Settings and check "Show total copy progress indicator".
    • Go to Menu -> Options -> Confirmations and uncheck "Delete non-empty folders" to disable second confirmation for folder deletion; also uncheck "Exit" confirmation as well.
    • Another one: goto Menu -> Options -> Viewer Settings and check "Show Scrollbar", do the same for Editor Settings.
    • Finally, do Menu -> Options -> Save Setup to save the changes (or you can turn Options -> System Settings -> Auto Save Setup).

Now, Norton Commander is something you either know how to use or you don't, so I will not go into details except the ones specific to using it in the Windows environment:

Press Ctrl+1,2,3,4,etc to switch the columns displayed on each pannel (use Tab to jump between the planels). I settled on Ctrl+2 which displays 2 columns instead of three. Press Alt+F9 to maximize the window to fill entire screen, press again for the normal size. Most other commands can be seen in the bottom bar (hold Ctrl, Alt or Shift to see them) or in the menu.

Tags:

Nov 2 2009

NHibernate parameter sizes controversy: trust but verify

Category: zvolkov @ 19:56

After my first correction of the original post, on 11/2, Naz replied to the NH thread. He clarified that prepare_sql is in fact NOT defaulted to true in NH 2.1, and so the users will still have to make this configuration change by hand.

The same day, Trent Niemeyer in a comment to my blog post corrected another Carsten Hess's statement and informed me that (at least in v2.1) NHibernate does NOT ignore length attribute in the mappings and that undocumented type="String(100)" syntax is no longer the only way to effectively specify the length of query parameters.

I just verified both news and found them to be correct (and their original counterparts, correspondingly, wrong). As V.I. Lenin once said: "Trust, but verify!"

To summarize, all we need to do is:

  1. set prepare_sql to true
  2. (optional) add type="AnsiString" and length="LENGTH" to mappings of string properties to make NH use varchar(LENGTH) instead of nvarchar(4000) for most basic queries
  3. Keep an eye on more complex queries and be ready to use the overload of SetParameter that takes TypeFactory.GetAnsiStringType(LENGTH)

The reason prepare_sql is an opt-in feature is because it may change behavior of existing applications. Still, it is highly recommended that users of MS SQL Server set it to true, to avoid the query cache pollution. The alternative solution -- the SqlDriverOverride hack -- can be employed to keep the NH-generated SQL queries visible in SQL Profiler every time they are executed, and not once per connection, which is what happens when prepare_sql is set to true.

Tags:

Nov 1 2009

NHibernate parameter sizes controversy: happy ending

Category: zvolkov @ 18:22

This post is a follow up to my previous post, digesting the facts and opinions on the infamous NHibernate parameter sizes controversy. I have also updated the ending of the original post, for those archeologists of the future who will research this topic.

Only two days after my original post, and four days after Daniel Auger's new thread on NH user group, on 10/30, Carsten Hess came back with a statement that turned the story on its head. According to his findings, execution plans generated by sp_prepexec ARE actually global and not per connection (unlike he said before), so having prepare_sql=true does not cause query cache pollution, at least not in NH 2.1 (apparently NH 2.0 may still have some issues since some parametrization defects were not fixed until 2.1). Among other things this means we no longer have to override GenerateCommand in SqlClientDriver to always call SetParameterSizes, as that's what NH 2.1 does by default, with prepare_sql set to true. This also means Fabio and Ayende were right in their downplaying of the issue, even though I still think they did a bad job communicating their position to the public (something I can't help but attribute to cultural differences).

That said, if your DBA requires your app to be SQL Profiler-compliant (and he has every right to demand that) you may have no other choice but set prepare_sql=false. In that case the following hack will make NH use fixed parameter sizes while keeping NH queries visible in SQL Profiler:

  1. set prepare_sql=false in NH settings
  2. Create you own class inheriting from SqlClientDriver and override GenerateCommand to always call SetParameterSizes:
    using System.Data;
    using NHibernate.Driver;
    using NHibernate.SqlCommand;
    using NHibernate.SqlTypes;
    
    namespace XXX.YYY.ZZZ
    {
        public class CustomSqlClientDriver : SqlClientDriver
        {
            public override IDbCommand GenerateCommand(CommandType type, SqlString sqlString, SqlType[] parameterTypes)
            {
                var command = base.GenerateCommand(type, sqlString, parameterTypes);
                SetParameterSizes(command.Parameters, parameterTypes);
                return command;
            }
        }
    }
  3. set connection.driver_class to assembly-qualified name of your custom driver implementation
  4. (optional) add type="AnsiString(LENGTH)" to mappings of string properties to make NH use varchar(LENGTH) instead of nvarchar(4000) for most basic queries
  5. Keep an eye on more complex queries and be ready to use the overload of SetParameter that takes TypeFactory.GetAnsiStringType(LENGTH)

To conclude the post, here are the lessons I learned from this story:

  • If you think you've found NH 2.1 does something stupid that will kill your production server you're most likely wrong. NHibernate 2.1 is a mature enterprise-ready ORM with most major wrinkles already ironed out.
  • If your customer community thinks your product has a bug and you know it does not, they will do their best to fix it anyway, unless you take time to explain there is no bug.
  • In serious organizations the DBAs are a big power, better have them as your friends.
  • Blogging about programming controversies is not the best way to drive traffic to your website. Breaking news work better and are much easier to make.

 

Tags:

Oct 28 2009

NHibernate parameter sizes controversy: history of the issue

Category: zvolkov @ 08:32

This post is my digest of facts and opinions on the infamous NHibernate parameter sizes controversy.
Disclaimer: Everything that follows is MY INTERPRETATION of the story, based on MY UNDERSTANDING of the statements and replics made by all the participants.

The issue was first reported in a blog post by Grant Fritchey on 04/29/2008. He discovered that the way NHibernate (then v1.2) generates some of its parametrized queries (specifically for inserts of entities) causes a proliferation of execution plans in MS SQL Server's (then v2005) execution plan cache. This happens because NHibernate uses actual length of parameter's value as the size of the variable in the generated SQL query.

The issue has resurfaced on 03/03/2009 in this thread on Italian section of NH User Group by Claudio Maccari (the conversation is in Italian so here's a link to google-translated version). In this post Claudio further reports that it's not just entity operations, but regular queries (HQL and ICriteria) that also suffer from the same problem. [Since entity inserts/upates/selects always go by PKs which rarely use variable-length types the impact of the issue would be low if it did not apply to queries -- zvolkov 10/28]. Fabio first thinks its a Fluent NH problem, then he theoretizes it could be a mapping problem (due to length="X" missing). Claudio provides numerous examples of incorrect behavior (even with length="X" present) and proposes setting "prepare_sql" to true as a possible solution. Claudio shows how this setting forces NH to use nvarchar(4000) for string parameter sizes. He also shows how using an overload of SetParameter (e.g. TypeFactory.GetStringType(50) or TypeFactory.GetAnsiStringType(50)) one can override both type and size. Fabio seems surprised and frustrated at the apparent difficulties NH presents for dealing with parameters.

On 03/10 Claudio publishes a post on his blog, in which he explains the issue and the solutions he found so far.

On the same day, 03/10, Daniel Auger, having found the original post by Grant Fritchey, starts another thread on NH Users Group. Fabio seems frustrated with apparent misunderstanding of the nature of the issue, points out this is a MS SQL Server specific issue and implies that the fix should be done in MS SQL Server itself. He also repeats Claudio's idea to use prepare_sql=true as a way to deal with the issue.

On 3/13, the question is raised at the very beginning of Herding Code podcast, episode #38 with Ayende et al. Despite lacking complete info, Ayende lightheartedly dismisses the issue as not existent or(!) perhaps being easy to fix.

On 03/15, in a comment to Claudio Maccari's blog post, Fabio again claims the problem to be a MS SQL Server issue. Claudio argues that the issue should be fixed in NHibernate by making MsSql2005Dialect "use Length attribute by-default".

On 03/16, by Fabio's request Claudio opens a JIRA ticket (NH-1707) to default prepare_sql to true for MS SQL Server. During subsequent several months, the change is implemented, reverted, and reimplemented again, as NH team discovers and fixes related issues (NH-1713, NH-1710, NH-1718). At one point Fabio even reverts the ticket, concerned that defaulting prepare_sql to true may require user code changes, to call a different overload of SetParameter. The final version of NH 2.1, has the change INCLUDED EXCLUDED (~ zvolkov 11/2).

On 04/01, Carsten Hess replies to Daniel Auger's NH user group thread, and points out the difference between parametrized (sp_executesql) and prepared (sp_prepexec) queries. He points out that prepared queries (used when prepare_sql is set to true) are only reused for the same connection which will cause proliferation of query plans as generated by multiple connections. (This statement later proves not accurate, see below -- zvolkov 11/2) Based on this argumentation he suggests to not set prepare_sq to true and instead solve the problem in SqlClientDriver.cs by making GenerateCommand always call SetParameterSizes. However, he argues, even that fix would be suboptimal as the generated parameters will be of type nvarchar(4000) unless the undocumented type="String(100)" syntax is used in the mappings (this turns out to be incorrect as well ~zvolkov 11/2)

On 05/02, Ayende posts on the issue, claiming that prepare_sql=true is the right solution to the problem. On 05/04 Carsten Hess comments on the post, repeating his line of argumentation, but Ayende disables the comments w/o replying.

The issue resurfaces again on 10/26/2009, in a blog post by Naz of objectreference.net. Besides restating the problem, the post clearly illustrates how this specific issue (whether it's a real problem or not) hurts NH adoption.

On same day of 10/26, Daniel Auger opens a new thread on NH Users Group, requesting a clarification on the status of the issue. At first, Fabio can't seem to remember the exact issue and even suggests it was fixed in NH 2.1.1, but finally he repeats his claim that this is a MS SQL Server issue. Other users agree with the assesment but repeat the NH adoption argument. Fabio provides detailed instructions for subclassing SqlClientDriver and injecting it through connection.driver_class setting. Commenting on the possibility of making this change a permanent part of NH trunk, Fabio insists that the condition of calling SetParameterSizes only when prepare_sql=true must be there for a reason and expresses concern that such change may have unintended consequences in some scenarios [i.e. for data types other than strings and RDBMSes other than MS SQL Server -- zvolkov 10/28]. He also replies to the adoption argument by saying that every technology has a limited lifecycle and so does NH. At the end, Fabio seems to be open to the idea of making the SetParameterSizes change, provided the normal procedure is followed (JIRA ticket complete with explanation of the issue and failing tests).

Note: this part of the post has been updated since it was first written. Click here to see the original ending.

On 10/30, Carsten Hess came back with a statement that turned the story on its head. According to his findings, execution plans generated by sp_prepexec ARE actually global and not per connection (unlike he said before), so having prepare_sql=true does not cause query cache pollution, at least not in NH 2.1 (apparently NH 2.0 may still have some issues since some parametrization defects were not fixed until 2.1). Among other things this means we no longer have to override GenerateCommand in SqlClientDriver to always call SetParameterSizes, as that's what NH 2.1 does by default, with prepare_sql set to true (this is no longer accurate, see below ~zvolkov 11/2). This also means Fabio and Ayende were right in their downplaying of the issue, even though I still think they did a bad job communicating their position to the public (something I can't help but attribute to cultural differences).

After my first correction of this post, on 11/2, Naz replies to the NH thread. He clarifies that prepare_sql is in fact NOT defaulted to true in NH 2.1, and so the users will still have to make this configuration change by hand.

The same day, Trent Niemeyer in a comment to my second blog post corrects another Carsten Hess's statement and says that (at least in v2.1) NHibernate does NOT ignore length attribute in the mappings and that undocumented type="String(100)" syntax is no longer the only way to effectively specify the length of query parameters.

I just verified both news and found them to be correct (and their original counterparts, correspondingly, wrong). As V.I. Lenin once said: "Trust, but verify!"

To summarize, all we need to do is:

  1. set prepare_sql to true
  2. (optional) add type="AnsiString" and length="LENGTH" to mappings of string properties to make NH use varchar(LENGTH) instead of nvarchar(4000) for most basic queries
  3. Keep an eye on more complex queries and be ready to use the overload of SetParameter that takes TypeFactory.GetAnsiStringType(LENGTH)

The reason prepare_sql is an opt-in feature is because it may change behavior of existing applications. Still, it is highly recommended that users of MS SQL Server set it to true, to avoid the query cache pollution. The alternative solution -- the SqlDriverOverride hack -- can be employed to keep the NH-generated SQL queries visible in SQL Profiler every time they are executed, and not once per connection, which is what happens when prepare_sql is set to true:

  1. leave prepare_sql at its default value of false
  2. Create you own class inheriting from SqlClientDriver and override GenerateCommand to always call SetParameterSizes:
    using System.Data;
    using NHibernate.Driver;
    using NHibernate.SqlCommand;
    using NHibernate.SqlTypes;
    
    namespace XXX.YYY.ZZZ
    {
        public class CustomSqlClientDriver : SqlClientDriver
        {
            public override IDbCommand GenerateCommand(CommandType type, SqlString sqlString, SqlType[] parameterTypes)
            {
                var command = base.GenerateCommand(type, sqlString, parameterTypes);
                SetParameterSizes(command.Parameters, parameterTypes);
                return command;
            }
        }
    }
  3. set connection.driver_class to assembly-qualified name of your custom driver implementation
  4. (optional) add type="AnsiString" and length="LENGTH" to mappings of string properties to make NH use varchar(LENGTH) instead of nvarchar(4000) for most basic queries
  5. Keep an eye on more complex queries and be ready to use the overload of SetParameter that takes TypeFactory.GetAnsiStringType(LENGTH)

To conclude the post, here are the lessons I learned from this story:

  • If you think you've found NH 2.1 does something stupid that will kill your production server you're most likely wrong. NHibernate 2.1 is a mature enterprise-ready ORM with most major wrinkles already ironed out.
  • If your customer community thinks your product has a bug and you know it does not, they will do their best to fix it anyway, unless you take time to explain there is no bug.
  • In serious organizations the DBAs are a big power, better have them as your friends.
  • Blogging about programming controversies is not the best way to drive traffic to your website. Breaking news work better and are much easier to make.
  • Never take anything on faith w/o verifying the facts for yourself (added 11/2)

 

Tags: