Overview
The sample includes these components:
- An Expression Tree serialization API: A general purpose XML serialization of Expression Trees. This should work over any expression tree – though there are inevitably bugs. The serialization format is fairly crude, but has been expressive enough to support the variety of expression trees I’ve tried throwing at it.
- A wrapper for serializing/deserializing LINQ to SQL queries: A wrapper around the expression serializer allows serializing LINQ to SQL queries and de-serializing into a query against a given DataContext.
- A WCF service which accepts serialized query expression trees and executes against a back-end LINQ to SQL: To enable querying across tiers, a WCF service exposes service methods which execute serialized queries. The service implementation deserializes the queries against its LINQ to SQL connection.
- An IQueryable implementation wrapping the client side of the WCF service: The client-side calling syntax is simplified by providing an IQueryable implementation. This implementation, RemoteTable, executes queries by serializing the query expression tree and calling the appropriate service. The object model that the service user is able to query against is imported by the WCF service reference per the DataContracts on the LINQ to SQL mapping on the server side.
Download
ExpressionTreeSerialization
Walkthrough
Very simple serialization example:
public static void BasicExpressionSerialization()
{
Console.WriteLine("BASIC SAMPLE - Serialize/Deserialize Simple Expression:");
Expression<Func<int, int, int>> addExpr = (x, y) => x + y;
ExpressionSerializer serializer = new ExpressionSerializer();
XElement addXml = serializer.Serialize(addExpr);
Expression<Func<int,int,int>> addExpResult = serializer.Deserialize<Func<int,int,int>>(addXml);
Func<int, int, int> addExpResultFunc = addExpResult.Compile();
int result = addExpResultFunc(1, 2); // evaluates to 3
Console.WriteLine("Deserialized Expression Tree:");
Console.WriteLine(" " + addExpResult.ToString());
Console.WriteLine();
}
Serializing an expression tree representing a query expression:
public static void ComplexExpressionSerializationSamples()
{
Console.WriteLine("COMPLEX SAMPLE - Serialize/Deserialize In-Memory Query Expression:");
Expression<Func<IEnumerable<int>>> queryExp = () => from i in Enumerable.Range(1, 10)
where i % 2 == 0
select i * i;
ExpressionSerializer serializer = new ExpressionSerializer();
XElement queryXml = serializer.Serialize(queryExp);
Expression<Func<IEnumerable<int>>> queryExpResult = serializer.Deserialize<Func<IEnumerable<int>>>(queryXml);
// Print out the expression tree: "(x, y) => x + y"
Console.WriteLine("Deserialized Expression Tree:");
Console.WriteLine(" " + queryExpResult.ToString());
// Call it
Func<IEnumerable<int>> f = queryExpResult.Compile();
IEnumerable<int> result = f();
Console.WriteLine("\nResults: ");
result.ToList().ForEach(n => Console.WriteLine(" " + n));
Console.WriteLine();
}
Example of scenario such as storing a query in a database and retreiving it later into the same object model:
public static void DLinqQuerySerializationSamples()
{
Console.WriteLine("DLINQ BASIC SAMPLE - Single Object Model used on both sides of serialization:");
// Write the query against RemoteTable<Customer>, a dummy implementation
// of IQueryable
RemoteTable<Customer> customers = new RemoteTable<Customer>();
var query = from c in customers
where c.City == "London"
select new {c.CompanyName,c};
XElement queryXml = query.SerializeQuery();
// On the other side - create a new DataContext, and deserialize
// the query xml into a query against this datacontext
NorthwindDataContext dbOther = new NorthwindDataContext();
IQueryable queryAfter = dbOther.DeserializeQuery(queryXml);
// Print out the IQueryable: "(x, y) => x + y"
Console.WriteLine("Deserialized IQueryable:");
Console.WriteLine(" " + queryAfter.ToString());
Console.WriteLine("\n Results: ");
queryAfter.Cast<object>().ToList().ForEach(n => Console.WriteLine(" " + n.ToString()));
Console.WriteLine();
}
Example of querying using LINQ against a LINQ to SQL implementation hidden behind a WCF service. Note that no database is being directly referenced - all types and calls are proxies generated by the service reference:
public static void AcrossTheWireSerializationSamples()
{
Console.WriteLine("DLINQ ACROSS THE WIRE SAMPLE - Query against an IQueryable wrapper over a web service:");
// Query is against a RemoteTable which is a proxy for the the WCF service which executes the DLinq query
// on the server. Note that the elements are the service-reference generated types that align with the
// DLinq mapping types via the DataContracts.
var queryService = new RemoteTable<RemoteQueryService.ServiceReference.Customer>();
var query = from c in queryService
where c.City == "London"
orderby c.CustomerID
select (from o in c.Orders
orderby o.OrderDate
select o.OrderDate).First();
Console.WriteLine("\n Query Results: ");
foreach (var c in query)
Console.WriteLine(" " + c.Value.ToShortDateString());
}
Serialization API
Example of a Serialized Expression Tree
Note that the current serialization format is quite heavyweight and includes a fair but of redundancy. It should be possible to reduce this size significantly while maintaining full fidelity with the in-memory Expression Trees.
Expression<Func<int, int, int>> addExpr = (x, y) => x + y;
<LambdaExpression NodeType="Lambda">
<Body>
<BinaryExpression IsLifted="false" IsLiftedToNull="false" NodeType="Add">
<Left>
<ParameterExpression Name="x" NodeType="Parameter">
<Type>
<Type Name="System.Int32" />
</Type>
</ParameterExpression>
</Left>
<Right>
<ParameterExpression Name="y" NodeType="Parameter">
<Type>
<Type Name="System.Int32" />
</Type>
</ParameterExpression>
</Right>
<Method />
<Conversion />
<Type>
<Type Name="System.Int32" />
</Type>
</BinaryExpression>
</Body>
<Parameters>
<ParameterExpression Name="x" NodeType="Parameter">
<Type>
<Type Name="System.Int32" />
</Type>
</ParameterExpression>
<ParameterExpression Name="y" NodeType="Parameter">
<Type>
<Type Name="System.Int32" />
</Type>
</ParameterExpression>
</Parameters>
<Type>
<Type Name="System.Func`3">
<Type Name="System.Int32" />
<Type Name="System.Int32" />
<Type Name="System.Int32" />
</Type>
</Type>
</LambdaExpression>