質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.78%

ラムダ式内でリフレクションを使うには?

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 1,914

score 13

いつもお世話になっております。
いつも回答して頂き助かっています

回答を頂いて無事解決しました。
最終的なコードは1番下に載せています

開発環境は以下の通りです
VS2017
NETFramework,Version=v4.5.21

下記のラムダ内のn => n.Furigana部分のプロパティ名をリフレクションで対応したいのですが
できますか?

return db.Entry(_validationObject).Property(n => n.Furigana).GetValidationErrors();

下記のようにしたら
System.ArgumentException が発生しました
HResult=0x80070057
Message=The expression passed to method Property must represent a property defined on the type 'Company'.
パラメーター名:property
Source=EntityFramework
スタック トレース:
場所 System.Data.Entity.Internal.DbHelpers.ParsePropertySelector[TEntity,TProperty](Expression1 property, String methodName, String paramName)    場所 System.Data.Entity.Infrastructure.DbEntityEntry1.Property[TProperty](Expression`1 property)    場所 

return db.Entry(_validationObject).Property(n => n.GetType().GetProperty("Furigana").GetValue(n)).GetValidationErrors();

ちなみに、以下のとおりにすると正しく出力されています

Debug.WriteLine(_validationObject.GetType().GetProperty("Furigana").GetValue(_validationObject));

下記が全体のコードです。 リフレクションで対応できればコード量がグッと減るんですけど・・・。

        private void _dgv_CellValidating(object sender, DataGridViewCellValidatingEventArgs e)
        {
            var names = _dgv.Columns[e.ColumnIndex].Name.Split('-'); // 0 クラス名、1 プロパティ名
            var t = _asm.GetType(this.GetType().Namespace + "." + names[0]);
            var method = t.GetMethod("Validate" + names[1]);
            if (method != null)
            {
                dynamic cellEx = _dgv[e.ColumnIndex, e.RowIndex];
                cellEx.ErrorText = "";
                var errors = (ICollection<DbValidationError>)method.Invoke(null, new object[] { _db, e.FormattedValue });
                if (errors.Count != 0)
                {
                    foreach (var err in errors)
                    {
                        cellEx.ErrorText += err.ErrorMessage + "\n";
                    }
                    cellEx.ErrorText = cellEx.ErrorText.TrimEnd('\n');
                }
            }
        }
        public static ICollection<DbValidationError> ValidateName(SeikouDbContext db, dynamic value)
        {
            _validationObject.Name = value;
            return db.Entry(_validationObject).Property(n => n.Name).GetValidationErrors();
        }

        public static ICollection<DbValidationError> ValidateFurigana(SeikouDbContext db, dynamic value)
        {
            _validationObject.Furigana = value;
            return db.Entry(_validationObject).Property(n => n.Furigana).GetValidationErrors();
        }

上記のValiate~メソッドがプロパティごとにあります。

以下が解決した最終的なコードです。 Valiate~メソッドを作る必要がなくなりました

        private void _dgv_CellValidating(object sender, DataGridViewCellValidatingEventArgs e)
        {
            dynamic cellEx = _dgv[e.ColumnIndex, e.RowIndex];

            var names = _dgv.Columns[e.ColumnIndex].Name.Split('-'); // 0 クラス名、1 プロパティ名
            typeof(U).GetProperty(names[1]).SetValue(_validationObject, e.FormattedValue);
            var errors = _db.Entry(_validationObject).Property(names[1]).GetValidationErrors();

            cellEx.ErrorText = "";
            foreach (var err in errors)
            {
                cellEx.ErrorText += err.ErrorMessage + "\n";
            }
            if (errors.Count != 0)
            {
                cellEx.ErrorText = cellEx.ErrorText.TrimEnd('\n');
                e.Cancel = true;
                _dgv.EndEdit();
                _dgv.InvalidateCell(cellEx);
            }
        }
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

こんにちは。
EF関連の部分はちょっと再現して検証する余裕がなく、ドキュメントを読みつつの回答です。

まず、EFのPropertyメソッドが取るラムダ式はFuncではなくExpressionです。
これは、実際にプロパティにアクセスする関数を渡している訳ではなく、Entryに登録したオブジェクトのどのプロパティを参照するのかのメタデータをラムダ式の形で入力できるようにしたものです。
EF側ではラムダ式の構造を解析することでユーザが指定するプロパティを特定しているのです。
よって、ラムダ式には単純な引数へのプロパティアクセス以外を記述することはできません。
質問への直接の回答としては、「この式中でリフレクションを使用することは不可能」です。
EFがなんのためにこんな面倒な設計をしているのかというと、ユーザが型安全にプロパティアクセスを記述できるようにするためです。Entryに渡したオブジェクトの型が分かっていれば、どのプロパティが存在するのかは静的に知ることができるので、それをコードに型安全に記述するためにExpressionを使用しているのです。

ところで。。
質問者さんのコードは、とっても、型の力が抜けてるので、プロパティアクセスにわざわざExpressionを取るオーバーロードを使用する理由がありません。
というわけでちょっと書いてみたのが以下です。

static ValidationObject _validationObject;

public static ICollection<DbValidationError> Validate(SeikouDbContext db, object value, string propertyName)
{
    typeof(ValidationObject).GetProperty(propertyName).SetValue(_validationObject, value);
    return db.Entry(_validationObject).Property(propertyName).GetValidationErrors();
}

これで動くかどうかは全く不明です。すみません。_validationObjectの情報が全く質問に書かれていなかったため、仮にValidationObjectクラスのオブジェクトであるとしました。
Propertyメソッドにはstringのみを取るオーバーロードが存在しており、プロパティ名から型情報を持たないDbPropertyEntryを手に入れることができるようです。
あとは、_validationObjectの対象プロパティにvalueをセットするためのリフレクションがあればいいです。
dynamicを使う必要がなくなったためvalueはobjectになります。
以上のやり方で、プロパティ名さえ正しく渡すことができれば、一つのメソッドで目的を達成できると思います。
_validationObjectの型が色々あるとかであれば、ジェネリックにするなりGetType()するなりで対応できると思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/03/08 12:31

    回答ありがとうございます。

    Property(n => n.Furigana)をProperty(propertyName)に直接プロパティ名を設定したら期待の動きをしました!!感動ものです。
    ありがとうございます。
    これでコード量がグッと減りましたし、変更にも強いものが出来上がりました!!

    ラムダ式にFuncとExpressionがあるの知りませんでした。
    Funcだけだと思ってました。というか気にもしてなかったです。

    最終的なコードを載せておきます

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.78%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る