Subtleties dari Queryable Provider
Penyedia Kueri tidak dapat menangani ini:
var result = _context.Humans .Select(x => $"Name: {x.Name} Age: {x.Age}") .Where(x => x != "") .ToList();
Itu tidak akan mengatasi ekspresi apa pun yang akan menggunakan string yang diinterpolasi, tetapi akan menguraikannya tanpa kesulitan:
var result = _context.Humans .Select(x => "Name " + x.Name + " Age " + x.Age) .Where(x => x != "") .ToList();
Sangat menyakitkan untuk memperbaiki bug setelah mengaktifkan ClientEvaluation (pengecualian saat menghitung pada klien), semua profil auto-mapper harus mengalami analisis yang ketat untuk menemukan interpolasi ini. Mari kita cari tahu apa masalahnya dan menawarkan solusi kami untuk masalah tersebut.
Kami benar
Interpolasi dalam Expression Tree diterjemahkan seperti ini (ini adalah hasil dari ExpressionStringBuilder.ExpressionToString metode, itu menghilangkan beberapa node, tetapi bagi kami itu
tidak fatal):
Atau lebih, ketika ada lebih dari 3 argumen
Format("Name:{0} Age:{1}", new [] {x.Name, Convert(x.Age, Object)))
Kita dapat menyimpulkan bahwa penyedia sama sekali tidak belajar bagaimana menangani kasus-kasus seperti itu, tetapi mereka bisa mengajarkannya untuk mengurangi kasus-kasus ini menjadi ToString lama yang baik (), yang diurutkan seperti ini:
((("Name: " + x.Name) + " Age: ") + Convert(x.Age, Object)))
Saya ingin menulis Pengunjung yang akan melalui Pohon Ekspresi, yaitu melalui node dari MethodCallExpression dan mengganti metode Format dengan penggabungan. Jika Anda terbiasa dengan Pohon Ekspresi, maka Anda tahu bahwa C # menawarkan kepada kami pengunjungnya untuk melintasi pohon - ExpressionVisitor, bagi mereka yang tidak terbiasa itu akan menarik .
Cukup untuk mengesampingkan hanya metode VisitMethodCall dan sedikit mengubah nilai kembali. Parameter metode adalah tipe MethodCallExpression, yang berisi informasi tentang metode itu sendiri dan tentang argumen yang diteruskan ke sana.
Mari kita bagi tugas menjadi beberapa bagian:
- Tentukan bahwa itu adalah metode Format yang datang ke VisitMethodCall
- Ganti metode ini dengan penggabungan string
- Memproses semua kelebihan metode Format yang dapat diterima
- Tulis metode ekstensi yang akan dihubungi pengunjung kami
Bagian pertama cukup sederhana, metode Format 4 memiliki kelebihan yang harus dibangun
di pohon Ekspresi
public static string Format(string format, object arg0) public static string Format(string format, object arg0,object arg1) public static string Format(string format, object arg0,object arg1,object arg2) public static string Format(string format, params object[] args)
Kami menggunakan refleksi dari MethodInfo mereka
private IEnumerable<MethodInfo> FormatMethods => typeof(string).GetMethods().Where(x => x.Name.Contains("Format"))
Kelas, sekarang kita dapat menentukan bahwa metode Format telah "datang" ke MethodCallExpression.
Saat melintasi pohon, VisitMethodCall dapat "datang":
- Memformat metode dengan argumen objek
- Format metode dengan argumen objek []
- Bukan metode Format sama sekali
Pola Maching kustom sedikit
Sejauh ini, hanya 3 kondisi yang dapat diselesaikan dengan bantuan if, tetapi kami, dengan asumsi bahwa di masa depan kami harus memperluas metode ini, letakkan semua kasing dalam struktur data seperti ini:
public class PatternMachingStructure { public Func<MethodInfo, bool> FilterPredicate { get; set; } public Func<MethodCallExpression, IEnumerable<Expression>> SelectorArgumentsFunc { get; set; } public Func<MethodCallExpression, IEnumerable<Expression>, Expression> ReturnFunc { get; set; } } var patternMatchingList = new List<PatternMachingStructure>()
Menggunakan FilterPredicate, kami menentukan mana dari 3 kasus yang kami hadapi. SelectorArgumentFunc diperlukan untuk membawa argumen metode Format ke bentuk yang seragam, metode ReturnFunc, yang akan mengembalikan Ekspresi baru kepada kami.
Sekarang mari kita coba mengganti representasi interpolasi dengan concatenation, untuk ini kita akan menggunakan metode berikut:
private Expression InterpolationToStringConcat(MethodCallExpression node, IEnumerable<Expression> formatArguments) {
InterpolationToStringConcat akan dipanggil dari Pengunjung, tersembunyi di balik ReturnFunc
(ketika node.Method == string.Format)
protected override Expression VisitMethodCall(MethodCallExpression node) { var pattern = patternMatchingList.First(x => x.FilterPredicate(node.Method)); var arguments = pattern.SelectorArgumentsFunc(node); var expression = pattern.ReturnFunc(node, arguments); return expression; }
Sekarang kita perlu menulis logika untuk menangani kelebihan berlebih dari metode Format, itu cukup sepele dan terletak di patternMachingList
patternMatchingList = new List<PatternMachingStructure> {
Oleh karena itu, dalam metode VisitMethodCall, kita akan melewati sheet ini sampai FilterPredicate positif pertama, kemudian mengonversi argumen (SelectorArgumentFunc) dan menjalankan ReturnFunc.
Mari kita menulis Extention, memanggil bahwa kita dapat mengganti interpolasi.
Kita bisa mendapatkan Ekspresi, meneruskannya ke Pengunjung kami, dan kemudian memanggil metode antarmuka IQuryableProvider CreateQuery, yang akan menggantikan pohon ekspresi asli dengan kita:
public static IQueryable<T> ReWrite<T>(this IQueryable<T> qu) { var result = new InterpolationStringReplacer<T>().Visit(qu.Expression); var s = (IQueryable<T>) qu.Provider.CreateQuery(result); return s; }
Perhatikan Cast qu.Provider.CreateQuery (hasil) dari tipe IQueryable di IQueryable <T>, ini umumnya praktik standar untuk c # (lihat IEnumerable <T>), itu muncul karena kebutuhan untuk memproses semua antarmuka umum dalam satu kelas, yang ingin menerima IQueryable / IEnumerable, dan memprosesnya menggunakan metode antarmuka umum.
Ini bisa dihindari dengan melemparkan T ke kelas dasar, ini dimungkinkan menggunakan kovarians, tetapi juga memberlakukan beberapa pembatasan pada metode antarmuka (lebih lanjut tentang ini di artikel berikutnya).
Ringkasan
Terapkan ReWrite ke ekspresi di awal artikel
var result = _context.Humans .Select(x => $"Name: {x.Name} Age: {x.Age}") .Where(x => x != "") .ReWrite() .ToList();
Github