Metadata Programming and LINQ

Published 24 July 07 08:44 PM

One of my favorite uses of LINQ is being able to query over metadata and use it to create flexible programs. Conversely, I also like to use LINQ to quickly create metadata about my objects. Metadata simply put is "data about data" or, for example, information that describes content. I want to walk through a simple example that shows how to use information stored in a database that describes some content for some controls on a form. And then I'll show you how to create data (XML) that describes that form and its controls. NOTE: The example I'm creating works with a pre-release Beta 2 version of Visual Studio 2008.

The idea for this example is that I want to read the text of the controls from a database and depending on the language I select, the text on the controls will change. I've created a very simple table in my database called ControlData that has three string fields, Name, Description and Language. The data looks like this:

Name       Description    Language

---------- -------------  ---------

Button1    Hello          English 

Button2    Goodbye        English 

Button3    I love VB      English 

Button4    LINQ Rocks!    English 

TextBox1   Hello          English 

TextBox2   Goodbye        English 

Button1    Buon Giorno    Italian 

Button2    Ciao           Italian 

Button3    Amo VB         Italian 

Button4    LINQ รจ potente Italian 

TextBox1   Buon Giorno    Italian 

So next what I've done is create a form with some controls on it. Some control names match the names here in the database and others do not. We're going to write queries that can work over all the controls on the form and join in the matching information here to change the Text they display, depending on the language we select (which I've placed in a Combobox). So my form looks like this:

The first thing we're going to do is create a LINQ to SQL class using the new O\R designer. It's very simple to use this, just right-click on your project and select "Add New Item" and select the LINQ to SQL Class template. This creates a file with a .dbml file extension and opens the O/R designer (I just went with the default name of DataClasses1). You can simply drag tables from your Server Explorer onto the surface and it will generate all the code for you similar to the DataSet designer experience.

In my case I just dragged the ControlData table onto the surface and it created what's called a DataContext with a single class called ControlData. We can now query over the ControlData and LINQ to SQL will translate our LINQ queries into SQL queries automatically. All we need to do is create an instance of the DataContext and then we can start querying over the ControlData.

So the first LINQ query I want to write will populate the contents of the Combobox that displays the Language. This is done by selecting the distinct rows and then setting the result as the DataSource of the Combobox:

Public Class Form1

 

    Private db As New DataClasses1DataContext

 

    Private Sub Form1_Load() Handles MyBase.Load

 

        Dim choices = From info In db.ControlDatas _

                      Select info.Language Distinct

 

        Me.ComboBox1.DataSource = choices

    End Sub

What the variable 'choices' ends up being from this query is a simple list of strings (IQueryable(Of String)) and that works fine for our Combobox's DataSource -- we'll end up with two strings, "English" and "Italian".

Next I want to handle the Combobox's SelectedIndexChanged event in order to change the Text property of our controls. I'm going to add a twist to this example and only set the text of Button controls. We can select all the top-level buttons on a Form my using the Me.Controls collection. This collection has been on WinForms since the beginning of time so it returns a ControlsCollection which means we'll have to cast our Buttons out of it. I chose to do it like this:

Dim buttons = From c In Me.Controls _

              Where TypeOf c Is Button _

              Select CType(c, Button)

 

We could iterate this list of buttons (IEnumerable(Of Button)) now and set the Text property manually if we wanted:

For Each button In buttons

    button.Text = "LINQ is cool"

Next

But what we want to do is join this list of buttons with the ControlData objects matching on the Name and filtering by the Language so when all is said and done the SelectedIndexChanged event handler code will look like so (I split the query up into two parts for readability):

Private Sub ComboBox1_SelectedIndexChanged() Handles ComboBox1.SelectedIndexChanged

 

    Dim buttons = From c In Me.Controls _

                Where TypeOf c Is Button _

                Select CType(c, Button)

 

      

    Dim buttonInfo = From Button In buttons _

                     From info In db.ControlDatas _

                     Where info.Name = Button.Name _

                        AndAlso info.Language = Me.ComboBox1.SelectedValue.ToString _

                     Select Button, info.Description

 

    For Each item In buttonInfo

        item.Button.Text = item.Description

    Next

End Sub

What we've selected (projected) for buttonInfo is a list of anonymous types. If you notice we're selecting the Button objects as well as the string Description field -- this creates a list of anonymous types for us that we are iterating and using to set the Text property. That's all there is to it. When we select the language in the combobox, the Text of the Buttons change.

The last thing I want to do is to create some metadata about this form by creating a simple XML document that describes the controls as well as the possible content in the database. We can use LINQ to XML and XML literals to do this easily. The only trick is that we'll need to perform a group join because some of the controls on our form are not contained in the database and some of the controls have multiple rows of information. So we have zero or more matching ControlData objects for each of the Form's Controls. So I perform a group join of the information in the database and then use that group as a sub-query to get the zero, one, or more Descriptions which I place into a <Text> element. This ends up under the <Control> elements that are selected which are under the root <Form> element:

Private Sub cmdMakeXML_Click() Handles cmdMakeXML.Click

 

    Dim formData = <?xml version="1.0"?>

               <Form

                   name=<%= Me.Name %>

                   height=<%= Me.Height %>

                   width=<%= Me.Width %>

                   top=<%= Me.Top %>

                   left=<%= Me.Left %>

                   font=<%= Me.Font %>>

                   <%= _

                       From item In Me.Controls _

                       Let c = CType(item, Control) _

                       Group Join info In db.ControlDatas On info.Name Equals c.Name Into Group _

                       Order By c.Name _

                       Select <Control

                                  type=<%= c.GetType %>

                                  name=<%= c.Name %>

                                  height=<%= c.Height %>

                                  width=<%= c.Width %>

                                  top=<%= c.Top %>

                                  left=<%= c.Left %>

                                  font=<%= c.Font %>>

                                  <%= From info In Group _

                                      Order By info.Language _

                                      Select <Text language=<%= info.Language %>>

                                                 <%= info.Description %>

                                             </Text> %>

                              </Control> _

                   %></Form>

 

    My.Computer.FileSystem.WriteAllText("Form1.xml", formData.ToString, False)

    Process.Start("notepad.exe", "Form1.xml")

End Sub

Here's a rip of the XML that is created:

LINQ is really powerful and simplifies a lot of common scenarios where we need to query over data in many forms, in this case a database, a collection of objects and XML. I've attached this project but please note that some features may not work as expected until the release of Visual Studio 2008 Beta 2. For more information on LINQ please check out the LINQ Project page on MSDN.

Enjoy!

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# Metadata Programming and LINQ « Tuff Stuff said on August 5, 2007 7:53 PM:

PingBack from http://tristanbates.wordpress.com/2007/08/06/metadata-programming-and-linq/

# JACKnSTEIN@AOL.COM said on August 10, 2007 12:08 PM:

Beth,

I hope you can help me.

I am an industrial designer using AutoCAD. I have written a program in VB5 which is a calculator. It uses the clipboard to automatically store numbers/text to be pasted at the command line in AutoCAD. I have used the program for many years with no problem. The company I work for now uses AutoCAD 2007 with Microsoft XP Professional. It seems AutoCAD now uses "Metafile"

format to copy/paste and when I try to paste my number/text from my calculator nothing happens. How can I fix my program so AutoCAD will accept my data? I am a self taught programmer so I don't understand all of the aspects of what I am trying to do. If you could help me with this I would vvveeerrryyy much appreciate it.

Thank You for your time,

Jack Stein

JACKnSTEIN@AOL.COM

# Beth Massi said on August 15, 2007 9:44 PM:

Hi Jack,

I'm not familiar with AutoCAD, you may have better luck asking your question in the forums here: http://forums.microsoft.com/MSDN/default.aspx?ForumGroupID=10&SiteID=1

Cheers,

-B

# Beth Massi - Sharing the goodness that is VB said on September 6, 2007 3:47 PM:

So I decided to post a summary of all the content the VB team members, including myself, have created

Leave a Comment

(required) 
(optional)
(required) 

About Beth Massi

Beth is a Program Manager on the Visual Studio Community Team at Microsoft and is responsible for producing and managing content for business application developers, driving community features and team participation onto MSDN Developer Centers (http://msdn.com), and helping make Visual Studio one of the best developer tools in the world. She also produces regular content on her blog (http://blogs.msdn.com/bethmassi), Channel 9, and a variety of other developer sites and magazines. As a community champion and a long-time member of the Microsoft developer community she also helps with the San Francisco East Bay .NET user group and is a frequent speaker at various software development events. Before Microsoft, she was a Senior Architect at a health care software product company and a Microsoft Solutions Architect MVP. Over the last decade she has worked on distributed applications and frameworks, web and Windows-based applications using Microsoft development tools in a variety of businesses. She loves teaching, hiking, mountain biking, and driving really fast.
Page view tracker