# Friday, January 21, 2011

This blog post is a repurposing of content I created for a presentation I gave on DVCS on 01-11-2011 to the Twin Cities Developer Group.

...

What is DVCS?

Source code version control without a central master server repository (think peer to peer).

Market leaders: git, hg / Mercurial

New competitor with potential: SourceGear Veracity

http://en.wikipedia.org/wiki/Distributed_revision_control

...

Is DVCS better than non-distributed source control (aka Subversion/SVN, VSS, TFS, etc.)?

Usually, yes, but there are exceptions.  Don't underestimate the learning curve.

http://stackoverflow.com/questions/111031/comparison-between-centralized-and-distributed-version-control-systems

...

What are the Disadvantages of DVCS?

Lack of mature graphical front ends (although people seem okay with TortoiseHg/TortoiseGit), the majority of DVCS users appear to use the command line.

The learning curve for DVCS is different.

There are different complexities like workflow & backup process creation.

Many people struggle with the concept of "no canonical master".

Large binary files don't work well in a DVCS where everyone has a local copy of basically the entirety of every version of every binary file.

There are currently tools to migrate to DVCS, but not necessarily tools to migrate away from DVCS.

...

Advantages of DVCS – Better Implementation

What could non-distributed version controls systems do better to compete with DVCS?

  • Implement better merging
  • Implement better handling of versioning directories

What are some of the reasons that merging is better in DVCS?

  • Change sets
  • Likely more version history available to work with since smaller check-ins are better supported
  • Better ancestor tracking/usage
  • Better directory revision management
  • Better file rename detection

The battle is not DVCS (git/hg) vs. SVN, but that is how it is often characterized.  SVN is lacking in some areas compared to it's (sometimes commercial, sometimes more mature) non-distributed version control competitors.

...

Advantages of DVCS – Better Design

What can’t non-distributed version controls systems do?

  • Working "offline" (faster history access, faster commits)
  • Better experience working peer to peer for integration testing
  • QA can manage their own repository
  • Can commit a logical unit of work without tests passing or the code even compiling

...

DVCS Trends

Where is DVCS catching up?

  • GUIs
  • ALM integration
  • Hard locks
  • User/role permissions
  • Centralized admin, etc.

DVCS in corporations is very interesting to me personally and Veracity will likely solve those problems better than git/hg ever will since they are primarily targeted at open source projects, not corporate/enterprise environments.

Obstacles to an enterprise DVCS - http://www.ericsink.com/articles/vcs_trends.html

...

Some differences between Mercurial and Veracity

http://www.capprime.com/software_development_weblog/2011/01/20/SourceGearVeracityMercurialLookupChart.aspx

Friday, January 21, 2011 2:58:14 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Thursday, January 20, 2011

Please note: The following information is specific to the 01-19-2011 Veracity nightly build (version 0.5.5.10370).  I may come back from time to time as the Veracity command API solidifies to update this to newer and more accurate information.


SourceGear Veracity tends to be more like Mercurial / hg than like git.  There is a ton of documentation on Mercurial, but not much available for Veracity at the moment.

While learning Veracity, I often found myself reading docs for Mercurial and then figuring out how Veracity was different through trial and error.

This blog posts contains my notes on some of the things I've learned.

It's often possible to replace an hg command one for one with a vv command (i.e. hg status ~= vv status) and the results will be meaningfully similar if not exactly the same.

...

Generally, step one is to create your repo.  In Mercurial, this is done by creating a new directory and executing "hg init" within that directory.  For Veracity, you do the same basic thing, but you also need to give the repo a name as a parameter to init:

vv init repo1

After initializing a repository, the first logical step is to setup your user.  In Mercurial, this is done by creating an .hgrc file in the appropriate location.  In Veracity, you must first create the user if they don't yet exist (use "vv users" to show existing users) using:

vv createuser myemail@example.com

Once the user exists, you set the current user using:

vv whoami myemail@example.com

You can use "vv whoami" at any time to see who Veracity thinks you currently are.

...

Once you have a repository, you can startup a web server to view the repository (although there obviously won't be much to look at if you just initialized it).  For Mercurial, this is simply:

hg serve

For Veracity, the "vv serve" command requires you first run "vv localsettings set server/files <path>" to provide a path to the website files.  I suspect Mercurial does not need this "set server files" step because the installer takes care of setting this path somehow.

...

The status command works the same, but the output is visibly different (git's output is visibly different for the status command as well).

...

The add command requires a file or directory name in Veracity, whereas Mercurial will add all files if you don't specify a parameter.

Like Mercurial (and unlike git), you only need to use the add command on new files (modified existing files are automatically part of the change set by default).

...

The commit command works similarly, although hg has a nice feature where it will pop up an editor to provide a commit comment while Veracity works more like git in that you provide the commit message with a command line parameter (which is also possible with hg).

...

A basic no parameter diff works very similarly between Veracity and Mercurial.

...

The branch and branches commands with no parameters work exactly the same.

Creating a new branch in Veracity requires adding a "--new" parameter (Mercurial doesn't need/use "--new"):

vv branch branch_name_here --new

One interesting branching difference is that the default branch in Mercurial is named "default", but in Veracity (as well as git), the default branch is named "master".

...

To switch branches in Veracity, you use the branch or update (with -b parameter before the branch name) command.

vv update -b master -aka- vv branch master
vv update -b branch1 -aka- vv branch branch1

To switch branches in hg, you use the update or checkout command.

hg update default
hg update branch1

(To switch branches in git, you use the checkout command.)

...

Cloning a repository from a web server is different.

hg:

hg clone http://localhost:8000/ .

Veracity:

vv clone http://localhost:8080/repos/corprepo localdevrepo
vv checkout localdevrepo

...

Pushing and pulling from a repository is basically the same in the no parameter scenario:

>hg pull
pulling from http://localhost:8000/
searching for changes
no changes found

>hg push
pushing to http://localhost:8000/
searching for changes
no changes found

>vv pull
Pulling from http://localhost:8080/repos/corprepo:
Retrieving dagnodes...done
Retrieving blobs...done
Committing changes...done
Merging databases...done

>vv push
Pushing to http://localhost:8080/repos/corprepo:
Sending dagnodes...done
Sending blobs...done
Committing changes...done
Cleaning up...done

Veracity currently only supports pushing and pulling from a cloned repo to it's "parent", but SourceGear is actively working on enhancements in this area.

Thursday, January 20, 2011 6:11:41 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Monday, November 29, 2010

In May of 2010, Google released a new 2.5 version of the Google Checkout API.  The most compelling feature of this version of the API is that it no longer requires https/SSL to retrieve notifications.  They didn't update the .NET sample code to show how to use the new API though (it's now November, so it's been six months).

Below I have not only included the missing sample code for notification handling with GCO 2.5, but I did it using ASP.NET MVC instead of ASP.NET Classic WebForms.  I believe this is the first published Google Checkout ASP.NET MVC sample.

You will want to check the Integration Console in the Google Checkout Sandbox website constantly while testing.  Some errors will only show up there.  You will also want to catch and log errors somewhere like a database, which the below sample does not do.  You may also want to send yourself an e-mail when an order comes through.  You'll have to code that feature yourself, it's not too difficult.


First, Download the Google Checkout .NET DLLs (at least version 2.5.0.3, published 10-16-2010)
Unzip the Google Checkout .NET DLLs and put them somewhere you can find them later


Next, Start Visual Studio 2008

File -> New -> Project
 Visual C# -> Web -> ASP.NET MVC Web Application
  GcoNotifHandlerForSandbox
  OK
 No, do not create a unit test project
  OK

In Solution Explorer -> Right Click References -> Add Reference...
 Select the "GCheckout.dll" you downloaded and unzipped above
 OK

Open web.config for editing:
 Replace "<appSettings/>" with:

<appSettings>
  <
add key="GoogleMerchantID" value="YourMerchantID" />
  <
add key="GoogleMerchantKey" value="YourMerchantKey" />
  <
add key="GoogleEnvironment" value="Sandbox" />
</
appSettings>

Be sure to substitute in your proper sandbox merchant ID and key.

In Solution Explorer -> Right Click Controllers -> Add -> Controller...
 Controller Name: GcoNotifHandlerController
 Add

Open Global.asax.cs for editing:
 Add a new route before the default route:

routes.MapRoute(
  "GcoNotifHandler",
  "{controller}/{action}/{id}",
  new { controller = "GcoNotifHandler", action = "Index", id = "" }
);

In Solution Explorer -> Right Click Models -> Add -> Class...
 Name: GoogleCheckoutHelper.cs
 Add

Add the following new static methods to GoogleCheckoutHelper:

public static string GetGoogleOrderNumber(string serialNumber)
{
  return serialNumber.Substring(0, serialNumber.IndexOf('-'));
}

private static void HandleAuthorizationAmountNotification(GCheckout.AutoGen.AuthorizationAmountNotification inputAuthorizationAmountNotification)
{
  // TODO: Add custom processing for this notification type
}

private static void HandleChargeAmountNotification(GCheckout.AutoGen.ChargeAmountNotification inputChargeAmountNotification)
{
  // TODO: Add custom processing for this notification type
}

private static void HandleNewOrderNotification(GCheckout.AutoGen.NewOrderNotification inputNewOrderNotification)
{
  // Retrieve data from MerchantPrivateData
  GCheckout.AutoGen.anyMultiple oneAnyMultiple = inputNewOrderNotification.shoppingcart.merchantprivatedata;
  System.Xml.
XmlNode[] oneXmlNodeArray = oneAnyMultiple.Any;
  string hiddenMerchantPrivateData = oneXmlNodeArray[0].InnerText;
  // TODO: Process the MerchantPrivateData if provided

  foreach (GCheckout.AutoGen.Item oneItem in inputNewOrderNotification.shoppingcart.items)
  {
    // TODO: Get MerchantItemId from shopping cart item (oneItem.merchantitemid) and process it
  }

  // TODO: Add custom processing for this notification type
}

private static void HandleOrderStateChangeNotification(GCheckout.AutoGen.OrderStateChangeNotification inputOrderStateChangeNotification)
{
  // Charge Order If Chargeable
  if ((inputOrderStateChangeNotification.previousfinancialorderstate.ToString().Equals("REVIEWING")) && (inputOrderStateChangeNotification.newfinancialorderstate.ToString().Equals("CHARGEABLE")))
  {
    GCheckout.OrderProcessing.
ChargeOrderRequest oneChargeOrderRequest = new GCheckout.OrderProcessing.ChargeOrderRequest(inputOrderStateChangeNotification.googleordernumber);
    GCheckout.Util.
GCheckoutResponse oneGCheckoutResponse = oneChargeOrderRequest.Send();
  }

  // Update License If Charged
  if ((inputOrderStateChangeNotification.previousfinancialorderstate.ToString().Equals("CHARGING")) && (inputOrderStateChangeNotification.newfinancialorderstate.ToString().Equals("CHARGED")))
  {
    // TODO: For each shopping cart item received in the NewOrderNotification, authorize the license
  }

  // TODO: Add custom processing for this notification type
}

private static void HandleRiskInformationNotification(GCheckout.AutoGen.RiskInformationNotification inputRiskInformationNotification)
{
  // TODO: Add custom processing for this notification type
}

public static void ProcessNotification(string serialNumber)
{
  string googleOrderNumber = GetGoogleOrderNumber(serialNumber);

  List<string> listOfGoogleOrderNumbers = new List<string> { googleOrderNumber };

  GCheckout.OrderProcessing.NotificationHistoryRequest oneNotificationHistoryRequest = new GCheckout.OrderProcessing.NotificationHistoryRequest(listOfGoogleOrderNumbers);

  GCheckout.OrderProcessing.NotificationHistoryResponse oneNotificationHistoryResponse = (GCheckout.OrderProcessing.NotificationHistoryResponse)oneNotificationHistoryRequest.Send();

  // oneNotificationHistoryResponse.ResponseXml contains the complete response

  // Iterate through the notification history for this order looking for the notification that exactly matches the given serial number
  foreach (object oneNotification in oneNotificationHistoryResponse.NotificationResponses)
  {
    if (oneNotification.GetType().Equals(typeof(GCheckout.AutoGen.NewOrderNotification)))
    {
      GCheckout.AutoGen.
NewOrderNotification oneNewOrderNotification = (GCheckout.AutoGen.NewOrderNotification)oneNotification;
      if (oneNewOrderNotification.serialnumber.Equals(serialNumber))
      {
        HandleNewOrderNotification(oneNewOrderNotification);
      }
    }
    else if (oneNotification.GetType().Equals(typeof(GCheckout.AutoGen.OrderStateChangeNotification)))
    {
      GCheckout.AutoGen.
OrderStateChangeNotification oneOrderStateChangeNotification = (GCheckout.AutoGen.OrderStateChangeNotification)oneNotification;
      if (oneOrderStateChangeNotification.serialnumber.Equals(serialNumber))
      {
        HandleOrderStateChangeNotification(oneOrderStateChangeNotification);
      }
    }
    else if (oneNotification.GetType().Equals(typeof(GCheckout.AutoGen.RiskInformationNotification)))
    {
      GCheckout.AutoGen.
RiskInformationNotification oneRiskInformationNotification = (GCheckout.AutoGen.RiskInformationNotification)oneNotification;
      if (oneRiskInformationNotification.serialnumber.Equals(serialNumber))
      {
        HandleRiskInformationNotification(oneRiskInformationNotification);
      }
    }
    else if (oneNotification.GetType().Equals(typeof(GCheckout.AutoGen.AuthorizationAmountNotification)))
    {
      GCheckout.AutoGen.
AuthorizationAmountNotification oneAuthorizationAmountNotification = (GCheckout.AutoGen.AuthorizationAmountNotification)oneNotification;
      if (oneAuthorizationAmountNotification.serialnumber.Equals(serialNumber))
      {
        HandleAuthorizationAmountNotification(oneAuthorizationAmountNotification);
      }
    }
    else if (oneNotification.GetType().Equals(typeof(GCheckout.AutoGen.ChargeAmountNotification)))
    {
      GCheckout.AutoGen.
ChargeAmountNotification oneChargeAmountNotification = (GCheckout.AutoGen.ChargeAmountNotification)oneNotification;
      if (oneChargeAmountNotification.serialnumber.Equals(serialNumber))
      {
        HandleChargeAmountNotification(oneChargeAmountNotification);
      }
    }
    else
    {
      throw new ArgumentOutOfRangeException("Unhandled Type [" + oneNotification.GetType().ToString() + "]!; serialNumber=[" + serialNumber + "];");
    }
  }
}


Open Controllers\GcoNotifHandlerController.cs for editing:
 Replace the Index method with:

public ActionResult Index()
{
  string serialNumber = null;

  // Receive Request
  System.IO.Stream requestInputStream = Request.InputStream;
  string requestStreamAsString = null;
  using (System.IO.StreamReader oneStreamReader = new System.IO.StreamReader(requestInputStream))
  {
    requestStreamAsString = oneStreamReader.ReadToEnd();
  }

  // Parse Request to retreive serial number
  string[] requestStreamAsParts = requestStreamAsString.Split(new char[] { '=' });
  if (requestStreamAsParts.Length >= 2)
  {
    serialNumber = requestStreamAsParts[1];
  }

  // Call NotificationHistory Google Checkout API to retrieve the notification for the given serial number and process the notification
  GcoNotifHandlerForSandbox.Models.GoogleCheckoutHelper.ProcessNotification(serialNumber);

  string notifAckBegin = "<notification-acknowledgment xmlns=\"http://checkout.google.com/schema/2\"";
  string notifAckSerialNumAttrBegin = " serial-number=\"";
  string notifAckSerialNumAttrEnd = "\"";
  string notifAckEnd = " />";
  return this.Content(notifAckBegin + notifAckSerialNumAttrBegin + serialNumber + notifAckSerialNumAttrEnd + notifAckEnd);
}


That's all there is for building the basic notification handling web service.

You need to publish your notification handling web service to a public website and configure your Google Checkout sandbox account (at https://sandbox.google.com/checkout/sell/ -> Settings -> Integration -> API Callback URL) to use the URL of your web service (http://www.<yourdomain>.com/GcoNotifHandlerForSandbox/).

The Callback contents should be set to "Notification Serial Number" and the API Version should be set to "Version 2.5".


To test your notification web service, you'll need a website with a Google Checkout button.  Instructions for building that are available here:

http://code.google.com/apis/checkout/samples/Google_Checkout_Sample_Code_NET.html


(Note: I have successfully tested this with both ASP.NET MVC 1 & 2 for Visual Studio 2008.)


Good luck!

Monday, November 29, 2010 12:06:24 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Saturday, August 07, 2010

Consuming OData from Silverlight 4 can be a very frustrating experience for people like me who are just now joining the Silverlight party.

When it doesn't work, it can fail silently and/or with incorrectly worded warnings.

This is a perfect example of where a blog post can hopefully fill in some of the gaps left by the Microsoft documentation.

First, let's setup a very simple Silverlight 4 application against the odata.org Northwind service.

Note: You may need to install the Silverlight 4 Tools for VS 2010 for this sample.
 http://www.bing.com/search?q=Microsoft+Silverlight+4+Tools+for+Visual+Studio+2010

In Visual Studio 2010:

File -> New -> Project
 Visual C# -> Silverlight -> Silverlight Application -> OK
  New Silverlight Application wizard
   Uncheck "Host the Silverlight application in a new website"
   Silverlight Version: Silverlight 4
   OK

Right Click References in Solution Explorer -> Add Service Reference...
 Address: http://services.odata.org/Northwind/Northwind.svc/
 Namespace: RemoteNorthwindServiceReference
 OK

From the toolbox, drag and drop a DataGrid from the "Common Silverlight Controls" section onto MainPage.xaml
 Change the AutoGenerateColumns property to True
 Change the Margin to 0,0,0,0
 Change the Width to 400

In MainPage.xaml.cs:
 Add the following using statement:

  using System.Data.Services.Client;

 Add the following class variable:

  private DataServiceCollection<RemoteNorthwindServiceReference.Shipper> _shippers;

 Add the following to the bottom of the MainPage constructor method:

  RemoteNorthwindServiceReference.NorthwindEntities remoteNorthwindService =
    new RemoteNorthwindServiceReference.NorthwindEntities(
      new Uri("http://services.odata.org/Northwind/Northwind.svc/"));

  _shippers = new DataServiceCollection<RemoteNorthwindServiceReference.Shipper>();
  _shippers.LoadCompleted +=
new EventHandler<LoadCompletedEventArgs>(_shippers_LoadCompleted);

  var query = from shippers in remoteNorthwindService.Shippers select shippers;
  _shippers.LoadAsync(query);

 Add the following new method:

  private void _shippers_LoadCompleted(object sender, LoadCompletedEventArgs e)
  {
    if (_shippers.Continuation != null)
    {
      _shippers.LoadNextPartialSetAsync();
    }
    else
    {
      this.dataGrid1.ItemsSource = _shippers;
      this.dataGrid1.UpdateLayout();
    }
  }

Debug -> Start Without Debugging
 You should get a dialog box titled "Silverlight Project" that says:
  "The Silverlight project you are about to debug uses web services.  Calls to the web service will fail unless the Silverlight project is hosted in and launched from the same web project that contains the web services.  Do you want to debug anyway?"
 Click "Yes"

This application runs fine.  The warning dialog message was obviously inaccurate and misleading.

I wish I would have understood what this warning dialog was trying to say, but since the remote OData service was working, I had no real choice but to ignore the dialog, which would haunt me when trying to use the same application to call a local OData service.

I believe this dialog is trying to warn you that Silverlight has special constraints when calling web services, but it's still not clear to me what those are.  Proceed cautiously.

Here is one way to successfully call a local OData webservice from Silverlight 4 in Visual Studio 10:

File -> New -> Project
 Visual C# -> Silverlight -> Silverlight Application -> OK
  New Silverlight Application wizard
   OK (Accept the defaults: ASP.NET Web Application Project, Silverlight 4, etc.)

Right Click SilverlightApplication<number>.Web in Solution Explorer -> Add New Item...
 Visual C# -> Data -> ADO.NET Entity Data Model -> Add
  Entity Data Model Wizard
   Next (Generate from database - this assumes you have Northwind running locally)
   New Connection... -> Point to your local Northwind Database Server -> Next
   Select the Tables check box (to select all tables) -> Finish

Right Click SilverlightApplication<number>.Web in Solution Explorer -> Add New Item...
 Visual C# -> Web -> WCF Data Service -> Add

In WcfDataService1.cs:
 Replace " /* TODO: put your data source class name here */ " with "NorthwindEntities" (no quotes)
 Add the following line to the InitializeService method:

  config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);

Debug -> Start Without Debugging

Right Click References under SilverlightApplication<number> in Solution Explorer -> Add Service Reference...
 Discover -> Services In Solution (note the port number of the discovered service, you will need it below)
 Namespace: LocalNorthwindServiceReference
 OK

Stop the browser that was started above

Follow the same basic steps as the first example above to modify MainPage except:
 Replace RemoteNorthwindServiceReference with LocalNorthwindServiceReference
 Replace "http://services.odata.org/Northwind/Northwind.svc/" with "http://localhost:<port number noted above>/WcfDataService1.svc/"

Debug -> Start Without Debugging

This application runs fine (without the warning dialog this time).

It can be ridiculously difficult to get a local OData service working with Silverlight 4 if you don't carefully dance around the project setup issues.
The warning dialog when you start debugging is nearly useless and the app will return no data with no apparent errors if you setup the project incorrectly.

Some references I found useful while building this sample:

 MSDN: How to: Create the Northwind Data Service (WCF Data Services/Silverlight)
 http://msdn.microsoft.com/en-us/library/cc838239(VS.95).aspx

 Audrey PETIT's blog: Use OData data with WCF Data Services and Silverlight 4
 http://msmvps.com/blogs/audrey/archive/2010/06/10/odata-use-odata-data-with-wcf-data-services-and-silverlight-4.aspx

 Darrel Miller's Bizcoder blog: World’s simplest OData service
 http://www.bizcoder.com/index.php/2010/03/26/worlds-simplest-odata-service/

Once you deploy your OData service to a real web hosting environment, you'll likely need to do some special setup to access your OData service from Silverlight 4 (clientaccesspolicy.xml / crossdomain.xml):

 Making a Service Available Across Domain Boundaries
 http://msdn.microsoft.com/en-us/library/cc197955(VS.95).aspx

Tremendous thanks go out to Scott Davis of Ignition Point Solutions for pointing me to the flawed project setup as the reason I couldn't get this working initially.

Saturday, August 07, 2010 12:49:17 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [1]  | 
# 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]  | 
# Monday, January 25, 2010

The main documentation for the NHibernate Validator is here:

http://nhforge.org/wikis/validator/nhibernate-validator-1-0-0-documentation.aspx

And it alludes to the fact that you can configure the validator to be called automatically before Insert/Update.

However, it fails to clearly communicate how to do this.

The important missing piece is that "ValidatorInitializer.Initialize()" needs to be called before the session factory gets created.

Here is some working sample code:

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

NHibernate.Validator.Engine.ValidatorEngine oneValidatorEngine =
 
new NHibernate.Validator.Engine.ValidatorEngine();

NHibernate.Validator.Cfg.INHVConfiguration oneINHVConfiguration =
 
new NHibernate.Validator.Cfg.Loquacious.FluentConfiguration();
//oneINHVConfiguration.Properties[NHibernate.Validator.Cfg.Environment.AutoregisterListeners] = "true";
oneINHVConfiguration.Properties[NHibernate.Validator.Cfg.Environment.ValidatorMode] = "UseExternal";
oneINHVConfiguration.Mappings.Add(
new NHibernate.Validator.Cfg.MappingConfiguration("YourNamespaceGoesHere", null));
oneValidatorEngine.Configure(oneINHVConfiguration);

NHibernate.Cfg.Configuration nHibernateConfiguration = new NHibernate.Cfg.Configuration();
nHibernateConfiguration.SetProperty(NHibernate.Cfg.
Environment.ConnectionString,
 
connectionString);
nHibernateConfiguration.SetProperty(NHibernate.Cfg.Environment.Dialect,
 
typeof(NHibernate.Dialect.MsSql2005Dialect).AssemblyQualifiedName);
nHibernateConfiguration.SetProperty(NHibernate.Cfg.Environment.ProxyFactoryFactoryClass,
 
typeof(NHibernate.ByteCode.Castle.ProxyFactoryFactory).AssemblyQualifiedName);
nHibernateConfiguration.AddAssembly(System.Reflection.
Assembly.GetCallingAssembly());

NHibernate.Validator.Cfg.ValidatorInitializer.Initialize(nHibernateConfiguration, oneValidatorEngine);

NHibernate.ISessionFactory oneISessionFactory = nHibernateConfiguration.BuildSessionFactory();

 

This code throws an NHibernate.PropertyValueException instead of an NHibernate.Exceptions.GenericADOException / System.Data.SqlClient.SqlException, which is what we want in this case.  Another exception I've seen the NHibernate Validator throw is NHibernate.Validator.Exceptions.InvalidStateException.

It's much easier and more pleasant to try to recover from a validation error than a database error.

Monday, January 25, 2010 2:42:45 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Sunday, September 27, 2009

I like to store my application's configuration in app.config or web.config (depending on the project type) which is pretty standard for .NET applications.  Therefore, I'm not a big fan of ActiveRecord's XmlConfigurationSource.

That said, the InPlaceConfigurationSource is somewhat difficult to puzzle out.  After reading the source code, I was able to bend it to my will.

In particular, I like this syntax well enough:

InPlaceConfigurationSource configuration =
    InPlaceConfigurationSource.Build(DatabaseType.MSSQLServer2005, connectionString);

It lets me setup my connection string and database type and default most everything else.

The "problem" is that I can't then do something clean and simple like this:

configuration.Add("show_sql", true);

Instead I have to do this:

Castle.Core.Configuration.MutableConfiguration oneMutableConfiguration =
  (Castle.Core.Configuration.MutableConfiguration)configuration.GetConfiguration(typeof(ActiveRecordBase));
oneMutableConfiguration.Children.Add(new
  Castle.Core.Configuration.MutableConfiguration("show_sql", true.ToString()));
configuration.Add(typeof(ActiveRecordBase), oneMutableConfiguration);

Which works, but it's not the friendliest API to program against.

Be careful, the "Add" method on InPlaceConfigurationSource is not really "Add".  It is really "Replace".

The other main alternative for using InPlaceConfigurationSource is something like this, which is useful for when I can't just use the defaults:

IDictionary<string, string> properties =
  new System.Collections.Generic.Dictionary<string, string>();
properties.Add("connection.driver_class", "NHibernate.Driver.SqlClientDriver");
properties.Add("connection.provider",
  "NHibernate.Connection.DriverConnectionProvider");
properties.Add("proxyfactory.factory_class",
  "NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle");

properties.Add("dialect", "NHibernate.Dialect.MsSql2005Dialect");
properties.Add("connection.connection_string", connectionString);

properties.Add("show_sql", true.ToString());

InPlaceConfigurationSource configuration = new InPlaceConfigurationSource();
configuration.Add(typeof(ActiveRecordBase), (IDictionary<string, string>)properties);


This was written and tested against "ActiveRecord 2.0 - August 1st, 2009".

Sunday, September 27, 2009 1:05:50 PM (GMT Daylight Time, UTC+01: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]  | 
# 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.

 

Saturday, July 18, 2009 1:48:28 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [2]  | 
# 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]  | 
# 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.

Monday, June 15, 2009 10:35:31 AM (GMT Daylight Time, UTC+01: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.)

Tuesday, June 09, 2009 7:53:45 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  | 
# 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.

Saturday, May 09, 2009 12:44:29 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  | 
# 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]  | 
# Saturday, March 01, 2008

When I went to install Microsoft Visual Studio 2005 Service Pack 1 on Windows Server 2003 I had a lot of unexpected trouble.

Windows Update gave me this obscure error code:

Error Code: 0x64C

So, I did some googling and landed on this very helpful web page:

Installing Visual Studio 2005 SP1

The main key was to look into the Event Log (how often I forget) and see this error:

---
Product: Microsoft Visual Studio 2005 Premier Partner Edition - ENU -- Error 1718.File C:\WINDOWS\Installer\66b5f.msp did not pass the digital signature check. For more information about a possible resolution for this problem, see http://go.microsoft.com/fwlink/?LinkId=73863.
---

I'm not even sure what "Premier Partner Edition" is.  It seems like I have that edition, plus the normal professional edition of VS 2005 installed.  Anyway, that helpful URL points to KB925336.  That knowledge base article points to a hotfix for Windows Server 2003, which is apparently required before installing large updates (and VS2K5 SP1 indeed appears to be large).

I did dig up my original installation media and did have that in my CD drive when I eventually successfully got the service pack to install, but I'm pretty confident it was the hotfix that got everything to work and I probably didn't need the CD.

It did take at least 20 minutes for the update to install (not including download time), so be patient.

Saturday, March 01, 2008 4:26:37 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Friday, August 05, 2005

As a Windows user, running applications from the command line is a major pain in the ass (if I preferred working from the command line, I would use Linux).  Yet, with NAnt, there is little choice but to open a command prompt.  Well, there is an open source project called NAntRunner that allows you to run NAnt from a VS.NET Add-In, but that doesn't look like what I want either as I often have to close VS.NET to allow NAnt to overwrite the DLLs VS.NET has locked (I believe this is a bug in VS.NET) and if I want to build a single solution, I just do that in the normal VS.NET way anyway.

So, I often open a command prompt, run a batch file to add nant.exe to my path (I intentionally change my path and other environment variables in an environment specific batch file so I'm sure to always grab the proper version of the applications for that particular development/build environment) and then start cd'ing around to different directory to run the minimal set of NAnt targets I need to get the job done.

So, I did a google search for how to add a right click "Open Command Prompt Here" to Windows File Explorer, so I can at least start the command prompt in an arbitrary directory quickly and easily.  There are some excellent options for this feature here.  I chose option 3 as it was simple and low risk (I can easily undo it - there is no "magic").

This makes running NAnt that much more tolerable, but I think what I really want is a Windows File Explorer Add-In to run NAnt for an arbitrary target in an arbitrary environment, running an arbitrary batch file to initialize the environment first.  It seems very doable, but just doesn't interest me enough to actually implement it.

And why does a google search for "NAnt Runner" (notice the subtle space between the words) return nothing useful?  Hmm...  MSN search would have at least gotten me where I needed to go with a couple of non-obvious clicks, but there still isn't a direct link on the first page of results.  Not that I have much search engine power, but I'm going create this link anyway in hopes that it helps other people: NAnt Runner.

Friday, August 05, 2005 11:36:34 AM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  | 
# Thursday, June 16, 2005

Gunjan Doshi: Using Visual Studio.Net to edit NAnt build files
[via Bill Arnette on WinTechOffTopic]

This very quickly makes the NAnt build script editing environment a lot more pleasant and productive.

Thursday, June 16, 2005 11:05:37 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  | 
# Tuesday, June 14, 2005

Trying to run NDoc (1.3.1) on a newly setup PC, I ran into this error:

From the NDoc GUI:
---
An error occured while trying to build the documentation.

Exception: NDoc.Core.DocumenterException
Unable to find the HTML Help Compiler. Please verify that the HTML Help Workshop has been installed.

Exception: NDoc.Core.DocumenterException
Unable to find the HTML Help Compiler. Please verify that the HTML Help Workshop has been installed.
---

From the NAnt NDoc Utility:
---
Error building documentation.
    Unable to find the HTML Help Compiler. Please verify that the HTML Help Workshop has been installed.
        Unable to find the HTML Help Compiler. Please verify that the HTML Help Workshop has been installed.
---

As of today, it's not well documented on Google what this error really means.

The fix was to rerun the VS.NET 2003 installer (Control Panel -> Add/Remove Programs -> Select VS.NET -> Change/Remove). Then add the "Enterprise Development Tools->Visual Studio SDKs->HTML Help 1.3 SDK" item.

My original guess was that I got the error because I hadn't installed the MSDN library yet.  Thanks to this weblog entry for pointing me in the right direction (toward the VS.NET installer):

What is "HTML Help Workshop"?

If I were Microsoft, I wouldn't consider the HTML Help compiler as part of the "Enterprise Development Tools" package and hide it in the installer.  NDoc is a best practice that every developer who is writing a component/library should use, not just enterprise developers.  IMHO, hhc.exe should be part of the core VS.NET install.

Tuesday, June 14, 2005 10:38:37 AM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [1]  |