Saturday, November 8, 2008

Linq to XML in F#

Last week in the entry titled "Mocking F#", we walked through the creation of a customer service implementation in F# and explored how a dynamic mock object framework can be used to help quickly drive development with tests. In that entry, we implemented a DAO (Data Access Object) that simply creates a dummy customer class . This blog entry will focus on adding functionality to that CustomerDao class so that it retrieves the data from an XML file using Linq to XML.

First, Add References:

The first step that we must do is add a reference to System.Xml and System.Xml.Linq in both the test project and the F# project.

The C# Tests:

The goal of these tests is to verify that a specific customer can be retrieved from the XML source.  Method "CanGetCustomerById" retrieves the customer from the actual XML source (which in this case is hard coded into the GetById member, but could have easily been retrieved from a file or database).  Method "CanGetCustomerByIdWithXmlProvided" creates a fake XElement and passes it to the function for test purposes.

        
[TestMethod]
public void CanGetCustomerById()
{
    FSharpMockExample.Data.ICustomerDao customerDao = new  FSharpMockExample.Data.CustomerDao();
    FSharpMockExample.Entities.ICustomer customer = customerDao.GetById(1);
    Assert.AreEqual(customer.Id, 1);
    Assert.AreEqual(customer.Name, "ABC Company");
    Assert.AreEqual(customer.Balance, 20);
}

[TestMethod]
public void CanGetCustomerByIdWithXmlProvided()
{
    XElement xElement =
        new XElement("c",
            new XElement("Customer",
                new XAttribute("Id", "1"),
                new XAttribute("Name", "ABC Company"),
                new XAttribute("Balance", "20.00")),
            new XElement("Customer",
                new XAttribute("Id", "2"),
                new XAttribute("Name", "AABB, Inc"),
                new XAttribute("Balance", "30.00")));
    FSharpMockExample.Data.ICustomerDao customerDao = new FSharpMockExample.Data.CustomerDao();
    FSharpMockExample.Entities.ICustomer customer = customerDao.GetById(xElement, 2);
    Assert.AreEqual(customer.Id, 2);
    Assert.AreEqual(customer.Name, "AABB, Inc");
    Assert.AreEqual(customer.Balance, 30);
}

The F# Signature File:

The signature file looks very similar to that which was created during the last entry. The main difference is a new overloaded GetById function, which contains a tuple (a tuple is common F# data structure used to group data types) of type "XElement * int". Since XElement is used, we must also open System.Xml.Linq.

#light
namespace FSharpMockExample.Data
open FSharpMockExample.Entities
open System.Xml.Linq

type ICustomerDao = interface
    abstract GetById: int -> ICustomer
    abstract GetById: XElement * int -> ICustomer
end

type CustomerDao = class
    new: unit -> CustomerDao
    interface ICustomerDao
end

The F# Source File:

Most of the changes are found in this file.  The first thing that an astute reader might notice is the XLinqHelper module.  Modules provide a way to group and reuse identifiers and functions.  The CustomerDao class encapsulates the majority of the changes.  Member GetById xml retrieves, and in this sample actually creates, the source xml.  It then casts the current class to the ICustomerDao interface, parses the raw xml into an XElement and calls the overloaded GetById XElement*int member.  The overloaded GetById XElement*int member uses the Seq library to find the customer by the specified CustomerId, news up a customer object, casts it to an ICustomer, and finally returns the result.

#light
namespace FSharpMockExample.Data
open FSharpMockExample.Entities
open System.Xml.Linq

module XLinqHelper = 
    let GetXName xname = XName.op_Implicit(xname)

type ICustomerDao = interface
    abstract GetById: int -> ICustomer
    abstract GetById: XElement * int -> ICustomer
end

type CustomerDao = class
    new()={}
    interface ICustomerDao with
        member this.GetById id =
            let rawXml = "<Customers>
                              <Customer Id=\"1\" Name=\"ABC Company\" Balance=\"20.00\"/>
                              <Customer Id=\"2\" Name=\"AABB, Inc\" Balance=\"30.00\"/>
                          </Customers>"
            let thisInterface = (this :> ICustomerDao)
            let xml = XElement.Parse rawXml
            thisInterface.GetById (xml, id)

        member this.GetById (xml, id) =
            let GetCustomer (customerElement:XElement) = 
                new Customer(int(customerElement.Attribute(XLinqHelper.GetXName "Id").Value), 
                    customerElement.Attribute(XLinqHelper.GetXName "Name").Value, 
                    decimal(customerElement.Attribute(XLinqHelper.GetXName "Balance").Value)) :> ICustomer

            xml.Elements() |> Seq.find(fun customer -> (int(customer.Attribute(XLinqHelper.GetXName "Id").Value) = id)) 
                           |> GetCustomer
end

Conclusion:

As you can see from the example, adding basic data access functionality using Linq to XML is pretty easy.  With the help of the Seq library and System.Xml.Linq, the possibilities of XML data manipulation are endless. 

A special thanks goes out to Elijah Manor for adding F# support to Syntax Higherlighter.  Very cool Elijah!!

No comments:

Post a Comment