Briefing
As mentioned in the title, our data provider is going to be very simple. Aim of this article is giving the main idea behind a Linq to Sql like data structure.
So what we are going to do now is as simple as this;
- Create a new database and a new table inside it. (I'll use Sql Server 2008 but you can choose 2005 too)
- Create a new class that will represent that table inside our code.
- Create a DataContext class that is inherited from IQueryable<T> and IQueryProvider interfaces. This class is going to create a sql server query as we use linq on it and when it's time to enumerate DataContext will run that query over a specified connection and return us the data.
Action
Ok now I'm starting with creating a new database called Shop and a new table inside it called Product. So here is our sql command for creating our table 'Product'.
USE [Shop]
GO
/****** Object: Table [dbo].[Product] Script Date: 01/20/2010 14:03:31 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Product](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](50) NOT NULL,
[Count] [int] NOT NULL,
CONSTRAINT [PK_Test] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
Product Class
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public int Count { get; set; }
public override string ToString()
{
return "Id: " + Id.ToString() + " Name " + Name.ToString() + " Count: " + Count.ToString();
}
}
DataContext Class
We start writing our data provider class with,
class DataContext:IQueryable<Product>,IQueryProvider
And right click both interfaces and choose implementing their methods.
Last form of our DataContext class looks like this.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Data.SqlClient;
using System.Data;
namespace BlogLINQ
{
class DataContext:IQueryable<Product>,IQueryProvider
{
//Main expression tree which we are going to use when building our sql query
private Expression mainExpression = null;
//SQL query that is going to be built
public string Query { get; set; }
//ConnectionString to our database
public string ConnectionString { get; set; }
public DataContext(string ConnectionString)
{
this.ConnectionString = ConnectionString;
}
//Method to return value of right node of the expression
private object ExpVal(BinaryExpression expression)
{
if (expression.Right.NodeType == ExpressionType.Constant)
{
return ((ConstantExpression)expression.Right).Value;
}
return null;
}
//This is our main function that creates our sql query from main expression tree
private void CreateSQLQuery(Expression expression)
{
if (expression.NodeType == ExpressionType.Equal)
{
Query = "select * from Product where Count = " + (int)ExpVal((BinaryExpression)expression);
}
if (expression.NodeType == ExpressionType.LessThan)
{
Query = "select * from Product where Count < " + (int)ExpVal((BinaryExpression)expression);
}
if (expression.NodeType == ExpressionType.GreaterThan)
{
Query = "select * from Product where Count > " + (int)ExpVal((BinaryExpression)expression);
}
if (expression is UnaryExpression)
{
UnaryExpression uExp = expression as UnaryExpression;
CreateSQLQuery(uExp.Operand);
}
else if (expression is LambdaExpression)
{
CreateSQLQuery(((LambdaExpression)expression).Body);
}
else if (expression is ParameterExpression)
{
if (((ParameterExpression)expression).Type == typeof(Product))
{
Query = "select * from Product";
}
}
}
#region IEnumerable<Product> Members
//Fires execute method and get enumerator
public IEnumerator<Product> GetEnumerator()
{
return (this as IQueryable).Provider.Execute<IEnumerator<Product>>(mainExpression);
}
#endregion
#region IEnumerable Members
//Fires GetEnumerator method above
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return(IEnumerator<Product>)(this as IQueryable).GetEnumerator();
}
#endregion
#region IQueryable Members
//Type to be enumerated
public Type ElementType
{
get { return typeof(Product); }
}
//Returns expression tree
public System.Linq.Expressions.Expression Expression
{
get { return Expression.Constant(this); }
}
//Returns query provider
public IQueryProvider Provider
{
get { return this; }
}
#endregion
#region IQueryProvider Members
//When any linq query is applied on our DataContext object this method is fired. The linq query that is applied is passed to this method as an expression tree. So we just set our main expression here.
public IQueryable<TElement> CreateQuery<TElement>(System.Linq.Expressions.Expression expression)
{
if (typeof(TElement) != typeof(Product))
throw new Exception("Sorry, product type only :S");
this.mainExpression = expression;
return (IQueryable<TElement>)this;
}
//Fires the function above
public IQueryable CreateQuery(System.Linq.Expressions.Expression expression)
{
return (IQueryable<Product>)(this as IQueryProvider).CreateQuery<Product>(expression);
}
//When our DataContext is enumerated we call this method. It calls CreateSQLQuery function. When our sql query is created some code for getting data from database is executed and a product list is produced. We return enumerator of this list.
public TResult Execute<TResult>(System.Linq.Expressions.Expression expression)
{
MethodCallExpression methodcall = mainExpression as MethodCallExpression;
foreach (var param in methodcall.Arguments)
{
CreateSQLQuery(param);
}
SqlConnection con = new SqlConnection(ConnectionString);
SqlDataAdapter da = new SqlDataAdapter(Query, con);
DataTable dt = new DataTable("test");
da.Fill(dt);
IList<Product> products = new List<Product>();
foreach (DataRow row in dt.Rows)
{
int Id = (int)row[0];
string Name = (string)row[1];
int Count = (int)row[2];
Product product = new Product();
product.Id = Id;
product.Name = Name;
product.Count = Count;
products.Add(product);
}
return (TResult)products.GetEnumerator();
}
//Fires the function above
public object Execute(System.Linq.Expressions.Expression expression)
{
return (this as IQueryProvider).Execute<IEnumerator<Product>>(expression);
}
#endregion
}
}
Testing Our Provider
I used a standart windows form for testing our DataContext class. Added a button and a listbox on it. So when button is clicked;
DataContext datamodel = new DataContext(@"Data Source=.;Initial Catalog=Shop;Integrated Security=True");
var q = from d in datamodel where d.Count > 30 select d;
foreach (var item in q)
{
listBox1.Items.Add(item);
}
Last Words
We've created a DataContext class that creates sql queries for basic linq expressions. And of course it's no way near production quality
. So things that can be done to move our data provider further;
- As you can see our CreateSQLQuery method only creates queries against Count column it's really written weakly. In a professional data provider this should handle every kind of possible linq expressions.
- We only created a simple DataContext class, this data structure should have been built over a nice Object Oriented Design Pattern. (Here is a blog that contains a very nice version which does this; http://blogs.msdn.com/mattwar/archive/2007/07/30/linq-building-an-iqueryable-provider-part-i.aspx)
- And ofcourse we created this class for a specific table and a specific class representing it. A professional data provider should read a database and it's tables and create all classes and methods needed

This is it for now see you next time
Source Code:
BlogLINQ.rar (44,42 kb)
