My F# Notes
Building LINQ Queries in F# and C#
My last post at the hubFS was an article that I wrote earlier to my personal blog and I wrote one more article some time ago that is related to F# and I wasn’t posted here. The article is a part of a short series that discusses how the same LINQ-related problem can be solved in F# and in C# 3.0 (and in fact, my implementation of the C# version was largely motivated by the F# solution). Because I think that the articles are closely related, I’ll write just a short summary here and add links to both articles available in my other blog.
The articles deal with a problem how can one build a LINQ (database) query at runtime. A typical example of a query that developers want to build at runtime is a query where the user can modify the filtering condition (where
clause) - for example the application may want to allow combining several conditions, like c.Country ==
with c.Name.StartsWith(
. In addition you may want to allow combining these single conditions using various operators (at least or
and and
).
The article about the F# version also shortly introduces the F# comprehensions and quotations that are used when writing LINQ queries using F#. Then it gives a few examples of using LINQ for database queries and finally, it fully describes the example that is shortly introduced below. In the article about the C# version, I described several extensions that are very useful when writing the queries in the F#-style as well as a solution that I use to allow a mechanism similar to F# holes (that are shortly introduced below). Finally, the F# article also contains a version of the FLINQ sample that is compatible with Visual Studio 2008 Beta 2.
- Building LINQ Queries at Runtime in F# [^] - Complete article at TomasP.Net
- Building LINQ Queries at Runtime in C# [^] - Complete article at TomasP.Net
Building LINQ Queries at Runtime in F#
In F#, the queries are represented using quotations which can be easily combined thanks to the support for holes (written as underscore). The following example first defines two basic conditions (one of them always returns true
and the second returns false
). Later, we define two combinators for combining conditions using ||
and &&
operators. Finally, we build a condition that is composed from several elementary conditions (stored in a dictionary called dict
). To do this we can use the Fold
function which takes an initial condition and combines it with all the conditions in the dictionary:
// Basic conditions that always return true/false let falseCond = « fun (c:Customer) -> false » let trueCond = « fun (c:Customer) -> true » // Condition combinators let (||*) f g = « fun (c:Customer) -> (_ c) || (_ c) » f g let (&&*) f g = « fun (c:Customer) -> (_ c) && (_ c) » f g // Which combinator will we use? let (^^) = if generateOr then (||*) else (&&*) // Build the expression by folding all items in a dictionary let expr = dict.Fold(fun key propSelector e -> Console.Write(“Enter value for ‘{0}’:n> “, key); let enteredVal = Console.ReadLine(); let currentCond = « fun (c:Customer) -> ((_ c):string).IndexOf(_:string) -1 » propSelector « §enteredVal » (currentExpr ^^ e)) (if generateOr then falseCond else trueCond)
Building LINQ Queries at Runtime in C#
In C# the representation of the lambda expression can be either a delegate (Func<...>
) or an expression tree (Expression
) and the decision whether to generate a delegate or an expression tree depends on the expected type, which means that the code needs to define a type alias. This is possible thanks to the using
statement (though this can be avoided by using helper functions, but in this example I’ll instead use aliases). Primitive conditions are easy to define once we have a type alias CustomerCondition
(which stands for an expression tree of a lambda expression taking a Customer
as an argument and returning a bool
). When defining a combinators, we need a way for calling an expression tree in another expression tree and this is done by the method Expand
(which is not part of LINQ and is further discussed in the article). Finally, the code that generates the result uses similar technique as the previous F# version:
// Basic conditions that always return true/false CustomerCondition trueCond = (c) => true; CustomerCondition falseCond = (c) => false; // Condition combinators CustomerConditionCombinator combineOr = (f, g) => (c) => f.Expand(c) || g.Expand(c); CustomerConditionCombinator combineAnd = (f, g) => (c) => f.Expand(c) && g.Expand(c); // Which combinator will we use? CustomerConditionCombinator combinator = generateOr ? combineOr : combineAnd; // Build the expression by folding all items in a dictionary var expr = dict.Fold((e, item) => { Console.Write(“Enter value for ‘{0}’:n> “, item.Key); string enteredVal = Console.ReadLine(); CustomerPropSelector propSelector = item.Value; CustomerCondition currentCond = (c) => propSelector.Expand(c).IndexOf(enteredVal) != -1; return combinator(e, currentCond); }, generateOr ? falseCond : trueCond);