1.1. Query a Message Queue

Problem

You want to be able to query for messages with specific criteria from an existing message queue.

Solution

Write a query using LINQ to retrieve messages using the System.Messaging.MessageQueue type:

	  // open an existing message queue
	  string queuePath = @".\private$\LINQMQ";
	  MessageQueue messageQueue = new MessageQueue(queuePath);
	      BinaryMessageFormatter messageFormatter = new BinaryMessageFormatter();

	  var query = from Message msg in messageQueue
	     // The first assignment to msg.Formatter is so that we can touch the
	     // Message object. It assigns the BinaryMessageFormatter to each message
	     // instance so that it can be read to determine if it matches the criteria.
	     // Next, a check is performed that the formatter was correctly assigned
	     // by performing an equality check, which satisfies the Where clause's need
	     // for a boolean result while still executing the assignment of the formatter.
	              where ((msg.Formatter = messageFormatter) == messageFormatter) &&
	                  int.Parse(msg.Label) > 5 &&
	                  msg.Body.ToString().Contains('D')
	              orderby msg.Body.ToString() descending
	              select msg;
	  // Check our results for messages with a label > 5 and containing a 'D' in the name
	    foreach (var msg in query) 
	    {
	        Console.WriteLine("Label: " + msg.Label + " Body: " + msg.Body); 
	    }

The query retrieves the data from the MessageQueue by selecting the messages where the Label is a number greater than 5 and the message body contains a capital letter "D". These messages are then returned sorted by the message body in descending order.

Discussion

There are a number of new keywords in this code using LINQ that were not previously used to access a message queue:

var

Instructs the compiler to infer the type of the variable from the right side of the statement. In essence, the type of the variable is determined by what is on the right side of the operator separating the var keyword and the expression. This allows for implicitly typed local variables.

from

The from keyword sets out the source collection to query against and a range variable to represent a single element from that collection. It is always the first clause in a query operation. This may seem counterintuitive if you are used to SQL and expect select to be first, but if you consider that first we need what to work on before we determine what to return, it makes sense. If we weren't used to how SQL does this already, it would be SQL that seems counterintuitive.

where

The where keyword specifies the constraints by which the elements to return are filtered. Each condition must evaluate to a Boolean result, and when all expressions evaluate to true, the element of the collection is allowed to be selected.

orderby

This keyword indicates that the result set should be sorted according to the criteria specified. The default order is ascending, and elements use the default comparer.

select

Allows the projection of an entire element from the collection, the construction of a new type with parts of that element and other calculated values, or a sub-collection of items into the result.

The messageQueue collection is of type System.Messaging.MessageQueue, which implements the IEnumerable interface. This is important, as the LINQ methods provided need a set or collection to implement at least IEnumerable for it to work with that set or collection. It would be possible to implement a set of extension methods that did not need IEnumerable, but most people will not have the need to. It is even better when the set or collection implements IEnumerable<T>, as LINQ then knows the type of element in the set or collection that it is working with, but in this case, MessageQueue has been in the framework for a while and isn't likely to change, so the query provides the element type Message, as shown in the "from" line:

	var query = from Message msg in messageQueue

For more about this, see Recipe 1.1.

In the Solution, the messages in the queue have been sent with the use of the BinaryFormatter. To be able to query against them correctly, the Formatter property must be set on each Message before it is examined as part of the where clause:

	// The first assignment to msg.Formatter is so that we can touch the
	// Message object. It assigns the BinaryMessageFormatter to each message
	// instance so that it can be read to determine if it matches the criteria.
	// This is done, and then it checks that the formatter was correctly assigned
	// by performing an equality check, which satisfies the Where clause's need
	// for a boolean result, while still executing the assignment of the formatter.
	where ((msg.Formatter = messageFormatter) == messageFormatter) &&

There are two uses of the var keyword in the solution code:

	var query = from Message msg in messageQueue 
	            ...

	foreach (var msg in query)
	...

The first usage infers that an IEnumerable<Message> will be returned and assigned to the query variable. The second usage infers that the type of msg is Message because the query variable is of type IEnumerablew<Message> and the msg variable is an element from that IEnumerable.

It is also worth noting that when performing operations in a query, actual C# code can be used to determine the conditions, and there is more than just the predetermined set of operators. In the where clause of this query, both int.Parse and string. Contains are used to help filter messages:

	int.Parse(msg.Label) > 5 &&
	msg.Body.ToString().Contains('D')

See Also

Recipe 1.9, and the "MessageQueue class," "Implicitly typed local variable," "from keyword," "where keyword," "orderby keyword," and "select keyword" topics in the MSDN documentation.

Get C# 3.0 Cookbook, 3rd Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.