I'm trying to DRY out some lambda expressions for security rights. Is it possible to take a lamda expression and apply it to a single entity for true?
Like lets say I have a Person and a DocumentFolder
Expression<Func<Person, bool>> CanSeePerson()
{
return c => !c.IsPrivate;
}
And one for the folder
Expression<Func<DocumentFolder, bool>> CanSeeFolder()
{
return c => !c.IsPrivate && c.Owner.CanSeePerson(); // <- ???
}
How the heck can I use that CanSeePerson() function on a single type to return true and maintain an expression that can be used in linq queries like such
Entities.DocumentFolder.Where(CanSeeFolder());
I know how to use the where on an iqueryable but I can't see how to apply the expression tree to a single value.
This throws the error: Unable to create a constant value of type 'Person'. Only primitive types ('such as Int32, String, and Guid') are supported in this context.
Expression<Func<DocumentFolder, bool>> CanSeeFolder()
{
return c => !c.IsPrivate &&_entities.Persons.Where(x => x.Id == c.Owner.Id).Any(CanSeePerson());
}
The difference seems to be based on putting the IQueryable Directly in the statement. This also does NOT work
Expression<Func<DocumentFolder, bool>> CanSeeFolder()
{
return c => !c.IsPrivate &&_entities.Persons.Where(CanSeePerson()).Contains(c.User);
}
but this DOES work
Expression<Func<DocumentFolder, bool>> CanSeeFolder()
{
var canSeePersons = _entities.Persons.Where(CanSeePerson());
return c => !c.IsPrivate && canSeePersons.Contains(c.User);
}
p.s. I know I suck # using this stackoverflow formatting thing lol
The reason why this works is because the CanSeePerson() function cannot be converted and used in an expression. When you put the canSeePersons variable in the function instead you are placing in an iQueryable type which can be used in an expression. Using "var" convolutes it a bit.
You defined a Expression<Func<Person, bool>> that solves your problem. The trick is to simply transform your list of objects to the list you want to filter. It is not always possible, but many times it is. You just have to be a bit creative :-)
When using extension methods you can easily come up with a solution that allows you to do this:
var visibleFolders = Entities.DocumentFolder.WhereCanSeeFolder();
Here is the (completely DRY) solution:
public static class SecurityExtensions
{
public static IQueryable<DocumentFolder> WhereCanSeeFolder(
this IQueryable<DocumentFolder> folders)
{
var visibleOwners = folders.Select(f => f.Owner)
.Where(CanSeePerson);
return
from folder in folders.Where(CanSeeFolder)
where visibleOwners.Contains(folder.Owner)
select folder;
}
private static readonly Expression<Func<DocumentFolder, bool>>
CanSeeFolder = folder => !folder.IsPrivate;
private static readonly Expression<Func<Person, bool>>
CanSeePerson = person => !person.IsPrivate;
}
I hope this helps.
The expression tree is only applied to a single value at a time, logically, within the LINQ expression.
If you're saying you want to apply it in-process later to a single value, you can just use:
// This can be cached
Func<DocumentFolder, bool> canSeeFolderDelegate = CanSeeFolder().Compile();
DocumentFolder folder = ...; // Get the value from somewhere
if (canSeeFolderDelegate(folder))
{
// Yes, you can see that folder
}
Ah, you want to evaluate it for a single entity?
var func = CanSeeFolder().Compile(); // <=== store and re-use this;
// this isn't free
bool canSee = func(obj);
Another approach might be:
bool canSee = Enumerable.Repeat(obj, 1).AsQueryable().Any(CanSeeFolder());
but this is probably still going to do the Compile somewhere in the chain, so you may as well use the more direct code (at the top).
Edit re comments:
To evaluate that at the database, you would need a restriction, for example:
bool canSee = db.Folders.Where(f => f.FolderId == id)
.Where(CanSeeFolder()).Any();
which limits us to the single row, then adds your extra filter.
Related
I have got the following expression that works with mockup data - hereby not using Entity Framework:
public static Expression<Func<Resource, bool>> FilterResourcesByUserCriteria(IEnumerable<FilterValue> filterValuesForUser)
{
Expression<Func<Resource, bool>> filter = (resource) =>
// Get filter values for the current resource in the loop
resource.ResourceFilterValues
// Group filter values for user
.GroupBy(filterValue => filterValue.FilterValue.FilterGroup.Id)
// Each group must fulfill the following logic
.All(filterGroup =>
// For each filter group, only select the user values from the same group
filterValuesForUser
.Where(filterValueForUser => filterValueForUser.FilterGroup.Id == filterGroup.Key)
.Select(filterValueForUser => filterValueForUser.FilterValue1)
// Each group must at least one value in the sublist of filter values of the current user
.Any(filterValueForUser => filterGroup
.Select(resourceFilterValue => resourceFilterValue.FilterValue.FilterValue1)
.Any(x => x == filterValueForUser))
);
}
However, I get this famous exception when I try to insert this expression in the where clause of my repository method (using Entity Framework):
Unable to create a constant value of type. Only primitive types or enumeration types are supported in this context.
I suspect this has something to do with a parameter called filterValuesForUser, which is a collection of a complex (i.e. custom) type.
Is this behavior even possible in Entity Framework where I do a subquery that is not directly related to Entity Framework? What I want to achieve here is to query on a subset of a custom list for each group in the query.
Any solutions for this or other workarounds? I'd like to minimize the amount of database calls, preferrably limit it to just one.
The exact query you are asking for is impossible with LinqToEF (due to limitation of SQL). But fear not. It is possible to salvage your problem with a slight tweaking.
public static Expression<Func<Resource, bool>> FilterResourcesByUserCriteria(FilterValue filterValueForUser)
{
//I assume you can write this part yourself.
}
public IQueryable<Resource> GetResources()
{
IQueryable<Resource> resources = _context.Resources;
IEnumerable<FilterValue> filterValuesForUser = GetFilterValues();
IEnumerable<IQueryable<Resource>> queries = from filter in filterValuesForUser
let filterExp = FilterResourcesByUserCriteria(filter)
select resources.Where(filterExp);
return Enumerable.Aggregate(queries, (l, r) => Queryable.Concat(l, r));
}
Types and Extension methods expanded for clarity.
In addition to Aron's answer, I used the PredicateBuilder utility in the LinqKit assembly to generate 1 expression rather than multiple and separate expresssions. This also avoids doing multiple database calls.
Here is how you can achieve this (pseudo-code):
public IQueryable<Resource> GetResources()
{
MyContext ctx = new MyContext ();
IEnumerable<Expression<Func<Resource, bool>>> queries =
filterValuesForUser.GroupBy(x => x.FilterGroup)
.Select(filter => SecurityFilters.FilterResourcesByUserCriteriaEF(filter.Select(y => y.FilterValue1)))
.Select(filterExpression => { return filterExpression; });
Expression<Func<Resource, bool>> query = PredicateBuilder.True<Resource>();
foreach (Expression<Func<Resource, bool>> filter in queries)
{
query = query.And(filter);
}
return ctx.Resources.AsExpandable().Where(query);
}
public static Expression<Func<Resource, bool>> FilterResourcesByUserCriteriaEF(IEnumerable<string> filterValuesForUser)
{
// From the resource's filter values, check if there are any present in the user's filter values
return (x) => x.ResourceFilterValues.Any(y => filterValuesForUser.Contains(y.FilterValue.FilterValue1));
}
I'm still having issues with getting this working in my repository but that has something do with something blocking AsExpandable() from working properly.
Let say I have a function like this:
var filterValue = GetCurrentFilter(state);
And then an EF query:
var result = context.EntitySet.Where(x=> x.column > filterValue);
this works, but as soon as I try to inline that:
var result = context.EntitySet.Where(x=> x.column > GetCurrentFilter(state));
It does not because EF Linq tried to parse GetCurrentFilter into expression tree and is unable to do that. This is all quite understandable.
My question is, is there a way to let EF Linq know that in needs to execute the GetCurrentFilter function when it builds the tree and use its result in the tree?
Something like
var result = context.EntitySet.Where(x=> x.column > EfUtil.ResultOf(GetCurrentFilter(state)));
Since GetCurrentFilter does not have parameters that is a part of the query this should be technically possible to do that if EF Linq can support it that is. I'm suspecting that I'm just missing the correct syntax for that.
Make GetCurrentFilter a (read only) property instead of a method. EF will evaluate properties to their values, rather than trying to translate them into SQL, unlike methods.
The only other road that you have is to traverse the entire expression tree, search for usage of your ResultOf method, evaluate its parameter to a value, and then inline that value where the ResultOf call once was, rebuiding the query around that value.
In order for this to work it means you need to not only wrap the code you want to inline in a call to EfUtil.ResultOf, but it also means calling a method on the query itself to force it to go back and evaluate it:
public class EfUtil
{
public static T ResultOf<T>(T value)
{
return value;
}
}
//Note this could probably use a better name
public static IQueryable<T> EvaluateResults<T>(this IQueryable<T> query)
{
return query.Provider.CreateQuery<T>(
new ExpressionEvaluator().Visit(query.Expression));
}
internal class ExpressionEvaluator : ExpressionVisitor
{
protected override Expression VisitMethodCall(MethodCallExpression m)
{
if (m.Method.Name == "ResultOf" && m.Method.DeclaringType == typeof(EfUtil))
{
Expression target = m.Arguments[0];
object result = Expression.Lambda(target)
.Compile()
.DynamicInvoke();
return Expression.Constant(result, target.Type);
}
else
return base.VisitMethodCall(m);
}
}
This would allow you to write:
var result = context.EntitySet.Where(x=> x.column > EfUtil.ResultOf(GetCurrentFilter(state)))
.EvaluateResults();
It would then evaluate GetCurrentFilter(state) on the client side and inline the result as a constant into the query.
As a slightly simpler test, we can write the following:
var query = new[] { 1, 2, 3 }
.AsQueryable()
.Where(x => x > EfUtil.ResultOf(Math.Max(1, 2)))
.EvaluateResults();
Console.WriteLine(query.ToString());
And it will print out:
System.Int32[].Where(x => (x > 2))
Which is exactly what we want.
Note that the use of the lambda's parameter (x in these examples) cannot be used anywhere within the call to EfUtil.ResultOf or the code won't work, and couldn't possibly be made to work (although we could generate a better error message if we cared enough).
I have a feeling I already know the answer to this question.
I have an expression that common but complex, and I would like to re-use the code in multiple locations. I would like to use a function that returns a Func with some code:
public static Func<MyClass, bool> GetCheck()
{
return (x) => x.Value > 10;
}
Seems easy. The problem that I'm having is when I apply it to a LINQ expression that's used in LINQ To Entities. I get an error that says Invoke is not supported. And I understand why. What I don't know is if there's a way that I can get around this. I would like to say...
var check = GetCheck();
IQueryable<MyClass> results = MyClasses.Where(y => check(y));
... and have the expression tree inspector realize that everything that happens in the Func is perfectly legal both in LINQ To Entities and on the DB. It seems like it should be inline'd.
Is there anything I can do to make this happen? Any form of declaration for the delegate that will allow this?
Since you are querying an IQueryable<> then you should try this:
public static Expression<Func<MyClass, bool>> GetCheck()
{
return (x) => x.Value > 10;
}
I'm having some trouble understanding the differences between how Expressions and Funcs work.
This problem turned up when someone changed a method signature from:
public static List<Thing> ThingList(Func<Thing, bool> aWhere)
To
public static List<Thing> ThingList(Expression<Func<Thing, bool>> aWhere)
Which broke my calling code. The old calling code (which worked) looked like this:
...
object y = new object();
Func<Thing, bool> whereFunc = (p) => p == y;
things = ThingManager.ThingList(whereFunc);
The new code (which doesn't work) looks like this:
...
object x = new object();
Expression<Func<Thing, bool>> whereExpr = (p) => p == x;
things = ThingManager.ThingList(whereExpr);
This fails inside ThingList(...) on the line utilizing the expression:
var query = (from t in context.Things.Where(aWhere)
...
With the runtime error:
Unable to create a constant value of type 'System.Object'. Only primitive types ('such as Int32, String, and Guid') are supported in this context.
This example is contrived, but my guess is it has something to do with the local object variable x not being properly "copied" into the expression.
Can someone explain how to handle this situation in general, and why the Func works but the Expression doesn't?
The reason for the change almost certainly was to "push" the evaluation of your predicate into the underlying store, which backs your context. Instead of bringing all Things into memory and then using Func<Thing,bool> to decide which ones to keep, the author of the changed API decided to use IQueryable, and needed an Expression<Func<Thing,bool>> for that.
You are correct on the origin of the error: unlike in-memory predicates, IQueryable cannot use objects that it does not know, e.g. arbitrary instances of object.
What you need to do is to change the expression to avoid referencing objects of data types not supported by your target data store (I assume the expression eventually makes its way into either an Entity Framework or a Linq2Sql context). For example, instead of saying
object x = new object();
Expression<Func<Thing, bool>> whereExpr = (p) => p == x;
things = ThingManager.ThingList(whereExpr);
you should say
Thing x = new Thing {id = 123};
Expression<Func<Thing, bool>> whereExpr = (p) => p.id == x.id;
things = ThingManager.ThingList(whereExpr);
(your backing store almost certainly understands integers)
The difference between Expression and Func is better described in the answers here: Difference between Expression<Func<>> and Func<>
A quick workaround to make this work again would be to compile the expression back into a Func.
var query = (from t in context.Things.Where(aWhere.Compile())
I am trying to write shared layout of queries which share similar restrictions and result set with exception of score property in report. I am passing in score property as lambda expression and I am able to use it in select but Where() is giving me grief.
Any ideas on make Where() work with expression func
OR how to write Expression acceptable to Where() using another expression func which points to property to be compared?
private void SomeMethod(Expression<Func<TDischargeResult, object>> field)
{
TBenchmark benchmark = null;
AnalyticsReport report = null;
IEnumerable<AnalyticsReport> reports = _session.QueryOver<TDischargeResult>()
.JoinAlias(record => record.Benchmark, () => benchmark)
//.Where(field > benchmark.Average) // compare score property to average
.SelectList(select =>
{
select.SelectAvg(field).WithAlias(() => report.Score);
return select;
}).Clone()
.TransformUsing(Transformers.AliasToBean<AnalyticsReport>())
.List<AnalyticsReport>();
}
I can suggest an alternate approach:
_session.QueryOver<TDischargeResult>()
.JoinQueryOver(x=>x.Benchmark)
.Where(x=>field>x.Average)
And rest of your code.