我可以修复你的表达式生成器,我可以编写你的GetDisplayNameExpression
(所以1和3)
public class Member
{
public string ScreenName { get; set; }
public string Name { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public static Expression<Func<Member, string>> GetRealNameExpression()
{
return m => (m.Name + " " + m.LastName).TrimEnd();
}
public static Expression<Func<Member, string>> GetDisplayNameExpression()
{
var isNullOrEmpty = typeof(string).GetMethod("IsNullOrEmpty", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(string) }, null);
var e0 = GetRealNameExpression();
var par1 = e0.Parameters[0];
// Done in this way, refactoring will correctly rename m.ScreenName
// We could have used a similar trick for string.IsNullOrEmpty,
// but it would have been useless, because its name and signature won't
// ever change.
Expression<Func<Member, string>> e1 = m => m.ScreenName;
var screenName = (MemberExpression)e1.Body;
var prop = Expression.Property(par1, (PropertyInfo)screenName.Member);
var condition = Expression.Condition(Expression.Call(null, isNullOrEmpty, prop), e0.Body, prop);
var combinedExpression = Expression.Lambda<Func<Member, string>>(condition, par1);
return combinedExpression;
}
private static readonly Func<Member, string> GetDisplayNameExpressionCompiled = GetDisplayNameExpression().Compile();
private static readonly Func<Member, string> GetRealNameExpressionCompiled = GetRealNameExpression().Compile();
public string DisplayName
{
get
{
return GetDisplayNameExpressionCompiled(this);
}
}
public string RealName
{
get
{
return GetRealNameExpressionCompiled(this);
}
}
public static Expression<Func<Member, bool>> FilterMemberByDisplayNameExpression(string displayName)
{
var e0 = GetDisplayNameExpression();
var par1 = e0.Parameters[0];
var combinedExpression = Expression.Lambda<Func<Member, bool>>(
Expression.Equal(e0.Body, Expression.Constant(displayName)), par1);
return combinedExpression;
}
请注意我如何重用GetDisplayNameExpression
表达式的相同参数e1.Parameters[0]
(放入par1
),这样我就不必重写表达式(否则我需要使用表达式重写器)。
我们可以使用这个技巧,因为我们只有一个表达式要处理,我们必须附加一些新代码。完全不同(我们需要一个表达式重写器)将是尝试组合两个表达式的情况(例如做 a GetRealNameExpression() + " " + GetDisplayNameExpression()
,都需要作为参数 a Member
,但它们的参数是分开的......可能这个https://stackoverflow .com/a/5431309/613130可以工作...
对于2,我认为没有任何问题。您正确使用static readonly
. 但是,请看看GetDisplayNameExpression
并思考“一些业务代码重复支付更好还是那个更好?”
通用解决方案
现在......我很确定它是可行的......事实上它是可行的:一个表达式“扩展器”,它“自动”“扩展”“特殊属性”到它们的表达式。
public static class QueryableEx
{
private static readonly ConcurrentDictionary<Type, Dictionary<PropertyInfo, LambdaExpression>> expressions = new ConcurrentDictionary<Type, Dictionary<PropertyInfo, LambdaExpression>>();
public static IQueryable<T> Expand<T>(this IQueryable<T> query)
{
var visitor = new QueryableVisitor();
Expression expression2 = visitor.Visit(query.Expression);
return query.Expression != expression2 ? query.Provider.CreateQuery<T>(expression2) : query;
}
private static Dictionary<PropertyInfo, LambdaExpression> Get(Type type)
{
Dictionary<PropertyInfo, LambdaExpression> dict;
if (expressions.TryGetValue(type, out dict))
{
return dict;
}
var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
dict = new Dictionary<PropertyInfo, LambdaExpression>();
foreach (var prop in props)
{
var exp = type.GetMember(prop.Name + "Expression", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static).Where(p => p.MemberType == MemberTypes.Field || p.MemberType == MemberTypes.Property).SingleOrDefault();
if (exp == null)
{
continue;
}
if (!typeof(LambdaExpression).IsAssignableFrom(exp.MemberType == MemberTypes.Field ? ((FieldInfo)exp).FieldType : ((PropertyInfo)exp).PropertyType))
{
continue;
}
var lambda = (LambdaExpression)(exp.MemberType == MemberTypes.Field ? ((FieldInfo)exp).GetValue(null) : ((PropertyInfo)exp).GetValue(null, null));
if (prop.PropertyType != lambda.ReturnType)
{
throw new Exception(string.Format("Mismatched return type of Expression of {0}.{1}, {0}.{2}", type.Name, prop.Name, exp.Name));
}
dict[prop] = lambda;
}
// We try to save some memory, removing empty dictionaries
if (dict.Count == 0)
{
dict = null;
}
// There is no problem if multiple threads generate their "versions"
// of the dict at the same time. They are all equivalent, so the worst
// case is that some CPU cycles are wasted.
dict = expressions.GetOrAdd(type, dict);
return dict;
}
private class SingleParameterReplacer : ExpressionVisitor
{
public readonly ParameterExpression From;
public readonly Expression To;
public SingleParameterReplacer(ParameterExpression from, Expression to)
{
this.From = from;
this.To = to;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return node != this.From ? base.VisitParameter(node) : this.Visit(this.To);
}
}
private class QueryableVisitor : ExpressionVisitor
{
protected static readonly Assembly MsCorLib = typeof(int).Assembly;
protected static readonly Assembly Core = typeof(IQueryable).Assembly;
// Used to check for recursion
protected readonly List<MemberInfo> MembersBeingVisited = new List<MemberInfo>();
protected override Expression VisitMember(MemberExpression node)
{
var declaringType = node.Member.DeclaringType;
var assembly = declaringType.Assembly;
if (assembly != MsCorLib && assembly != Core && node.Member.MemberType == MemberTypes.Property)
{
var dict = QueryableEx.Get(declaringType);
LambdaExpression lambda;
if (dict != null && dict.TryGetValue((PropertyInfo)node.Member, out lambda))
{
// Anti recursion check
if (this.MembersBeingVisited.Contains(node.Member))
{
throw new Exception(string.Format("Recursively visited member. Chain: {0}", string.Join("->", this.MembersBeingVisited.Concat(new[] { node.Member }).Select(p => p.DeclaringType.Name + "." + p.Name))));
}
this.MembersBeingVisited.Add(node.Member);
// Replace the parameters of the expression with "our" reference
var body = new SingleParameterReplacer(lambda.Parameters[0], node.Expression).Visit(lambda.Body);
Expression exp = this.Visit(body);
this.MembersBeingVisited.RemoveAt(this.MembersBeingVisited.Count - 1);
return exp;
}
}
return base.VisitMember(node);
}
}
}
- 它是如何工作的?魔法、倒影、仙尘……
- 它是否支持引用其他属性的属性?是的
- 它需要什么?
它需要 name 的每个“特殊”属性Foo
都有一个相应的静态字段/静态属性,命名为FooExpression
返回一个Expression<Func<Class, something>>
Expand()
它需要在具体化/枚举之前的某个时刻通过扩展方法“转换”查询。所以:
public class Member
{
// can be private/protected/internal
public static readonly Expression<Func<Member, string>> RealNameExpression =
m => (m.Name + " " + m.LastName).TrimEnd();
// Here we are referencing another "special" property, and it just works!
public static readonly Expression<Func<Member, string>> DisplayNameExpression =
m => string.IsNullOrEmpty(m.ScreenName) ? m.RealName : m.ScreenName;
public string RealName
{
get
{
// return the real name however you want, probably reusing
// the expression through a compiled readonly
// RealNameExpressionCompiled as you had done
}
}
public string DisplayName
{
get
{
}
}
}
// Note the use of .Expand();
var res = (from p in ctx.Member
where p.RealName == "Something" || p.RealName.Contains("Anything") ||
p.DisplayName == "Foo"
select new { p.RealName, p.DisplayName, p.Name }).Expand();
// now you can use res normally.
限制 1:一个问题是使用 , 和类似的方法Single(Expression)
,First(Expression)
它们Any(Expression)
不返回IQueryable
. 通过使用第一个更改Where(Expression).Expand().Single()
限制 2:“特殊”属性不能在循环中引用自己。因此,如果 A 使用 B,B 就不能使用 A,并且使用三元表达式之类的技巧不会使它起作用。