Have a look at the full example code here.
The most important parts explained quickly:
We create a filter object that can turn itself into an expression:
public class ProductFilter
{
public string? Location { get; init; } = null;
public Guid? Warehouse { get; init; } = null;
public string? ProductName { get; init; } = null;
// one could probably use reflection to automate this
public Expression<Func<ProductFilterData, bool>> ToExpression()
{
var parameter = Expression.Parameter(typeof(ProductFilterData), "product");
// we need some default expression to start out with: `true == true`
var expression = Expression.Equal(Expression.Constant(true), Expression.Constant(true));
if (ProductName != null)
{
var propertyName = Expression.Property(parameter, nameof(ProductFilterData.ProductName));
var constantName = Expression.Constant(ProductName);
var nameExpr = Expression.Equal(propertyName, constantName);
expression = Expression.AndAlso(expression, nameExpr);
}
// repeat logic for other properties...
return Expression.Lambda<Func<ProductFilterData, bool>>(expression, parameter);
}
}
The filter object expects data to be of a specific form:
public class ProductFilterData
{
public string Location { get; init; }
public Guid Warehouse { get; init; }
public string ProductName { get; init; }
}
Finally, we can create SQL queries like:
using var db = new WarehouseContext();
// let's query our database:
var result = db.ProductsAtStorageBins
.Include(ps => ps.Product)
.Include(ps => ps.StorageBin)
// first we need to put data into correct form...
// (Use property initializer instead of constructors; constructors cannot
// be translated into SQL!)
.Select(ps => new ProductFilterData
{
Location = ps.StorageBin.Id,
Warehouse = ps.StorageBin.Warehouse.Id,
ProductName = ps.Product.Name,
})
// ...and then we apply our filter
.Where(new ProductFilter { ProductName = "Dingsbums", Location = "00-01-00-05-03" }.ToExpression());
The following SQL (MariaDB) is generated:
SELECT `s`.`Id` AS `Location`, `w`.`Id` AS `Warehouse`, `p0`.`Name` AS `ProductName`
FROM `ProductsAtStorageBins` AS `p`
INNER JOIN `Products` AS `p0` ON `p`.`ProductId` = `p0`.`Id`
INNER JOIN `StorageBins` AS `s` ON `p`.`StorageBinId` = `s`.`Id`
INNER JOIN `Warehouses` AS `w` ON `s`.`WarehouseId` = `w`.`Id`
WHERE (`p0`.`Name` = 'Dingsbums') AND (`s`.`Id` = '00-01-00-05-03')