2

注: 長い投稿です。一番下までスクロールして質問を確認してください。問題が理解しやすくなることを願っています。ありがとう!


次のように定義された「メンバー」モデルがあります。

public class Member
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string ScreenName { get; set; }

    [NotMapped]
    public string RealName
    {
         get { return (FirstName + " " + LastName).TrimEnd(); }
    }

    [NotMapped]
    public string DisplayName
    {
        get
        {
            return string.IsNullOrEmpty(ScreenName) ? RealName : ScreenName;
        }
    }
}

これは既存のプロジェクトとモデルであり、これを変更したくありません。ここで、 DisplayNameによるプロファイルの取得を有効にするリクエストを受け取りました。

public Member GetMemberByDisplayName(string displayName)
{
     var member = this.memberRepository
                      .FirstOrDefault(m => m.DisplayName == displayName);
     return member;
}

DisplayNameがデータベースのフィールドにマップされていないため、このコードは機能しません。さて、私は式を作ります:

public Member GetMemberByDisplayName(string displayName)
{
     Expression<Func<Member, bool>> displayNameSearchExpr = m => (
                string.IsNullOrEmpty(m.ScreenName) 
                    ? (m.Name + " " + m.LastName).TrimEnd() 
                    : m.ScreenName
            ) == displayName;

     var member = this.memberRepository
                      .FirstOrDefault(displayNameSearchExpr);

     return member;
}

これは機能します。唯一の問題は、表示名を生成するビジネス ロジックが 2 つの異なる場所にコピー アンド ペーストされていることです。これは避けたい。しかし、これを行う方法がわかりません。私が持ってきた最高のものは次のとおりです。

  public class Member
    {

        public static Expression<Func<Member, string>> GetDisplayNameExpression()
        {
            return m => (
                            string.IsNullOrEmpty(m.ScreenName)
                                ? (m.Name + " " + m.LastName).TrimEnd()
                                : m.ScreenName
                        );
        }

        public static Expression<Func<Member, bool>> FilterMemberByDisplayNameExpression(string displayName)
        {
            return m => (
                string.IsNullOrEmpty(m.ScreenName)
                    ? (m.Name + " " + m.LastName).TrimEnd()
                    : m.ScreenName
            ) == displayName;
        }

        private static readonly Func<Member, string> GetDisplayNameExpressionCompiled = GetDisplayNameExpression().Compile();

        [NotMapped]
        public string DisplayName
        {
            get
            {
                return GetDisplayNameExpressionCompiled(this);
            }
        }

        [NotMapped]
        public string RealName
        {
             get { return (FirstName + " " + LastName).TrimEnd(); }
        }

   }

質問:

(1) FilterMemberByDisplayNameExpression ( ) 内でGetDisplayNameExpression ()を再利用する方法は? 私はExpression.Invokeを試しました:

public static Expression<Func<Member, bool>> FilterMemberByDisplayNameExpression(string displayName)
{
    Expression<Func<string, bool>> e0 = s => s == displayName;
    var e1 = GetDisplayNameExpression();

    var combinedExpression = Expression.Lambda<Func<Member, bool>>(
           Expression.Invoke(e0, e1.Body), e1.Parameters);

    return combinedExpression;
}

しかし、プロバイダーから次のエラーが表示されます。

LINQ 式ノード タイプ 'Invoke' は、LINQ to Entities ではサポートされていません。

(2) DisplayNameプロパティ内でExpression.Compile()を使用するのは良い方法ですか? 何か問題はありますか?

(3) RealNameロジックをGetDisplayNameExpression ()内に移動する方法は? 別の式と別のコンパイル済み式を作成する必要があると思いますが、GetDisplayNameExpression ()内からRealNameExpressionを呼び出す方法がわかりません。

ありがとうございました。

4

2 に答える 2

2

私はあなたの式ジェネレーターを修正することができ、あなたのGetDisplayNameExpression(つまり13 )

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付きの対応する静的フィールド/静的プロパティが必要ですFooExpressionExpression<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)IQueryableWhere(Expression).Expand().Single()

  • 制限 2: 「特別な」プロパティは、サイクル内で自分自身を参照できません。したがって、A が B を使用する場合、B は A を使用できず、三項式を使用するようなトリックは機能しません。

于 2013-08-28T13:04:17.777 に答える