We have implemented a security layer around our NHibernate persistence layer in a way that hopes to prevent a user from even receiving an object back from the database if he shouldn't have access to it. That security layer looks like this:
public static IQueryable<T> Secure<T>(this Queryable<T> query){
//if T does not implement ISecurable, then return query
//else
return query.Where(expressionFactory.GetExpression(securityKey));
}
We essentially restrict access to our ISession by wrapping it with a decorator that calls ISession.Query().Secure().
So we have numerous types that return an Expression<Func<T, bool>>, such that we can pass it to Where():
public class DocumentSecurityExpressionFactory : ISecurityExpressionFactory<Document> {
public Expression<Func<Document, bool>> GetExpression(SecurityKey key) {
return doc => doc.MasterDocument.Compartments.Where(c => c.AssociatedCompartment.Type != ProgramTypes.AccessGroup) //Look at non-access group compartments for access
.All(c => key.Compartments.Contains(c.AssociatedCompartment.ID))
&& (
//person has to be either NTK
doc.MasterDocument.NeedToKnowAccessList.Count() == 0
|| doc.MasterDocument.NeedToKnowAccessList.Any(p => p.PersonID == key.PersonID)
|| doc.MasterDocument.NeedToKnowAccessList.Any(p => key.AccessGroups.Contains(p.CompartmentID))
);
}
}
public class DocumentSummarySecurityExpressionFactory : ISecurityExpressionFactory<DocumentSummary> {
public Expression<Func<DocumentSummary, bool>> GetExpression(SecurityKey key) {
return doc => doc.MasterDocument.Compartments.Where(c => c.AssociatedCompartment.Type != ProgramTypes.AccessGroup)
.All(c => key.Compartments.Contains(c.AssociatedCompartment.ID))
&& (
doc.MasterDocument.NeedToKnowAccessList.Count() == 0
|| doc.MasterDocument.NeedToKnowAccessList.Any(p => p.PersonID == key.PersonID)
|| doc.MasterDocument.NeedToKnowAccessList.Any(p => key.AccessGroups.Contains(p.CompartmentID))
);
}
}
public class LatestDocumentVersionSecurityExpressionFactory : ISecurityExpressionFactory<LatestDocumentVersion> {
public Expression<Func<LatestDocumentVersion, bool>> GetExpression(SecurityKey key) {
return version => version.BaseDocument.MasterDocument.Compartments.Where(c => c.AssociatedCompartment.Type != ProgramTypes.AccessGroup)
.All(c => key.Compartments.Contains(c.AssociatedCompartment.ID))
&& (
version.BaseDocument.MasterDocument.NeedToKnowAccessList.Count() == 0
|| version.BaseDocument.MasterDocument.NeedToKnowAccessList.Any(p => p.PersonID == key.PersonID)
|| version.BaseDocument.MasterDocument.NeedToKnowAccessList.Any(p => key.AccessGroups.Contains(p.CompartmentID))
);
}
}
And there are actually several more for different types that look just like this.
The problem here should be clear: each of our entities that does this is essentially the same. They each have a reference to a MasterDocument object, on which all the logic is done. Repeating this code totally sucks (and it all sits in one file so they can all change together if they ever do).
I feel like I should be able to just tell a method how to get the MasterDocument from type T, and then have a generalized method that builds the expression. Something like this:
public static class ExpressionFactory {
public static Expression<Func<T, bool>> Get<T>(Expression<Func<T, MasterDocument>> mdSource, SecurityKey key) {
return t => {
var md = mdSource.Compile()(t);
return md.Compartments.Where(c => c.AssociatedCompartment)...
};
}
}
And call it like so:
public class DocumentSecurityExpressionFactory : ISecurityExpressionFactory<Document> {
public Expression<Func<Document, bool>> GetExpression(SecurityKey key) {
return ExpressionFactory.Get<Document>(doc => doc.MasterDocument, key);
}
}
Now, I understand why this code doesn't work. What I can't figure out is how to build up this expression tree correctly in order to vastly simplify our code. I imagine I could pass in the Expression<Func<T, MasterDocument>> mdSource like that and then use the Expression API to build it out with MemberAccessExpressions and such, but I'm anticipating the mess that would look like, and I'm not sure what would be the lesser evil.
Any help is greatly appreciated.
What you can do is use a Compose method that can compose one expression with another:
public static Expression<Func<TFirstParam, TResult>>
Compose<TFirstParam, TIntermediate, TResult>(
this Expression<Func<TFirstParam, TIntermediate>> first,
Expression<Func<TIntermediate, TResult>> second)
{
var param = Expression.Parameter(typeof(TFirstParam), "param");
var newFirst = first.Body.Replace(first.Parameters[0], param);
var newSecond = second.Body.Replace(second.Parameters[0], newFirst);
return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}
Which uses the following method to replace all instances of one expression with another:
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
Now you can write:
public static class ExpressionFactory
{
public static Expression<Func<T, bool>> Get<T>(
Expression<Func<T, MasterDocument>> mdSource, SecurityKey key)
{
return mdSource.Compose(document =>
document.Compartments.Where(c => c.AssociatedCompartment.Type != ProgramTypes.AccessGroup)
.All(c => key.Compartments.Contains(c.AssociatedCompartment.ID))
&& (
doc.MasterDocument.NeedToKnowAccessList.Count() == 0
|| doc.MasterDocument.NeedToKnowAccessList.Any(p => p.PersonID == key.PersonID)
|| doc.MasterDocument.NeedToKnowAccessList.Any(p => key.AccessGroups.Contains(p.CompartmentID))
);
}
}
Related
What I'm trying to do
I have a repository function that I want to be able support searching by string Equals, Contains, StartsWith, EndsWith. I've created a simple extension method that wraps around these string functions, but EFCore seems unable to translate this.
Are there are any alternative, reusable approaches similar to this?
How I'm trying to do it
public enum StringComparisonType
{
Equals,
Contains,
BeginsWith,
EndsWith
}
public static bool CompareTo(this string inputText, string comparisonText, StringComparisonType comparisonType) => comparisonType switch
{
StringComparisonType.Equals => inputText.Equals(comparisonText),
StringComparisonType.BeginsWith => inputText.StartsWith(comparisonText),
StringComparisonType.Contains => inputText.Contains(comparisonText),
StringComparisonType.EndsWith => inputText.EndsWith(comparisonText),
_ => throw new NotImplementedException($"{nameof(StringComparisonType)} {comparisonType} not currently supported.")
};
var searchText = "hello";
var comparison = StringComparisonType.BeginsWith;
_context.Records.Where(r => r.Text.CompareTo(searchText, comparison))
The problem with the approach
This throws an error along the lines of:
The LINQ expression could not be translated
Alternative approach
The only alternative I've found that works is just inlining the logic to determine the type of comparison to apply, but this is horrible to read, horrible to write, and is not reusable, e.g.
_context.Records
.Where(r => comparison == StringComparisonType.Equals
? r.Text.Equals(searchText)
: comparison == StringComparisonType.BeginsWith
? r.Text.StartsWith(searchText)
: comparison == StringComparisonType.EndsWith
? r.Text.EndsWith(searchText)
: r.Text.Contains(searchText))
I'm currently using EFCore 7.
If you use it on predefined type (be Record in example) try something like this:
public static IQueryable<Record> WhereCompare(this IQueryable<Record> query, string comparisonText, StringComparisonType comparisonType) => comparisonType switch
{
StringComparisonType.Equals => query.Where(r => r.Text.Equals(comparisonText)),
StringComparisonType.BeginsWith => query.Where(r => r.Text.StartsWith(comparisonText)),
StringComparisonType.Contains => query.Where(r => r.Text.Contains(comparisonText)),
StringComparisonType.EndsWith => query.Where(r => r.Text.EndsWith(comparisonText)),
_ => throw new NotImplementedException($"{nameof(StringComparisonType)}
{comparisonType} not currently supported.")
}
And then use it like this:
var result = _context.Records.WhereCompare(searchText, comparison).ToList();
Generic method
Maybe this can be done easier, but let's do this. First implement Compose function (combine sequence of expressions into one):
using System;
using System.Linq;
using System.Linq.Expressions;
...
private static Expression<Func<TSource, TResult>> Compose<TSource, TIntermediate, TResult>(
this Expression<Func<TSource, TIntermediate>> first,
Expression<Func<TIntermediate, TResult>> second)
{
var param = Expression.Parameter(typeof(TSource));
var intermediateValue = first.Body.ReplaceParameter(first.Parameters[0], param);
var body = second.Body.ReplaceParameter(second.Parameters[0], intermediateValue);
return Expression.Lambda<Func<TSource, TResult>>(body, param);
}
private static Expression ReplaceParameter(this Expression expression,
ParameterExpression toReplace,
Expression newExpression)
{
return new ParameterReplaceVisitor(toReplace, newExpression)
.Visit(expression);
}
private class ParameterReplaceVisitor : ExpressionVisitor
{
private ParameterExpression from;
private Expression to;
public ParameterReplaceVisitor(ParameterExpression from, Expression to)
{
this.from = from;
this.to = to;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return node == from ? to : node;
}
}
With this methods we can now create this:
public static IQueryable<T> WhereCompare<T>(this IQueryable<T> query, Expression<Func<T, string>> selector, string comparisonText, StringComparisonType comparisonType)
{
var filter = Compose<T, string, bool>(selector, WhereCompareSelector<T>(comparisonText, comparisonType));
return query.Where(filter);
}
public static Expression<Func<string, bool>> WhereCompareSelector<T>(string comparisonText, StringComparisonType comparisonType) => comparisonType switch
{
StringComparisonType.Equals => r => r.Equals(comparisonText),
StringComparisonType.BeginsWith => r => r.StartsWith(comparisonText),
StringComparisonType.Contains => r => r.Contains(comparisonText),
StringComparisonType.EndsWith => r => r.EndsWith(comparisonText),
_ => throw new NotImplementedException($"{nameof(StringComparisonType)}{comparisonType} not currently supported.")
};
Usage:
var result = _context.Records.WhereCompare(t => t.Text, searchText, comparison).ToList();
I am trying to write a custom Linq extension, which can also be performed within the DB.
Basically, what I want to do is collectionOfStrings.Where(x => !x.IsNullOrWhiteSpace) Unfortunately it's not supported.
=============== What I've tried so far =============
This part is only interesting to those, who might come up with another idea apart from the one below.
There is a workaround by going this way collection.Where(x => x != null && x.Trim() != string.Empty), but since I use it frequently, it's not the best solution.
The prettiest solution would be, to find a way to write a string extension IsNullOrWhiteSpaceDB, which works or to kind of add the IsNullOrWhiteSpace method to the database programmatically to ensure support.
That's been my attempt to create a working IsNullOrWhiteSpace method, but it's not supported too:
public static bool IsNullOrWhiteSpaceDB(this string? str) =>
str == null || str.Trim() == String.Empty;
So I've started writing a predicate, which is working fine:
public IQueryable<string> GetAll() =>
GetAll().Select(x => x.property).Where(StringIsNotNullOrWhiteSpace).Distinct();
private static Expression<Func<string?, bool>> StringIsNotNullOrWhiteSpace =>
x => x != null && x.Trim() != string.Empty;
=============== The current problem =============
Neverthless I'd actually like to be able to run it on another collection than on a collection of strings. So I tried to build a custom linq extension (inspired by this solution (https://stackoverflow.com/a/40924558/9487478)):
public class QueryVisitor : ExpressionVisitor
{
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.IsStatic && node.Method.Name == "IsNullOrWhiteSpace")
{
//!(b.Diameter == null || b.Diameter.Trim() == string.Empty)
var arg = node.Arguments[0];
var argTrim = Expression.Call(arg, typeof(string).GetMethod("Trim", Type.EmptyTypes));
var exp = Expression.MakeBinary(ExpressionType.Or,
Expression.MakeBinary(ExpressionType.Equal, arg, Expression.Constant(null, arg.Type)),
Expression.MakeBinary(ExpressionType.Equal, argTrim, Expression.Constant(string.Empty, arg.Type))
);
return exp;
}
return base.VisitMethodCall(node);
}
}
public static class EfQueryableExtensions
{
public static IQueryable<T> WhereEx<T>(this IQueryable<T> queryable, Expression<Func<T, bool>> where)
{
var visitor = new QueryVisitor();
return queryable.Where(visitor.VisitAndConvert(where, "WhereEx"));
}
}
And that's what my custom extension actually looks like:
public static class QueryHelper
{
public static IQueryable<T> WhereIsNotNullOrWhiteSpace<T>(this IQueryable<T> query, Expression<Func<T, string?>> expression)
{
var arg = expression.Body;
var argTrim = Expression.Call(arg, typeof(string).GetMethod("Trim", Type.EmptyTypes));
var exp = Expression.MakeBinary(ExpressionType.And,
Expression.MakeBinary(ExpressionType.NotEqual, arg, Expression.Constant(null, arg.Type)),
Expression.MakeBinary(ExpressionType.NotEqual, argTrim, Expression.Constant(string.Empty, arg.Type))
);
var lambda = Expression.Lambda<Func<T, bool>>(exp, expression.Parameters);
var result = query.Where(lambda);
return result;
}
}
After the query.Where(lambda) is executed there is an inner exception within the result:
NHibernate.Hql.Ast.ANTLR.QuerySyntaxException: A recognition error occurred.
The "original version" throws the same error too, so I thought it might be the created expression ((x == null) Or (x.Trim() == "")) (copied from the debugger). For me it looks actually quite good and I don't understand the cause of the error.
Any ideas? I would be pleased!
You don't need to build an expression for this. You just need an extension method that takes and returns IQueryable<string>.
public static class Extensions
{
public static IQueryable<string> IsNullOrWhiteSpaceDB(this IQueryable<string> input)
{
return input.Where(x => x != null && x.Trim() != string.Empty);
}
}
I have created PR for LINQKit which should simplify your life https://github.com/scottksmith95/LINQKit/pull/127
Idea is to add ExpandableAttribute to such methods which points to static function with expression for substitution.
public static class Extensions
{
[Expandable(nameof(IsNotNullOrWhiteSpaceDBImpl))]
public static bool IsNotNullOrWhiteSpaceDB(string str)
=> throw new NotImplementedException();
public static Expression<Func<string, bool>> IsNotNullOrWhiteSpaceDBImpl()
=> x => x != null && x.Trim() != string.Empty;
}
So query should use AsExpandable() at least once. Put this call somewhere in repository.
db.Users.AsExpandable()
.Where(u => u.FirstName.IsNotNullOrWhiteSpaceDB() || u.MiddleName.IsNotNullOrWhiteSpaceDB())
Is it possible to dynamically rewrite an Expression<T>, replacing an element of T with another type?
For example, replacing the DocumentTypeA with DocumentTypeB in the following situations:
Expression<Func<DocumentTypeA, bool>> expression = m => m.Details.Id == "an-id"
Expression<Func<DocumentTypeA, bool>> expression = m => m.Details.Id == "an-id" && m.AnInt == 42
Expression<Func<DocumentTypeA, bool>> expression = m => m.AString == "I'm a string"
I'll need to make the decision about what type to use at runtime, rather than compile time.
It's also worth noting that DocumentTypeA and DocumentTypeB don't relate to each other, apart from their properties are identical.
The end result would be to reprocess them so they now look like
Expression<Func<DocumentTypeB, bool>> expression = m => m.Details.Id == "an-id"
Expression<Func<DocumentTypeB, bool>> expression = m => m.Details.Id == "an-id" && m.AnInt == 42
Expression<Func<DocumentTypeB, bool>> expression = m => m.AString == "I'm a string"
So the actual comparison part of the expression remains unchanged, only the top-level type has changed.
You can use ExpressionVisitor to replace the type.
class ParameterRewriter<T, U> : ExpressionVisitor
{
protected override Expression VisitParameter(ParameterExpression node)
{
if (node.Type.Equals(typeof(T)))
{
return Expression.Parameter(typeof(U), node.Name);
}
return base.VisitParameter(node);
}
protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression is ParameterExpression paramExp && paramExp.Type.Equals(typeof(T)))
{
return Expression.MakeMemberAccess(
Expression.Parameter(typeof(U), paramExp.Name),
typeof(U).GetMember(node.Member.Name).Single());
}
return base.VisitMember(node);
}
protected override Expression VisitLambda<L>(Expression<L> node)
{
var parameters = node.Parameters.ToList();
var found = false;
for (var i = 0; i < parameters.Count; i++)
{
if (parameters[i].Type.Equals(typeof(T)))
{
parameters[i] = Expression.Parameter(typeof(U), parameters[i].Name);
found = true;
}
}
if (found)
{
return Expression.Lambda(node.Body, parameters);
}
return base.VisitLambda(node);
}
}
In this case, create an instance new ParameterRewriter<DocumentTypeA, DocumentTypeB>() and visit the original expression tree, you will get what you want. An extension method maybe more readable:
public static class ExpressionExtensions
{
public static Expression<Func<U, R>> RewriteParameter<T, U, R>(this Expression<Func<T, R>> expression)
{
var rewriter = new ParameterRewriter<T, U>();
return (Expression<Func<U, R>>)rewriter.Visit(expression);
}
}
The usage is simple:
Expression<Func<A, bool>> expA = x => x.Id == 1;
Expression<Func<B, bool>> expB = expA.RewriteParameter<A, B, bool>();
Use an interface that both classes inherit from and contains the properties that are identical in both of the classes e.g.
interface IDocumentTypes
{
string AString { get; set; } //indicates that both classes need to implement this
//etc...
}
class DocumentTypeA : IDocumentTypes
{
//your class
}
Then both of your classes can use the Expression when it implements the interface IDocumentTypes and still be strongly typed. The classes don't need to have anything in common other than implementing the properties/functions defined in the interface.
public IEnumerable<Table1> GetMatchingTable1(string param, double[] Thicknesses)
{
return DBContext.Table1.Where(c => c.Field1 == param
&& Thicknesses.Any(Thickness => Thickness >= c.MinThickness && Thickness <= c.MaxThickness))
.ToList();
}
Above query return the following exception. "Some part of your SQL statement is nested too deeply. Rewrite the query or break it up into smaller queries."
So far, all my research on the web for this error pointed toward replacing "ANY" with "CONTAINS". Here is one site where they fix the problem using this solution : http://blog.hompus.nl/2010/08/26/joining-an-iqueryable-with-an-ienumerable/
But in my case, "CONTAINS" doesn't seem usable since I check a RANGE with Min and Max.
How should this query be written to have a proper SQL Statement generated by LinqToEntity?
Thanks
You could try to build the query dynamically:
public IEnumerable<Table1> GetAllCoilLengthSettingsWithChilds(string param, double[] Thicknesses)
{
// Base query
var query = LinqKit.Extensions.AsExpandable(DBContext.Table1.Where(c => c.Field1 == param));
// All the various || between the Thickness ranges
var predicate = LinqKit.PredicateBuilder.False<Table1>();
foreach (double th in Thicknesses)
{
// Don't want a closure around th
double th2 = th;
predicate = predicate.Or(c => th2 >= c.MinThickness && th2 <= c.MaxThickness);
}
// This is implicitly in && with the other Where
query = query.Where(predicate);
return query.ToList();
}
The PredicateBuilder helps you build an || query. Take it from the LinqKit (source available)
I've tested it with 1000 parameters (but they where DateTime, and I didn't have other query pieces), and it seems to work. Note that the program uses another extension of LinqPad, AsExpandable, used to make the PredicateBuilder "trick" work. Note that I'm using EF 6.1.3, so your mileage may vary.
If you don't want to use LinqKit, I'm appending my version of PredicateBuilder. It doesn't require the use of AsExpandable(), but its syntax is slightly different:
public class PredicateBuilder<T>
{
// We share a single parameter for all the PredicatBuilder<T>
// istances. This isn't a proble, because Expressions are immutable
protected static readonly ParameterExpression Parameter = Expression.Parameter(typeof(T), "x");
protected Expression Current { get; set; }
// Returns an empty PredicateBuilder that, if used, is true
public PredicateBuilder()
{
}
// Use it like this: .Where(predicate) or .Any(predicate) or
// .First(predicate) or...
public static implicit operator Expression<Func<T, bool>>(PredicateBuilder<T> predicate)
{
if (object.ReferenceEquals(predicate, null))
{
return null;
}
// Handling of empty PredicateBuilder
Expression current = predicate.Current ?? Expression.Constant(true);
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(current, Parameter);
return lambda;
}
public static implicit operator PredicateBuilder<T>(Expression<Func<T, bool>> expression)
{
var predicate = new PredicateBuilder<T>();
if (expression != null)
{
// Equivalent to predicate.Or(expression)
predicate.And(expression);
}
return predicate;
}
public void And(Expression<Func<T, bool>> expression)
{
if (expression == null)
{
throw new ArgumentNullException("expression");
}
var expression2 = new ParameterConverter(expression.Parameters[0], Parameter).Visit(expression.Body);
this.Current = this.Current != null ? Expression.AndAlso(this.Current, expression2) : expression2;
}
public void Or(Expression<Func<T, bool>> expression)
{
if (expression == null)
{
throw new ArgumentNullException("expression");
}
var expression2 = new ParameterConverter(expression.Parameters[0], Parameter).Visit(expression.Body);
this.Current = this.Current != null ? Expression.OrElse(this.Current, expression2) : expression2;
}
public override string ToString()
{
// We reuse the .ToString() of Expression<Func<T, bool>>
// Implicit cast here :-)
Expression<Func<T, bool>> expression = this;
return expression.ToString();
}
// Small ExpressionVisitor that replaces the ParameterExpression of
// an Expression with another ParameterExpression (to make two
// Expressions "compatible")
protected class ParameterConverter : ExpressionVisitor
{
public readonly ParameterExpression From;
public readonly ParameterExpression To;
public ParameterConverter(ParameterExpression from, ParameterExpression to)
{
this.From = from;
this.To = to;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (node == this.From)
{
node = this.To;
}
return base.VisitParameter(node);
}
}
}
public static class PredicateBuilder
{
// The value of source isn't really necessary/interesting. Its type
// is :-) By passing a query you are building to Create, the compiler
// will give to Create the the of the object returned from the query
// Use it like:
// var predicate = PredicateBuilder.Create<MyType>();
// or
// var predicate = PredicateBuilder.Create(query);
public static PredicateBuilder<T> Create<T>(IEnumerable<T> source = null)
{
return new PredicateBuilder<T>();
}
// Useful if you want to start with a query:
// var predicate = PredicateBuilder.Create<MyType>(x => x.ID != 0);
// Note that if expression == null, then a new PredicateBuilder<T>()
// will be returned (that by default is "true")
public static PredicateBuilder<T> Create<T>(Expression<Func<T, bool>> expression)
{
// Implicit cast to PredicateBuilder<T>
return expression;
}
}
Use it like:
var predicate = PredicateBuilder.Create(query);
and then everything is the same (but remove the LinqKit.Extensions.AsExpandable part)
I have a C# LINQ query that has a main query and then 2 other queries depending on if a variable is not set 0.
The query is working, but I need to combine the resultsets and return that.
I want the final resultset to contain the results of the two subqueries combined. Kind of like in a SQL query where you have:
SELECT * FROM myTable WHERE column1 = 'abc' OR column2 = 'xyz'
Right now, I think it's using an AND instead of an OR
var GeoLocations = rows.Select(r => new ElementSearchGeoLocation(r))
.Where(t => (t.GeoLocationType == "State" && t.CanViewState(t.GeoLocationState, user)) ||
(t.GeoLocationType == "City" && t.CanViewCity(t.GeoLocationCity, user)));
if(SystemList != 0)
{
GeoLocations = GeoLocations.Where(t => (dto.SystemList.Contains(t.SystemID)));
}
if (groupList != 0)
{
GeoLocations = GeoLocations.Where(t => (dto.groupList.Contains(t.PoliticalID)));
}
return Ok(GeoLocations);
Is there a way to do this in LINQ?
Here is an implementation of a PredicateBuilder that is able to Or together two different expressions:
public static class PredicateBuilder
{
public static Expression<Func<T, bool>> True<T>() { return f => true; }
public static Expression<Func<T, bool>> False<T>() { return f => false; }
public static Expression<Func<T, bool>> Or<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var secondBody = expr2.Body.Replace(
expr2.Parameters[0], expr1.Parameters[0]);
return Expression.Lambda<Func<T, bool>>
(Expression.OrElse(expr1.Body, secondBody), expr1.Parameters);
}
public static Expression<Func<T, bool>> And<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var secondBody = expr2.Body.Replace(
expr2.Parameters[0], expr1.Parameters[0]);
return Expression.Lambda<Func<T, bool>>
(Expression.AndAlso(expr1.Body, secondBody), expr1.Parameters);
}
}
It's dependant on the following code to be able to replace all instances of one expression with another:
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
internal static class ExpressionExtensions
{
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
}
Using this you can now write:
var GeoLocations = rows.Select(r => new ElementSearchGeoLocation(r))
.Where(t => (t.GeoLocationType == "State" && t.CanViewState(t.GeoLocationState, user)) ||
(t.GeoLocationType == "City" && t.CanViewCity(t.GeoLocationCity, user)));
var predicate = PredicateBuilder.False();
if(SystemList != 0)
{
predicate = predicate.Or(t => dto.SystemList.Contains(t.SystemID));
}
if (groupList != 0)
{
predicate = predicate.Or(t => dto.groupList.Contains(t.PoliticalID));
}
return Ok(GeoLocations.Where(predicate));
Use Concat to add in the additional rows. To make the code super minimal, I would store off the initial `Select first:
var AllLocations = rows.Select(r => new ElementSearchGeoLocation(r));
var mainQuery = AllLocations.Where(t => (t.GeoLocationType == "State" && t.CanViewState(t.GeoLocationState, user)) ||
(t.GeoLocationType == "City" && t.CanViewCity(t.GeoLocationCity, user)));
Then:
IEnumerable<GeoLocation> subQuery;
if (SystemList != 0)
subQuery = AllLocations.Where(...);
else
subQuery = AllLocations.Where(...);
var GeoLocations = mainQuery.Concat(subQuery);
If you care about duplicates, you can use Union instead of Concat for the last step.
Two methods come up for this behaviour
Union, which combines two result sets while culling for duplicates
Concat, which simply slams two result sets together
Which one you choose depends on desired behaviour. Note that either of these may or may not work if your queries are actually IQueryables and running from a database (via linq-to-sql or Entity Framework or the like).
As has been mentioned, do not forget that LINQ results are lazily-evaluated, and this parts of a query can be safely saved and rehashed for later.