私はあなたの式ジェネレーターを修正することができ、あなたの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]
( put in par1
) に注意してください (そうしないと、式リライターを使用する必要がありました)。
このトリックを使用できたのは、処理する式が 1 つしかなく、それに新しいコードを追加する必要があったからです。まったく異なる (式リライターが必要だった) のは、2 つの式を結合しようとする場合です (たとえば、 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);
}
}
}
- それはどのように機能しますか?魔法、反射、妖精の粉…
- 他のプロパティを参照するプロパティはサポートされていますか? はい
- 何が必要ですか?
名前のすべての「特別な」プロパティには、名前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: 1 つの問題は、 を返さない 、 などのメソッドSingle(Expression)
にありFirst(Expression)
ます。最初の a を使用して変更するAny(Expression)
IQueryable
Where(Expression).Expand().Single()
制限 2: 「特別な」プロパティは、サイクル内で自分自身を参照できません。したがって、A が B を使用する場合、B は A を使用できず、三項式を使用するようなトリックは機能しません。