Mike Taulty's Blog
Bits and Bytes from Microsoft UK
LINQ (to XSD)

Blogs

Mike Taulty's Blog

Elsewhere

Archives

One of the things that I've done in introducing LINQ at various talks that I've given over the past 12 months or so is to talk about how LINQ is meant to get us nearer to data.

I tend to put up a slide of how we do data access today and I usually drone on about data access today being "Objects and Strings" (sometimes I perhaps called it "Chewing Gum and String", sometimes I talk about "Signal to Noise ratios").

Whichever way I try and explain it, to me the essential idea is that, we tend to write code such as (this is pseudo-code) today;

SqlConnection con = new SqlConnection(...);

SqlCommand command = new SqlCommand("select * from foo", com);

SqlDataReader reader = command.ExecuteReader();

while (reader.Read())
{

   int x = (int)reader["someColumn"];
}

and my point is usually that anywhere we type a string like a column name or a string like that SQL statement we are prone to run-time errors and anywhere we have to cast from object we are prone to run-time errors and, for me, I'd like to get more errors at compile time rather than run-time. I also probably make the point as to how much code has to get written just to get a single row of data from the database (the same applies pretty much to XML API's as well).

I'd usually then go on and talk about LINQ to SQL and LINQ to XML and about how they are making all of this a lot better and, somewhere along the line, I'll probably show a piece of LINQ to XML code that looks, perhaps, something like this one;

XElement fruitXml = XElement.Load("fruit.xml");

    var query = from f in fruitXml.Descendants("lineItem")
                group (decimal)f.Attribute("price") *
                  (int)f.Attribute("quantity")
                by
                  (string)f.Attribute("fruit")
                into groupedData
                select new
                       {
                         Fruit = groupedData.Key,
                         TotalSales = groupedData.Sum()
                       };

    foreach (var v in query)
    {
      Console.WriteLine(v);
    }

 

Which is accompanied by my fruit.xml file;

<?xml version="1.0" encoding="utf-8" ?>
<fruitSales>
  <lineItem fruit="oranges" price="0.50" quantity="100"/>
  <lineItem fruit="apples" price="0.45" quantity="320"/>
  <lineItem fruit="pears" price="0.75" quantity="25"/>
  <lineItem fruit="bananas" price="0.80" quantity="50"/>
  <lineItem fruit="cherries" price="2.30" quantity="5"/>
  <lineItem fruit="apples" price="0.42" quantity="80"/>
  <lineItem fruit="satsumas" price="0.15" quantity="70"/>
  <lineItem fruit="pineapples" price="1.50" quantity="45"/>
  <lineItem fruit="apples" price="0.42" quantity="32"/>
</fruitSales>

 

Now, quite often I get away with this but, equally often, someone comes to me after the talk and quietly says something along the lines of;

"You know at the start of the talk when you spoke about getting away from passing strings into API's and getting away from receiving objects that needed casting? Well, LINQ to SQL seems to do that but, whilst LINQ to XML looks really neat, it seems to me that we still have strings and casts".

At this point I generally agree, smile, shrug and say "Yep, you're right that LINQ to XML doesn't get us as close to the data as LINQ to SQL seems to right now but it's still doing very cool things when it comes to creating and querying XML". I probably also add something like "I'm sure we'll get there on the XML front in the future".

Quite often I'll also mention the "LINQ to XSD" project that's kicking around and how that may well be the answer providing the bridge between XML and .NET types that the mapping technology (attribute based or XML based) does in LINQ to SQL at the moment.

For the first time though today, I went and downloaded the "LINQ to XSD Preview Alpha 0.2" from the web to have a play with it.

I installed this on top of Visual Studio "Orcas"/2008 Beta 1 and I was quite surprised to find that it even has its own project templates in there. The first thing that I need to go from my type-less dealings with XML above to a typed version is to introduce a schema so I wrote one. Actually, I was just about to write a schema when I thought I'd let Visual Studio try and infer it and the schema it gave me was both quick and seemed reasonable so I went with it;

<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="fruitSales">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" name="lineItem">
          <xs:complexType>
            <xs:attribute name="fruit" type="xs:string" use="required" />
            <xs:attribute name="price" type="xs:decimal" use="required" />
            <xs:attribute name="quantity" type="xs:unsignedShort" use="required" />
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

 

With that in place, I added it to my new "LINQ to XSD" project and set its build action to "LinqToXsdSchema" and did a quick build. I noticed that I got some generated code (note - most of which is omitted here for brevity);

public partial class fruitSales : XTypedElement, IXMetaData {
    
   
    public fruitSales() {
    }
    
    public IList<fruitSales.lineItemLocalType> lineItem {
        get {
        }
        set {
        }
    }

and there's also the lineItem type itself (again, most code omitted);

public partial class lineItemLocalType : XTypedElement, IXMetaData {
        
         public static explicit operator lineItemLocalType(XElement xe) { return XTypedServices.ToXTypedElement<lineItemLocalType>(xe,LinqToXsdTypeManager.Instance as ILinqToXsdTypeManager); }
        
        public lineItemLocalType() {
        }
        
        public string fruit {
            get {
            }
            set {
            }
        }
        
        public decimal price {
            get {
            }
            set {
            }
        }
        
        public ushort quantity {
            get {
            }
            set {
            }
        }
        
    }

I thought that the explicit cast from untyped XElement as we have it today in VS 2008 Beta 1 to a typed lineItemLocalType was cool and I also had a quick look to check that XTypedElement is not derived from XElement but is, instead, a wrapper around XElement and it surfaces that as a property called (I think) Untyped. It also surfaces a typed Descendants<T> but children (as we can see up above with lineItem) are surfaced naturally as properties.

So, with this in place I guess I can now write some code much neater code to replace what I had above;

    fruitSales sales = fruitSales.Load("fruit.xml");

    var query = from f in sales.lineItem
                group f.price * f.quantity
                by f.fruit
                into groupedFruit
                select new { Fruit = groupedFruit.Key, TotalSales = groupedFruit.Sum() };

    foreach (var v in query)
    {
      Console.WriteLine(v);
    }

There's not a string or a cast in sight - doesn't get much better than that :-) What if I want to create some XML?

    var salesData = new[] 
    {
      new { Fruit="apples", Quantity=(ushort)100, Price=0.50m },
      new { Fruit="pears", Quantity=(ushort)50, Price=0.75m },
      new { Fruit="bananas", Quantity=(ushort)25, Price=0.90m },
      new { Fruit="cherries", Quantity=(ushort)250, Price=1.00m },
      new { Fruit="grapes", Quantity=(ushort)750, Price=2.20m },
    };

    fruitSales sales = new fruitSales()
                       {
                         lineItem = new List<fruitSales.lineItemLocalType>(
                           from s in salesData
                           select new fruitSales.lineItemLocalType()
                                 {
                                   fruit = s.Fruit,
                                   price = s.Price,
                                   quantity = s.Quantity
                                 })
                       };

    sales.Save("c:\\temp\\fruit.xml");

 

Again, pretty neat - I didn't get this to be quite as neat as it can be today with XElement because, XElement has some nice variable-argument-list constructors that these generated classes seem to lack right now but, then again, today's version would be something more like;

   var salesData = new[] 
    {
      new { Fruit="apples", Quantity=(ushort)100, Price=0.50m },
      new { Fruit="pears", Quantity=(ushort)50, Price=0.75m },
      new { Fruit="bananas", Quantity=(ushort)25, Price=0.90m },
      new { Fruit="cherries", Quantity=(ushort)250, Price=1.00m },
      new { Fruit="grapes", Quantity=(ushort)750, Price=2.20m },
    };

    var sales =
      new XElement("fruitSales",
        from s in salesData
        select new XElement("lineItem",
                 new XAttribute("fruit", s.Fruit),
                 new XAttribute("quantity", s.Quantity),
                 new XAttribute("price", s.Price)));

    sales.Save("c:\\temp\\fruit.xml");

So, arguably, it's a little neater to do the construction but you have to get those 5 strings correct otherwise you've produced the wrong document (this, no doubt, gets harder once we bring in namespaces). Also, I'm sure that the generated classes will grow in terms of constructors and so on as more previews come out.

Lots of food for thought in there - I'll add "LINQ to XSD" to my ridiculous, ever growing list of technologies to look at :-)


Posted Wed, Jun 6 2007 4:07 PM by mtaulty

Comments

Christopher Steen wrote Link Listing - June 10, 2007
on Sun, Jun 10 2007 8:13 PM
Give it a REST! [Via: Anil John ] GWT a Year Later: Was it the correct level of abstraction? [Via: Dietrich...