Logical Inverse of a Func<T, bool>
Asked Answered
S

2

6

I have some fairly complex Entity Framework queries throughout my codebase and I decided to centralize the logic into the models. Basically, picture a bunch of controllers with large queries and lots of repeated code in their expression trees. So I took out parts of those expression trees and moved them to the models, allowing for less repetition.

For example, let's say I often need to fetch models called Entity which are in a state of Not Deleted. On my Entity model I have:

public static Func<Entity, bool> IsNotDeleted = e =>
    e.Versions != null ?
        e.Versions.OrderByDescending(v => v.VersionDate).FirstOrDefault() != null ?
            e.Versions.OrderByDescending(v => v.VersionDate).First().ActivityType != VersionActivityType.Delete :
            false :
       false;

(This is one of the smaller examples, mostly just checking for valid data before trying to examine that data.)

And using it would look like:

var entities = EntityRepository.Entities.Where(Entity.IsNotDeleted).Where(...

I'm finding, however, that while sometimes I want records which are not deleted, other times I want records which are deleted. To do that, is there a way to invert the logic from the consuming code? Something conceptually akin to this (which obviously doesn't work):

var entities = EntityRepository.Entities.Where(!Entity.IsDeleted).Where(...

I'd prefer not to have two Func<>s on the object, one for IsDeleted and one for IsNotDeleted which are nearly identical. The Func<> returns a bool, is there a syntax to call the inverse of it when putting it in a .Where() clause?

Submediant answered 19/6, 2014 at 21:10 Comment(2)
Are you aware, that having that variable declared as Func<T, TResult> makes the query run as LINQ to Objects, not LINQ to Entities? You shouldn't do that.Peristome
@Marcin: Ah, I hadn't even noticed that. We're working with such small data sets thus far that materializing them hasn't made a difference. But it certainly will at some point. Thanks!Submediant
A
15

Consider the following extension methods.

public static class Functional
{
    public static Func<T, bool> Not<T>(this Func<T, bool> f)
    {
        return x => !f(x);
    }

    public static Expression<Func<T, bool>> Not<T>(
        this Expression<Func<T, bool>> f)
    {
        // 1. Break the lambda f apart into its parameters and body.
        // 2. Wrap the body expression with a unary not expression (!).
        // 3. Construct a new lambda with the modified body.
        return Expression.Lambda<Func<T, bool>>(
            Expression.Not(f.Body), f.Parameters);
    }
}

Entity.IsDeleted.Not() is the same as Entity.IsNotDeleted().

Note you probably want to be using Expression<Func<T, bool>> - not Func<T, bool> - so that your lambda logic can be used database-side rather than client-side.

You can use it like this:

Expression<Func<int, bool>> isNegative = x => x < 0;
Expression<Func<int, bool>> isNonNegative = isNegative.Not();
Arequipa answered 19/6, 2014 at 21:15 Comment(1)
Perfect, that should do the trick nicely. And get me in more of the right way of thinking for the rest of these expression trees. Thanks!Submediant
A
1

You don't need full blown functional lambda decalration. Go the Church-Turing way - reccursion for the IsNotDeleted definition:

public static Func<Entity, bool> IsNotDeleted = e => !IsDeleted(e);

Answer above is even more "Church-Turing-y" :)

Agna answered 19/6, 2014 at 21:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.