Saturday, July 18, 2009

Disclaimer: I am not an ORM expert.  These tools have long learning curves and very few people could effectively provide adequate unbiased guidance.  That said, I am going to try to provide guidance based on the research I have done.  Your mileage may vary.  This information is very time sensitive, these tools change rapidly and this information will likely be significantly out of date by the end of 2009.  I have no vested interest in any of these projects.

First off, what is ORM?  ORM stands for Object Relational Mapping.

For any given project that updates data in a database:

Should you use an ORM - Yes!
Should you build your own custom ORM from scratch - No!
Should you customize the ORM solution you choose - Probably

These aren’t even really controversial opinions.  There was a time when ORMs weren’t mature enough to be “safe”, but that time has passed.  The time saved by using an ORM is a no brainer decision.

There is even a pretty straight-forward answer to what .NET ORM you should be using:

NHibernate

The major advantages NHibernate has are:

  • It's free (and open source)
  • It can handle enterprise class ORM problems
  • It has an active community surrounding it providing things it doesn't necessarily have “out of the box” like code generation and "convention over configuration"
  • The majority of developer opinions regarding .NET ORM you will find on the Internet mention NHibernate
  • It supports Linq (although not necessarily cleanly out of the box yet)

That isn’t to say that NHibernate doesn’t have disadvantages.  In particular, the API can be difficult to work with.  In reality, I believe any ORM capable of handling enterprise level ORM problems will have to expose a complex API and I believe that justifies NHibernate’s complex API to some extent.  That said, there are projects surrounding NHibernate to expose simpler interfaces into NHibernate (more on a couple of those projects included below).

In the end, unfortunately in my opinion, simplicity is not something you really want in an ORM (I’m a simplicity nut, so this particular issue is very hard for me to cope with personally).  The object relational impedance mismatch is a deceptively complex problem.  Database performance is usually a very critical component of any non-trivial application.  If you opt for a “simpler” ORM than NHibernate today, you may regret that decision tomorrow by underestimating your needs for an ORM.

That said, if you aren’t an experienced programmer, or you just can’t make it over the NHibernate learning curve, or you just don’t have enough time to work through the NHibernate ramp up process right now, there is a good alternative worth recommending.

Subsonic is a free, open source, "Microsoft subsidized" ORM that aims for simplicity of the API and definitely delivers on that front.  It can actually handle many of the common use cases that are required of an ORM, but at some point you will fall off that “feature” cliff (for example: having to deal with a poorly designed database that you cannot change).  You can be productive with Subsonic very quickly and it is very powerful.

Also, if you can’t use open source or would prefer a commercial product for whatever reason, LLBLGen Pro is worth recommending.  It’s harder to find information about LLBLGen Pro since it’s not free, but it appears to be a quality solution.  There is a fully functional 30 day demo available.

Microsoft has entered the ORM market recently with two products: LinqToSql and EntityFramework.  While Linq itself is a wonderful step forward for pretty much all .NET developers, Microsoft’s ORM products are a major disappointment.  While Microsoft has promised that EntityFramework will improve significantly with .NET 4.0, the evidence to back that up so far is not compelling.  NHibernate seems to be way ahead of EntityFramework and as I mentioned above, it’s pretty risky to gamble on a “partial ORM” solution.

Thankfully, NHibernate and Subsonic support Linq, so you don't have to settle for Microsoft’s ORM tools (although I’m sure many projects will be stuck using Microsoft’s ORM, because it’s from Microsoft - I’ve been there personally and I’m sure I will be there again).

I mentioned above that you should probably customize/wrap your ORM of choice.  Especially if you don’t choose NHibernate, I would work to isolate your ORM from the rest of your code base so you can swap in NHibernate later if necessary.  While it’s possible that there are unit test generators for ORMs out there, I haven’t seen any and that seems like an obvious feature to have (generate basic unit tests for your basic data and business level objects based on the models exposed by the ORM).  I like to have auto generated unit tests for my data and business layers as a confidence builder and as a base upon which to build more complex unit tests quickly.  There are many other good reasons to wrap your ORM in a simpler, domain specific API.

The biggest issue with integrating an ORM is to make sure you have very clear separation of concerns.  Your data layer should be the only layer with any exposure to a connection string.  Your UI/Presentation Layer should not have any exposure to your data layer.

A very common temptation with ORMs is to use the “entity” objects they generate from the database model as your “business layer”, when that is really your data layer and you need to avoid mixing the two.  Please resist that temptation.  Unfortunately, there is a truck load of sample code available on the Internet that blatantly violates the separation of concerns concept.

People new to NHibernate (and ORMs in general) should probably look into using one of the following “helper” projects along with NHibernate to help with the ramp up.

(1) Fluent NHibernate

This will help generate the XML mapping files between your objects and database so you don’t have to write a bunch of XML by hand.

(2) Castle ActiveRecord

I recommend this project, but have reservations at the same time.  The Active Record pattern can really help beginners, especially people new to ORM, get their head around how ORM works and why it’s beneficial.

It can also trap you in a box that is difficult to get out of, so I would recommend not getting too aggressive with the adoption of ActiveRecord until you have a good feeling for its limitations.

I would say start with ActiveRecord if you must, but get out of that box as soon as you can and once you have enough experience with the alternatives, you can more safely judge when ActiveRecord is the right answer.

While there are many alternatives to ActiveRecord, one of the more common alternative patterns is the Unit of Work pattern.

I share that just to provide additional information, you will probably not be able to apply either pattern as documented without bumping into their limitations eventually.


I spent quite a bit of time reading about many other .NET ORMs before posting this article, but for various reasons they weren’t included in the article.  Some of the primary reasons for non-inclusion were:

(1) Lack of Linq support or lack of stated intent to provide Linq support
(2) Lack of recommendations from real developers while doing various Google searches

These seem like two pretty easy to apply criteria to separate “good” ORM from “bad” ORM (and there are a lot of bad ORMs - and I would hope most people try to code their own simple ORM sometime to help them understand how easy it is to create a bad ORM).

I would like to thank the following four bloggers specifically:

Ayende Rahien/Oren Eini, Rob Conery, Frans Bouma, and David Hayden

for their extensive writing on .NET ORM who helped frame much of what I have recommended in this article.  Blogging about your ORM helps give confidence to those who want to use or recommend your ORM, so you will see a correlation between these bloggers and the tools I recommended.

 

7/18/2009 6:48:28 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [2]  | 
 Saturday, July 04, 2009

This release includes fixes for two uncommon server errors:

1) Error occurred when Lua code contained apparent HTML tags.
2) Error occurred when Lua variable name was missing.

These two scenarios are now handled properly without errors being generated.


Since the Capprime Lua Obfuscator was first publically released two months ago, the web application has been visited by approximately 70 unique (non-bot) IP addresses.


Feedback is welcome and encouraged!

7/4/2009 10:43:20 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  | 
 Wednesday, June 24, 2009

First, create the application setup described in my WCF - Establishing a sample app baseline blog entry.

We will then modify the client program so that it encounters some errors while communicating with the WCF Service.

Next, add a new method to ConsoleApp1:

        private static string GenerateStringOfCertainLength(int stringLength)
        {
            string returnValue = String.Empty;
            int numTens = stringLength / 10;
            int remainder = stringLength % 10;

            for (int counter = 0; counter < numTens; counter++)
            {
                returnValue += "0123456789";
            }
            for (int counter = 0; counter < remainder; counter++)
            {
                returnValue += counter.ToString();
            }

            return returnValue;
        }

Replace the contents of the try block in ConsoleApp1 with:

                ServiceReference1.Service1Client oneService1Client = new ServiceReference1.Service1Client();
                ServiceReference1.CompositeType oneCompositeType = new ServiceReference1.CompositeType();
                oneCompositeType.StringValue = GenerateStringOfCertainLength(8193);
                oneCompositeType = oneService1Client.GetDataUsingDataContract(oneCompositeType);

Ctrl-F5 (Debug -> Start Without Debugging)

This may crash Cassini (the VS Web Server) and will result in the following exception:

--
oneException=[System.ServiceModel.FaultException: The formatter threw an exception while trying to deserialize the message: There was an error while trying to deserialize parameter http://tempuri.org/:composite. The InnerException message was 'There was an error deserializing the object of type WcfApp1.CompositeType. The maximum string content length quota (8192) has been exceeded while reading XML data. This quota may be increased by changing the MaxStringContentLength property on the XmlDictionaryReaderQuotas object used when creating the XML reader. Line 1, position 8652.'.  Please see InnerException for more details.

Server stack trace:
   at System.ServiceModel.Channels.ServiceChannel.HandleReply(ProxyOperationRuntime operation, ProxyRpc& rpc)
   at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
   at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs)
   at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
   at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)

Exception rethrown at [0]:
   at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
   at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
   at ConsoleApp1.ServiceReference1.IService1.GetDataUsingDataContract(CompositeType composite)
   at ConsoleApp1.ServiceReference1.Service1Client.GetDataUsingDataContract(CompositeType composite) in C:\dev\Prototyping\WCF\WcfApp1\ConsoleApp1\Service References\ServiceReference1\Reference.cs:line 120
   at ConsoleApp1.Program.Main(String[] args) in C:\dev\Prototyping\WCF\WcfApp1\ConsoleApp1\Program.cs:line 17]
--


If we send 50000 instead of 8193, we get a different exception:

--
oneException=[System.ServiceModel.ProtocolException: The remote server returned an unexpected response: (400) Bad Request. ---> System.Net.WebException: The remote server returned an error: (400) Bad Request.
   at System.Net.HttpWebRequest.GetResponse()
   at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan timeout)
   --- End of inner exception stack trace ---

Server stack trace:
   at System.ServiceModel.Channels.HttpChannelUtilities.ValidateRequestReplyResponse(HttpWebRequest request, HttpWebResponse response, HttpChannelFactory factory, WebException responseException)
   at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan timeout)
   at System.ServiceModel.Channels.RequestChannel.Request(Message message, TimeSpan timeout)
   at System.ServiceModel.Channels.ClientReliableChannelBinder`1.RequestClientReliableChannelBinder`1.OnRequest(TRequestChannel channel, Message message, TimeSpan timeout, MaskingMode maskingMode)
   at System.ServiceModel.Channels.ClientReliableChannelBinder`1.Request(Message message, TimeSpan timeout, MaskingMode maskingMode)
   at System.ServiceModel.Channels.ClientReliableChannelBinder`1.Request(Message message, TimeSpan timeout)
   at System.ServiceModel.Security.SecuritySessionClientSettings`1.SecurityRequestSessionChannel.Request(Message message, TimeSpan timeout)
   at System.ServiceModel.Dispatcher.RequestChannelBinder.Request(Message message, TimeSpan timeout)
   at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
   at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs)
   at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
   at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)

Exception rethrown at [0]:
   at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
   at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
   at ConsoleApp1.ServiceReference1.IService1.GetDataUsingDataContract(CompositeType composite)
   at ConsoleApp1.ServiceReference1.Service1Client.GetDataUsingDataContract(CompositeType composite) in C:\dev\Prototyping\WCF\WcfApp1\ConsoleApp1\Service References\ServiceReference1\Reference.cs:line 120
   at ConsoleApp1.Program.Main(String[] args) in C:\dev\Prototyping\WCF\WcfApp1\ConsoleApp1\Program.cs:line 17]
--


The out of the box wizard generated service has a couple of limitations in string lengths that it will support.

These are essentially client side errors and can't be usefully debugged on the server side or using tools like Fiddler.

Let's go back to the 8193 character string and solve that issue first.  We can do this by modifying the web.config for the WCF web service and the app.config for the console client application.

There is a tool in Visual Studio that gives us a property editor for WCF configuration.  You may or may not find it preferable to use the tool over modifying the xml files directly, but that is how I am going to describe the changes we need to make.

If you right click on the web.config in the Solution Explorer and don't see a menu option called "Edit WCF Configuration", do the following to get it added:

  In Visual Studio 2008, go to the tools menu, then select "WCF Service Configuration Editor"
    File -> Exit

Now, right click web.config in Solution Explorer -> Edit WCF Configuration

  Select Bindings -> New Binding Configuration...
    Please select a binding type: wsHttpBinding
  Select Bindings -> NewBinding0 (wsHttpBinding)
    Set Configuration -> Name to: CustomWsHttpBindingConfiguration
    Set ReaderQuotas Properties -> MaxStringContentLength to: 9000

  Select Services -> WcfApp1.Service1 -> Endpoints -> (Empty Name) [the first one - not mexHttpBinding]
    Select Endpoint Properties -> Binding Configuration: CustomWsHttpBindingConfiguration

  File -> Save
  File -> Exit

Any time we update the web.config file, we should update the service reference, even though in this case it is likely unnecessary:

  Solution Explorer
    Right Click ConsoleApp1 -> Service References -> ServiceReference1
      Update Service Reference
      OK

We need to make the same change on the client side as we made on the server side:

  Right Click app.config in Solution Explorer -> Edit WCF Configuration
    Select Bindings -> WSHttpBinding_IService1 (wsHttpBinding)
      Set ReaderQuotas Properties -> MaxStringContentLength to: 9000
    Save & Exit


If we run the application now using 8193, the exception is gone.  So, it is now clear how to send strings larger than 8192 characters to a WCF web service (the exception helped point us in the right direction).

Now, let's go back to the 50000 scenario.  The above changes are not enough to fix it as we are now exceeding a different character limit.

Right click web.config in Solution Explorer -> Edit WCF Configuration
  Select Bindings -> CustomWsHttpBindingConfiguration (wsHttpBinding)
    Set General -> MaxReceivedMessageSize to: 75000
    Set ReaderQuotas Properties -> MaxStringContentLength to: 50000
  Save & Exit

Right Click app.config in Solution Explorer -> Edit WCF Configuration
  Select Bindings -> WSHttpBinding_IService1 (wsHttpBinding)
    Set General -> MaxReceivedMessageSize to: 75000
    Set ReaderQuotas Properties -> MaxStringContentLength to: 50000
  Save & Exit

Again, just for best practice reasons, we update the service reference:

  Solution Explorer
    Right Click ConsoleApp1 -> Service References -> ServiceReference1
      Update Service Reference
      OK

How did I come up with those numbers?  Trial and error.  The value for MaxStringContentLength logically matches the length of the string we are trying to send.  MaxReceivedMessageSize seems to include extra characters, but it's not clear to me what exactly those extra characters are.

If you increase the values of these two config settings on the server, but not the client, the client starts giving useful error messages (like: The maximum message size quota for incoming messages (70000) has been exceeded.) instead of the mostly useless "(400) Bad Request." which basically means that the server rejected your request and is pretty much unwilling to tell you why.

Please note that increasing the size of these config settings makes your web service more vulnerable to denial of service attacks.

It's pretty easy to exceed the wizard generated values however if you are, for example, trying to send the contents of a text file as a string parameter to a WCF method.

So, now you know how to configure WCF to allow sending larger strings back and forth.

6/24/2009 7:49:23 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [1]  | 
 Monday, June 15, 2009

I had restored a backup of a SQL 2000 database into SQL Server 2008 Express.  When I created a second database in the same SQL Server 2008 Express instance, and created a user for that database, that user would get this error when trying to expand the Object Explorer->Databases node:

--
Failed to retrieve data for this request. (Microsoft.SqlServer.Management.Sdk.Sfc)

For help, click: http://go.microsoft.com/fwlink?ProdName=Microsoft+SQL+Server&LinkId=20476

An exception occurred while executing a Transact-SQL statement or batch. (Microsoft.SqlServer.ConnectionInfo)

The server principal "username" is not able to access the database "dbname" under the current security context. (Microsoft SQL Server, Error: 916)

For help, click: http://go.microsoft.com/fwlink?ProdName=Microsoft+SQL+Server&ProdVer=10.00.1600&EvtSrc=MSSQLServer&EvtID=916&LinkId=20476
--

There is a knowledge base article on this issue here: KB956179

There is some additional information here: http://www.orcsweb.com/blog/post/The-server-principal-A-is-not-able-to-access-the-database-B-under-the-current-security-context-(Microsoft-SQL-Server-Error916).aspx

This seems like a relatively easy bug to hit and it took me too long to google up the answer, so I'm hoping this blog post helps other people find an answer more quickly.

6/15/2009 3:35:31 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  | 
 Tuesday, June 09, 2009

First, create the application setup described in my WCF - Establishing a sample app baseline blog entry.
We will then modify that simple application so that it's traffic will show up in Fiddler2.

Second, install and startup Fiddler.  It is probably worth watching the QuickStart video if you haven't already.
After you install and run Fiddler, and then run ConsoleApp1, you'll notice that the WCF traffic doesn't show up in Fiddler.  There are multiple ways to enable that, but we are only going to cover one simple one right now.

Third, modify the client application so that Fiddler will display it's traffic.

After this line of code in ConsoleApp1 Program.cs Main:

ServiceReference1.Service1Client oneService1Client = new ServiceReference1.Service1Client();

Add:
                oneService1Client.Endpoint.Address = new System.ServiceModel.EndpointAddress(
                    new Uri(oneService1Client.Endpoint.Address.Uri.ToString().Replace("localhost", "127.0.0.1.")),
                    oneService1Client.Endpoint.Address.Identity,
                    oneService1Client.Endpoint.Address.Headers);

This essentially pushes all the network traffic into a place where Fiddler can see it (the period after the 1 is critical, it's not a typo!).

Ctrl-F5 (Debug -> Start Without Debugging)

You should now see traffic showing up in Fiddler.

If you shut down Fiddler, the application will still work fine (but if you used "ipv4.fiddler" instead of "127.0.0.1.", you would get an exception like: [EndpointNotFoundException: There was no endpoint listening at http://ipv4.fiddler:1100/Service1.svc that could accept the message.]).

At this point, for readability/testability purposes, I would encourage you to modify the web.config for the WCF service and replace the wsHttpBinding with the basicHttpBinding.  This will make it much more clear what is going on.

If you modify the WCF web.config, you have to update the reference in the client (which will generally regenerate the client's app.config to match):

Solution Explorer
 Right Click ConsoleApp1 -> Service References -> ServiceReference1 -> Update Service Reference

(This is basically needed anytime the WCF application changes it's public interface.)

6/9/2009 12:53:45 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  | 

We are going to run the WCF application wizard and then create a client program that establishes basic communication with that WCF Service.

We will be using Visual Studio 2008 with Service Pack 1

File -> New -> Project
 Visual C# -> Web -> WCF Service Application
  WcfApp1 -> OK
Solution Explorer
 Right Click Solution -> Add -> New Project
  Visual C# -> Windows -> Console Application
   ConsoleApp1 -> OK

Solution Explorer
 Right Click ConsoleApp1 -> References -> Add Service Reference...
  Discover
  OK

If adding the service reference fails, try to run WcfApp1 (Debug -> Start Without Debugging) and click on "Service1.svc" first.  For whatever reason, the WCF service doesn't always "auto-start" when it should.  I end up using this "workaround" quite often.

Add the following to the Main method in Program.cs in ConsoleApp1:

            try
            {
                ServiceReference1.Service1Client oneService1Client = new ServiceReference1.Service1Client();
                string serviceOutput = oneService1Client.GetData(-1);
                Console.WriteLine("serviceOutput=[" + serviceOutput + "]");
            }
            catch (Exception oneException)
            {
                Console.WriteLine("oneException=[" + oneException.ToString() + "]");
                Environment.Exit(-1);
            }


Solution Explorer
 Right Click ConsoleApp1 -> Set as StartUp Project

Ctrl-F5 (Debug -> Start Without Debugging)

Program output should be:

--
serviceOutput=[You entered: -1]
Press any key to continue . . .
--

We now have a basic WCF application with a basic client that calls it.

One very helpful tip once the basics are running is to get Fiddler up and running to be able to watch WCF traffic in case things go downhill after you make some changes.

Click here for instructions on how to do that.

6/9/2009 12:46:17 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  | 
 Saturday, June 06, 2009

There are a number of very significant drawbacks in the O/R Designer that have no reasonable work around.

  • The Visual Studio 2008 LinqToSql O/R Designer is fine for a database with ~5 tables, no views, no stored procedures, and no functions, but it doesn't scale well much beyond that.
    • If the database changes, it's very difficult to manually update the O/R Designer properly.  As database complexity increases, this becomes near impossible.
    • When you need to remove an existing object from the O/R Designer to force it to update, it can be difficult to find that object.
  • It is overly difficult to diff the generated files from one version to another since the files are rewritten in a different order than they were read in when changes are made.  Even if the files can be sorted in a reasonable way, diffing the files is still not an effective solution for managing changes.
  • There are bugs with how the O/R Designer generates code.  In general, it is difficult to evaluate if the O/R Designer is working properly since it is difficult to diff the generated files before and after generation.  There are cases where the O/R Designer output is different than the SQL Metal output and in some of those cases, it could be a bug in either or both of the tools.
  • If two or more developers are working under an edit/merge/commit style of source control, it is difficult to resolve conflicts in the LinqToSql O/R Designer files during the merge phase.
  • It is not effective to try to diff O/R Designer generated files against files generated by SQL Metal as they don't use the same underlying code generation techniques and file sorting.  The two tools also have meaningfully different feature sets which negatively impacts the ability to diff the underlying files.

Once you start down the path of the O/R Designer, it gets increasingly harder to migrate away from it the farther you go.  This is a significant hidden risk as many of the scalability issues with the tool don’t present themselves right away.

It's not clear if any of these problems will be addressed in Visual Studio 2010.

This is not to say that the O/R Designer doesn't have very interesting and useful features.  It does have interesting and useful features.  They just aren't packaged in a way to make them at all usable for anything but very tiny databases.

Are there alternative LinqToSql code generation tools?

The main alternative tool is SQL Metal, which has quite a few drawbacks of its own.  While SQL Metal can scale up to much larger databases than the O/R Designer, it still can't scale effectively past a certain point.

Another free alternative is Damien Guard's LinqToSQL T4 Template.  Since the T4 template is not an official product of Microsoft and was built by a developer in their spare time, you'll have to judge for yourself whether it meets your criteria for "production worthy".

I may write a future blog entry with additional details on SQL Metal and the T4 templates.

There are non-free alternatives to the O/R Designer as well, but in many cases it is just not possible to get software tool purchases approved as part of the software development project budget.  It would be surprising if LinqToSql adoption hinged on the success of commercial third party tools.

Updated 2009/09/16 - Related blog posts:

The drawbacks of adopting Linq To Sql
.NET and ORM - Decisions, decisions

6/6/2009 3:02:30 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  | 
 Friday, May 29, 2009

LinqToSql does offer benefits to corporate IT organizations.  Microsoft marketing has helped spread that message.

It's less clear what the drawbacks of Linq To Sql are, yet I believe the drawbacks are important to consider before adopting such a large disruptive change into a software development environment.

Here are some of the downsides to LinqToSql that I have identified so far:

  • There is a significant learning curve.  The technology is deceptively approachable.  On the surface, for instance, LinqToSql takes a SQL Server database table and generates .NET source code for a .NET CLR class.  This appears to be a nice abstraction.  However, there is a significant amount of complexity involved.  The abstraction leaks.  Something doesn't work as you expect, so you start searching and reading and the reading seems to go on a lot longer than expected.  We are still early enough in the adoption cycle that many common issues and questions are under documented.
  • There is immature documentation.  As technologies mature, the documentation improves.  This is especially true of the documentation of more obscure issues available through things like google searches.  Right now, there are "gotchas" that remain hard to find clear discussion around.
  • There is a requirement for "seasoned" developers on the team.  The development team needs one or more developers who can learn new things quickly, handle significant amounts of complexity, and do the right thing without micromanagement.  These developers likely need significant SQL Server, ADO.NET, and object relational mapping expertise.  There is a limited quantity of these seasoned developers to go around.
  • There is significant competition.  There are a lot of players in the object relational mapping (ORM) world.  LinqToSql, other than being included free in Visual Studio, arguably doesn't add enough value (yet) to the space.  Many of the highest traffic parts of LinqToSql can be (and have been extensively) emulated with ADO.NET.
  • There is a significant amount of FUD and confusion around the future roadmap (i.e. version 2 and beyond) for Linq To Sql.  Microsoft is actively de-emphasizing Linq To Sql while maintaining that they will continue to support it for the foreseeable future.  While the Entity Framework may some day be a clear and straight-forward migration path for current Linq To Sql users, that is not the case today and Microsoft is having trouble with damage control around their confusing and mixed messages.  I wish I could link to something worth reading on this topic, but as I've said, Microsoft has really botched the communication on this issue.
  • There are significant bugs.  That is a normal course of business with anything that is basically on version 1 (and since VS 2008 SP1 has been released, LinqToSql could possibly be called version 2).  Here's an example of a bug that is likely to hit a decent portion of the LinqToSql user base.
  • There are significant tooling issues.  The O/R Designer and SQLMetal tools that come with VS 2008 are fairly blunt objects with fairly significant limitations.  While some commercial products have stepped in to fill some of the gaps, there are very limited free options available.  I hope to write a future blog entry with more details on the limitations in the VS 2008 LinqToSql tools (for one, see Do not use the Visual Studio 2008 LinqToSql O/R Designer).

LinqToSql will appeal to many people for many reasons and I think its momentum is likely unstoppable.

As with adopting any new technology, it's important to go in with your eyes open. 

Your mileage may vary.

Update 2009/09/16 - Here is a somewhat related follow-up blog post discussing where Linq2Sql matches up against other .NET ORMs: .NET and ORM - Decisions, decisions

5/29/2009 5:22:38 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [3]  | 
 Saturday, May 09, 2009

Here's a fairly nasty error message with a (sometimes) pretty subtle solution.  Let's say you want to insert a null value into a nullable database column with ADO.NET using parameterized SQL.  The insert statement could look something like this:

INSERT INTO TableName (ColumnName) VALUES (@NullableValue)

Let's say I setup my SqlParameter array as follows:

SqlParameter[] sqlParameterArray = new SqlParameter[1];
SqlParameter oneSqlParameter = new SqlParameter("@NullableValue", SqlDbType.Int);
oneSqlParameter.Value = null; // I want to insert the value null

If I ran the insert using the SqlCommand.ExecuteNonQuery method, I would get this exception:

--
System.Data.SqlClient.SqlException

Prepared statement '(@NullableValue int)INSERT INTO TableName (ColumnName) VALUES (@' expects parameter @NullableValue, which was not supplied.
--

What's wrong?  Apparently, "null" is not a valid value for the Value of a SqlParameter.  You need to do this instead:

oneSqlParameter.Value = DBNull.Value;

Is that error message helpful?  I don't think so.

There are plenty of other reasons why that exception could be thrown, but I've been using ADO.NET for a while and I still stumbled upon this particular under documented problem recently.  Very, very subtle.  Crystal clear error messages from software are so important, yet so uncommon in the real world.

5/9/2009 5:44:29 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |