CLinq, short for Composable Linq, is a library which brings the ability to compose Linq2Entity queries at runtime and therefore the reusability of queries and the possibility to create complex queries at runtime, instead of always having to predefine them.
An often encountered problem when using Linq is the inability to share query snippets. Often used snippets have to written again and again which leads to duplicate code all over the applications. It also means that for usecases, where the query depends on the state of the application, all required variations of a query that go above the state of variable and very simple control flows need to be predefined and applied at runtime.
CLinq to solve this problem with the concept of a ComposableQuery, which is lent by LinqKit's ExpandableQuery. A composable query allows the developer to pass any kind of expression into the query, no matter if the expression is a simple variable access or complex hirarchy of method calls and member accesses. Expressions can also be merged and combined together which ultimatly allow the developer to reused query snippets in application, wherever he sees fit.
Imagine a method which returns if an employee has too much overtime worked up and needs to relax more:
bool IsOverworked(int employeeId)
{
return employees.Where(e => e.Id == employeeId)
.First(e => e.Overtime >= 100);
}
This works and is perfectly fine, until you want want to build a page which displays a list of all overworked employees. Now you write another method like this:
IQueryable<Employee> GetAllOverworkedEmployees()
{
return employees.Where(e => e.Overtime >= 100);
}
Now we have another method which uses the same condition e.Overtime >= 100
which confronts us with the same problems of duplicate code we've always know.
Changing the definition of overworked requires us to change every line of code, which checks for overworked.
How much easier would it be if we could just create a method
bool IsOverworked(Employee e) => e.Overtime >= 100
and call it in the above examples
bool IsOverworked(int employeeId)
{
return employees.Where(e => e.Id == employeeId)
.First(e => IsOverworked(e));
}
IQueryable<Employee> GetAllOverworkedEmployees()
{
return employees.Where(e => IsOverworked(e));
}
But since Linq builds its queries based on Expression Trees which get translated into SQL code (or whatever you need to query your data store), we cannot just pass a method to the query, since Linq would have no way of knowing how to translate that into SQL.
This is where CLinq comes into play. CLinq allows us to define expressions, plug them into an existing Linq query and compose more complex queries based on smaller building blocks.
The first step is therefore to define an expression which can later be passed into the expression tree of the query.
So instead of the above method IsOverworked
we define the following expression.
Expression<Func<Employee, bool>> IsOverworkedExpression = e => e.Overtime >= 100;
Before we can use this query, we need to mark the used query as composable, which we do with the extension method AsComposable
which can be used on every kind of IQueryable
.
The next step is to bring this all together.
To use the expression and build a query out from it, we have to tell the composable query how that expression has to be called, and mainly on which parameters.
Basically we need to Pass
the employee to the expression, before beeing able to make any calculation on it.
Doing all of this, and our methods from above look like this:
bool IsOverworked(int employeeId)
{
return employees.AsComposable()
.Where(e => e.Id == employeeId)
.First(e => IsOverworkedExpression.Pass(e));
}
IQueryable<Employee> GetAllOverworkedEmployees()
{
return employees.AsComposable()
.Where(e => IsOverworkedExpression.Pass(e));
}
And this is all that needs to be done.
In the background, CLinq now takes the composable query, analyzes it for all occurences of Pass
and projects the underlying expression into the linq query before compiling it into SQL.
More complex scenarios are also possible. Check out Usage for more information.
Make sure to have read the above example, before continuing this section. Not all of the following examples make complete sense from a technical or business related standpoint, but are used to describe the basic concepts of CLinq.
You're completely free on how you want to define your expressions. It's also possible to return expressions from methods, and even modify them based on your provided parameters.
You can define methods to return your expression and use the method parameters in the expression. This even works with default parameter values
Expression<Func<Employee, bool>> IsOverworkedExpression(int overworkedThreshold = 100)
{
return e => e.Overtime > overtimeThresshold;
}
Now you can use this expression like before e => IsOverworkedExpression().Pass(e)
or you can define your own threshold on the fly e => IsOverworkedExpression(500).Pass(e)
, which leads to a different query everytime you call it.
You can als return completely different expression based on the parameters. Say, you don't want to think about overworked employees during a company wide crisis:
Expression<Func<Employee, bool>> IsOverworkedExpression(bool crisisMode)
{
if (crisisMode)
return e => false;
else
return e => e.Overtime > overtimeThresshold;
}
Of course you cannot only change your expressions based on parameters but basically on everything state related in your application. E.g. you could return a different expression when the application is in Developer mode. Some randomization during the dev mode for example?
Expression<Func<Employee, bool>> IsOverworkedExpression()
{
if (new Random().Next() % 2 == 0)
return e => false;
else
return e => true;
}
Expressions are not limited to single parameters.
Expression<Func<Employee, Employee, bool>> AreEmployeesTeamMembersExpression()
{
return (e1, e2) => e1.TeamId == e2.TeamId;
}
This can be used straight forward, and as you can see you can even pass the provided employee into the expression
IQueryable<Employee> GetTeamMembersOf(Employee employee)
{
return employees.AsComposable()
.Where(e => AreEmployeesTeamMembersExpression().Pass(employee, e));
}
It's also possible to combine multiple expressions at runtime. Lets assume the following expressions.
Expression<Func<Employee, IEnumerable>> IsTeamMemberAndOverworked(Employee employee)
{
return e => AreEmployeesTeamMembersExpression().Pass(e, employee)
&& IsOverworkedExpression().Pass(e);
}
Or maybe you want to Pass one expression into another expression? Lets assume the following expression to get the superior of an employee:
Expression<Func<Employee, Employee>> GetSuperiorExpression()
{
return e => e.Superior;
}
Then we can create the following expression to check if a superior is overworked:
Expression<Func<Employee, bool>> IsSuperiorOverworkedExpression()
{
return e => IsOverworkedExpression().Pass(GetSuperiorExpression().Pass(e));
}
It's even possible to call normal methods inside an expression. In [Paremetrizing Expressions](#Paremetrizing Expressions) we passed a paremeter to the expression to change the behavior of the expression. But we could also call a method at runtime which defines this overworkedThreshold at runtime
Expression<Func<Employee, bool>> IsOverworkedExpression()
{
return e => e.Overtime > Configuration.GetOverworkedThreshold();
}
The Method Configuration.GetOverworkedThreshold()
will be evaluated before the expression is projected into the Linq query, and the returned value is used inside the query.
When using CLinq one has to thing about the performance implications. Using Linq has never been the fastest method to query databases, as the translation from an expression tree to an SQL query needs time. CLinq introduces another step to this process, since the expression tree itself first needs to be modified, before it can be translated to SQL. The time required for this depends on the complexity of the query itself, and how long it takes to evaluate all the methods that are used inside the query.
Generaly though, one can say that in use cases where Linq2Entity is fast enough, CLinq does not degrade the performance in a meaningful way and can most times be used without issues.
One of the possibilities to mitigate performance issues while using Linq2Entity is the use of Compiled Queries. CLinq is fully compatible to Compiled Queries, but there is a small pitfall in play here. When modifying a query at runtime
Expression<Func<object, bool>> SampleExpression()
{
if (Configuration.SomeValue)
return o => true;
else
return o => false;
}
the compiled query is cached depending on the value of the parameter at the moment of query compilation, meaning later usages of the query will always use the originally cached query. This can not be avoided should be kept in mind when working with compiled queries.
I'm always glad about feedback. If you come arround a bug using CLinq, please provide me a bug report and I fix as soon as possible. Also, if there is some scenario which is not working at the moment with CLinq, provide a feature request and I'll see if it's possible to implement this scenario.
This project is strongly influenced by LinqKit but takes it a step further. Especially the concept and implementation of the ComposableQuery is heavily influenced by LinqKit.
CLinq is published under the MIT license.