# Sunday, August 01, 2010

This post is an update to a post I made last September (~10 months ago):

http://www.capprime.com/software_development_weblog/PermaLink,guid,162facaa-a3b9-4dc5-b770-657e27e887ad.aspx

The biggest roadblock I ran into when writing that blog post is that ActiveWriter doesn't work well with the Northwind database which had significant ripple effects throughout the sample.

That inspired me to spend quite a bit of time over the last 10 months to make the Castle ActiveRecord code generation story better.

So here is an updated sample using the free version of the Agility for ORMs Castle ActiveRecord code generator in place of ActiveWriter.

--

This is a quick guide to getting up and running with NHibernate and Linq quickly.

We are going to assume our database already exists.  We are going to assume that database is Northwind, and we are going to assume that we are doing database driven design (as opposed to domain driven design).  Northwind setup is described below.

We are going to use Visual Studio 2008 with Service Pack 1 and SQL Server 2008 Express.  (Note: A web application variant of this should work with Visual Web Developer 2008 Express Edition resulting in a completely free development stack.  This sample should also work fine in Visual Studio 2010, but you'll need to change the project to target the .NET Full Profile instead of the .NET Client Profile.)

Step 1:

Download and install the Northwind database.

Jeff Atwood provides approximate instructions here (ask specific questions in the comments if you get stuck):

http://www.codinghorror.com/blog/archives/000434.html

Follow the path for installing the binary files from the command line.  I have tested that the SQL 2005 instructions work on SQL 2008.


Step 2:

Next Download Castle ActiveRecord.  The download link is available from here:

http://www.castleproject.org/castle/projects.html

At the time of writing, the current version is "2.1.2 (2010-01-31)".

Unzip it and remember where you put it, you'll need that info in step 3.


Step 3:

Start Visual Studio 2008

We will create a new console application project:

File -> New -> Project
 Visual C# -> Windows -> Console Application
  ConsoleApplication1 -> OK

We need to add the appropriate NHibernate & Castle Active Record references:

Solution Explorer
 Right Click ConsoleApplication1 -> References -> Add
  Browse Tab
   Go to your Castle ActiveRecord download location and add:
    Castle.ActiveRecord.dll
    Castle.ActiveRecord.Linq.dll
    Castle.DynamicProxy2.dll
    NHibernate.dll
    NHibernate.Linq.dll
   Click OK

And add a reference to System.Configuration as well:

Solution Explorer
 Right Click ConsoleApplication1 -> References -> Add
  .NET Tab
   System.Configuration
  Click OK


Step 4:

Download the free (aka Convention Only) version of the Agility for ORMs Castle ActiveRecord code generator by registering and then downloading here:

http://www.agilityfororms.com/Apps/Register.aspx

The current version is 1.0.0.4.


Step 5:

Run the AFO Castle ActiveRecord code generator, specifying the correct connection string for the Northwind database you setup in Step 1 above.

Note the output directory where the generated files went, you'll need that info in step 6.


Step 6:

Add the generated files to the console application project.

Solution Explorer
 Right Click ConsoleApplication1 -> Add -> New Folder -> DataLayer
 Right Click DataLayer -> Add -> Existing Item...
  Add all of the .cs files generated in Step 5.


Step 7:

We will now add an Application Configuration File to the project and put the Northwind Connection String into it:

Solution Explorer
 Right Click ConsoleApplication1 -> Add -> New Item...
  Visual C# Items -> General -> Application Configuration File
  Add

Modify the file to look like this and use your specific DB connection info:

--
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <connectionStrings>
    <add name="Northwind" connectionString="Data Source=.\SQLExpress;Initial Catalog=Northwind;Trusted_Connection=True;"/>
  </connectionStrings>
</configuration>
--


Step 8:

Open the main "Program.cs" class and add the following new method:

        private static void InitializeNHibernateActiveRecord()
        {
            string connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["Northwind"].ToString();
            InPlaceConfigurationSource configuration = InPlaceConfigurationSource.Build(DatabaseType.MsSqlServer2008, connectionString);

            ActiveRecordStarter.Initialize(System.Reflection.Assembly.GetExecutingAssembly(), configuration);
        }

Add the following code to the Main Method:

        static void Main(string[] args)
        {
            try
            {
                InitializeNHibernateActiveRecord();

                using (new SessionScope())
                {
                    var queryToExecute = from oneProduct in ActiveRecordLinq.AsQueryable<Product>()
                                         select oneProduct;

                    foreach (Product oneProduct in queryToExecute.Take(5).ToList())
                    {
                        Console.WriteLine("ProductID=[" + oneProduct.ProductID + "] ProductName=[" + oneProduct.ProductName + "] Supplier CompanyName=[" + oneProduct.Supplier.CompanyName + "]");
                    }
                }
            }
            catch (Exception oneException)
            {
                Console.WriteLine("oneException=[" + oneException + "]");
                throw; // you can remove this if you'd rather the program exit "more normally"
            }
        }

Add the following using statements at the top of Program.cs:

using Castle.ActiveRecord;
using Castle.ActiveRecord.Framework.Config;
using Castle.ActiveRecord.Linq;

using Model.Northwind;


Step 9:

Copy the NHibernate.ByteCode.Castle.dll file from your Castle Active Record download unzip directory to your projects bin\Debug\ folder.


Step 10:

Run the application:

Ctrl-F5 (Debug -> Start Without Debugging)

And you should see the following output:

--
ProductID=[1] ProductName=[Chai] Supplier CompanyName=[Exotic Liquids]
ProductID=[2] ProductName=[Chang] Supplier CompanyName=[Exotic Liquids]
ProductID=[3] ProductName=[Aniseed Syrup] Supplier CompanyName=[Exotic Liquids]
ProductID=[4] ProductName=[Chef Anton's Cajun Seasoning] Supplier CompanyName=[New Orleans Cajun Delights]
ProductID=[5] ProductName=[Chef Anton's Gumbo Mix] Supplier CompanyName=[New Orleans Cajun Delights]
Press any key to continue . . .
--

We have successfully executed a join query through Linq.


Please note:

You'll notice that the Agility for ORMs Castle ActiveRecord code generator only generated 5 class files in addition to a readme.txt file (there are 13 tables in Northwind which would otherwise result in 11 class files).  Northwind is not a convention based database due to use of assignable keys, surrogate keys, and composite keys.  The free version of the AFO code generator only works with convention oriented database tables and only 5 of the tables in Northwind follow conventions.  The readme.txt file explains why the other tables were not generated.

The commercial version ($30) of the AFO code generator will properly generate code for all 13 tables in Northwind and is intended to support the entire feature set of Castle ActiveRecord including things like Composite Keys, which aren't currently supported by ActiveWriter.


Now that you have the basic NHibernate Linq infrastructure in place, there are plenty of Linq examples and sample code available elsewhere.

Enjoy!

Sunday, August 01, 2010 12:24:48 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  | 
# Monday, March 15, 2010

Apparently SMO can't get the SqlDataType of a UserDefinedDataType.

SQL Server Management Objects (SMO) is a pretty powerful API into Microsoft SQL Server.  I've been pretty happy using it in various scenarios over the years.

Recently, I was surprised to find out that SMO can't get the SqlDataType underlying a UserDefinedDataType.  This is reproducible using the Microsoft Pubs sample database.

Attempt #1:

string databaseName = "pubs";
string connectionString = System.Configuration.ConfigurationManager.ConnectionStrings[databaseName].ToString();

SqlConnection oneSqlConnection = new SqlConnection(connectionString);
ServerConnection oneServerConnection = new ServerConnection(oneSqlConnection);
Server oneServer = new Server(oneServerConnection);
Database oneDatabase = new Database(oneServer, databaseName);
oneDatabase.Refresh();

UserDefinedDataType tidUserDefinedDataType =
 
   new Microsoft.SqlServer.Management.Smo.UserDefinedDataType(oneDatabase, "tid", "dbo");
bool initializeDidSucceed = tidUserDefinedDataType.Initialize(true);

In this scenario, for reasons unknown to me, initializeDidSucceed is false!

Now, if we try to access tidUserDefinedDataType.SystemType which seems like the appropriate data item, we get this exception:

Microsoft.SqlServer.Management.Smo.PropertyNotSetException: To accomplish this action, set property SystemType.
   at Microsoft.SqlServer.Management.Smo.SqlSmoObject.OnPropertyMissing(String propname, Boolean useDefaultValue)
   at Microsoft.SqlServer.Management.Smo.PropertyCollection.RetrieveProperty(Int32 index, Boolean useDefaultOnMissingValue)
   at Microsoft.SqlServer.Management.Smo.PropertyCollection.GetValueWithNullReplacement(String propertyName, Boolean throwOnNullValue, Boolean useDefaultOnMissingValue)
   at Microsoft.SqlServer.Management.Smo.UserDefinedDataType.get_SystemType()


Attempt #2:

string databaseName = "pubs";
string connectionString = System.Configuration.ConfigurationManager.ConnectionStrings[databaseName].ToString();

SqlConnection oneSqlConnection = new SqlConnection(connectionString);
ServerConnection oneServerConnection = new ServerConnection(oneSqlConnection);

Server oneServer = new Server(oneServerConnection);
Database oneDatabase = new Database(oneServer, databaseName);
oneDatabase.Refresh();

Table oneTable = new Table(oneDatabase, "titles", "dbo");
oneTable.Refresh();

Now the following values are returned:

oneTable.Columns[0].Name                             = title_id
oneTable.Columns[0].DataType.Name               = tid
oneTable.Columns[0].DataType.SqlDataType      = UserDefinedDataType
oneTable.Columns[0].DataType.MaximumLength  = 6

So, we can successfully get the length of the underlying data type, but we can't get the SqlDataType.


Attempt #3:

USE pubs

DECLARE @CurrentSchemaName VARCHAR(128)
DECLARE @UserDefinedDataTypeName VARCHAR(128)

SET @CurrentSchemaName = 'dbo'
SET @UserDefinedDataTypeName = 'tid'

SELECT name
FROM sys.types
WHERE user_type_id =
  (
   SELECT system_type_id
   FROM sys.types
     INNER JOIN sys.schemas
       ON sys.types.schema_id = sys.schemas.schema_id
   WHERE sys.schemas.name = @CurrentSchemaName
     AND sys.types.name = @UserDefinedDataTypeName
  )

Using straight SQL, we appear to be able to get the answer we are looking for: "varchar".


I wonder why SMO is missing this capability?

Monday, March 15, 2010 12:56:31 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Saturday, February 20, 2010

T4 Templates are really nice given that the tools are included free in Visual Studio (the Express Editions of Visual Studio appear to have some under-documented limitations when it comes to T4 support).

The primary way that T4 templates are processed is from within Visual Studio.  That works pretty well.

What if you want to process a T4 template outside of Visual Studio (as part of an automated build process, for example)?

The easiest option for running a T4 template outside of Visual Studio is through the TextTransform.exe (custom T4 host) command line tool.

The main difficulty I've encountered using the TextTransform.exe command line tool is error handling.

Here's a sample error (written to StandardError):
--
MyT4Template.tt(0,0) : error : Running transformation: System.InvalidCastException: Unable to cast object of type 'Microsoft.VisualStudio.TextTemplating.CommandLine.CommandLineHost' to type 'System.IServiceProvider'.
   at Microsoft.VisualStudio.TextTemplatingc7fe0d0277b54f7c95c25936373918ff.GeneratedTextTransformation.TransformText()
--

Notice the (0,0).  That is where the line and column numbers would normally go for where the error occurred.  Also notice the stack trace contains no useful information (the error message, at least, in this case *is* useful).

The other downsides of using TextTransform.exe are similar to situations where you are calling out to an EXE file from code as opposed to working against an API.

The main positive I've encountered using the TextTransform.exe command line tool is if the template processes okay in Visual Studio, it will likely process okay using TextTransform.exe.  The above error was generated by using a template that tried to reference EnvDTE.DTE which works fine from within Visual Studio, but doesn’t necessarily make sense to do outside of Visual Studio.


As an alternative, you might attempt to use the Visual Studio T4 engine from .NET code like so:
--
ITextTemplatingEngineHost host = new Microsoft.VisualStudio.TextTemplating.VSHost.TextTemplatingService();
ITextTemplatingEngine engine = new Microsoft.VisualStudio.TextTemplating.Engine();
string outputCode = engine.ProcessTemplate(inputContent, host);
--

But that won't work.  You will get these compile time errors:
--
The type 'Microsoft.VisualStudio.TextTemplating.VSHost.TextTemplatingService' has no constructors defined
'Microsoft.VisualStudio.TextTemplating.VSHost.TextTemplatingService' is inaccessible due to its protection level
--


The hardest option for running a T4 template outside of Visual Studio is by implementing the ITextTemplatingEngineHost interface.  There is an example of how to do that here:

Walkthrough: Creating a Custom Text Template Host

The problem with that example is that it fails for multiple distinct reasons for templates that run just fine in Visual Studio and from TextTransform.exe.  It doesn’t appear to be even close to a full featured ITextTemplatingEngineHost implementation (it’s quite a "teaser" of a sample, enough to show it has potential, but not enough to show you how far from reality you are).

Mono provides an alternative implementation of the ITextTemplatingEngineHost interface here:

http://anonsvn.mono-project.com/viewvc/trunk/monodevelop/main/src/addins/TextTemplating/Mono.TextTemplating/Mono.TextTemplating/TemplateGenerator.cs

And while that fails less often on templates that run just fine in Visual Studio, it still fails in multiple ways.  I appreciate what the Mono folks have done, but for this use case (trying to run T4 templates outside of Visual Studio), there isn’t much value add.

The advantage of using a custom ITextTemplatingEngineHost host implementation is you have significantly more power and control than you have with TextTransform.exe.  TextTransform.exe has a limited input/output/error mechanism and its internals are not very extensible/customizable.

The disadvantage of using a custom ITextTemplatingEngineHost host implementation (as opposed to TextTransform.exe) is that you possibly have to write a decent amount of code against an undocumented system before your T4 template that processes fine in Visual Studio will process equivalently from .NET code (this will depend greatly on the contents of your T4 template).

If anyone is aware of other (free or commercial) custom implementations of the ITextTemplatingEngineHost interface that are usable from .NET code, please let me know!

Update March 7, 2010: Apparently this will be much easier in Visual Studio 2010:

Generating Text Files at Run Time by Using Preprocessed Text Templates

Saturday, February 20, 2010 3:09:50 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Thursday, September 24, 2009

As of 08/01/2010, this post has been rewritten and is now available here:

http://www.capprime.com/software_development_weblog/PermaLink,guid,d404eb9a-57bf-46e0-976a-9f81312ef5a8.aspx

ActiveWriter was replaced in the sample with the Agility for ORMs Castle ActiveRecord code generator (which I wrote) which generates higher quality Castle ActiveRecord code which results in a much smoother development process and a production worthy result.

The unedited original post follows:

--

NHibernate is a great ORM.  It's mature.  It's feature rich.  It's free.  Source code is available if necessary.

It now also supports Linq and Linq is great!

This is a quick guide to getting up and running with NHibernate and Linq quickly.  The overall experience feels vaguely similar to LinqToSql, without all the drawbacks and lock-in of LinqToSql.

We are going to assume our database already exists.  We are going to assume that database is Northwind, and we are going to assume that we are doing database driven design (as opposed to domain driven design).  Northwind setup is described below.

We are going to use Visual Studio 2008 with Service Pack 1 and SQL Server 2008 Express.  (Note: I'm not sure if everything here will work with Visual Web Developer 2008 Express Edition (a completely free stack).  I have Visual Web Developer 2008 Express Edition installed, but I'm not sure if the Active Writer install will work with it since the file type isn't showing up for me.)

To speed the process along, we are going to use Castle Active Record to implement a basic Active Record pattern.

This means we won't be using NHibernate's famous XML mapping files.

And to really speed things along, we are going to use Castle ActiveWriter to get some basic Active Record code generation.

While I wouldn't necessarily use this stack for a production application, it's a very quick way to get up and running.  This example is built on NHibernate and so all that goodness is still available later when necessary.  You aren't really tied to any proprietary or inflexible features.


Step 1:

Download and install ActiveWriter on top of VS 2008.  The download link is available from here:

http://using.castleproject.org/display/Contrib/ActiveWriter

At the time of writing, the current version is "Preview 4.1 (26/06/2008)".

I rebooted after the install since I have been burned one too many times, but rebooting is possibly not necessary.


Step 2:

Next, we will download and install the Northwind database.

Jeff Atwood provides approximate instructions here (ask specific questions in the comments if you get stuck):

http://www.codinghorror.com/blog/archives/000434.html

Follow the path for installing the binary files from the command line.  I have tested that the SQL 2005 instructions work on SQL 2008.


Step 3:

Download Castle Active Record:

http://www.castleproject.org/castle/download.html

At the time of writing, the current version is "ActiveRecord 2.0 - August 1st, 2009".  Unzip it and remember where you put it, you'll need that info in the next step.


Step 4:

Start Visual Studio 2008

We will create a new console application project:

File -> New -> Project
 Visual C# -> Windows -> Console Application
  ConsoleApplication1 -> OK

We need to add the appropriate NHibernate & Castle Active Record references:

Solution Explorer
 Right Click ConsoleApplication1 -> References -> Add
  Browse Tab
   Go to your Castle ActiveRecord 2.0 download location and add:
    Castle.ActiveRecord.dll
    Castle.DynamicProxy2.dll
    NHibernate.dll
    NHibernate.Linq.dll
   Click OK

And add a reference to System.Configuration as well:

Solution Explorer
 Right Click ConsoleApplication1 -> References -> Add
  .NET Tab
   System.Configuration
  Click OK


Step 5:

We will add the ActiveWriter1.actiw files to the project:

Solution Explorer
 Right Click ConsoleApplication1 -> Add -> New Item...
  Visual C# Items -> ActiveWriter (it's at the bottom of the list for me)
  Add

Click OK on the Security Warning

Close the class details window


Step 6:

We will find the Northwind database in the Server Explorer:

View -> Server Explorer
 Data Connections -> Add Connection...
  Choose your Server name
  Choose the correct login info
  Select "Northwind" as the database
  Click "Test Connection"
  Assuming that succeeds, click OK
 Expand the Northwind Tables node
 Now drag and drop the following tables to the Active Writer Designer surface:
  Orders
  Shippers

Save the ActiveWriter1.actiw file
Click OK on the Security Warning
Close the ActiveWriter1.actiw file

You can look through the new ActiveWriter1.cs file in Solution Explorer to see the generated classes we are going to use later.


Step 7:

We will now add an Application Configuration File to the project and put the Northwind Connection String into it:

Solution Explorer
 Right Click ConsoleApplication1 -> Add -> New Item...
  Visual C# Items -> General -> Application Configuration File
  Add

Modify the file to look like this and use your specific DB connection info:

--
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <connectionStrings>
    <add name="Northwind" connectionString="Data Source=ServerName;Initial Catalog=Northwind;User ID=sa;Password=password;"/>
  </connectionStrings>
</configuration>
--


Step 8:

Open the main "Program.cs" class and add the following new methods:

        private static void InitializeNHibernateActiveRecord()
        {
            string connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["Northwind"].ToString();
            InPlaceConfigurationSource configuration = InPlaceConfigurationSource.Build(DatabaseType.MSSQLServer2005, connectionString);

            ActiveRecordStarter.Initialize(configuration,
                typeof(Orders),
                typeof(Shippers)
                );
        }

        private static ISession CreateNHibernateActiveRecordSession()
        {
            // This uses the values setup during ActiveRecordStarter.Initialize() to configure and create the session
            return ActiveRecordMediator.GetSessionFactoryHolder().CreateSession(typeof(ActiveRecordBase));
        }

Add the following code to the Main Method:

        static void Main(string[] args)
        {
            try
            {
                InitializeNHibernateActiveRecord();

                using (ISession dbSession = CreateNHibernateActiveRecordSession())
                {
                    var queryToExecute = from oneShippers in dbSession.Linq<Shippers>()
                                         select oneShippers;

                    foreach (Shippers oneShipper in queryToExecute.ToList())
                    {
                        Console.WriteLine("oneShipper ShipperID=[" + oneShipper.ShipperID + "] CompanyName=[" + oneShipper.CompanyName + "]");
                    }                }
            }
            catch (Exception oneException)
            {
                Console.WriteLine("oneException=[" + oneException + "]");
                throw; // you can remove this if you'd rather the program exit "more normally"
            }
        }

Add the following using statements at the top of Program.cs:

using Castle.ActiveRecord;
using Castle.ActiveRecord.Framework.Config;

using NHibernate;
using NHibernate.Linq;


Step 9:

Copy the NHibernate.ByteCode.Castle.dll file from your Castle Active Record download unzip directory to your projects bin\Debug\ folder.


Step 10:

Run the application:

Ctrl-F5 (Debug -> Start Without Debugging)

And you should see the following output:

--
oneShipper ShipperID=[1] CompanyName=[Speedy Express]
oneShipper ShipperID=[2] CompanyName=[United Package]
oneShipper ShipperID=[3] CompanyName=[Federal Shipping]
Press any key to continue . . .
--

So, the basics are working.

I would have loved to show some Order Details instead of Shippers, but there appear to be a lot of complexities with the Northwind database that don't seem to work well with the current release versions of ActiveWriter / ActiveRecord.  Hopefully those issues will be fixed over time.


Step 11:

Now let's do a query which requires a join.

The CustomerID field on the Orders class first needs to have it's column type changed for it to work.

(Re)open the ActiveWriter1.actiw file.

Right Click on Orders -> CustomerID -> Properties
 Set the Column Type to String


Step 12:

Let's model the relationship between Orders and Shippers in the Designer.

The foreign key relationship is Orders.ShipVia <-> Shippers.ShipperID, with one shipper having 0 to many orders.

With the ActiveWriter1.actiw designer open, go to the toolbox (View -> Toolbox).

Select the "Many To One Relationship" option under "ActiveWriter".

Click (and hold the click) on the Orders table and drag the line onto the Shippers table.

Highlight the line between the two tables and right click -> Properties.

Set the Source Column to ShipVia
Set the Target Table to Orders


Step 13:

Let's add a Linq query to the Main method for Orders to exercise that new relationship:

                    var queryToExecute = from oneOrders in dbSession.Linq<Orders>()
                                         select oneOrders;

                    foreach (Orders oneOrder in queryToExecute.ToList())
                    {
                        Console.WriteLine("oneOrder OrderID=[" + oneOrder.OrderID + "] ShipVia=[" + oneOrder.ShipVia + "] Shippers.CompanyName=[" + oneOrder.Shippers.CompanyName + "]");
                    }


Step 14:

Run the application:

Ctrl-F5 (Debug -> Start Without Debugging)

And you should see the following output:

--
oneOrder OrderID=[10248] ShipVia=[3] Shippers.CompanyName=[Federal Shipping]
oneOrder OrderID=[10249] ShipVia=[1] Shippers.CompanyName=[Speedy Express]

<snip>

oneOrder OrderID=[11076] ShipVia=[2] Shippers.CompanyName=[United Package]
oneOrder OrderID=[11077] ShipVia=[2] Shippers.CompanyName=[United Package]
--


We have successfully executed a join query through Linq.

I was somewhat surprised when I found out this was running 4 queries under the covers.  Basically:

SELECT Orders.*, Shippers.* FROM Orders LEFT OUTER JOIN Shippers ON Orders.ShipVia = Shippers.ShipperID
SELECT * FROM Orders WHERE ShipVia = 2
SELECT * FROM Orders WHERE ShipVia = 1
SELECT * FROM Orders WHERE ShipVia = 3

I either expected 1 query (eager fetch) or 831 queries (lazy fetch - 1 query for the list of orders + 1 query for each order to get the Shipper Company Name).

The results of first query contain enough information to display what the code is asking for, so it's definitely eager fetch behavior.

Changing the code to only print OrderID does not appear to change which queries are run.

So don't necessarily make assumptions about how your ORM is going to be generating queries under the covers, especially if performance seems to be an issue when it shouldn't be.


Some additional things to note:

Castle ActiveRecord provides an ActiveRecordLinq.AsQueryable method that would appear to be useable in place of the ISession.Linq method (with a call to new up a SessionScope instead of calling CreateSession), but I couldn't get that to work (I didn't dig into it).

While the code sample above makes for a decent demo and a quick start, your mileage will likely vary.  When things work great, they work great.  This sample didn't end up being as I planned though because I had to work around / avoid things that I couldn't easily get to work (like Order Details).  In the end, I am very satisfied that the code sample is pretty short and straight-forward, yet so much power is happening behind the scenes.

NHibernate supports pretty much all the weirdness your database needs, but ActiveRecord supports less weirdness, and ActiveWriter supports even less.  The higher up the stack/abstraction you get, the more likely edge cases haven't been tested/implemented yet.

Now that you have the basic NHibernate Linq infrastructure in place, there are plenty of Linq examples and sample code available elsewhere.

Enjoy!

Thursday, September 24, 2009 9:53:46 AM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [1]  | 
# 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.

Wednesday, June 24, 2009 2:49:23 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [1]  | 
# 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.)

Tuesday, June 09, 2009 7:53:45 PM (GMT Daylight Time, UTC+01: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.

Tuesday, June 09, 2009 7:46:17 PM (GMT Daylight Time, UTC+01: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

Saturday, June 06, 2009 10:02:30 AM (GMT Daylight Time, UTC+01: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

Friday, May 29, 2009 12:22:38 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [3]  | 
# Saturday, April 25, 2009

If you launch an xbap file from the file system (i.e. Windows Explorer), the xbap web application will get cached in the ClickOnce cache (as opposed to launching the application from Visual Studio, which does not affect the ClickOnce cache).  You will always get that version of the application from then on no matter how you try to change or rebuild it in Visual Studio.  There are two workarounds:

1) Run "mage -cc" to clear the ClickOnce cache (this affects the entire ClickOnce Cache, not just this one xbap file).
2) Use the publish wizard in Visual Studio, which increments the version number of the xbap file, which causes ClickOnce to separately cache that version.

(This information is relevant to Visual Studio 2008 Service Pack 1 / .NET 3.5 SP1.)

Saturday, April 25, 2009 2:01:07 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  | 
# Tuesday, August 19, 2008

What is the difference between Visual Studio 2008 Standard and Visual Studio 2008 Professional?

Microsoft has provided multiple answers to this question:

(1)  This answer is quick and high level:

http://msdn.microsoft.com/en-gb/vs2008/products/bb980920.aspx

(2) This answer is less quick and slightly more detailed:

http://msdn.microsoft.com/en-us/library/zcbsd3cz(VS.80).aspx

(3) This answer is very detailed.  It comes in two flavors:

Web: http://msdn.microsoft.com/en-us/vs2008/products/cc149003.aspx

Downloadable: http://www.microsoft.com/downloads/details.aspx?FamilyID=727bcfb0-b575-47ab-9fd8-4ee067bb3a37&DisplayLang=en

 

Tuesday, August 19, 2008 1:22:32 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  |