Tuesday, July 29, 2008

Extending Linq class

Extending Linq.
Filed under: Extension Methods, IQueryable, Linq — Tags: DataSet, DataTable, Extension Methods, Linq — peteohanlon @ 12:47 pm

Sorry for the delay in continuing with the discussions on regular expressions, but I got a little sidetracked with the upgrade to Visual Studio 2008 (.NET 3.5), and all the “goodies” that it brings to you. For the moment I’m going to be putting the discussion on regular expressions to one side. I will get back to them but, for the moment, I want to take you on a journey into .NET 3.5.

Possibly the biggest headline in .NET 3.5 is the introduction of Linq. I have to admit that I was highly skeptical about how useful it will actually be in the “real world”, but having played around with it for a little while I have to admit that I’m actually quite impressed. It’s a well thought out area that actually does do a lot to increase productivity. Now, in this entry I’m not going to delve into the internals of Linq and how to use it. Instead, I’m going to talk about a feature of .NET 3.5 and how it can be used to “extend” the behaviour of Linq. Specifically, we’re going to look at how to use Extension Methods to add the ability to return a DataSet and DataTable from Linq in the same way you’d return a generic list.

The following code shows what you actually need to do to extend the IQueryable object in Linq. For the moment, don’t worry about what IQueryable actually does - once you’ve used Linq for a little while it becomes apparent what this interface is for, and where it fits into the big Linq picture.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Reflection;
using System.Data.Common;
using System.Data;
using System.Data.SqlClient; namespace LinqTesting
{
///
/// This class contains miscellaneous extension methods for Linq to Sql.
///

public static class LinqDataExtensions
{
///
/// Add the ability to return a DataTable based on a particular query
/// to a queryable object.
///

/// The IQueryable object to extend.
/// The query to execute.
/// The name of the datatable to be added.
/// The populated DataTable.
public static DataTable ToDataTable(this IQueryable extenderItem,
DbCommand query,
string tableName)
{
if (query == null)
{ throw new ArgumentNullException("query");
}
SqlCommand cmd = (SqlCommand)query;
SqlDataAdapter adapter = new SqlDataAdapter();
adapter.SelectCommand = cmd;
DataTable dt = new DataTable(tableName);
try
{
cmd.Connection.Open();
adapter.Fill(dt);
}
finally
{
cmd.Connection.Close(); }
return dt;
}
///
/// Add the ability to return a DataSet based on a particular query
/// to a queryable object.
///

/// The IQueryable object to extend.
/// The query to execute.
/// The name of the DataTable to be added.
/// The populated dataset.
public static DataSet ToDataSet(this IQueryable extenderItem,
DbCommand query,
string tableName)
{
if (query == null)
{
throw new ArgumentNullException("query");
}
return ToDataSet(extenderItem, query, null, tableName);
} ///
/// Add the ability to return a dataset based on a particular query
/// to a queryable object.
///

/// The IQueryable object to extend.
/// A generic dictionary containing the
/// query to execute along with the name of the table to add it to.
/// The populated dataset.
public static DataSet ToDataSet(this IQueryable extenderItem,
Dictionary query)
{
if (query == null)
{
throw new ArgumentNullException("query");
}
if (query.Count == 0)
{
throw new ArgumentException("query");
}
return ToDataSet(extenderItem, query, null);
} ///
/// Add the ability to return a dataset based on a particular query /// to a queryable object.
///

/// The IQueryable object to extend.
/// A generic dictionary containing the
/// query to execute along with the name of the table to add it to.
/// An optional DataSet. This allows application
/// to add multiple tables to the dataset.
/// The populated dataset.
public static DataSet ToDataSet(this IQueryable extenderItem,
Dictionary DbCommand> query,
DataSet dataSet)
{
if (query == null)
{
throw new ArgumentNullException("query");
}
if (query.Count == 0)
{
throw new ArgumentException("query");
}
if (dataSet == null) dataSet = new DataSet();

foreach (KeyValuePair kvp in query)
{
dataSet = LinqDataExtensions.ToDataSet(extenderItem,
kvp.Value,
dataSet,
kvp.Key);
}
return dataSet;
} ///
/// Add the ability to return a dataset based on a particular
/// query to a queryable object.
///

/// The IQueryable object to extend.
/// The query to execute.
/// An optional DataSet. This allows
/// application to add multiple tables to the dataset.
/// The name of the DataTable to be added.
/// The populated dataset.
public static DataSet ToDataSet(this IQueryable extenderItem,
DbCommand query,
DataSet dataSet,
string tableName)
{
if (query == null)
{
throw new ArgumentNullException("query");
}
if (dataSet == null)
dataSet = new DataSet();
DataTable tbl = LinqDataExtensions.ToDataTable(extenderItem,
query,
tableName);
if (tbl != null)
{
if (dataSet.Tables.Contains(tableName))
dataSet.Tables.Remove(tableName);
dataSet.Tables.Add(tbl);
}
return dataSet;
}
}
}

A couple of points to note about the class. Extension methods have to be in static classes, and they have to be static methods. In order to note what class is being extended, you use the “this” keyword before the first parameter. The code itself is fairly self explanatory and doesn’t do anything clever. The clever bit actually occurs in the Linq side. Here’s an example:

public DataSet GetDataSet()
{
IQueryable q = GetAllQuery();
return q.ToDataSet(context.GetCommand(GetAllQuery()), null, "MyTable");
}

private IQueryable GetAllQuery()
{
IQueryable q = from p in context.MyTables
select p; return q;
}

No comments: