Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generate Linq Or Clause from List of Enumeration

Tags:

c#

linq

Greetings Overflowers,

I am working on an application that allows for a user to generate a custom report and I have a scenario where I need to generate a Linq Or clause from a list of enumeration values. The problem I'm having is I cannot see an elegant way of generating the Or clause.

For example:

//Enumeration of possible 'OR' conditions
public enum Conditions
{
    ByAlpha,
    ByBeta,
    ByGamma
}

//'Entity' I'm querying against.
class ResultObject
{
    public bool AlphaValue { get; set; }
    public bool BetaValue { get; set; }
    public bool GammaValue { get; set; }
    public string Name { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        //Create list of desired conditions.  
        //Basically I want this to mimic the query, 
        // "Show me all of the ResultObjects where the AlphaValue is true or the GammaValue is true".
        var conditions = new List<Conditions>
        {
            Conditions.ByAlpha,
            Conditions.ByGamma
        };

        //Sample collection of objects.  This would normally be a collection of EF entities. 
        var sampleCollection = new List<ResultObject>
        {
            new ResultObject
            {
                Name = "Sample 1",
                AlphaValue = true,
                BetaValue = true,
                GammaValue = true,
            },
            new ResultObject
            {
                Name = "Sample 2",
                AlphaValue = false,
                BetaValue = false,
                GammaValue = false,
            },
            new ResultObject
            {
                Name = "Sample 3",
                AlphaValue = true,
                BetaValue = false,
                GammaValue = true,
            }
        };

        var sampleCollectionQueryable = sampleCollection.AsQueryable();

        //This should filter the sampleCollection down to containing only the 
        //"Sample 3" ResultObject; instead, it filters out all of the ResultObjects.
        var query = GenerateOrClause(sampleCollectionQueryable, conditions);
    }

    static IQueryable<ResultObject> GenerateOrClause(IQueryable<ResultObject> query, List<Conditions> conditions)
    {
        //This approach generates a series of AND statements, instead I need a series of OR statements
        //for each condition.
        foreach (var condition in conditions)
        {
            switch (condition)
            {
                case Conditions.ByAlpha:
                    query = query.Where(x => x.AlphaValue);
                    break;
                case Conditions.ByBeta:
                    query = query.Where(x => x.BetaValue);
                    break;
                case Conditions.ByGamma:
                    query = query.Where(x => x.GammaValue);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        return query;
    }
}

Any ideas?

like image 372
Garry Avatar asked Feb 04 '26 01:02

Garry


2 Answers

You should make Conditions a Flags enum:

[Flags]
public enum Conditions {
    ByNone = 0,
    ByAlpha = 1,
    ByBeta = 2,
    ByGamma = 4
}

and change your ResultObject:

class ResultObject {
    public Conditions Conditions { get; set; }
    public string Name { get; set; }
}

and then you can say:

var conditions = new List<Conditions> { Conditions.ByAlpha, Conditions.ByGamma };
var matches = sampleCollection
                  .Where(x => conditions.Select(c => c & x != 0).Any());

This is the right design for the problem that you're trying to solve.

If for some reason you need to keep your current ResultObject, which I will now call OldResultObject for the sake of clarity:

class OldResultObject {
    public bool AlphaValue { get; set; }
    public bool BetaValue { get; set; }
    public bool GammaValue { get; set; }
    public string Name { get; set; }
}

it's easy to project that to a new ResultObject:

var resultObject = new ResultObject {
    Conditions =
        (oldResultObject.AlphaValue ? Conditions.ByAlpha : Conditions.ByNone) | 
        (oldResultObject.BetaValue ? Conditions.ByBeta : Conditions.ByNone) |
        (oldResultObject.GammaValue ? Conditions.ByGamma : Conditions.ByNone),
    Name = oldResult.Name;
}

so this is really very little effort on your part to redesign at all.

like image 133
jason Avatar answered Feb 05 '26 13:02

jason


You could also probably use a Union if you don't want to have to change the code you have very much:

static IQueryable<ResultObject> GenerateOrClause(IQueryable<ResultObject> query, List<Conditions> conditions)
{
    if( conditions.Count == 0 )
        return query;

    var resultQuery = new List<ResultObject>().AsQueryable();

    foreach (var condition in conditions)
    {
        switch (condition)
        {
            case Conditions.ByAlpha:
                resultQuery = resultQuery.Union(query.Where(x => x.AlphaValue));
                break;
            case Conditions.ByBeta:
                resultQuery = resultQuery.Union(query.Where(x => x.BetaValue));
                break;
            case Conditions.ByGamma:
                resultQuery = resultQuery.Union(query.Where(x => x.GammaValue));
                break;
            default:
                throw new ArgumentOutOfRangeException();
        }
    }

    return resultQuery;
}
like image 23
Sterno Avatar answered Feb 05 '26 13:02

Sterno



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!