- Home
- .NET tutorials
- Using LINQ expressions to build dynamic queries in Entity Framework
Using LINQ expressions to build dynamic queries in Entity Framework
Published: Saturday 10 August 2019
Entity Framework is a Object Relational Mapper (ORM) that relates objects and properties in your .NET application with tables and columns in your database. As a result of it's relationship between application and database, it has become widely used in .NET applications.
f => f.Id == 3
The problem with Lambda Expressions in code
On most occasions, writing a Lambda expression directly is fine for what you need it to do. The problem comes if you want to write a Lambda expression where you want to use the same Lambda expression, but use different object names, or properties within it. You can't do that with Lambda expressions directly as you are passing in objects and properties, rather than strings.
LINQ expressions to the rescue
There is a way around this and it comes in the form of LINQ expressions. You can keep on building up each part of the LINQ expression bit by bit. And when you are happy, you can convert it into a Lambda expression ready to be used in Entity Framework.
The sample application
You can download the application to try it out yourself. The examples below are all related to this repository. SQL Server and .NET Core power the application and it contains three examples of building a dynamic query using LINQ expressions.
1. The And Or Statement
The whole point of this example is to produce a Lambda expression that contains an AndAlso statement, but also to produce an OrElse statement. It's important to get the brackets in the right way round. Here is the example:
// AndOrExpression.cs
using RoundTheCode.LinqExpressions_Example.Data;
using RoundTheCode.LinqExpressions_Example.Data.Models.Types;
using System;
using System.Linq;
using System.Linq.Expressions;
namespace RoundTheCode.LinqExpressions_Example.Expressions
{
public partial class AndOrExpression : ExpressionBase<FilmTimeEntity>
{
public override IQueryable<FilmTimeEntity> GetExampleQuery(LinqExpressionsDbContext context)
{
var expression = GetExpression(context);
// Store the filter as a dynamic query.
return context.FilmTimeEntities.Where(expression);
}
protected override Expression<Func<FilmTimeEntity, bool>> GetExpression(LinqExpressionsDbContext context)
{
var beginTime = DateTimeOffset.Parse("2019-08-03 12:00");
var endTime = DateTimeOffset.Parse("2019-08-04 11:00");
// We want to build the expression for the "Where" clause
// Example of Query we will eventually run.
// context.FilmTimeEntities.Where(ft => ft.FilmId == 3 && (ft.StartTime < beginTime || ft.StartTime >= endTime));
// Time to build up the clause in the ANY field
var ftParameter = Expression.Parameter(typeof(FilmTimeEntity), "ft"); // ft =>
var ftIdProperty = Expression.Property(ftParameter, "FilmId"); // ft.FilmId
var ftIdClause = Expression.Equal(ftIdProperty, Expression.Constant(3)); // ft.FilmId == 3
// Begin the OrElse statement
var ftStartTimeProperty = Expression.Property(ftParameter, "StartTime"); // ft.StartTime
var ftStartTimeFirstClause = Expression.LessThan(ftStartTimeProperty, Expression.Constant(beginTime)); // ft.StartTime < beginTime
var ftStartTimeSecondClause = Expression.GreaterThanOrEqual(ftStartTimeProperty, Expression.Constant(endTime)); // ft.StartTime >= endTime
// Or statement
var ftOrElseClause = Expression.OrElse(ftStartTimeFirstClause, ftStartTimeSecondClause); // (ft.StartTime < beginTime || ft.StartTime >= endTime)
// AndAlso statement
var ftAndClause = Expression.AndAlso(ftIdClause, ftOrElseClause); // ft.FilmId == 3 && (ft.StartTime < beginTime || ft.StartTime >= endTime)
// Lambda Expression
return Expression.Lambda<Func<FilmTimeEntity, bool>>(ftAndClause, ftParameter);
}
}
}
2. The Any Statement
In this example, we are doing a where clause on the Film entity, but we are also producing a subquery where we are query the FilmTime entity from the DbContext. Here is the example:
// AnyExpression.cs
using RoundTheCode.LinqExpressions_Example.Data;
using RoundTheCode.LinqExpressions_Example.Data.Models.Types;
using System;
using System.Linq;
using System.Linq.Expressions;
namespace RoundTheCode.LinqExpressions_Example.Expressions
{
public partial class AnyExpression : ExpressionBase<FilmEntity>
{
public override IQueryable<FilmEntity> GetExampleQuery(LinqExpressionsDbContext context)
{
var expression = GetExpression(context);
// Store the filter as a dynamic query.
return context.FilmEntities.Where(expression);
}
protected override Expression<Func<FilmEntity, bool>> GetExpression(LinqExpressionsDbContext context)
{
var startTime = DateTimeOffset.Parse("2019-08-04");
// We want to build the expression for the "Where" clause
// Example of Query we will eventually run.
// _context.FilmEntities.Where(f => _context.FilmTimeEntities.Any(ft => ft.FilmId == f.Id && ft.StartTime >= startTime));
var fParameter = Expression.Parameter(typeof(FilmEntity), "f"); // f =>
// Create the FilmTime Entities Query
var ftQueryExpression = context.Set<FilmTimeEntity>().AsQueryable().Expression; // _context.FilmTimeEntities
// Build up the Any part of the query from Film Times
var ftParameter = Expression.Parameter(typeof(FilmTimeEntity), "ft"); // ft =>
var ftFilmIdProperty = Expression.Property(ftParameter, "FilmId"); // ft.FilmId
var ftFilmIdEquals = Expression.Equal(ftFilmIdProperty, Expression.Property(fParameter, "Id")); // ft.FilmId == f.Id
var ftStartTimeProperty = Expression.Property(ftParameter, "StartTime"); // ft.StartTime
var ftStartTimeEquals = Expression.GreaterThanOrEqual(ftStartTimeProperty, Expression.Constant(startTime)); // ft.StartTime >= startTime
// Builds up the AND expression
var ftFullQueryExpression = Expression.AndAlso(ftFilmIdEquals, ftStartTimeEquals); // ft => ft.FilmId == f.Id && ft.StartTime >= startTime
// Make the Lambda Expression for Film Time
var ftLambda = Expression.Lambda<Func<FilmTimeEntity, bool>>(ftFullQueryExpression, ftParameter);
// Join in an ANY statement
var anyMethod = typeof(Queryable).GetMethods().FirstOrDefault(method => method.Name == "Any" && method.GetParameters().Count() == 2); // Use reflection to find the ANY method
var anyFilmTimeMethod = anyMethod.MakeGenericMethod(typeof(FilmTimeEntity)); // Any is a generic method, so create a method specific to FilmTimeEntity
var anyCall = Expression.Call(anyFilmTimeMethod, ftQueryExpression, ftLambda); // (f => _context.FilmTimeEntities.Any(ft => ft.FilmId == f.Id && ft.StartTime >= startTime)
return Expression.Lambda<Func<FilmEntity, bool>>(anyCall, fParameter);
}
}
}
3. The Include Statement
This example is similar to the any statement, but rather than performing a sub query for the FilmTime entity, as we have included the FilmTime entity as a collection of Film entities, so we are going to supply that in an Include statement. Here is the example:
// IncludeExpression.cs
using RoundTheCode.LinqExpressions_Example.Data;
using RoundTheCode.LinqExpressions_Example.Data.Models.Types;
using System;
using System.Linq;
using System.Linq.Expressions;
namespace RoundTheCode.LinqExpressions_Example.Expressions
{
public partial class IncludeExpression : ExpressionBase<FilmEntity>
{
public override IQueryable<FilmEntity> GetExampleQuery(LinqExpressionsDbContext context)
{
var expression = GetExpression(context);
// Store the filter as a dynamic query.
return context.FilmEntities.Where(expression);
}
protected override Expression<Func<FilmEntity, bool>> GetExpression(LinqExpressionsDbContext context)
{
var beginTime = DateTimeOffset.Parse("2019-08-03 12:30");
var endTime = DateTimeOffset.Parse("2019-08-03 16:30");
// We want to build the expression for the "Where" clause
// Example of Query we will eventually run.
// context.FilmEntities.Include("FilmTimes").Where(f.FilmTimes.Any(ft => ft.StartTime >= beginTime && ft.StartTime < endTime));
var fParameter = Expression.Parameter(typeof(FilmEntity), "f"); // f =>
// Get Film Times between beginTime and endTime
var fFilmTimeProperty = Expression.Property(fParameter, "FilmTimes"); // f.FilmTimes
// Time to build up the clause in the ANY field
var ftParameter = Expression.Parameter(typeof(FilmTimeEntity), "ft"); // ft =>
var ftStartTimeProperty = Expression.Property(ftParameter, "StartTime"); // ft.StartTime
var ftBeginTimeClause = Expression.GreaterThanOrEqual(ftStartTimeProperty, Expression.Constant(beginTime)); // ft.StartTime >= startTime
var ftEndTimeClause = Expression.LessThan(ftStartTimeProperty, Expression.Constant(endTime)); // ft.StartTime < endTime
// Builds up the AND expression
var ftFullQueryExpression = Expression.AndAlso(ftBeginTimeClause, ftEndTimeClause); // ft => ft.StartTime >= beginTime && ft.StartTime < endTime
// Make the Lambda Expression for Film Time
var ftLambda = Expression.Lambda<Func<FilmTimeEntity, bool>>(ftFullQueryExpression, ftParameter);
// Join in an ANY statement
var anyMethod = typeof(Enumerable).GetMethods().FirstOrDefault(method => method.Name == "Any" && method.GetParameters().Count() == 2); // Use reflection to find the ANY method (We use Enumerable for collections)
var anyFilmTimeMethod = anyMethod.MakeGenericMethod(typeof(FilmTimeEntity)); // Any is a generic method, so create a method specific to FilmTimeEntity
var anyCall = Expression.Call(anyFilmTimeMethod, fFilmTimeProperty, ftLambda); // FilmTimes.Any(ft => ft.StartTime >= beginTime && ft.StartTime < endTime)
// Return the lamba expression for film so we can put in the where clause.
return Expression.Lambda<Func<FilmEntity, bool>>(anyCall, fParameter);
}
}
}
Should I build dynamic queries?
It's worth noting that building dynamic queries can get confusing very quickly. In my examples above, I've commented the part of the Lambda expression that it's creating. It certainly helped me out.
Related tutorials