日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > C# >内容正文

C#

【翻译】C#表达式中的动态查询

發(fā)布時(shí)間:2023/12/4 C# 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【翻译】C#表达式中的动态查询 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

當(dāng)您使用LINQ來(lái)處理數(shù)據(jù)庫(kù)時(shí),這種體驗(yàn)是一種神奇的體驗(yàn),對(duì)嗎?你把數(shù)據(jù)庫(kù)實(shí)體像一個(gè)普通的收集,使用Linq中像Where,Select或者?Take,這些簡(jiǎn)單的使用就能讓代碼可用了。

但是,讓我們考慮一下這里是如何通過(guò)動(dòng)態(tài)查詢(xún)和表達(dá)式樹(shù)實(shí)現(xiàn)此功能的:幕后發(fā)生的事情。您編寫(xiě)的LINQ查詢(xún)將轉(zhuǎn)換為SQL(或其他方式),并將該SQL查詢(xún)發(fā)送到數(shù)據(jù)庫(kù)。然后將數(shù)據(jù)庫(kù)的響應(yīng)映射到C#對(duì)象。但是,如何完全轉(zhuǎn)換為SQL?

在本文中,您將看到諸如Entity Framework和MongoDB C#驅(qū)動(dòng)程序之類(lèi)的框架如何使用表達(dá)式樹(shù)進(jìn)行轉(zhuǎn)換。您將看到如何親自使用表達(dá)式樹(shù)來(lái)構(gòu)建動(dòng)態(tài)查詢(xún)。這些查詢(xún)是您無(wú)法在編譯時(shí)創(chuàng)建的查詢(xún),因?yàn)槟鷮⒅涝摬樵?xún)僅在運(yùn)行時(shí)的外觀。

對(duì)可查詢(xún)樹(shù)和表達(dá)式樹(shù)進(jìn)行揭秘

考慮以下使用Entity Framework 6的C#代碼:

DbSet<Student> students = context.Students; var billie = await students.Where(s => s.StudentName == "Billie").ToListAsync();

執(zhí)行時(shí),實(shí)體框架會(huì)產(chǎn)生以下SQL查詢(xún):

SQL:SELECT[Extent1].[StudentID] AS [StudentID],[Extent1].[StudentName] AS [StudentName],[Extent1].[DateOfBirth] AS [DateOfBirth],FROM [dbo].[Students] AS [Extent1]WHERE N'Billie' = [Extent1].[StudentName]

請(qǐng)注意,WHERESQL查詢(xún)中有一個(gè)操作。那不是很明顯。如果SQL不包含WHERE,則所有學(xué)生都將從數(shù)據(jù)庫(kù)中帶走,并且篩選將在.NET進(jìn)程中執(zhí)行。實(shí)際上,以下代碼可以做到這一點(diǎn):

//BAD: DbSet<Student> students = context.Students; Func<Student, bool> predicate = s => s.StudentName == "Billie"; var x = students.Where(predicate).ToList();

在最后一個(gè)示例中,SQL查詢(xún)使所有學(xué)生進(jìn)入流程,并將其映射到常規(guī)集合。不同之處在于,在第一段代碼中,lambda是一個(gè)Expression<Func<Student, bool>>,它允許實(shí)體框架將其添加到SQL查詢(xún)中。在第二段代碼中,lambda是a Func<Student, bool>,因此將Where執(zhí)行操作符之前的所有操作并將其轉(zhuǎn)換為常規(guī)IEnumerable集合,然后執(zhí)行其余的查詢(xún)。

第二段代碼在性能,內(nèi)存和網(wǎng)絡(luò)方面很糟糕。我們從網(wǎng)絡(luò)中獲取了許多對(duì)象,而不是僅從數(shù)據(jù)庫(kù)中獲取一個(gè)項(xiàng)目。然后,我們使用CPU將它們序列化為C#對(duì)象。并用完內(nèi)存將它們存儲(chǔ)在進(jìn)程的堆中。

因此,讓我們回到第一段代碼。如何await students.Where(s => s.StudentName == "Billie").ToListAsync()產(chǎn)生一個(gè)包含的SQL查詢(xún)WHERE N'Billie' = [Extent1].[StudentName]?

答案是表達(dá)樹(shù)。該代碼s => s.StudentName == "Billie"實(shí)際上是一個(gè)結(jié)構(gòu)化查詢(xún),可以通過(guò)編程將其分解為節(jié)點(diǎn)樹(shù)。在此示例中,有6個(gè)節(jié)點(diǎn)。最頂層的節(jié)點(diǎn)是lambda表達(dá)式。左側(cè)是lambda參數(shù)。在它的右邊是Equal表示表達(dá)式的lambda主體。實(shí)體框架具有遍歷這些表達(dá)式樹(shù)并構(gòu)造SQL查詢(xún)的算法。其他數(shù)據(jù)源提供程序(如Mongo DB C#驅(qū)動(dòng)程序)也會(huì)發(fā)生同樣的事情,除了它會(huì)構(gòu)造一個(gè)MongoDB json查詢(xún)。

C#表達(dá)式樹(shù)

在第一段代碼中,類(lèi)型s => s.StudentName == "Billie"為Expression<Func<Student, bool>>。這表示生成樹(shù)的表達(dá)式樹(shù)Func<Student, bool>。該Where子句接受這種類(lèi)型的參數(shù),因?yàn)閍DbSet<TEntity>實(shí)現(xiàn)了IQueryable<T>接口,這要求它與表達(dá)式樹(shù)配合使用。相反,常規(guī)集合(如數(shù)組或a List<T>)IEnumerable意味著它將lambdas => s.StudentName == "Billie"用作常規(guī)函數(shù)。

好的,但是我該如何利用它呢?

在大多數(shù)情況下,使用表達(dá)樹(shù)的人們就是在構(gòu)建世界實(shí)體框架的人們。但是在某些特定情況下,它變得非常有用。這是我們最近在Ozcode[1](我的日常工作)中遇到的一個(gè)用例:

我們想在名為Error的數(shù)據(jù)庫(kù)實(shí)體上創(chuàng)建動(dòng)態(tài)服務(wù)器端過(guò)濾。該實(shí)體具有許多屬性,我們希望允許用戶(hù)對(duì)其進(jìn)行過(guò)濾。因此過(guò)濾應(yīng)根據(jù)被允許Username,Country,Version,或任何其他財(cái)產(chǎn)。這是我們需要實(shí)現(xiàn)的API:

IQueryable<Error> _errors; public IEnumerable<Error> GetErrors(string propertyToFilter, string value){ /*..*/}

在這種情況下,propertyToFilter是的屬性Error。使用常規(guī)的LINQ,唯一的方法就是使用巨大的switch / case語(yǔ)句。有點(diǎn)像這樣:

IQueryable<Error> _errors;public IEnumerable<Error> GetErrors(string propertyToFilter, string value) {switch (propertyToFilter){case "Username":return await _errors.Where(e=> e.Username == value).ToListAsync();case "Country":return await _errors.Where(e=> e.Country == value).ToListAsync();case "Version":return await _errors.Where(e=> e.Version == value).ToListAsync();// ... } }

您可能會(huì)同意這不是理想的選擇。除了必須編寫(xiě)所有這些東西之外,它還非常容易出現(xiàn)錯(cuò)誤。如果添加了屬性怎么辦?如果重命名怎么辦?整個(gè)事情一團(tuán)糟。

通過(guò)動(dòng)態(tài)查詢(xún)和表達(dá)式樹(shù)可以實(shí)現(xiàn)此功能的方法如下:

private async static Task<IEnumerable<Error>> GetErrors(string propertyToFilter, string value) {var error = Expression.Parameter(typeof(Error));var memberAccess = Expression.PropertyOrField(error, propertyToFilter);var exprRight = Expression.Constant(value);var equalExpr = Expression.Equal(memberAccess, exprRight);Expression<Func<Error, bool>> lambda = Expression.Lambda<Func<Error, bool>>(equalExpr, error);return await _errors.Where(lambda).ToListAsync(); }

這里的每一行代碼代表表達(dá)式樹(shù)中的一個(gè)節(jié)點(diǎn)。它們共同構(gòu)成了最高節(jié)點(diǎn)-lambda。然后,可以在LINQ中使用動(dòng)態(tài)表達(dá)式,并生成服務(wù)器端SQL查詢(xún)。我認(rèn)為很好。

解決此問(wèn)題的另一種方法是構(gòu)建自定義SQL查詢(xún)字符串。在Ozcode中,我們使用的是MongoDB,因此SQL不適合使用,但我們可以創(chuàng)建一個(gè)自定義的MongoDB JSON查詢(xún)字符串。也不是太難,但是我認(rèn)為表達(dá)式樹(shù)方法更加靈活和可靠。一方面,您可以將其放在LINQ中并與其他LINQ運(yùn)算符組合。此外,當(dāng)有諸如Entity Framework之類(lèi)的經(jīng)過(guò)測(cè)試的框架可以為您執(zhí)行此操作時(shí),為什么還要編寫(xiě)自己的查詢(xún)。

概要

回顧一下。這是本文中的一些關(guān)鍵點(diǎn):

?常規(guī)函數(shù)/委托與表達(dá)式之間的區(qū)別在于,表達(dá)式可以用結(jié)構(gòu)化樹(shù)表示。可以輕松地分析該樹(shù)以創(chuàng)建諸如數(shù)據(jù)庫(kù)查詢(xún)之類(lèi)的東西。?支持表達(dá)式的數(shù)據(jù)源實(shí)現(xiàn)該IQueryable接口。?如果您無(wú)法使用表達(dá)式(以及使用常規(guī)方法或委托),則查詢(xún)將在服務(wù)器端而不在數(shù)據(jù)庫(kù)端,這對(duì)于性能而言將是可怕的。?使用lambda(不帶主體)時(shí),表達(dá)式是無(wú)縫創(chuàng)建的,因此這些年來(lái)您可能一直都在這樣做。?您可以自己使用表達(dá)式樹(shù)來(lái)創(chuàng)建動(dòng)態(tài)查詢(xún)。這在無(wú)法在編譯時(shí)僅在運(yùn)行時(shí)構(gòu)建查詢(xún)的情況下很有用。

References

[1]?Ozcode:?https://oz-code.com

[2]:?https://www.mediavine.com/

總結(jié)

以上是生活随笔為你收集整理的【翻译】C#表达式中的动态查询的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。