问候Overflowers,
我正在开发一个允许用户生成自定义报告的应用程序,我有一个场景,我需要从枚举值列表中生成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;
}
}
有什么想法吗?
答案 0 :(得分: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());
此 是您尝试解决的问题的正确设计。
如果出于某种原因,您需要保留当前的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;
}
所以这真的是非常你根本不需要重新设计。
答案 1 :(得分:2)
如果您不想更改自己拥有的代码,也可以使用联盟:
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;
}
答案 2 :(得分: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;
}
我建议使用Jason的Flags
解决方案。
以下是生成谓词的示例:
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)
答案 3 :(得分:0)
。在IEnumerable上有一个扩展方法&lt; T&gt;,它采用Func类型的谓词。为什么不创建一个List&lt; Func&lt; T,bool&gt; &GT; `用你的谓词如x =&gt; x.AlphaValue,当你有这个列表时,迭代它并在IEnumerable的.Where中传递值?
希望我能告诉你我的想法......