1.8. Creating XML Straight from a Database

Problem

You want to be able to take a set of data from a database and represent it as XML.

Solution

Use LINQ to SQL and LINQ to XML to retrieve and transform the data all in one query. In this case, we will select the top five customers in the Northwind database whose contact is the owner and those owners who placed orders totaling more than $10,000, then create XML containing the company name, contact name, phone number, and total amount of the orders. Finally, the results are written out to the BigSpenders.xml file:

	Northwind dataContext = new Northwind(Settings.Default.NorthwindConnectionString);
	// Log the generated SQL to the console
	dataContext.Log = Console.Out;

	var bigSpenders = new XElement("BigSpenders",
	            from top5 in
	            (
	               from customer in
	               (
	                   from c in dataContext.Customers
	                   // get the customers where the contact is the owner
	                   // and they placed orders
	                   where c.ContactTitle.Contains("Owner")
	                      && c.Orders.Count > 0
	                   join orderData in
	                     (
	                         from c in dataContext.Customers
	                         // get the customers where the contact is the owner
	                         // and they placed orders
	                         where c.ContactTitle.Contains("Owner")
	                            && c.Orders.Count > 0
	                         from o in c.Orders
	                         // get the order details
	                         join od in dataContext.OrderDetails
	                             on o.OrderID equals od.OrderID
	                         select new
	                         {
	                             c.CompanyName,
	                             c.CustomerID,
	                             o.OrderID,
	                             // have to calc order value from orderdetails
	                             //(UnitPrice*Quantity as Total)- (Total*Discount)
	                             // as NetOrderTotal
	                             NetOrderTotal = (
	                                 (((double)od.UnitPrice) * od.Quantity)
	                                 (((double)od.UnitPrice) * od.Quantity) * od.Discount))
	                         }
	                     )
	                   on c.CustomerID equals orderData.CustomerID
	                   into customerOrders
	                   select new
	                   {
	                       c.CompanyName,
	                       c.ContactName,
	                       c.Phone,
	                       // Get the total amount spent by the customer
	                       TotalSpend = customerOrders.Sum(order => order.NetOrderTotal)
	                   }
	               )
	               // place focus on the customers that spent > 10000
	               where customer.TotalSpend > 10000
	               orderby customer.TotalSpend descending
	               // only take the top five spenders
	               select customer).Take(5)
	            )
	            // format the data as XML
	            select new XElement("Customer",
	                       new XAttribute("companyName", top5.CompanyName),
	                       new XAttribute("contactName", top5.ContactName),
	                       new XAttribute("phoneNumber", top5.Phone),
	                       new XAttribute("amountSpent", top5.TotalSpend)));

	using (XmlWriter writer = XmlWriter.Create("BigSpenders.xml"))
	{
	    bigSpenders.WriteTo(writer);
	}

Tip

When building larger queries, you may find it is sometimes easier to use the functional approach (.Join()) to building up the query instead of the query expression manner (join x on y equals z) if you have done more C# than SQL.

Discussion

LINQ to SQL is the part of LINQ to ADO.NET that facilitates rapid database development. It is targeted at the scenarios where you want to program almost directly against the database schema. Most of these scenarios have one-to-one correlations between strongly typed classes and database tables. If you are in more of an enterprise development scenario with lots of stored procedures and databases that have moved away from "one table equals one entity" scenarios, you would want to look into LINQ to Entities.

To use LINQ to SQL, there are two design tools to help you get started, the visual designer for LINQ to SQL in Visual Studio 2008 and the command line utility SqlMetal.exe in the SDK. You can access the visual designer by adding a new or opening an existing "LINQ to SQL Classes" item (*.dbml file) to the project, which opens the designer. Both of these help you to build out the DataContext and Entity Classes for your database that can then be used with LINQ (or other programming constructs if you wish). A DataContext is analogous to an ADO.NET Connection and Command object rolled into one. You use it to establish your connection, execute queries, or access tables directly via Entity Classes. The Northwind Data Context is a strongly typed instance of a DataContext generated by SqlMetal and is partially shown here:

	public partial class Northwind : System.Data.Linq.DataContext
	{

	        private static System.Data.Linq.Mapping.MappingSource mappingSource = new
	AttributeMappingSource();

	  #region Extensibility Method Definitions
	  /// removed code for extensibility points for clarity
	  #endregion

	        static Northwind()
	        {
	        }

	        public Northwind(string connection) :
	                        base(connection, mappingSource)
	        {
	                OnCreated();
	        }

	        public Northwind(System.Data.IDbConnection connection) :
	                        base(connection, mappingSource)
	        {
	                OnCreated();
	        }

	        public Northwind(string connection, System.Data.Linq.Mapping.MappingSource
mappingSource) :
	                        base(connection, mappingSource)
	        {
	                OnCreated();
	        }

	        public Northwind(System.Data.IDbConnection connection, System.Data.Linq.
Mapping.MappingSource mappingSource) :
	                        base(connection, mappingSource)
	        {
	                OnCreated();
	        }

	        public System.Data.Linq.Table<Customers> Customers
	        {
	                get
	                {
	                        return this.GetTable<Customers>();
	                }
	        }
	   // More Table<EntityClass> definitions, one for each table in the database
	}

The Entity Class definitions for the Northwind database are all present in the generated code as well, with each table having an Entity Class defined for it. The Entity Classes are indicated by the Table attribute with no parameters. This means that the name of the Entity Class matches the table name:

	[Table()]
	public partial class Customers : INotifyPropertyChanging, INotifyPropertyChanged
	{

	    #region Extensibility Method Definitions
	    partial void OnLoaded();
	    partial void OnValidate();
	    partial void OnCreated();
	    partial void OnCustomerIDChanging(string value);
	    partial void OnCustomerIDChanged();
	    // more extensibility methods to indicate change states for each property...
	    #endregion

	        public event PropertyChangingEventHandler PropertyChanging;

	        public event PropertyChangedEventHandler PropertyChanged;

The standard property change notifications are implemented via INotifyPropertyChanging and INotifyPropertyChanged and have PropertyChanging and PropertyChanged events for conveying the change to a property. There are also a set of partial methods that will report when a specific property is modified on this Entity Class if the partial method is implemented in another partial class definition for the Entity Class. If no other partial class definition is found, the compiler will remove those notifications. Partial methods enable the declaration of a method signature in one file of a partial class declaration and the implementation of the method in another. If the signature is found but the implementation is not, the signature is removed by the compiler.

The properties in the Entity Class match up to the columns in the database via the Column attribute, where the Name value is the database column name and the Storage value is the internal storage for the class of the data. Events for the property changes are wired into the setter for the property:

	[Column(Name="Company Name", Storage="_CompanyName", DbType="NVarChar(40) NOT
NULL", CanBeNull=false)]
	public string CompanyName
	{
	        get
	        {
	                return this._CompanyName;
	        }
	        set
	        {
	                if ((this._CompanyName != value))
	                {
	                        this.OnCompanyNameChanging(value);
	                        this.SendPropertyChanging();
	                        this._CompanyName = value;
	                        this.SendPropertyChanged("CompanyName");
	                        this.OnCompanyNameChanged();
	                }
	        }
	}

For a one-to-many child relationship, an EntitySet<T> of the child Entity Class is declared with an Association attribute. The Association attribute specifies the relationship information between the parent and child Entity Classes, as shown here for the Orders property on Customer:

	[Association(Name="Orders_FK00", Storage="_Orders", ThisKey="CustomerID",
OtherKey="CustomerID", DeleteRule="NO ACTION")]
	public EntitySet<Orders> Orders
	{
	        get
	        {
	                return this._Orders;
	        }
	        set
	        {
	                this._Orders.Assign(value);
	        }
	}

LINQ to SQL covers much more than what has been shown here; we encourage you to investigate it more, but let's now see the other data domain we are dealing with: LINQ to XML.

LINQ to XML is not only how you perform queries against XML; it is a more developer-friendly way to work with XML. One of the main classes in LINQ to XML is XElement.XElement allows you to create XML in a manner that more closely resembles the structure of the XML itself. This may not seem like a big deal, but when you can see the XML taking shape in your code, it makes it easier to know where you are. (Ever forget which XmlWriter.WriteEndElement you were on? We have!) You can get more details and examples about using XElement in Chapter 15, so we won't go much further into it here, but as you can see, it is very easy to build up XML in a query.

The first part of the query deals with setting up the main XML element "BigSpenders", getting the initial set of customers where the contact is the owner:

	var bigSpenders = new XElement("BigSpenders",
	            from top5 in
	            (
	                (from customer in
	                     (
	                         from c in dataContext.Customers
	                         // get the customers where the contact is the owner
	                         // and they placed orders
	                         where c.ContactTitle.Contains("Owner")
	                            && c.Orders.Count > 0

The middle of the query deals with joining the order and order detail information with the customer information to get the NetOrderTotal for the order. It also creates order data containing that value and the customer and order ids and the customer name. We need the NetOrderTotal in the last part of the query, so stay tuned!

	    join orderData in
	        (
	            from c in dataContext.Customers
	            // get the customers where the contact is the owner
	            // and they placed orders
	            where c.ContactTitle.Contains("Owner")
	               && c.Orders.Count > 0
	            from o in c.Orders
	            // get the order details
	            join od in dataContext.OrderDetails
	                on o.OrderID equals od.OrderID
	            select new
	            {
	                c.CompanyName,
	                c.CustomerID,
	                o.OrderID,
	                // have to calc order value from orderdetails
	                //(UnitPrice*Quantity as Total)
	                  (Total*Discount)
	                // as NetOrderTotal
	                NetOrderTotal = (
	(((double)od.UnitPrice) * od.Quantity) -
	((((double)od.UnitPrice) * od.Quantity) * od.Discount))
	            }
	        )
	    on c.CustomerID equals orderData.CustomerID
	    into customerOrders

The last part of the query determines the TotalSpend for that customer across all orders using the Sum function on NetOrderTotal for the generated customerOrders collection. The last criteria evaluated is that only the top five customers with a TotalSpend value > 10000 are selected by using the Take function. Take is the equivalent to TOP in SQL. The records are then used to construct one inner Customer element with attributes that nest inside the BigSpenders root element started in the first part of the query:

	             select new
	             {
	                  c.CompanyName,
	                  c.ContactName,
	                  c.Phone,
	                  // Get the total amount spent by the customer
	                  TotalSpend = customerOrders.Sum(order => order. NetOrderTotal)
	             }
	          )
	      // only worry about customers that spent > 10000
	      where customer.TotalSpend > 10000
	      orderby customer.TotalSpend descending
	     // only take the top 5 spenders
	     select customer).Take(5)
	)
	// format the data as XML
	select new XElement("Customer",
	           new XAttribute("companyName", top5.CompanyName),
	           new XAttribute("contactName", top5.ContactName),
	           new XAttribute("phoneNumber", top5.Phone),
	           new XAttribute("amountSpent", top5.TotalSpend)));

Tip

It is much easier to build large-nested queries as individual queries first and then put them together once you are sure the inner query is working.

At this point, for all of the code here, nothing has happened yet. That's right, until the query is accessed, nothing happens through the magic of deferred execution. LINQ has constructed a query expression, but nothing has talked to the database; there is no XML in memory, nada. Once the WriteTo method is called on the bigSpenders query expression, then the query is evaluated by LINQ to SQL, and the XML is constructed. The WriteTo method writes out the constructed XML to the XmlWriter provided, and we are done:

	using (XmlWriter writer = XmlWriter.Create("BigSpenders.xml"))
	{
	    bigSpenders.WriteTo(writer);
	}

If you are interested in what that SQL will look like, connect the DataContext.Log property to a TextWriter (like the console):

	// Log the generated SQL to the console
	dataContext.Log = Console.Out;

This query generates SQL that looks like this:

	Generated SQL for query - output via DataContext.Log
	SELECT [t10].[CompanyName], [t10].[ContactName], [t10].[Phone], [t10].[TotalSpend]
	FROM (
	    SELECT TOP (5) [t0].[Company Name] AS [CompanyName], [t0].[Contact Name] AS
	[ContactName], [t0].[Phone], [t9].[value] AS [TotalSpend]
	    FROM [Customers] AS [t0]
	    OUTER APPLY (
	        SELECT COUNT(*) AS [value]
	        FROM [Orders] AS [t1]
	        WHERE [t1].[Customer ID] = [t0].[Customer ID]
	        ) AS [t2]
	    OUTER APPLY (
	        SELECT SUM([t8].[value]) AS [value]
	        FROM (
	            SELECT [t3].[Customer ID], [t6].[Order ID],
	                 ([t7].[Unit Price] *
	                 (CONVERT(Decimal(29,4),[t7].[Quantity]))) - ([t7].[Unit Price] *
	                     (CONVERT(Decimal(29,4),[t7].[Quantity])) *
	                         (CONVERT(Decimal(29,4),[t7].[Discount]))) AS [value],
	                 [t7].[Order ID] AS [Order ID2], 
	                 [t3].[Contact Title] AS [ContactTitle],
	                 [t5].[value] AS [value2],
	                 [t6].[Customer ID] AS [CustomerID]
	        FROM [Customers] AS [t3]
	        OUTER APPLY (
	            SELECT COUNT(*) AS [value]
	            FROM [Orders] AS [t4]
	            WHERE [t4].[Customer ID] = [t3].[Customer ID]
	            ) AS [t5]
	        CROSS JOIN [Orders] AS [t6]
	        CROSS JOIN [Order Details] AS [t7]
	        ) AS [t8]
	    WHERE ([t0].[Customer ID] = [t8].[Customer ID]) AND ([t8].[Order ID] = [
t8].[Order ID2]) AND ([t8].[ContactTitle] LIKE @p0) AND ([t8].[value2] > @p1) AN
D ([t8].[CustomerID] = [t8].[Customer ID])
	        ) AS [t9]
	    WHERE ([t9].[value] > @p2) AND ([t0].[Contact Title] LIKE @p3) AND ([t2].[va
	lue] > @p4)
	    ORDER BY [t9].[value] DESC
	    ) AS [t10]
	ORDER BY [t10].[TotalSpend] DESC
	-- @p0: Input String (Size = 0; Prec = 0; Scale = 0) [%Owner%]
	-- @p1: Input Int32 (Size = 0; Prec = 0; Scale = 0) [0]
	-- @p2: Input Decimal (Size = 0; Prec = 29; Scale = 4) [10000]
	-- @p3: Input String (Size = 0; Prec = 0; Scale = 0) [%Owner%]
	-- @p4: Input Int32 (Size = 0; Prec = 0; Scale = 0) [0]
	-- Context: SqlProvider(SqlCE) Model: AttributedMetaModel Build: 3.5.20706.1

The final XML is shown below:

	<BigSpenders>
	  <Customer companyName="Folk och fa HB" contactName="Maria Larsson"
	            phoneNumber="0695-34 67 21" amountSpent="39805.162472039461" />
	  <Customer companyName="White Clover Markets" contactName="Karl Jablonski"
	            phoneNumber="(206) 555-4112" amountSpent="35957.604972146451" />
	  <Customer companyName="Bon app'" contactName="Laurence Lebihan"
	            phoneNumber="91.24.45.40" amountSpent="22311.577472746558" />
	  <Customer companyName="LINO-Delicateses" contactName="Felipe Izquierdo"
	            phoneNumber="(8) 34-56-12" amountSpent="20458.544984650609" />
	  <Customer companyName="Simons bistro" contactName="Jytte Petersen"
	            phoneNumber="31 12 34 56" amountSpent="18978.777493602414" />
	</BigSpenders>

See Also

The "The Three Parts of a LINQ Query," "DataContext.Log, property," "DataContext class," "XElement class," and "LINQ to SQL" 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.