假设我有以下课程:
public class Show
{
public string Language { get; set; }
public string Name { get; set; }
}
根据这些信息,我的目标是创建一个像这样的lambda表达式:
g => g.Language == lang && g.Name == name
lang
和name
是我想在创建表达式时添加为常量值的局部变量。
如您所见,编译函数的类型为Func<Genre, bool>
为了帮助您更清楚地理解,我想实现类似的目标:
string lang = "en";
string name = "comedy";
Genre genre = new Genre { Language = "en", Name = "comedy" };
Expression<Func<Genre, bool>> expression = CreateExpression(genre, lang, name);
// expression = (g => g.Language == "en" && g.Name == "comedy")
我知道表达树的存在,但我对这个主题很新,所以我甚至不知道如何开始。
这个问题可以解决吗?如何动态创建这样的表达式?
答案 0 :(得分:1)
public Expression<Func<TValue, bool>> CreateExpression<TValue, TCompare>(TValue value, TCompare compare)
{
var pv = Expression.Parameter(typeof(TValue), "data");
var compareProps = typeof(TCompare).GetProperties();
// First statement of the expression
Expression exp = Expression.Constant(true);
foreach (var prop in typeof(TValue).GetProperties())
{
// Check if the compare type has the same property
if (!compareProps.Any(i => i.Name == prop.Name))
continue;
// Build the expression: value.PropertyA == "A"
// which "A" come from compare.PropertyA
var eq = Expression.Equal(
Expression.Property(pv, prop.Name),
Expression.Constant(compareProps
.Single(i => i.Name == prop.Name)
.GetValue(compare)));
// Append with the first (previous) statement
exp = Expression.AndAlso(exp, eq);
}
return Expression.Lambda<Func<TValue, bool>>(exp, pv);
}
用法:
var value = new { Lang = "en", Name = "comedy"};
// The compareValue should have the same property name as the value,
// or the expression will just ignore the property
var compareValue = new { Lang = "en", Name = "comedy", Other = "xyz" };
// The create expression content is
// {data => ((True AndAlso (data.Lang == "en")) AndAlso (data.Name == "comedy"))}
bool isMatch = CreateExpression(value, compareValue).Compile()(value); // true
答案 1 :(得分:0)
您可以使用接口来为已定义必要属性的接口创建Func。
示例:
interface IExpressionable {
string Language { get;set; }
string Name { get;set; }
}
class Genre : IExpressionable {
string Language {get;set;}
string Name {get;set;}
}
Genre genre = new Genre();
Expression<Func<IExpressionable, bool>> expression = CreateExpression(genre, lang, name);
expression = (g => g.Language == "en" && g.Name == "comedy")
可以通过在接口上提供GetLanguage和GetName方法来实现此概念的替代实现,这将强制订阅者实现基础方法,以便根据自己的内部方法返回“语言”和“名称”
interface IExpressionable {
string GetExpressionOne();
string GetExpressionTwo();
}
class Genre : IExpressionable {
string Language {get;set;}
string Name {get;set;}
public string GetExpressionOne() {
return Language;
}
public string GetExpressionOne() {
return Name;
}
}
class SomethingElse {
string Orange {get;set;}
string BananaPeel {get;set;}
public string GetExpressionOne() {
return Orange;
}
public string GetExpressionOne() {
return BananaPeel;
}
}
Genre genre = new Genre();
SomethingElse else = new SomethingElse();
Expression<Func<IExpressionable, bool>> expression = CreateExpression(genre, lang, name);
Expression<Func<IExpressionable, bool>> expression2 = CreateExpression(else, lang, name);
expression = (g => g.GetExpressionOne() == "en" && g.GetExpressionTwo() == "comedy");
根据上面的评论进行修改:@kaveman I only have the key values, but I can fetch the key properties via reflection using some custom attributes that I defined. In this example, Language and Name would be decorated with an attribute that defines them as key properties
我的回答是完全避免这种想法。创建一个接口,定义执行所需表达式所需的方法,并让实体从中继承。您可以让接口上的方法为“GetKeyOne”和“GetKeyTwo”。然后你的表达式不必是动态的,你只需要定义表达式在与每个实现者中定义的KeyOne和KeyTwo交互时的作用。
答案 2 :(得分:0)
假设您有兴趣在此处理的每个实体的每个属性都有属性(请参阅comments),并且您有类型参数(称之为T
)a CreateExpression
的可能签名是:
Expression<Func<T, bool>> CreateExpression(T entity, IOrderedDictionary<string, string> propertyTests);
通过这种方式,您的CreateExpression
可以使用已知的泛型类型,并且调用者可以指定针对实体属性进行的测试。这里我们假设key
是要反映的属性名称(确保它具有已知属性),而value
是所述属性的必需值。 IOrderedDictionary
是一种强制&&
测试短路的方法,如果这是你的事情。
您可以T
添加其他约束(例如T
必须实现某些界面)generic type constraints using where
答案 3 :(得分:0)
这样做,并且应该使它对用户友好构建
private Expression<Func<Genre,bool>> CreateExpression(params Expression<Func<Object>>[] selectors)
{
//We are working on a Genre type, and make a parameter of it
//Query so far looks like g =>
var param = Expression.Parameter(typeof(Genre),"g");
//Set the base expression to make it easy to build
//Query so far looks like g => true
Expression expression = Expression.Constant(true);
foreach(var selector in selectors) {
//Find out the name of the variable was passed
var selectorname = TestExtension.nameof(selector);
//Get the value
var selectorValue = selector.Compile()();
//Create an accessor to the genre (g.Language for example)
var accessMethod = Expression.PropertyOrField(param, selectorname);
//Check if it equals the value (g.Language == "en")
var equalExpr = Expression.Equal(accessMethod, Expression.Constant(selectorValue));
//Make it an And expression
//Query so far looks like g => true && g.Language == "en"
expression = Expression.AndAlso(expression, equalExpr);
//Second pass through the loop would build:
//g => true && g.Language == "en" && g.Name == "comedy"
}
//Turn it into a lambda func and cast it
var result = Expression.Lambda(expression, param) as Expression<Func<Genre, bool>>;
return result;
}
public class Genre
{
public string Language { get; set; }
public string Name { get; set; }
}
//Taken from my answer at http://stackoverflow.com/a/31262225/563532
public static class TestExtension
{
public static String nameof<T>(Expression<Func<T>> accessor)
{
return nameof(accessor.Body);
}
public static String nameof<T, TT>(this T obj, Expression<Func<T, TT>> propertyAccessor)
{
return nameof(propertyAccessor.Body);
}
private static String nameof(Expression expression)
{
if (expression.NodeType == ExpressionType.MemberAccess)
{
var memberExpression = expression as MemberExpression;
if (memberExpression == null)
return null;
return memberExpression.Member.Name;
}
return null;
}
}
然后你就这样使用它:
string language = "en";
string name = "comedy";
var genre = new Genre { Language = "en", Name="comedy" };
var query = CreateExpression(() => language, () => name);
请注意,变量必须与属性名称匹配。否则,您将需要某种映射[lang =&gt;语言]等。
评估它:
var matches = query.Compile()(genre);
或者,您可以将其传递给EF,例如:
dtx.Genre.Where(query);