Spesifikasi QueryProvider
QueryProvider tidak dapat menangani ini:
var result = _context.Humans .Select(x => $"Name: {x.Name} Age: {x.Age}") .Where(x => x != "") .ToList();
Itu tidak bisa berurusan dengan kalimat apa pun menggunakan string interpolasi, tetapi akan dengan mudah menangani ini:
var result = _context.Humans .Select(x => "Name " + x.Name + " Age " + x.Age) .Where(x => x != "") .ToList();
Yang paling menyakitkan adalah memperbaiki bug setelah mengaktifkan ClientEvaluation (pengecualian untuk perhitungan sisi klien), karena semua profil Automapper harus dianalisis secara ketat untuk interpolasi. Mari cari tahu apa dan mengusulkan solusi kami untuk masalah tersebut.
Memperbaiki sesuatu
Interpolasi di Pohon Ekspresi dikonversi seperti ini (ini adalah hasil dari metode ExpressionStringBuilder.ExpressionToString , ia melewatkan beberapa node tetapi ini OK):
Atau seperti ini, jika ada lebih dari 3 argumen:
Format("Name:{0} Age:{1}", new [] {x.Name, Convert(x.Age, Object)))
Kita dapat menyimpulkan bahwa penyedia tidak diajarkan untuk memproses kasus-kasus ini, tetapi mungkin diajarkan untuk membawa kasus-kasus ini dengan ToString yang terkenal () , diproses seperti ini:
((("Name: " + x.Name) + " Age: ") + Convert(x.Age, Object)))
Saya ingin menulis Pengunjung yang akan mengikuti Pohon Ekspresi (khususnya, node MethodCallExpression ) dan mengganti metode Format dengan concatenation. Jika Anda terbiasa dengan pohon ekspresi, Anda tahu bahwa C # menyediakan pengunjungnya sendiri untuk memotong pohon - ExpressionVisitor . Info lebih lanjut untuk mereka yang tertarik .
Yang kita butuhkan hanyalah mengganti metode VisitMethodCall dan sedikit mengubah nilai yang dikembalikan. Parameter metode adalah tipe MethodCallExpression , yang berisi informasi tentang metode itu sendiri dan argumen yang dimasukkan ke dalamnya.
Mari kita bagi tugas menjadi beberapa bagian:
- Tentukan bahwa itu adalah metode Format yang datang ke VisitMethodCall;
- Ganti metode dengan rangkaian string;
- Tangani semua kelebihan metode Format yang bisa kita miliki;
- Tulis metode ekstensi untuk memanggil pengunjung kami.
Bagian pertama adalah sederhana: Metode format memiliki 4 kelebihan yang 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)
Mari kita ekstrak mereka, menggunakan refleksi MethodInfo mereka:
private IEnumerable<MethodInfo> FormatMethods => typeof(string).GetMethods().Where(x => x.Name.Contains("Format"))
Luar biasa. Sekarang kita dapat menentukan apakah metode Format βmasukβ ke MethodCallExpression .
Saat melewati pohon di VisitMethodCall , metode berikut ini bisa masuk:
- Format dengan argumen objek
- Format dengan argumen objek []
- Sesuatu yang sama sekali berbeda.
Sedikit pencocokan pola khusus
Karena kami hanya memiliki 3 kondisi, kami dapat menanganinya menggunakan if, tetapi karena kami menganggap kami perlu memperluas metode ini di masa mendatang, mari kita muat semua kasus ke dalam struktur data 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 tangani. SelectorArgumentFunc diperlukan untuk membawa semua argumen dari metode Format ke dalam bentuk terpadu, metode ReturnFunc , yang akan mengembalikan Ekspresi penuh.
Sekarang mari kita ganti interpolasi dengan concatenation, dan untuk itu kita akan memerlukan metode ini:
private Expression InterpolationToStringConcat(MethodCallExpression node, IEnumerable<Expression> formatArguments) {
InterpolationToStringConcat akan dipanggil dari Pengunjung , tersembunyi di balik ReturnFunc :
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 semua kelebihan metode Format . Ini agak sepele dan terletak di dalam patternMatchingList :
patternMatchingList = new List<PatternMachingStructure> {
Oleh karena itu, kami akan mengikuti daftar itu dalam metode VisitMethodCall hingga FilterPredicate positif pertama, kemudian mengonversi argumen ( SelectorArgumentFunc ) dan menjalankan ReturnFunc .
Mari kita menulis metode ekstensi yang dapat kita panggil untuk menggantikan interpolasi.
Kita bisa mendapatkan Ekspresi , memberikannya kepada Pengunjung , dan kemudian memanggil metode CreateQuery 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; }
Berikan perhatian Anda untuk menggunakan qu.Provider.CreateQuery (hasil) yang memiliki metode IQueryable di IQueryable <T>. Ini banyak digunakan untuk C # (lihat antarmuka IEnumerable <T> !), Dan itu berasal dari kebutuhan untuk menangani semua antarmuka umum dengan satu kelas yang ingin mendapatkan IQueryable / IEnumerable , dan menanganinya menggunakan metode antarmuka umum.
Kita bisa menghindarinya dengan membawa T ke baseclass (melalui covariance), tetapi ia menetapkan beberapa batasan pada metode antarmuka.
Hasil
Terapkan ReWrite ke ekspresi LINQ di bagian atas artikel:
var result = _context.Humans .Select(x => $"Name: {x.Name} Age: {x.Age}") .Where(x => x != "") .ReWrite() .ToList();
Github