Click here to monitor SSC
  • Av rating:
  • Total votes: 45
  • Total comments: 14
Nick Harrison

Dynamic LINQ Queries with Expression Trees

25 February 2013

It's possible to build up dynamic LINQ queries or queries with several conditional criteria. In fact there are several options for doing this, including the use of expression trees

Like it or not, LINQ is here to stay. Personally I think this is a good thing. After some initial hesitation, I have sipped the Kool Aid and it’s not that bad.

LINQ brings a lot of benefits:

  • No magic strings (makes refactoring tools much more effective)
  • SQL-like syntax outside of the database
  • LINQ providers for various data sources (SQL, Entities, Objects, nHibernate, XML, etc)

Initially, I thought that there was a big limitation in not being able to build up dynamic queries or queries with conditional criteria. Fortunately, this turned out to be a deficiency in the samples and documentation, not LINQ itself. There are actually lots of options available.

In this article, we will review various approaches for building dynamic LINQ queries, starting with some fairly straightforward options and moving on to building the query from scratch with Expression Trees. None of this will require the System.LINQ.Dynamic namespace.

Hopefully you'll enjoy the ride.

A Little Background

There are two flavors for expressing a LINQ query; they go by different names depending on the source you are reading, the two most common culprits are: Query (or Query Expression) and Method Chaining (or Fluent). In some circles, debates will range long and loud over which method to use as well as what to call it.

For purposes of this discussion, we will use the latter syntax and call it Method chaining. We are chaining together method calls and it's a fluent interface. So I give props to both names. This is personal preference, but I believe that IntelliSense kicks in quicker with Method chaining. I also think this syntax makes a couple of implications more obvious. Also, I should point out that Visual Studio and C#/ VB.Net don’t really care which format you use. You can use either one, or you could use both. For the sake of your team and transitioning from section to section or project to project, I recommend that you pick an approach and then stick with it. I prefer Method chaining.

Let’s see what a simple query will look like in Query syntax and Method chaining syntax. Here we want to query a list of files to retrieve the ones that imported in the last week.

With Query syntax, it may look like this:

public IQueryable<FileImport> QuerySyntax(IQueryable<FileImport> files)
{
	return (from file in files where file.ImportDate >
		DateTime.Today.AddDays(-7) select file);
  
}

With Method chaining it may look like this:

public IQueryable<FileImport> ChainedSyntax(IQueryable<FileImport> files)
{
	return files.Where(file => file.ImportDate >
		DateTime.Now.AddDays(-7));
        }

These two queries look very similar and they will do exactly the same thing.

If you wanted to add additional criteria, you could simply add them to the “Where” section.

public IQueryable<FileImport> QuerySyntax(IQueryable<FileImport> files)
{
	return (from file in files where file.ImportDate >
DateTime.Today.AddDays(-7)
		&& file.ImportDate < DateTime.Today
		select file);
}

public IQueryable<FileImport>ChainedSyntax(IQueryable<FileImport> files)
{
	return files.Where(file => file.ImportDate > DateTime.Now.AddDays(-7) && file.ImportDate < DateTime.Today );
}

So far everything seems comfortable and if you look at most of the examples on the Internet or MSDN this is what you would see. As the query gets more complex, the “Where” section gets more complex. As we will soon see, this is not the only option.

From here on out, we will include the sample code using Method chaining syntax. Rest assured, in most cases, you can still do the same thing with either format. In fact, Resharper even has helper links to automate switching back and forth.

Simple Dynamic Query With Multiple Where Statements

In a traditional SQL statement, we are limited to a single WHERE statement and all of the criteria goes in that one WHERE statement. When you are first learning LINQ, it looks as if you have the same limitation and, in fact, you will search high and low on the Internet before finding examples that don’t reinforce this perception. Facing such restrictions, dynamic queries are tough. Fortunately, we don’t face such restrictions, we know we can have any number of WHERE methods in a LINQ query. Consider this:

Our last example could be written like this:

public IQueryable<FileImport> ChainedSyntax(IQueryable<FileImport> files)
{
	return files.Where(file => file.ImportDate >
							DateTime.Now.AddDays(-7))
				.Where(file => file.ImportDate < DateTime.Today);
}

The two queries are identical in meaning, but note the subtle difference here:

public IQueryable<FileImport> ChainedSyntax(IQueryable<FileImport> files)
{
	files.Where(file => file.ImportDate > DateTime.Now.AddDays(-7));
	return files.Where(file => file.ImportDate < DateTime.Today);
}

This looks very similar to the previous query but will have dramatically different results. In this last query, the initial filter requiring that the Import date had to be in the last seven days is ignored. This is because the Queryable is immutable. The first call to the Where method on the files object does nothing to the original files object. Instead, it creates a brand new object and returns it. This is why when we call the Where method on the files object the second time we have lost what happened to the first call. This is a subtle but very important difference.

When we chain methods, we are automatically operating on the returned value from the previous call.

So the correct way to write the last example would be something like this:

public IQueryable<FileImport>ChainedSyntax(IQueryable<FileImport> files)
{
	var query = files.Where(file => file.ImportDate > DateTime.Now.AddDays(-7));
	query = query.Where( file => file.ImportDate < DateTime.Today);
	return query;
}

Now to make the query dynamic, we change things up slightly:

public IQueryable<FileImport> DynamicChainedSyntax
		(IQueryable<FileImport> files, bool pastOnly)
{
	var query = files.Where(file => file.ImportDate >
				DateTime.Now.AddDays(-7));
	if (pastOnly)
		query = query.Where(file => file.ImportDate <
				DateTime.Today);
	return query;
}

Now, depending on the value of the pastOnly parameter, the second filtering criteria may or may not be included in the query.

Type Safe Dynamic Sorting

It is common to display a grid of data to a user and allow the user to sort it any way that they want. We can easily accommodate this dynamically by adding the appropriate OrderBy method call to our query.

If we know that there are only a handful of options that the user could specify, we could do something like this:

public IQueryable<FileImport> DynamicChainedSyntaxSorted
		(IQueryable<FileImport> files, bool pastOnly)
			string orderBy)

{
	var query = files.Where(file => file.ImportDate >
							DateTime.Now.AddDays(-7));
	if (pastOnly)
		query = query.Where(file => file.ImportDate <
							DateTime.Today);
	if (orderBy == "FileName")
		query = query.OrderBy(o => o.FileName);
	if (orderBy == "ImportDate")
		query = query.OrderBy(o => o.ImportDate);
	return query;
}

This is very intuitive and it makes it very obvious what you are doing. It can be very tedious as well. If we need to add a new property that could be sorted by, we would have to change this code. It also has the name of the property hard coded in the logic. We will also have to duplicate this logic everywhere that we want to allow dynamic sorting on the FileImport object.

One way to improve this will be to encapsulate the OrderBy logic into its own method.

We start by noting the signature of the OrderBy method. The method signature that we are interested in looks like this:

public static IOrderedQueryable<TSource> OrderBy<TSource, TKey>(
	this IEnumerable<TSource> source,
	Func<TSource, TKey> keySelector
)

We are passing in a Lambda expression that is a function expecting TSource and returning TKey. We can encapsulate the sorting logic by defining a method that will take a string for the property name and return such a function.

By pulling the conditional logic out of the query, we limit the number of places that will need to change as new sort options become available.

public Func<FileImport, object> EvaluateOrderBy(string propertyName)
{
	if (propertyName == "FileName")
		return f => f.FileName;
	if (propertyName == "ImportDate")
		return f => f.ImportDate;
	return f => f.Id;
}

Armed with such a function, our dynamic query can be rewritten like this:

public IQueryable<FileImport> DynamicChainedSyntaxSorted
		(IQueryable<FileImport> files, bool pastOnly)
			string orderBy)

{
	var query = files.Where(file => file.ImportDate >
							DateTime.Now.AddDays(-7));
	if (pastOnly)
		query = query.Where(file => file.ImportDate <
							DateTime.Today);
	if (!string.IsNullOrEmpty(orderBy))
    		query = query.OrderBy(EvaluateOrderBy(orderBy));
	return query;
}

Now our dynamic query looks good. There are no magic strings, and it does not have to be changed when we add new sorting options.

But we can do even more for the EvaluateOrderBy method.

Improving the EvaluateOrderBy Method

The EvaluateOrderBy method has a couple of problems:

  • It will only work with the FileImport object.
  • It will only sort by the options hard coded in.
  • It still has to be updated when we add new options.

Ideally we would like to have a method without the magic strings. We would like to have a method that will work with any TSource. Well that’s a pretty tall order, but I think we are up for it.

This is where express trees come into play. Expression Trees allow us to build up the logic for the method (lambda expression) that we need to return. Expression Trees may seem a little intimidating at first, but don’t worry, the expression that we need to build up is very simple, we need to build a property reference expression. The expression that is created will look like this:

p=> p.PropertyName;

We are going to ignore the more complex cases of:

p=> p.Property.Property.Property...

It is possible that the property references could go on indefinitely. This is possible, but rare in actual practice. If you do find yourself having to manage such a complex object hierarchy, you can use reflection to map out the relationship and then adopt recursion to build up the property references.

For such a simple case, the GenericEvaluateOrderBybecomes this:

public Func<TSource, object> GenericEvaluateOrderBy<TSource>
			(string propertyName)
{
	var type = typeof (TSource);
	var parameter = Expression.Parameter(type, "p");
	var propertyReference = Expression.Property(parameter,
			propertyName);
	return Expression.Lambda<Func<TSource, object> >
			(propertyReference, new[] { parameter }).Compile();
}

There is a lot going on for such a short method.

We start by determining the type for the generic argument. This will also be the type for our lambda expression.

Next we define the parameter by specifying the name and type.

Next we specify the only thing that we are going to do in this function, reference a property. We specify that it is a property of the parameter and we specify the name. If you wanted additional protection, you could use reflection to ensure that the specified property is a property of the type that you found earlier. This is also where you would need to build up a complex structure to support nested property references.

Now that we have all of the “logic” in place, we simply need to convert it to a lambda expression and compile it.

Alternately, we could make this an extension method to the Queryable itself:

public static IOrderedQueryable<TSource>
	GenericEvaluateOrderBy<TSource>
		(this IQueryable<TSource> query,
		string propertyName)
{
	var type = typeof (TSource);
	var parameter = Expression.Parameter(type, "p");
	var propertyReference = Expression.Property(pe, propertyName);
	var sortExpression = Expression.Call(
		typeof (Queryable),
		"OrderBy",
		new Type[] {type},
		null,
		Expression.Lambda<Func<TSource, bool>>
			(propertyReference, new[] { parameter }));
	return query.Provider.CreateQuery<TSource>(sortExpression);
}

With this approach, we pass in the IQueryable and return an IQueryable. The big difference is that now we will explicitly call “OrderBy” passing in the lambda expression that we created earlier.

From a usage perspective, we now have:

public IQueryable<FileImport> DynamicChainedSyntaxSorted
		(IQueryable<FileImport> files, bool pastOnly)
		string orderBy)

{
	var query = files.Where(file => file.ImportDate >
							DateTime.Now.AddDays(-7));
	if (pastOnly)
		query = query.Where(file => file.ImportDate <
							DateTime.Today);
	if (!string.IsNullOrEmpty(orderBy))
    		query = query.GenericEvaluateOrderBy(orderBy);
	return query;
}

This will now allow us to sort any IQueryable by any property regardless of the object being referenced or who the LINQ provider is.

Using Expression Trees to Build a Query from Scratch

For this bit of magic, we are not going to be able generically add any filter criteria in place, but we can add some nice reusable logic.

When we define filter criteria, it will generally take one of just a handful of forms:

p=>p.Property == "Value"
p=>p.Property < "Value"
p=>p.Property > "Value"

The Expression class in the Expressions namespace provides static methods to cover all of our bases.

Here we will focus our attention on three static methods:

  • Equal(Expression, Expression)
  • LessThanOrEqual(Expression, Expression)
  • GreaterThanOrEqual(Expression, Expression)

The other methods available will follow the same pattern of taking two expressions, a Left Expression, a Right Expression, and the method itself defining the binary operator.

In most cases, the two expressions passed into our comparison expression will be a property reference expression and a constant expression.

To create a simple comparison method, we might use something like this:

public Func<TSource, bool> SimpleComparison<TSource>			
			string property, object value)
{
	var type = typeof (TSource);
	var pe = Expression.Parameter(type, "p");
	var propertyReference = Expression.Property(pe, property);
	var constantReference = Expression.Constant(value);
	return Expression.Lambda<Func<TSource, bool>>
		(Expression.Equal(propertyReference, constantReference),
		new[] { pe }).Compile();
}

This will do a simple equality comparison. Simply change the Expression.Equal to any of the other comparison functions to get the different types of comparisons. Everything else stays the same.

The resulting function that is returned is suitable for passing directly into a Where method on our IQueryable.

files.Where(SimpleComparison<FileImport>("FileName",
	"SomeRandomFileName"));

In terms of usability, this is a giant step backwards. The original syntax is much easier to write and read. Not to mention that we now have a magic string in our query, but we are just getting started.

Now that we know how to programmatically create a function that can be included in the where clause, we are ready to explore some exciting options. Consider how we could write a method to require that all dates have passed.

public Func<TSource, bool> AllDatesHavePassed<TSource>()
{
	var type = typeof (TSource);
	var pe = Expression.Parameter(type, "p");
	var dates = type.GetProperties()
		.Where(p => p.PropertyType == typeof (DateTime));
	Expression dateCap = Expression.Constant(DateTime.Today);
	Expression selectLeft = null;
	Expression selectRight = null;
	Expression filterExpression = null;
	foreach (var date in dates)
	{
		Expression left = Expression.Property(pe, date.Name);
		Expression comparison =
			Expression.LessThanOrEqual(left, dateCap);
		if (selectLeft == null)
		{
			selectLeft = comparison;
			filterExpression = selectLeft;
			continue;
		}
		if (selectRight == null)
		{
			selectRight = comparison;
			filterExpression =
				Expression.AndAlso(selectLeft, selectRight);
			continue;
		}
		filterExpression =
			Expression.AndAlso(filterExpression, comparison);
	}
	return Expression.Lambda<Func<TSource, bool>>
		(filterExpression, pe).Compile();
}

We start by getting the type for the Generic Argument. Then we build up a list of the dates that are in this type. We then initialize our parameter expression as we have done in the past. Next we setup a constant expression that we will compare each date property against.

Now things get a bit more interesting. We have a couple of different possibilities in the final comparison depending on how many dates are in the object. Ultimately, we will return a lambda based on filterExpression

  • If there is only one date, filterExpression will use the property reference and thedateCap as left and right.
  • If there are two dates, filterExpression will be a “logical and” expression with one comparison being the “left” and the other comparison being the “right”
  • If there are more than two dates, filterExpression will be a “logical and” expression with one comparison being the “left” and the “right” being a new “logical and” expression. This pattern can go on indefinitely regardless of how many dates there are.

So this one method can handle everything from there being a single property to dozens of date properties. Regardless of how many date properties there are, we can call it like this:

public IQueryable<FileImport> FindExpiredFiles
		(IQueryable<FileImport> files)
{
	return files.Where(AllDatesHavePassed < FileImport>());
}

This simple pattern can easily be extended to create methods such as:

  • AllDatesAreBlank
  • AllBooleansAreTrue
  • AllBooleansAreFalse
  • AllStringFieldsHaveValues
  • AllIntegersArePositive

Conclusion

We live in a brave new world. For years programmers have dreamed of being able to unify data access logic regardless of source. With LINQ we are closer to realizing that goal, and Expression Trees give us complete access to all the magic.

Nick Harrison

Author profile:

Nick Harrison is a Software Architect and .NET advocate in Columbia, SC. Nick has over 14 years experience in software developing, starting with Unix system programming and then progressing to the DotNet platform. You can read his blog as www.geekswithblogs.net/nharrison

Search for other articles by Nick Harrison

Rate this article:   Avg rating: from a total of 45 votes.


Poor

OK

Good

Great

Must read
Have Your Say
Do you have an opinion on this article? Then add your comment below:
You must be logged in to post to this forum

Click here to log in.


Subject: My mind is officially blown
Posted by: Anonymous (not signed in)
Posted on: Wednesday, March 13, 2013 at 9:40 AM
Message: Definitely some strange voodoo going on here.

I have never even heard of Expression Trees. Wow!

Makes me want to rethink everything that I have ever done with linq.

Subject: Generated SQL?
Posted by: TheSQLGuru (view profile)
Posted on: Monday, March 18, 2013 at 6:34 AM
Message: Could you do me a favor and provide the generated sql for those samples above that are built to hit a SQL Server database? I am wondering about what the TSQL looks like and how it might perform. Thanks in advance!

Best,

Kevin G. Boles

Subject: RE: Generated SQL?
Posted by: nick harrison (view profile)
Posted on: Tuesday, March 19, 2013 at 7:17 AM
Message: Kevin,

You bring up a good point. You do need to keep an eye on the SQL generated. For the examples shown in this article, the generated SQL is trivial. For other cases, the SQL could be much more complex.

Hibernating Rhinos makes profilers specifically designed to monitor and identify problems from an ORM http://www.hibernatingrhinos.com/products.

Also the Ants Performance Profiler has some nice features to put the SQL into context of everyhing else that is happening to the database. https://www.simple-talk.com/dotnet/performance/under-the-orm-hood-revealing-the-sql/ provides more background on this. http://www.red-gate.com/products/dotnet-development/ants-performance-profiler/

Each LINQ provider may generate different SQL. You may see subtly different SQL from the same LINQ query by using Entity Framework or NHibernate or any other LINQ provider.

You really need to run a good profiler against your application hitting your data using your provider.

Subject: LINQ Providers
Posted by: nick harrison (view profile)
Posted on: Tuesday, March 19, 2013 at 7:24 AM
Message: Another point that may not be readily apparent, a LINQ provider does not necessarily need to target a database.

Charlie Calvert maintains a list of LINQ providers here http://www.red-gate.com/products/dotnet-development/ants-performance-profiler/

The individual providers take the expression tree expressed by your query and converts it into something meaningful for the platform it is targeting. In the most common case, this is converting the expression tree to a SQL Statement to run against a database, but that is not the only option.

Subject: Code Errors
Posted by: Anonymous (not signed in)
Posted on: Wednesday, March 20, 2013 at 9:23 AM
Message: You might want to revisit the code snippet for the extension method GenericEvaluateOrderBy since it will not compile (VS2012).

Otherwise a very interesting article.

Subject: RE: Code Errors
Posted by: nick harrison (view profile)
Posted on: Wednesday, March 20, 2013 at 1:00 PM
Message: What error do you get when you try to compile?

It may be that you have the wrong references for Expression. Make sure that in your using section you include:

using System.Linq.Expressions;

Let me know if you are getting a different compile error.

Subject: RE: RE: Code Errors
Posted by: Henry Minute (view profile)
Posted on: Friday, March 22, 2013 at 10:13 AM
Message: The following two lines are problematic
var parameter = Expression.Parameter(type, "p");
var propertyReference = Expression.Property(pe, propertyName);

In the first the var is called 'parameter'.
In the second it is referenced as 'pe'

It looks as if a little cut and pasting has been going on :)

Subject: RE: RE: Code Errors
Posted by: nick harrison (view profile)
Posted on: Friday, March 22, 2013 at 12:51 PM
Message: Henry,

You are correct. Or actually an incomplete Rename Variable refactor :)

I tried to convert to more meaningful names at the end and missed a couple.

Sorry about that, and I do hope that this does not cause too much confusion.


Subject: Have a look at Microsoft's System.Linq.Dynamic
Posted by: JoNSmith (view profile)
Posted on: Saturday, March 23, 2013 at 7:03 AM
Message: I have just been writing a server sorting and filtering class to work with JqxWidgets grids in MVC4. I looked at a number of solutions but finally used System.Linq.Dynamic written by Microsoft. It allows you to define Linq Command dynamically using text, e.g.
var query =
db.Customers.
Where("City = @0 and Orders.Count >= @1", "London", 10).
OrderBy("CompanyName").
Select("new(CompanyName as Name, Phone)");

The assembly is available as a download or as a NuGet package put up by King Wilder.

To find out more about Linq.Dynamic follow this link: http://www.lcs.syr.edu/faculty/fawcett/handouts/CoreTechnologies/CSharp/samples/CSharpSamples/LinqSamples/DynamicQuery/Dynamic%20Expressions.html

Subject: Have a look at Microsoft's System.Linq.Dynamic - more
Posted by: JoNSmith (view profile)
Posted on: Saturday, March 23, 2013 at 10:12 AM
Message: Another useful link for you:
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

Subject: RE: Have a look at Microsoft's System.Linq.Dynamic
Posted by: nick harrison (view profile)
Posted on: Saturday, March 23, 2013 at 2:26 PM
Message: Depending on your needs this is a viable alternative.

The main concern that I have with this approach is that if you rename properties on the objects involved, refactoring tools will not catch the references in these strings.

Depending on what you are doing, this may not be a problem.

The other concern is the need to verify the data in the strings being used. Data originating from a user would need to be scrubbed and sanitized before he could be used like this.

I don't think that you would be vulnerable to a traditional SQL injection attack, but still keep the validations in place. Internally the string passed to the Dynamic methods will eventually be converted to Expression Trees which should catch malicious code, but you want to catch potential attacks as early in the process as you can.

John, I would love to hear how this project works out for you.

Subject: Re: Have a look at Microsoft's System.Linq.Dynamic - more
Posted by: JoNSmith (view profile)
Posted on: Saturday, March 23, 2013 at 2:57 PM
Message: Hi Nick,

Thanks for your comments. I am developing a generic jQWidgets GridBuilder class that looks at the classes, including the DataAnnotations, to build the grid. This means that any changes in the properties will be picked up automatically.

The strings are produced by the jQWidget grid sort and filter, so they won't be wrong (although JSON data and .NET date are fun). I worry about a malicious attack and plan to us a GET version of the MVC AntiForgeryToken (I have a link about that).

Project is coming on well, but still early days. Impressed by jQWidgets but takes some digging to find the things you need.

Subject: RE: Have a look at Microsoft's System.Linq.Dynamic
Posted by: nick harrison (view profile)
Posted on: Monday, March 25, 2013 at 7:34 AM
Message: I am not familar with jQWidgets. I will have to look into them.

Dealing with dates can be a pain.

I use this library for my client side date manipulation. http://www.datejs.com/ It is often easier to format the date on the server side rather than sending a raw date over JSON.

Sounds like you are taking some interesting approaches. Please post back to keep us up to date on how this goes.

Subject: RE: Have a look at Microsoft's System.Linq.Dynamic
Posted by: nick harrison (view profile)
Posted on: Monday, March 25, 2013 at 7:56 PM
Message: Depending on your needs this is a viable alternative.

The main concern that I have with this approach is that if you rename properties on the objects involved, refactoring tools will not catch the references in these strings.

Depending on what you are doing, this may not be a problem.

The other concern is the need to verify the data in the strings being used. Data originating from a user would need to be scrubbed and sanitized before he could be used like this.

I don't think that you would be vulnerable to a traditional SQL injection attack, but still keep the validations in place. Internally the string passed to the Dynamic methods will eventually be converted to Expression Trees which should catch malicious code, but you want to catch potential attacks as early in the process as you can.

John, I would love to hear how this project works out for you.

 

Top Rated

Acceptance Testing with FitNesse: Multiplicities and Comparisons
 FitNesse is one of the most popular tools for unit testing since it is designed with a Wiki-style... Read more...

Acceptance Testing with FitNesse: Symbols, Variables and Code-behind Styles
 Although FitNesse can be used as a generic automated testing tool for both applications and databases,... Read more...

Acceptance Testing with FitNesse: Documentation and Infrastructure
 FitNesse is a popular general-purpose wiki-based framework for writing acceptance tests for software... Read more...

TortoiseSVN and Subversion Cookbook Part 11: Subversion and Oracle
 It is only recently that the tools have existed to make source-control easy for database developers.... Read more...

TortoiseSVN and Subversion Cookbook Part 10: Extending the reach of Subversion
 Subversion provides a good way of source-controlling a database, but many operations are best done from... Read more...

Most Viewed

A Complete URL Rewriting Solution for ASP.NET 2.0
 Ever wondered whether it's possible to create neater URLS, free of bulky Query String parameters?... Read more...

Visual Studio Setup - projects and custom actions
 This article describes the kinds of custom actions that can be used in your Visual Studio setup project. Read more...

.NET Application Architecture: the Data Access Layer
 Find out how to design a robust data access layer for your .NET applications. Read more...

Calling Cross Domain Web Services in AJAX
 The latest craze for mashups involves making cross-domain calls to Web Services from APIs made publicly... Read more...

Web Parts in ASP.NET 2.0
 Most Web Parts implementations allow users to create a single portal page where they can personalize... Read more...

Why Join

Over 400,000 Microsoft professionals subscribe to the Simple-Talk technical journal. Join today, it's fast, simple, free and secure.