3

问候溢出者,

我正在开发一个允许用户生成自定义报告的应用程序,并且我有一个场景,我需要从枚举值列表中生成 Linq Or 子句。我遇到的问题是我看不到生成 Or 子句的优雅方式。

例如:

//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;
    }
}

有任何想法吗?

4

4 回答 4

6

你应该做Conditions一个Flags枚举:

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

并更改您的 ResultObject:

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

然后你可以说:

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

您要解决的问题的正确设计。

如果由于某种原因您需要保留 current ,为了清楚起见ResultObject,我现在将调用它:OldResultObject

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

很容易将其投影到新的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;
}

所以这真的是你重新设计一点努力。

于 2013-07-31T13:21:18.543 回答
2

如果您不想更改您拥有的代码,您也可以使用 Union :

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;
}
于 2013-07-31T13:24:41.860 回答
1

这是使用表达式树的解决方案:

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

    var pe = Expression.Parameter(typeof(ResultObject), "c");
    Expression builder = GetProperty(pe, conditions.First());

    foreach(var condition in conditions.Skip(1))
    {
        var property = GetProperty(pe, condition);
        builder = Expression.OrElse(builder, property);
    }

    var predicate = Expression.Lambda(builder, pe);

    return query.Where((Func<ResultObject, bool>)predicate.Compile()).AsQueryable();
}

private static MemberExpression GetProperty(ParameterExpression pe,
                                            Conditions condition)
{
    MemberExpression property;

    switch (condition)
    {
        case Conditions.ByAlpha:
            property = Expression.Property(pe, "AlphaValue");
            break;
        case Conditions.ByBeta:
            property = Expression.Property(pe, "BetaValue");
            break;
        case Conditions.ByGamma:
            property = Expression.Property(pe, "GammaValue");
            break;
        default:
            throw new ArgumentOutOfRangeException();
    }

    return property;
}

我建议使用FlagsJason 的解决方案。

以下是生成的谓词示例:

GenerateOrClause(Enumerable.Empty<ResultObject>().AsQueryable(),
                 new List<Conditions> { Conditions.ByAlpha });
// (c.AlphaValue)

GenerateOrClause(Enumerable.Empty<ResultObject>().AsQueryable(),
                 new List<Conditions> { Conditions.ByAlpha, Conditions.ByBeta });
// (c.AlphaValue OrElse c.BetaValue)

GenerateOrClause(Enumerable.Empty<ResultObject>().AsQueryable(),
                 new List<Conditions> { Conditions.ByAlpha, Conditions.ByBeta, Conditions.ByGamma });
// ((c.AlphaValue OrElse c.BetaValue) OrElse c.GammaValue)
于 2013-07-31T13:50:28.540 回答
0

.Where 是 IEnumerable<T> 上的扩展方法,它采用 Func 类型的谓词。为什么不使用 x => x.AlphaValue 之类的谓词创建一个 List < Func < T,bool >> `,当你有这个列表时,迭代它并在你的 IEnumerable 上的 .Where 中传递值?

希望我能告诉你我的想法......

于 2013-07-31T13:19:51.387 回答