-
This post assumes you’ve read my previous Converting SQL to LINQ posts.
I haven’t had much in the way of specific requests for more LINQ posts, so I’d still welcome any suggestions that people want to offer. I did get one request about how to mimic the LIKE keyword functionality in VB LINQ.
In SQL, the LIKE keyword compares a string field against a pattern and returns a Boolean value representing whether the string matches the pattern. For example, if I wanted to select every customer in the 206 area code, I could use this SQL query:
|
SQL |
|
SELECT *
FROM CustomerTable
WHERE Phone LIKE ‘206*’
|
The LIKE expression can include certain wildcard characters, which may differ from system to system. In the above example, * matches any string, so ‘206*’ represents any string that begins with 206.
VB happens to include a Like operator, so this is an easy conversion to make. The LINQ version of this above SQL expression is specified below:
|
VB |
|
From Contact In CustomerTable _
Where Contact.Phone Like “206*”
|
The VB Like keyword has the following wildcards:
? Matches any single character
* Matches zero or more characters
# Matches any single digit
[ charlist ] Matches any single character in the charlist
[! charlist ] Matches any single character not in the charlist
More extensive documentation of the Like operator can be found here.
- Bill Horst, VB IDE Test
-
I will be presenting on the VB IDE in Southern California this week at a series of user group meetings. The events will be held in the San Diego and the Los Angeles area. Charlie Calvert from the C# team will be flying down from Redmond to speak as well.
The schedule is as follows:
I will be speaking on Visual Basic 2008 IDE Tips and Tricks:
In this talk, we’ll show how to turn yourself into a Visual Studio 2008 guru with the new language and IDE features. Tips and tricks will include how to maximize your VB IntelliSense experience, leverage Refactoring features, and improve the performance of your query and XML code. We’ll explore integrated XML, and show how to navigate XML gotchas and express what you wish in fewer lines of code. With respect to LINQ we’ll go deep into best practices, pitfalls to avoid, and answers to most frequently asked questions
Charlie will be talking on C# 3.0 Best Practices and LINQ:
C# 3.0 introduces a number of new language features such as query expressions, lambda expressions, extension methods, automatically implemented properties, local type inference and more. These are all features that can improve the quality of your code. They also provide new opportunities for making mistakes This talk focuses on both the good and the bad: how to use and how not to use the new features of C#. Each feature will be introduced with a small example, and you should be able to follow the talk even if you are not already familiar with the new language constructs. The talk will also explore the theoretical underpinnings of LINQ.
Tuesday Night:
Wednesday Night
- Microsoft
- Three Park Plaza, Suite 1800
- Irvine, CA 92614
Thursday Night
- American Honda Motors
- Bldg 100, # 100-1E-13
- 1919 Torrance Blvd
- Torrance, CA 90501
See you there!
-
Recipe 3 showed one way to work with mixed XML content using the XML Descendant axis property and the ReplaceWith method. This is one way to do an identity transform and we’ll look at another method in a later post. There is a key capability not mentioned in Recipe 3 that you will need for fully-functioning identity transforms. That is the concept of copying the inner XML of an element. You get the inner XML of an element from the Nodes property.
Recipe 3 transformed e-mail objects from our AdventureWorks sample document into hyper-links in an HTML document and preserved the original formatting by performing an identity transform. To keep things simple in Recipe 3, however, I left out the fact that the schema for the <eMailAddress> element (found in the ContactTypes.xsd schema file) allows for both an <eMail> element that contains the e-mail address, and also a <SpecialInstructions> element for additional instructions such as phone numbers to use in place of an e-mail, when to call, and so on. The <SpecialInstructions> element can contain any of the other elements identified in ContactTypes.xsd. To include all of the information for an <eMailAddress> element, we must also include any special instructions. (Note: You can download the XML document and related schemas from the Recipe 1 post).
Transforming the <eMail> element from an e-mail address is simple because the <eMail> element contains only the e-mail address value. However, transforming the <SpecialInstructions> element is not as simple because the element value can contain sub- elements as well as values. If you embedded the Value of a <SpecialInstructions> element, you would get the text found within the element, but any XML found would be stripped out. This is also referred to as the inner text of an element.
If you’re familiar with the classes in the System.Xml namespace, you will immediately recognize this behavior. The solution is to embed the inner XML of the element rather than the inner text (the Value property). While the System.Xml classes expose both InnerText and InnerXml properties, LINQ to XML does not. Inner text is returned from the Value property, and inner XML is returned from the Nodes property. Why the difference? The InnerXml property returns a string of XML, which would have to be re-parsed into XML objects. The Nodes property returns a collection of LINQ to XML objects, which you can manipulate or embed as a whole into XML Literals making the Nodes property a powerful tool for transforming XML.
So, with all of this in mind, let’s look at both the Value (inner text) and Nodes (inner XML) properties in an example. Here we have a function (template), TransformEmail, that transforms the XML source into HTML. The e-mail address is retrieved using the Value property and also a call to the GetSpecialInstructions function to embed the HTML for any special instructions that may be included. The GetSpecialInstructions method returns HTML markup and the inner XML of the <SpecialInstructions> element as it can contain mixed markup including text, or other XML.
Private Sub TransformEmail(ByVal email As XElement)
Dim emailHtml = <div class="Email">
<a href=<%= "mailto:" & email.<act:eMailAddress>.Value %>>
<%= email.<act:eMailAddress>.Value %>
</a> 
<%= GetSpecialInstructions(email.<act:SpecialInstructions>) %>
</div>
email.ReplaceWith(emailHtml)
End Sub
Private Function GetSpecialInstructions( _
ByVal instructions As IEnumerable(Of XElement)) As IEnumerable(Of XElement)
If instructions IsNot Nothing Then _
Return From instruction In instructions _
Select <span class="SpecialInstructions">
<%= instruction.Nodes %>
</span>
Return Nothing
End Function
-
An identity transform in XSLT does just what the name implies: replace the identity of an element or attribute with a new identity. Identity transforms are especially critical when dealing with XML markup that is less rigid in its structure. Consider a documentation structure for news articles. Whenever a title is referred to within an article, it is highlighted with italics. However, the XML schema for the article does not specify italics, it specifies that the term is some type of title so that different transforms can handle the term in their own fashion. When you transform XML such as this into a readable format, such as HTML, you need to preserve the structure of the text around the highlighted term so that the original intent remains intact. That is…
<Paragraph sequenceID=”1”>This week a remastered version of the movie <title type=”movie”>Raider’s of the Lost Ark</title> was released.</Paragraph>
becomes…
<p>This week a remastered version of the movie <i>Raider’s of the Lost Ark</i> was released.</p>
In a browser you see…
This week a remastered version of the movie Raider’s of the Lost Ark was released.
(Note: I have no idea if they are really releasing a remastered version of Raider’s of the Lost Ark, I just typed the first thing that popped into my head).
You can perform an identity transform using Visual Basic and XML Literals by combining XML Axis properties and the ReplaceWith method for LINQ to XML objects.
Let’s look at a more complete example. In the AdventureWorks contacts source document that we used in previous recipes (you can download the XML document and related schemas from the Recipe 1 post), there is an <AdditionalContactInfo> element that contains information about the contact such as phone numbers, shipping and billing addresses, and so on. To keep things simple, we’ll just look at the <eMail> element. The <eMail> element can show up in a number of places in the contents of the <AdditionalContactInfo> element. As a result, you can use the XML Descendant axis property discussed in Recipe 2 to retrieve all of the references to <eMail> elements. You can then iterate through the query results and call the ReplaceWith method for each <eMail> element and replace it with a new identity. For example:
Dim emails = (From email In _
xmlDoc.<Contacts>.<Contact>.<aci:AdditionalContactInfo>...<act:eMail>).ToList()
For Each email In emails
TransformEmail(email)
Next
In this example, the output is HTML and the <eMail> element is replaced with an anchor element that specifies a mailto: link to the e-mail address. The previous code snippet passes the <eMail> XElement object to the function TransformEmail, which does the actual replacing.
Private Sub TransformEmail(ByVal email As XElement)
Dim emailHtml = <div class="Email">
<a href=<%= "mailto:" & email.<act:eMailAddress>.Value %>>
<%= email.<act:eMailAddress>.Value %>
</a>
</div>
email.ReplaceWith(emailHtml)
End Sub
A simple class that creates the HTML document using these samples is shown here.
Imports <xmlns="http://SampleSchema/AWContacts">
Imports <xmlns:aci="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactInfo">
Imports <xmlns:act="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactTypes">
Public Class Recipe3
Public Function GetContactsHtml(ByVal xmlDoc As XDocument) As XElement
' Replace e-mail address tags with mailto links.
Dim emails = (From email In xmlDoc.<Contacts>.<Contact>.<aci:AdditionalContactInfo>...<act:eMail>).ToList()
For Each email In emails
TransformEmail(email)
Next
' Create the HTML document
Return <html>
<body>
<table border="1">
<%= From contact In xmlDoc.<Contacts>.<Contact> _
Select <tr>
<td valign="top">
<%= contact.<FirstName>.Value & " " & contact.<LastName>.Value %>
</td>
<td valign="top">
<%= contact.<aci:AdditionalContactInfo> %>
</td>
</tr> _
%>
</table>
</body>
</html>
End Function
Private Sub TransformEmail(ByVal email As XElement)
Dim emailHtml = <div class="Email">
<a href=<%= "mailto:" & email.<act:eMailAddress>.Value %>>
<%= email.<act:eMailAddress>.Value %>
</a>
</div>
email.ReplaceWith(emailHtml)
End Sub
End Class
In XSLT, the same transform would look like the following:
<?xml version='1.0'?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:aw="http://SampleSchema/AWContacts"
xmlns:aci="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactInfo"
xmlns:act="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactTypes">
<xsl:output method="html" indent="yes"/>
<xsl:template match="aw:Contacts">
<html>
<body>
<table border="1">
<xsl:apply-templates select="aw:Contact" />
</table>
</body>
</html>
</xsl:template>
<xsl:template match="aw:Contact">
<tr>
<td valign="top">
<xsl:value-of select="aw:FirstName"/>
<xsl:text> </xsl:text>
<xsl:value-of select="aw:LastName"/>
</td>
<td valign="top">
<xsl:apply-templates select="aci:AdditionalContactInfo" />
</td>
</tr>
</xsl:template>
<xsl:template match="aci:AdditionalContactInfo">
<div class="AdditionalInfo">
<xsl:copy>
<xsl:apply-templates select="@* | node()" />
</xsl:copy>
</div>
</xsl:template>
<xsl:template match="act:eMail">
<div class="Email">
<a>
<xsl:attribute name="href">
<xsl:text>mailto:</xsl:text>
<xsl:value-of select="act:eMailAddress" />
</xsl:attribute>
<xsl:value-of select="act:eMailAddress" />
</a>
</div>
</xsl:template>
</xsl:stylesheet>
Grouping by Element Type
One thing that you can take advantage of in Visual Basic is the ability to group transforms by element type. For example, the ContactTypes.xsd file that we have been using in our AdventureWorks samples defines an address type, and e-mail type, and a phone number type. ContactTypes.xsd also identifies three different element names that are the address type and five different element names that are the phone number type. In XSLT, you would create a template for each address type and end up with three different templates for information that is, essentially, formatted the same. In Visual Basic, you can easily create a query for all different address or phone number elements and pass that to the same VB function, which acts as your XSLT template. For example:
Dim addresses = (From addr In info...<act:homePostalAddress>).Union( _
From addr In info...<act:physicalDeliveryOfficeName>).Union( _
From addr In info...<act:registeredAddress>).ToList()
For Each address In addresses
TransformAddress(address)
Next
...
Private Sub TransformAddress(ByVal address As XElement)
Dim addressHtml = _
<div class="Address">
<%= address.<act:Street>.Value %><br/>
<%= address.<act:City>.Value & ", " %> 
<%= address.<act:StateProvince>.Value %> 
<%= address.<act:PostalCode>.Value %><br/>
<%= address.<act:CountryRegion>.Value %><br/>
<%= _
GetSpecialInstructions(address.<act:SpecialInstructions>.ToList()) %>
</div>
address.ReplaceWith(addressHtml)
End Sub
-
On Wednesday we released onto the VB Developer Center the second part of Maurice de Beijer's article on Windows Workflow. In this second article about developing custom Workflow Activities, Maurice takes a look at some of the more advanced aspects of Workflow Activity development.
Enjoy,
-Beth Massi, VS Community
-
Supporting the “pajama programmer”
Telecommuting is a great thing, and as the environmentally-conscious person that I strive to be, is something I wholeheartedly endorse when it can be done practically. My current job involves a lot of face-to-face meetings with people, so I don’t actually do a lot of it. However, I do work from home in the evenings (for example, when crafting blog posts), so it’s important to me that my setup for working from home is usable.
The challenge that I face is that my work machine is dual-monitor, whereas my home machine is single-monitor. Consequently, unless I make a conscious decision to move all of my windows to monitor #1 before I go home, many of the windows I need are off-screen when I remotely access my work machine from home. Choosing to “cascade” or “tile” the windows doesn’t work, since those commands don’t actually move windows from their current monitor. I can, of course, right-click on the relevant window’s icon in the task bar and choose “Move” to slowly move the window to monitor #1 with the cursor keys, but that’s a hassle. What I want is a quick way to say “git over here!” to one or more windows.
Now, I’m sure there is plenty of freeware out there that already does this sort of thing, but I’d also been looking for an opportunity to play around with WPF (Windows Presentation Foundation, formerly known as “Avalon”) windows, so I decided to take the time to put this together by hand as a way of learning more about them since I haven’t had too much experience with them yet. So, note that I’m not going to be demonstrating anything “WPF-ish” in this post – no transparency or special effects or whatnot – mostly, this exercise was just a way for me to get used to the variations in the designer and the properties before I jump in to anything trickier.
The goal is to create an application with a listview containing all of the titled windows on the system, and a button to move the selected window to location (10,10), which would certainly be on monitor #1. (VS2005 and earlier users: although I’ll be doing this using a WPF window in VS2008, there’s absolutely no reason this code won’t also work with forms – the translations are pretty straightforward.)
Creating the basic app – the designer
First, choose “New Project…” and select “WPF Application” from the list of Windows project types, give it a name (I chose “VBGitOverHere”) and press “OK.” After the project is created, you’ll see a designer which is rather different looking than the one you’d be used to for forms. At the top is something that sort of looks like the Forms designer, but at the bottom you’ll see a XAML pane. XAML is the backbone of WPF and ultimately describes the layout of the window, rather like an ActiveX form, if you’ve used one of those before. (We won’t actually be interacting with the XAML editor in this example, as everything I discuss in this post can be done in the designer and editor.)
The property grid for a window works pretty much the same way it does for forms. One of the biggest differences is that the name of the object is specified at the top of the properties window containing the grid, and not in the grid itself – go ahead & change that to whatever you like. Also, let’s change the Title property to “VBGitOverHere” (or whatever makes sense to you), Topmost to “True,” WindowStartupLocation to “Manual”, and both Left and Top to 10. This will make sure that this window always starts up in the upper-left corner of the first monitor, on top of everything else. (It would obviously defeat the purpose of this app to have it pop up on the second monitor, and I also want to make sure it stays on top as windows are moved around.)
One thing to notice is that the window in the designer already controls a control – a grid, which you can think of as the client area for your controls. It can of course be resized and moved around. The other controls you add will be nested in this, and their position property values are relevant to it, not to the enclosing window. So, drag out a ListBox from the toolbox into the grid and call it “WindowList.” Resize it so that it takes up the left side of the grid. Set its TabIndex property to “1” and its SelectionMode property to Multiple. This is the list that we’ll put the window titles in.
Now add four buttons into the right side of the Grid. You’ll want to set their TabIndex values appropriately (2, 3, 4, 5), and you’ll want to give then appropriate names (GitOverHereBtn, RefreshBtn, SelectAllBtn, ClearAllBtn) to code against. They also need titles as well. To set the title of the button, you use the Content property – there is no “Text” property such as Forms controls have.
Adding helper code
Now, to the code. Select the window (not the grid – the actual window) and double-click it. You’ll be taken to a code editor and dropped into editing the Window1_Loaded method (or whatever you called it). This is the equivalent of the “Form1_Load” method for forms. The only thing you’ll be doing in this method is populate the listview. You’ll want to write a helper method for that, since you’ll also want a way to refresh the list of windows in case a new one opens or one is closed. But before that, you’ll need to declare some Windows APIs to he