日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

EFCore查缺补漏(二):查询

發(fā)布時間:2023/12/4 编程问答 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 EFCore查缺补漏(二):查询 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

相關(guān)文章:?EFCore查缺補(bǔ)漏

第 20 輪 TechEmpower 評測結(jié)果出爐了,ASP.NET Core 的 Plaintext 成績名列前茅,帶著 EFCore 的測試卻在 Single query / Multiple queries / Fortunes 中落了下風(fēng),成績遠(yuǎn)不如 dapper,更不如直接 ado.net。

人人都說 EFCore 性能差,人人都在寫性能低的代碼……

EFCore 是如何進(jìn)行查詢的?除了查詢語句本身的合理性,EFCore 本身的性能瓶頸又會出現(xiàn)在哪里呢?如何讓 EFCore 的查詢變得更快呢?

今天,先從?IQueryable?這個接口說起。

IQueryable 與 IEnumerable

IEnumerable<>?的核心作用是提供一些基礎(chǔ)數(shù)據(jù)通過?GetEnumerator?函數(shù)來創(chuàng)建一個?IEnumerator<>。

IEnumerator<>?是一個非常單純的單向迭代器,你可以像系統(tǒng)自帶的集合類那樣手動實現(xiàn)一個,也可以你可以通過自己編寫?yield return?/?yield break?語句,讓編譯器將你的程序控制流和變量狀態(tài)保存在編譯器翻譯設(shè)計的專有?Enumerator?中。

除此之外,System.Linq?這個命名空間提供了大量針對?IEnumerable<>?的拓展。這些拓展將?IEnumerator<>?們通過類似于責(zé)任鏈模式的方法組合起來,提供了很多神奇的 LINQ 功能。

而?IQueryable<>?是什么呢?

IQueryable<>?接口除了實現(xiàn)?IEnumerable<>?以外,還有三個成員

  • Expression:保存了一個表達(dá)式樹

  • ElementType:這個?IQueryable<>?的返回類型

  • Provider:一個?IQueryProvider?實例對象

而?IQueryProvider?則有這樣幾個成員函數(shù)

  • IQueryable<> CreateQuery(Expression expression)?根據(jù)傳入的表達(dá)式樹構(gòu)建一個?IQueryable?對象

  • TResult Execute(Expression expression)?執(zhí)行這個表達(dá)式,獲得對應(yīng)結(jié)果

再參考?System.Linq.Queryable?對?IQueryable<>?的拓展函數(shù)的實現(xiàn)

// System.Linq.Queryable public static int Count<TSource>(this IQueryable<TSource> source) {if (source == null)throw Error.ArgumentNull(nameof(source));return source.Provider.Execute<int>(Expression.Call(null,CachedReflectionInfo.Count_TSource_1(typeof(TSource)),source.Expression)); }public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate) {if (source == null)throw Error.ArgumentNull(nameof(source));if (predicate == null)throw Error.ArgumentNull(nameof(predicate));return source.Provider.CreateQuery<TSource>(Expression.Call(null,CachedReflectionInfo.Where_TSource_2(typeof(TSource)),source.Expression, Expression.Quote(predicate))); }

那么我們有如下結(jié)論

  • IQueryable<>?保存著一個查詢表達(dá)式樹和一個?IQueryProvider

  • IQueryProvider?支撐著?IQueryable<>?的創(chuàng)建和查詢執(zhí)行

  • IQueryable<>?的拓展函數(shù)們僅僅是將表達(dá)式樹拼接成與函數(shù)調(diào)用相同形態(tài)的表達(dá)式

而在 EFCore 中,完成這樣功能的類則是?EntityQueryable<>?和?EntityQueryProvider。后者在 EFCore 的依賴注入容器中是 Scoped 服務(wù)?IAsyncQueryProvider?的實現(xiàn),完成所有的?IQueryable<>?的創(chuàng)建,并將所有的?Execute?和?ExecuteAsync?的請求轉(zhuǎn)發(fā)給?IQueryCompiler?這一服務(wù)。

而?IQueryCompiler?中的執(zhí)行代碼大約是這樣的

// Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler public virtual TResult Execute<TResult>(Expression query) {Check.NotNull(query, nameof(query));var queryContext = _queryContextFactory.Create();query = ExtractParameters(query, queryContext, _logger);var compiledQuery= _compiledQueryCache.GetOrAddQuery(_compiledQueryCacheKeyGenerator.GenerateCacheKey(query, async: false),() => CompileQueryCore<TResult>(_database, query, _model, false));return compiledQuery(queryContext); }

其中

  • _compiledQueryCache?是一個由?IMemoryCache?驅(qū)動的緩存

  • _compiledQueryCacheKeyGenerator?是將該表達(dá)式與數(shù)據(jù)庫模型、數(shù)據(jù)庫驅(qū)動等信息的 Hash 值合并產(chǎn)生一個對應(yīng)的?QueryCacheKey

  • _queryContextFactory?用于生成一個?QueryContext?實例

  • ExtractParameters?將查詢表達(dá)式中的閉包變量等計算完畢并加入?queryContext?實例

  • compiledQuery?是一個?Func<QueryContext, TResult>?實例,又稱為?QueryExecutor

而其中?QueryContext?支持和提供

  • 并行檢測(也就是 EFCore 一個上下文實例只能有一個正在執(zhí)行的查詢語句的基石)

  • 執(zhí)行邏輯(例如失敗重試)

  • 異步任務(wù)的 CancellationToken

  • 各種日志輸出

  • 查詢參數(shù)的添加和讀取

  • 實體跟蹤和狀態(tài)管理

在關(guān)系型數(shù)據(jù)庫驅(qū)動中,RelationalQueryContext?另外附加

  • 數(shù)據(jù)庫連接

  • 生成 SQL 片段的工廠類

我們每次要執(zhí)行一個查詢,就要先在這個內(nèi)存緩存中查找是否已經(jīng)有編譯好的執(zhí)行語句;而這個緩存的鍵需要利用表達(dá)式樹來生成。如果我們的查詢過于復(fù)雜,則會對緩存帶來一定的性能負(fù)擔(dān)。

Expression 樹與 EF.CompileQuery

我們稍后討論?CompileQueryCore?的作用。先來討論一下表達(dá)式樹吧。

眾所周知,EFCore 的強(qiáng)類型特性是由表達(dá)式樹這個玩意帶來的。

編譯器為了減少人為構(gòu)建表達(dá)式樹的負(fù)擔(dān),提供了語法糖,讓我們可以像寫 Lambda 函數(shù)一樣書寫表達(dá)式。然而編譯器并沒有開洞,而是實打?qū)嵉倪M(jìn)行了表達(dá)式樹的構(gòu)建。

可以在圖上看到,我們經(jīng)常執(zhí)行的根據(jù) ID 查找一個實體的操作會產(chǎn)生如此之多的中間代碼。甚至,藍(lán)框內(nèi)還有閉包變量捕捉的步驟。

如果我們的查詢很簡單,那似乎也沒什么……如果我們要執(zhí)行一個超級復(fù)雜的查詢,又要 join 好幾個表又要 concat 還要 group 呢?

表達(dá)式樹畢竟是表達(dá)式樹,為了創(chuàng)建表達(dá)式樹,這么多中間代碼總是需要執(zhí)行的。

有沒有辦法直接跳過這么多表達(dá)式樹的構(gòu)建呢?有的。看?EF.CompileQuery。其中的一個典型函數(shù)

public static Func<TContext, IEnumerable<TResult>> CompileQuery<TContext, TResult>([NotNull] Expression<Func<TContext, IQueryable<TResult>>> queryExpression)where TContext : DbContext=> new CompiledQuery<TContext, IEnumerable<TResult>>(queryExpression).Execute;

這里實際上是構(gòu)建了一個?CompileQuery<,>?類型的對象,并且將他的 Execute 函數(shù)打包成委托返回。

你可能會問,這不是還需要表達(dá)式樹嗎?

那么請回顧官方文檔中這個函數(shù)的使用場景:將該函數(shù)返回的委托放在 靜態(tài)字段 或者 單例的成員變量 中。也就是說,對于某個參數(shù)設(shè)計好了的函數(shù),這個?EF.CompileQuery?函數(shù)理應(yīng)只執(zhí)行一次。

CompiledQuery 對象在第一次執(zhí)行時,通過接下來的代碼創(chuàng)建一個?Func<QueryContext, TResult>?委托;在這個委托中,本次執(zhí)行產(chǎn)生的 SQL 語句表達(dá)式樹、SQL 語句文本會被緩存下來;在第二次執(zhí)行的時候,就跳過對表達(dá)式樹的處理,直接執(zhí)行上面那個委托,甚至在特定情況下快進(jìn)到直接執(zhí)行 SQL 語句文本了。

// Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler public virtual Func<QueryContext, TResult> CreateCompiledQuery<TResult>(Expression query) {Check.NotNull(query, nameof(query));query = ExtractParameters(query, _queryContextFactory.Create(), _logger, parameterize: false);return CompileQueryCore<TResult>(_database, query, _model, false); }

是不是感覺會快上很多?我們可以直接獲得這個委托,而不是通過字典查找,可以節(jié)省很多時間;而且不涉及查詢語句緩存,不會存在系統(tǒng)內(nèi)其他查詢太多、某些不常用查詢被定期清理掉的情況。

個人認(rèn)為,在需要比較高性能的同時,又不是直接執(zhí)行 SQL 語句文本的情況下,這樣兩種情況可以嘗試使用?EF.CompileQuery:

  • 查詢表達(dá)式樹太過復(fù)雜

  • 高頻訪問路徑上的查詢

另外,如果查詢是只讀,不涉及到實體的增刪改,此時完全可以考慮到使用?AsNoTracking?這類拓展,將實體更改追蹤關(guān)掉,在此基礎(chǔ)上可以再提高一點(diǎn)性能,大約能與 Dapper 和 ADO.NET 直接讀取數(shù)據(jù)的性能比肩(沒有測試數(shù)據(jù))。

筆者在閱讀網(wǎng)上現(xiàn)存的對?EF.CompileQuery?的介紹中,有讀到過一篇說這個函數(shù)不接受?ToList?和?ToListAsync?之類的函數(shù),說這個功能支持不完全。

ToList?是?IEnumerable<>?的拓展方法,并不是?IQueryable<>?的拓展方法。也就是說,ToList?實際上是將之前的?IQueryable<>?進(jìn)行了?foreach?枚舉,并手動構(gòu)建?List<>?對象,所以說不支持似乎情有可原。

而?ToListAsync?是 EFCore 的拓展方法。實際上,他的代碼是這樣的:

public static async Task<List<TSource>> ToListAsync<TSource>([NotNull] this IQueryable<TSource> source,CancellationToken cancellationToken = default) {var list = new List<TSource>();await foreach (var element in source.AsAsyncEnumerable().WithCancellation(cancellationToken)){list.Add(element);}return list; }

對,利用了?IAsyncEnumerable?和?await foreach?來達(dá)到異步查詢的目的。所以,當(dāng)我們需要使用?ToList、ToArray、ToDictionary?類似功能的時候,使用那個?Func<TContext, IAsyncEnumerable<TResult>>?然后手動構(gòu)架集合就好了。

這里再簡單給幾個使用這個函數(shù)使用的例子吧。

private static readonly Func<MyContext, int, Task<User>> _findUser =EF.CompileAsyncQuery((MyContext context, int id) => context.Users.Where(u => u.Id == id).FirstOrDefault());private static readonly Func<MyContext, IAsyncEnumerable<UserDto>> _listUsers =EF.CompileAsyncQuery((MyContext context) => context.Users.Select(u => new UserDto(u.Id, u.Name, true)));private static readonly Func<MyContext, DateTimeOffset, CancellationToken, Task<int>> _countUsers =EF.CompileAsyncQuery((MyContext context, DateTimeOffset time, CancellationToken _) => context.Users.Where(u => u.RegisterTime < time).Count());public async Task DoAsync(CancellationToken cancellationToken = default) {using var context = CreateContext();var user = await _findUser(context, 233);var list = new List<UserDto>();await foreach (var item in _listUsers(context).WithCancellation(cancellationToken)){list.Add(item);}var count = await _countUsers(context, DateTimeOffset.Now.AddDays(-1), cancellationToken); }

可以創(chuàng)建帶有 CancellationToken 的異步版本。同步版本就不用 CancellationToken 了。

真正的查詢編譯與執(zhí)行

我們需要結(jié)合 SqlServer 這個關(guān)系型數(shù)據(jù)庫解釋所謂的?QueryExecutor。其他關(guān)系型數(shù)據(jù)庫的大致構(gòu)建過程其實差不多,非關(guān)系型的 InMemory 和 Cosmos 的驅(qū)動用的少就不解釋了哈。

以?context.Set<User>().Where(u => u.Id != id).ToList()?這一查詢的翻譯為例。

我們可以發(fā)現(xiàn),這個查詢的?QueryExecutor?是?Func<QueryContext, IEnumerable<User>>,傳入?queryContext?會返回一個?IEnumerable?對象。

在 EFCore 3.1 中,返回的是?QueryingEnumerable;在 EFCore 5.0 中,返回?SingleQueryingEnumerable?或者?SplitQueryingEnumerable?或者?FromSqlQueryingEnumerable。5.0 的改動是因為帶來了?AsSplitQuery?這個拓展,避免笛卡爾爆炸的問題。

先跳過?QueryExecutor?函數(shù)體,看看返回值。

以?SingleQueryingEnumerable?為例,我們看到它實現(xiàn)了?IEnumerable?和?IAsyncEnumerable。以下是?GetEnumerator?結(jié)果的?MoveNext?和?InitializeReader?的實現(xiàn)。

private bool InitializeReader(DbContext _, bool result) {EntityFrameworkEventSource.Log.QueryExecuting();var relationalCommand = _relationalCommandCache.GetRelationalCommand(_relationalQueryContext.ParameterValues);_dataReader = relationalCommand.ExecuteReader(new RelationalCommandParameterObject(_relationalQueryContext.Connection,_relationalQueryContext.ParameterValues,_relationalCommandCache.ReaderColumns,_relationalQueryContext.Context,_relationalQueryContext.CommandLogger,_detailedErrorsEnabled));_resultCoordinator = new SingleQueryResultCoordinator();_relationalQueryContext.InitializeStateManager(_standAloneStateManager);return result; }public bool MoveNext() {try{using (_relationalQueryContext.ConcurrencyDetector.EnterCriticalSection()){if (_dataReader == null){_relationalQueryContext.ExecutionStrategyFactory.Create().Execute(true, InitializeReader, null);}var hasNext = _resultCoordinator.HasNext ?? _dataReader.Read();Current = default;if (hasNext){while (true){_resultCoordinator.ResultReady = true;_resultCoordinator.HasNext = null;Current = _shaper(_relationalQueryContext, _dataReader.DbDataReader, _resultCoordinator.ResultContext,_resultCoordinator);if (_resultCoordinator.ResultReady){// We generated a result so null out previously stored values_resultCoordinator.ResultContext.Values = null;break;}if (!_dataReader.Read()){_resultCoordinator.HasNext = false;// Enumeration has ended, materialize last element_resultCoordinator.ResultReady = true;Current = _shaper(_relationalQueryContext, _dataReader.DbDataReader, _resultCoordinator.ResultContext,_resultCoordinator);break;}}}return hasNext;}}catch (Exception exception){_queryLogger.QueryIterationFailed(_contextType, exception);throw;} }

其中

  • ExecutionStrategy?處理查詢重試之類的執(zhí)行邏輯

  • _relationalCommandCache?保存了翻譯的 SQL 表達(dá)式和語句文本

  • _dataReader?保存著 ADO.NET 中常用的?DbCommand,DbDataReader,DbConnection?等工具,是的,底層讀取庫就是 ADO.NET

  • _resultCoordinator?將 ADO.NET 讀取的一行結(jié)果存入?ResultContext,然后當(dāng)一條記錄完整讀取以后(如果有集合 Include,則是集合整個讀取完成),由?_shaper?轉(zhuǎn)換成最終實體

這個?QueryingEnumerable?是如何構(gòu)建出來的?終于到了喜聞樂見的?CompileQueryCore?函數(shù)講解了。

// Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler public virtual Func<QueryContext, TResult> CompileQueryCore<TResult>([NotNull] IDatabase database,[NotNull] Expression query,[NotNull] IModel model,bool async)=> database.CompileQuery<TResult>(query, async);// Microsoft.EntityFrameworkCore.Storage.Database public virtual Func<QueryContext, TResult> CompileQuery<TResult>(Expression query, bool async)=> Dependencies.QueryCompilationContextFactory.Create(async).CreateQueryExecutor<TResult>(query);// Microsoft.EntityFrameworkCore.Query.QueryCompilationContext public virtual Func<QueryContext, TResult> CreateQueryExecutor<TResult>(Expression query) {query = _queryTranslationPreprocessorFactory.Create(this).Process(query);// Convert EntityQueryable to ShapedQueryExpressionquery = _queryableMethodTranslatingExpressionVisitorFactory.Create(this).Visit(query);query = _queryTranslationPostprocessorFactory.Create(this).Process(query);// Inject actual entity materializer// Inject trackingquery = _shapedQueryCompilingExpressionVisitorFactory.Create(this).Visit(query);// If any additional parameters were added during the compilation phase (e.g. entity equality ID expression),// wrap the query with code adding those parameters to the query contextquery = InsertRuntimeParameters(query);var queryExecutorExpression = Expression.Lambda<Func<QueryContext, TResult>>(query,QueryContextParameter);try{return queryExecutorExpression.Compile();}finally{Logger.QueryExecutionPlanned(new ExpressionPrinter(), queryExecutorExpression);} }

嗯,這玩意……老套娃人了。

這里的代碼其實挺抽象的。另外不要被那個?.Compile()?嚇到,那個只是利用表達(dá)式樹把動態(tài)構(gòu)建的函數(shù)體生成為真正的委托對象而已,我們需要在它 Compile 之前看到這個函數(shù)體內(nèi)部究竟是什么。

至于查詢翻譯過程本身的設(shè)計,這次先不介紹。

依然是上面那個例子,調(diào)試 EFCore 源代碼設(shè)斷點(diǎn),可以看到,此時?queryExecutor?是這樣的:

return new SingleQueryingEnumerable<User>(relationalQueryContext: (RelationalQueryContext)queryContext,relationalCommandCache: value(RelationalCommandCache),shaper: value(Func<QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator, User>),contextType: typeof(MyContext),standAloneStateManager: False,detailedErrorsEnabled: False);

在剛才的過程中,?InsertRuntimeParameters?并沒有實際發(fā)揮作用,因為閉包捕捉的?id?變量早在?QueryCompiler.ExtractParameters?時就變?yōu)榱?ParameterExpression?并被加入?QueryContext,而并沒有在查詢翻譯過程中使用任何“運(yùn)行時參數(shù)”。

在不使用導(dǎo)航屬性的情況下,試了幾個常見的例子,基本上都不會觸發(fā)“運(yùn)行時參數(shù)”。筆者找到了這樣的兩個例子:

public class User {public int Id { get; set; } }public class Tenant {public int Id { get; set; }public ICollection<User> Users { get; set; } }modelBuilder.Entity<Tenant>(entity => entity.HasMany(e => e.Users).WithOne());var uu = new User { /* .. */ }; context.Users.Where(u => u.Equals(uu)).ToList(); context.Tenants.Where(t => t.Users.Contains(uu)).ToList();

C#

Copy

此時?queryExecutor?是這樣的:

queryContext.AddParameter("__entity_equality_uu_0_Id",new Func<QueryContext, int?>(queryContext => ParameterValueExtractor(queryContext, "__uu_0", IProperty)).Invoke(queryContext));return new SingleQueryingEnumerable<User>(relationalQueryContext: (RelationalQueryContext)queryContext,relationalCommandCache: value(RelationalCommandCache),shaper: value(Func<QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator, User>),contextType: typeof(MyContext),standAloneStateManager: False,detailedErrorsEnabled: False);

也就是說,所謂“運(yùn)行時參數(shù)”是 SQL 語句執(zhí)行時的參數(shù),但是,要想讀取它的值,就需要反過來從?QueryContext?中讀取。為什么會在?QueryContext?中呢?因為之前?QueryCompiler.ExtractParameters?的時候,這個對象的整體被加入了?QueryContext?中,而不是被直接計算好。對于需要判斷實體包含和實體相等的情況,就需要用到這種奇怪的方法。

現(xiàn)在問題來了:relationalCommandCache?和?shaper?是在何時構(gòu)建好的?

前者保存著翻譯完成的 SQL 表達(dá)式樹的對象,他會在創(chuàng)建?DbCommand?對象的時候,調(diào)用?QuerySqlGenerator?將表達(dá)式樹拍平成為 SQL 語句文本。

這里我們來研究一下后者這個?shaper?委托。

這個委托也是由 EFCore 動態(tài)創(chuàng)建的,但是這個委托的具體實現(xiàn)是和數(shù)據(jù)庫類型有關(guān)系的。在關(guān)系型數(shù)據(jù)庫中,由?RelationalShapedQueryCompilingExpressionVisitor?進(jìn)行創(chuàng)建。

對于剛才直接拿到實體的情況,它產(chǎn)生的代碼是這樣的

// SELECT [u].[Id], [u].[Name], [u].[RegisterTime], [u].[TenantId] // FROM [Users] AS [u]User Shape(QueryContext queryContext, DbDataReader dataReader, ResultContext resultContext, SingleQueryResultCoordinator resultCoordinator) {User var1 ={IEntityType entityType1;var materializationContext1 = new MaterializationContext(valueBuffer: ValueBuffer.Empty,context: queryContext.Context);User instance1 = null;InternalEntityEntry entry1 = queryContext.TryGetEntry(key: value(IKey: "Key: User.Id PK"),keyValues: new object[] { dataReader.GetInt32(0) },throwOnNullKey: True,out bool hasNullKey1));if (!hasNullKey1){if (entry1 != default(InternalEntityEntry)){entityType1 = entry1.EntityType;instance1 = (User)entry1.Entity;}else{ValueBuffer shadowValueBuffer1 = ValueBuffer.Empty;entityType1 = value("EntityType: User");instance1 = entityType1 switch{value("EntityType: User") =>{// EFCore生成的shadow property,此處為 int? TenantIdshadowValueBuffer1 = new ValueBuffer(new[]{dataReader.IsDBNull(3) ? default(object) : dataReader.GetInt32(3)});User instance = new User();instance.<Id>k__BackingField = dataReader.GetInt32(0);instance.<Name>k__BackingField = dataReader.IsDBNull(1)? default(string): dataReader.GetString(1);instance.<RegisterTime>k__BackingField = dataReader.GetFieldValue(2);block-return instance;},_ => null,};entry1 = entityType1 == default(IEntityType)? default(InternalEntityEntry): queryContext.StartTracking(entityType1, instance1, shadowValueBuffer1);}}block-return instance1;};return var1; }

注意此處擺出的代碼是從 EFCore 生成的表達(dá)式樹改寫而來,與原來的表達(dá)式樹并不完全相同,原來的一些寫法在 C# 中無法直接表達(dá)(例如 kotlin 那樣,一對花括號最后一個值作為整個花括號的值,此處用?block-return?表示;以及?default(void)?作為三元表達(dá)式值的使用),所以稍有改寫。

扔代碼出來不是讓大家看懂,而是讓大家體會一下。Don’t try to understand it, feel it.

可以看到大致的實體生成過程,以及實體跟蹤的流程:先看上下文是否已經(jīng)追蹤了這樣的實體,有則直接使用,無則跳過。

而?switch?則是給實體繼承關(guān)系做出的設(shè)計。在有實體繼承的情況下,entityType1?的值是通過讀取查詢結(jié)果某個 Shadow Property 字段來確定的。

如果使用?AsNoTracking?標(biāo)記查詢呢?

// SELECT [u].[Id], [u].[Name], [u].[RegisterTime], [u].[TenantId] // FROM [Users] AS [u]User Shape(QueryContext queryContext, DbDataReader dataReader, ResultContext resultContext, SingleQueryResultCoordinator resultCoordinator) {User var1 ={IEntityType entityType1;var materializationContext1 = new MaterializationContext(valueBuffer: ValueBuffer.Empty,context: queryContext.Context);User instance1 = null;if (((object)dataReader.GetInt32(0)) != null){ValueBuffer shadowValueBuffer1 = ValueBuffer.Empty;entityType1 = value("EntityType: User");instance1 = entityType1 switch{value("EntityType: User") =>{User instance = new User();instance.<Id>k__BackingField = dataReader.GetInt32(0);instance.<Name>k__BackingField = dataReader.IsDBNull(1)? default(string): dataReader.GetString(1);instance.<RegisterTime>k__BackingField = dataReader.GetFieldValue(2);block-return instance;},_ => null,};}block-return instance1;};return var1; }

可以看到,實體跟蹤相關(guān)的代碼沒了,Shadow Property 相關(guān)的也沒了,畢竟上下文不追蹤這個實體,怎么會知道有哪些虛擬屬性呢。上下文能有什么壞心思呢。

如果是查詢中 Select 創(chuàng)建了一個非實體類型呢?(這里其實和?.Count()、.Sum()?之類的函數(shù)效果差不多)

例如?context.Users.Select(u => new UserDto(u.Id, u.Name, false)).ToList();。

// SELECT [u].[Id], [u].[Name] // FROM [Users] AS [u]UserDto Shape(QueryContext queryContext, DbDataReader dataReader, ResultContext resultContext, SingleQueryResultCoordinator resultCoordinator) {var param0 = (int?)dataReader.GetInt32(0);var param1 = dataReader.IsDBNull(1) ? default(string) : dataReader.GetString(1);return new UserDto((int)param0, param1, false); }

嗯,甚至直接跳過了?IEntityType?的檢查……不過也正常,畢竟這里沒有一個實體對應(yīng)多種 CLR 類型的狀況。

再來一個使用了單個實體 Include 的吧。以?context.Users.Include(u => u.Tenant).ToListAsync()?為例

// SELECT [u].[Id], [u].[Name], [u].[RegisterTime], [u].[TenantId], [t].[Id] // FROM [Users] AS [u] // LEFT JOIN [Tenants] AS [t] ON [u].[TenantId] = [t].[Id]User Shape(QueryContext queryContext, DbDataReader dataReader, ResultContext resultContext, SingleQueryResultCoordinator resultCoordinator) {User var1 ={IEntityType entityType1;var materializationContext1 = new MaterializationContext(valueBuffer: ValueBuffer.Empty,context: queryContext.Context);User instance1 = null;InternalEntityEntry entry1 = queryContext.TryGetEntry(key: value(IKey: "Key: User.Id PK"),keyValues: new object[] { dataReader.GetInt32(0) },throwOnNullKey: True,out bool hasNullKey1));if (!hasNullKey1) { ... } // 此處與上述帶 Tracking 的類似block-return instance1;};Tenant var2 ={IEntityType entityType2;var materializationContext2 = new MaterializationContext(valueBuffer: ValueBuffer.Empty,context: queryContext.Context);Tenant instance2 = null;InternalEntityEntry entry2 = queryContext.TryGetEntry(key: value(IKey: "Key: Tenant.Id PK"),keyValues: new object[] { dataReader.IsDBNull(4) ? default(object) : dataReader.GetInt32(4) },throwOnNullKey: False,out bool hasNullKey2));if (!hasNullKey2) { ... } // 此處與上述帶 Tracking 的類似block-return instance2;};IncludeReference(queryContext: queryContext,entity: var1,relatedEntity: var2,navigation: value("Navigation: User.Tenant (Tenant) ToPrincipal Tenant Inverse: Users"),inverseNavigation: value("Navigation: Tenant.Users (ICollection<User>) Collection ToDependent User Inverse: Tenant"),fixup: (entity, relatedEntity) =>{entity.<Tenant>k__BackingField = relatedEntity;// value(ClrICollectionAccessor<Tenant, ICollection<User>, User>) = inverseNavigation.GetCollectionAccessor()value(IClrICollectionAccessor).Add(relatedEntity, entity, forMaterialization: True);},trackingQuery: True);return var1; }// Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor+ShaperProcessingExpressionVisitor private static void IncludeReference<TEntity, TIncludingEntity, TIncludedEntity>(QueryContext queryContext,TEntity entity,TIncludedEntity relatedEntity,INavigationBase navigation,INavigationBase inverseNavigation,Action<TIncludingEntity, TIncludedEntity> fixup,bool trackingQuery)where TEntity : classwhere TIncludingEntity : class, TEntitywhere TIncludedEntity : class {if (entity is TIncludingEntity includingEntity){if (trackingQuery&& navigation.DeclaringEntityType.FindPrimaryKey() != null){// For non-null relatedEntity StateManager will set the flagif (relatedEntity == null){queryContext.SetNavigationIsLoaded(includingEntity, navigation);}}else{navigation.SetIsLoadedWhenNoTracking(includingEntity);if (relatedEntity != null){fixup(includingEntity, relatedEntity);if (inverseNavigation != null&& !inverseNavigation.IsCollection){inverseNavigation.SetIsLoadedWhenNoTracking(relatedEntity);}}}} }

再來一個集合 Include 的吧。集合的特別復(fù)雜。

以?context.Tenants.Include(t => t.Users).ToListAsync()?為例

// SELECT [t].[Id], [u].[Id], [u].[Name], [u].[RegisterTime], [u].[TenantId] // FROM [Tenants] AS [t] // LEFT JOIN [Users] AS [u] ON [t].[Id] = [u].[TenantId] // ORDER BY [t].[Id], [u].[Id]Tenant Shape(QueryContext queryContext, DbDataReader dataReader, ResultContext resultContext, SingleQueryResultCoordinator resultCoordinator) {if (resultContext.Values == null){Tenant var1 ={var materializationContext1 = new MaterializationContext(valueBuffer: ValueBuffer.Empty,context: queryContext.Context);Tenant instance1 = null;InternalEntityEntry entry1 = queryContext.TryGetEntry(key: value(IKey: "Key: Tenant.Id PK"),keyValues: new object[] { dataReader.GetInt32(0) },throwOnNullKey: True,out bool hasNullKey1));if (!hasNullKey1) { ... } // 此處與上述帶 Tracking 的類似block-return instance1;};resultContext.Values = new[] { var1 };InitializeIncludeCollection(collectionId: 0,queryContext: queryContext,dbDataReader: dataReader,resultCoordinator: resultCoordinator,entity: (Tenant)resultContext.Values[0],parentIdentifier: (queryContext, dataReader) => new object[] { (int?)dataReader.GetInt32(0) },outerIdentifier: (queryContext, dataReader) => new object[] { (int?)dataReader.GetInt32(0) },navigation: value("Navigation: Tenant.Users (ICollection<User>) Collection ToDependent User Inverse: Tenant"),clrCollectionAccessor: value(ClrCollectionAccessor),trackingQuery: True);}PopulateIncludeCollection(collectionId: 0,queryContext: queryContext,dbDataReader: dataReader,resultCoordinator: resultCoordinator,parentIdentifier: (queryContext, dataReader) => new object[] { (int?)dataReader.GetInt32(0) },outerIdentifier: (queryContext, dataReader) => new object[] { (int?)dataReader.GetInt32(0) },selfIdentifier: (queryContext, dataReader) => new object[] { dataReader.IsDBNull(1) ? default(int?) : (int?)dataReader.GetInt32(1) },parentIdentifierValueComparers: value(IReadOnlyList<ValueComparer>),outerIdentifierValueComparers: value(IReadOnlyList<ValueComparer>),selfIdentifierValueComparers: value(IReadOnlyList<ValueComparer>),innerShaper: (queryContext, dataReader, resultContext, resultCoordinator) =>{User var1 ={var materializationContext2 = new MaterializationContext(valueBuffer: ValueBuffer.Empty,context: queryContext.Context);User instance2 = null;InternalEntityEntry entry2 = queryContext.TryGetEntry(key: value(IKey: "Key: User.Id PK"),keyValues: new[] { dataReader.IsDBNull(1) ? default(object) : dataReader.GetInt32(1) },throwOnNullKey: False,out bool hasNullKey2));if (!hasNullKey2) { ... } // 此處與上述帶 Tracking 的類似block-return instance2;};return var1;},inverseNavigation: value("Navigation: User.Tenant (Tenant) ToPrincipal Tenant Inverse: Users"),fixup: (Tenant including, User included) =>{value(IClrICollectionAccessor).Add(including, included, True);included.<Tenant>k__BackingField = including;},trackingQuery: True);return resultCoordinator.ResultReady? (Tenant)resultContext.Values[0]: default(Tenant); }// Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor+ShaperProcessingExpressionVisitor private static void InitializeIncludeCollection<TParent, TNavigationEntity>(int collectionId,QueryContext queryContext,DbDataReader dbDataReader,SingleQueryResultCoordinator resultCoordinator,TParent entity,Func<QueryContext, DbDataReader, object[]> parentIdentifier,Func<QueryContext, DbDataReader, object[]> outerIdentifier,INavigationBase navigation,IClrCollectionAccessor clrCollectionAccessor,bool trackingQuery)where TParent : classwhere TNavigationEntity : class, TParent {object collection = null;if (entity is TNavigationEntity){if (trackingQuery){queryContext.SetNavigationIsLoaded(entity, navigation);}else{navigation.SetIsLoadedWhenNoTracking(entity);}collection = clrCollectionAccessor.GetOrCreate(entity, forMaterialization: true);}var parentKey = parentIdentifier(queryContext, dbDataReader);var outerKey = outerIdentifier(queryContext, dbDataReader);var collectionMaterializationContext = new SingleQueryCollectionContext(entity, collection, parentKey, outerKey);resultCoordinator.SetSingleQueryCollectionContext(collectionId, collectionMaterializationContext); }// Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor+ShaperProcessingExpressionVisitor private static void PopulateIncludeCollection<TIncludingEntity, TIncludedEntity>(int collectionId,QueryContext queryContext,DbDataReader dbDataReader,SingleQueryResultCoordinator resultCoordinator,Func<QueryContext, DbDataReader, object[]> parentIdentifier,Func<QueryContext, DbDataReader, object[]> outerIdentifier,Func<QueryContext, DbDataReader, object[]> selfIdentifier,IReadOnlyList<ValueComparer> parentIdentifierValueComparers,IReadOnlyList<ValueComparer> outerIdentifierValueComparers,IReadOnlyList<ValueComparer> selfIdentifierValueComparers,Func<QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator, TIncludedEntity> innerShaper,INavigationBase inverseNavigation,Action<TIncludingEntity, TIncludedEntity> fixup,bool trackingQuery)where TIncludingEntity : classwhere TIncludedEntity : class {var collectionMaterializationContext = resultCoordinator.Collections[collectionId];if (collectionMaterializationContext.Parent is TIncludingEntity entity){if (resultCoordinator.HasNext == false){// Outer Enumerator has endedGenerateCurrentElementIfPending();return;}if (!CompareIdentifiers(outerIdentifierValueComparers,outerIdentifier(queryContext, dbDataReader), collectionMaterializationContext.OuterIdentifier)){// Outer changed so collection has ended. Materialize last element.GenerateCurrentElementIfPending();// If parent also changed then this row is now pointing to element of next collectionif (!CompareIdentifiers(parentIdentifierValueComparers,parentIdentifier(queryContext, dbDataReader), collectionMaterializationContext.ParentIdentifier)){resultCoordinator.HasNext = true;}return;}var innerKey = selfIdentifier(queryContext, dbDataReader);if (innerKey.All(e => e == null)){// No correlated elementreturn;}if (collectionMaterializationContext.SelfIdentifier != null){if (CompareIdentifiers(selfIdentifierValueComparers, innerKey, collectionMaterializationContext.SelfIdentifier)){// repeated row for current element// If it is pending materialization then it may have nested elementsif (collectionMaterializationContext.ResultContext.Values != null){ProcessCurrentElementRow();}resultCoordinator.ResultReady = false;return;}// Row for new element which is not first element// So materialize the elementGenerateCurrentElementIfPending();resultCoordinator.HasNext = null;collectionMaterializationContext.UpdateSelfIdentifier(innerKey);}else{// First row for current elementcollectionMaterializationContext.UpdateSelfIdentifier(innerKey);}ProcessCurrentElementRow();resultCoordinator.ResultReady = false;}void ProcessCurrentElementRow(){var previousResultReady = resultCoordinator.ResultReady;resultCoordinator.ResultReady = true;var relatedEntity = innerShaper(queryContext, dbDataReader, collectionMaterializationContext.ResultContext, resultCoordinator);if (resultCoordinator.ResultReady){// related entity is materializedcollectionMaterializationContext.ResultContext.Values = null;if (!trackingQuery){fixup(entity, relatedEntity);if (inverseNavigation != null){inverseNavigation.SetIsLoadedWhenNoTracking(relatedEntity);}}}resultCoordinator.ResultReady &= previousResultReady;}void GenerateCurrentElementIfPending(){if (collectionMaterializationContext.ResultContext.Values != null){resultCoordinator.HasNext = false;ProcessCurrentElementRow();}collectionMaterializationContext.UpdateSelfIdentifier(null);} }

嗯,這件事情很神奇。

理論上,在不帶過濾的情況下,One Include Many 和 Many Include One 應(yīng)該是一致的?

為何代碼為什么差別這么大?實際上,這就是“查詢跟蹤”的神秘之處了。

不知道各位朋友是否在網(wǎng)上看見過這樣的文章,說使用?AsNoTracking?可以提高查詢性能,并且還建議大家直接?optionsBuilder.UseQueryTrackingBehavior(NoTracking)?

實際上,這樣有一種隱藏的坑:

如果你使用了一對多的 SQL JOIN,并且還保持著原來的實體形狀,如以下代碼所示:

var results = context.Tenants.AsNoTracking().Join(inner: context.Users.AsNoTracking(),outerKeySelector: t => t.Id,innerKeySelector: u => u.TenantId,resultSelector: (t, u) => new { t, u }).ToList();

假設(shè)你的?results[0].t.Id == results[1].t.Id,也就是前兩條在數(shù)據(jù)庫中是同一個?t?實例,當(dāng)你拉取到本地時,你會發(fā)現(xiàn)?object.ReferenceEquals(results[0].t, results[1].t) == false,或者說在本地不是同一個實例,而兩者僅僅是值相等。

如果上述代碼使用的都是?AsTracking,那么?object.ReferenceEquals(results[0].t, results[1].t) == true,從頭到尾只會創(chuàng)建一個這樣的實體。

為了保證 One Include Many 在未開啟實體追蹤的時候也正常工作,不得不使用一些奇怪的代碼來保證結(jié)果正確性。

這種情況是在筆者兩年前做 JOIN 以后拉到本地 GroupBy 的時候發(fā)現(xiàn)的。當(dāng)年在已經(jīng)寫了很多代碼以后才開啟了 NoTracking,結(jié)果導(dǎo)致很多原有功能失效。從那之后,筆者都是老老實實開 Tracking 的。

對,確實有很多“不開啟實體跟蹤”還要“生成出來的實體不重復(fù)”的情況,這種情況下要怎么做呢?

EFCore 5.0 新推出了?NoTrackingWithIdentityResolution。用這個就能保證上述的引用相等,但是實體不會最終被上下文追蹤。

另外,所謂“不開啟實體跟蹤”最大的影響場景,是查詢大量數(shù)據(jù)以后,對上下文進(jìn)行多次保存操作。如果你覺得頻繁保存時處理大量實體變得異常緩慢,你應(yīng)該考慮修改?DbContext.ChangeTracker.AutoDetectChangesEnabled?為?false,然后對所有修改過的實體手動調(diào)用?context.Update。這樣即使上下文追蹤上萬個實體,保存都是幾乎一瞬間的事情。

另外,實體追蹤還有什么用呢?

記得?IQueryable<>?們的一個拓展?.LoadAsync()?嗎?這個?.LoadAsync()?的注釋說,它的功能約等于?.ToListAsync()?并立馬將這個列表扔掉。那查詢到的東西怎么利用?使用?context.Set<MyEntity>().FindAsync(id)?即可。它僅在?id?不存在于本地的時候才到數(shù)據(jù)庫查找結(jié)果。

所以筆者給出的建議是

  • 在性能要求高的關(guān)鍵路徑上盡量少使用導(dǎo)航屬性

  • 盡量不依賴實體更改自動檢測

當(dāng)然了,導(dǎo)航屬性還是很好的,編寫?Where?的時候可以少編寫很多?Join……

今天關(guān)于查詢編譯和查詢跟蹤的討論就到這里啦。

總結(jié)

以上是生活随笔為你收集整理的EFCore查缺补漏(二):查询的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

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

国产精品 中文字幕 亚洲 欧美 | av久久在线 | 69av在线播放| 91精品蜜桃 | 色综合久久久久久久 | 久久久亚洲精华液 | 国产精品一区二区久久精品爱微奶 | 亚洲精品理论 | 在线看污网站 | 亚洲精品国产高清 | 永久免费的啪啪网站免费观看浪潮 | 亚洲成人av一区二区 | 久久久免费观看视频 | 国产对白av | 亚洲人成人天堂h久久 | 国产精品久久久久久超碰 | 美女网站色免费 | 亚洲综合小说电影qvod | 精品在线亚洲视频 | 亚洲成人av免费 | 国产天天综合 | 丝袜美女视频网站 | 中文字幕日韩一区二区三区不卡 | 人人草在线视频 | 久久亚洲福利视频 | 国产 日韩 中文字幕 | 国产精品亚州 | 激情五月伊人 | 欧美,日韩 | 操操综合 | 亚洲国产影院av久久久久 | 日韩欧美一区二区三区视频 | 欧美久久久久久久久久久 | 中文字幕丝袜一区二区 | 久久九九精品久久 | 日韩欧美国产视频 | 五月婷婷综合久久 | 国产在线欧美在线 | 97超碰在线免费观看 | 国产福利av在线 | 亚洲六月丁香色婷婷综合久久 | 成人黄色大片在线观看 | 天天爱天天舔 | 国产精品久久久久久久久久久久久 | 黄色毛片网站在线观看 | 亚洲免费av观看 | 91在线中文字幕 | 久久久久久久久久久久久9999 | 欧美极度另类性三渗透 | 免费99| 中文字幕在线观看一区二区三区 | 最近日本中文字幕a | 欧美黄在线| 亚洲视屏 | 狠狠色丁香婷婷综合基地 | 成人在线观看网址 | 欧美精品小视频 | 最新免费中文字幕 | 精品视频在线视频 | 黄色毛片大全 | 久久avav| 国产精品美女久久久网av | 天天看天天干天天操 | 超级碰99 | 免费观看国产精品视频 | 在线观看激情av | 精品亚洲成人 | 精品国产一区二区三区在线观看 | 欧美日韩精品综合 | 黄色在线看网站 | 亚洲国产三级在线 | 日韩欧美在线不卡 | 99久久久久免费精品国产 | 色一级片| 日韩av男人的天堂 | 亚洲欧洲久久久 | 国产精品久久久久三级 | 精品国产伦一区二区三区观看说明 | 精品久久久久一区二区国产 | 成人综合婷婷国产精品久久免费 | 国产视频在线播放 | 免费在线一区二区 | 久久久久色| 97超碰总站 | 久久在线一区 | 九九久久久 | 国产最新在线 | 亚洲视频电影在线 | 国产免费一区二区三区最新 | 夜色资源网 | 在线观看香蕉视频 | 久久精品视频免费播放 | 国产视频日韩视频欧美视频 | 综合久久精品 | 亚洲区另类春色综合小说校园片 | 亚洲成人av在线播放 | 久草视频中文在线 | 99在线观看免费视频精品观看 | 日本aa在线| 中国一级片在线播放 | 国产精品久久久久久久久久久久冷 | 中文字幕欧美日韩va免费视频 | 免费看的av片 | 免费v片 | 涩涩爱夜夜爱 | 丁香婷婷电影 | 国产精品久久久久久久av电影 | 国产在线一区二区三区播放 | 精品亚洲二区 | 亚洲视频免费在线观看 | 国产黄色片网站 | 在线观看免费av网 | 国产福利精品一区二区 | 久久精品视频国产 | 国产精品第一页在线观看 | .国产精品成人自产拍在线观看6 | 国产成人精品在线观看 | 亚洲资源视频 | 狠狠的操你| 在线观看国产成人av片 | 欧美日韩性视频 | 午夜av色| 欧美日韩电影在线播放 | 欧美性精品 | aa级黄色大片 | 国产精品男女 | 国产精品嫩草影视久久久 | 国产视频一二三 | av一级在线观看 | a天堂最新版中文在线地址 久久99久久精品国产 | 色综合咪咪久久网 | 超碰97免费在线 | 国产成人精品综合 | 天天操天天操天天操天天 | av综合 日韩| 国产在线黄色 | 91探花系列在线播放 | 国产69精品久久app免费版 | 国产尤物在线视频 | 国产精品 国内视频 | 亚洲精品午夜久久久 | 丁香婷婷社区 | 五月激情站 | 久艹在线播放 | 久久国产女人 | 国产亚洲精品久久久久久久久久 | 久久免费片 | 欧美激情视频一二区 | 超碰人人av | 欧美日韩调教 | 亚洲午夜大片 | 日韩在线观看 | 久草免费新视频 | 中文字幕乱码亚洲精品一区 | 久久久99国产精品免费 | 93久久精品日日躁夜夜躁欧美 | 在线免费av电影 | 精品久久久久久久久亚洲 | 国产高清视频色在线www | 日韩午夜小视频 | 尤物九九久久国产精品的分类 | 操操操人人 | 欧美精品乱码久久久久久 | 青青河边草观看完整版高清 | 成人av一区二区在线观看 | 97精品国产一二三产区 | 国产精品久久久久久久久软件 | 国产激情电影综合在线看 | 日韩av中文字幕在线 | 99亚洲国产 | 日韩一区二区三免费高清在线观看 | 亚洲精品小区久久久久久 | 国产99免费视频 | 国产1级视频 | 精品久久久久一区二区国产 | 在线视频中文字幕一区 | 午夜av免费看 | 亚洲撸撸 | 91成人免费看 | 91最新地址永久入口 | 久久久久久久久亚洲精品 | 九九热在线播放 | 国外av在线| 免费大片黄在线 | 99精品视频免费看 | 青青草国产免费 | 国产一卡在线 | 日韩三级在线观看 | 色av色av色av | 久久久国产精品网站 | 亚洲婷婷丁香 | 欧美a级在线播放 | 六月激情| 毛片美女网站 | 91精品久久久久久综合乱菊 | 国产精品久久久久久久久久久久午夜 | 高清一区二区三区 | 91久久奴性调教 | 狠狠色噜噜狠狠 | 伊人永久 | 五月婷婷在线观看 | 久久1电影院 | 免费看一级特黄a大片 | 高潮久久久久久 | 亚洲人天堂| av丝袜在线 | 九九九九精品九九九九 | 精品国产亚洲日本 | 国产又黄又硬又爽 | 亚洲精品乱码久久久久久 | 欧美激精品 | 日韩一区二区三区视频在线 | 精品国产综合区久久久久久 | 四虎永久精品在线 | 国产综合视频在线观看 | 少妇bbw搡bbbb搡bbbb | se婷婷| 91精品久久香蕉国产线看观看 | 色搞搞| 激情一区二区三区欧美 | 97夜夜澡人人爽人人免费 | 日本久久影视 | 九九精品视频在线观看 | 国产高清99| 久久特级毛片 | 久久成人国产精品 | 美女一级毛片视频 | 久久国产精品久久国产精品 | 国产黄a三级 | 青青久视频 | 欧美va电影 | 欧美ⅹxxxxxx | 亚州国产精品久久久 | 99在线精品免费视频九九视 | 伊在线视频 | 国产精品9999久久久久仙踪林 | 九九久久久久久久久激情 | 国产女人40精品一区毛片视频 | 亚洲va欧洲va国产va不卡 | 91精品视频一区二区三区 | 国产精品视频观看 | 热久久最新地址 | 国产亚洲视频系列 | 国产精品美女久久久 | 日本中文字幕视频 | 国产精品涩涩屋www在线观看 | 久久久久这里只有精品 | 在线观看亚洲专区 | 干干操操 | www.人人草| 亚洲资源一区 | 国产精品99蜜臀久久不卡二区 | 国产精品综合av一区二区国产馆 | 天天干天天做天天爱 | 91在线观看黄 | 亚洲精品伦理在线 | 极品中文字幕 | 激情黄色av | 国产在线观看免费观看 | 中文字幕乱码电影 | 国产精品地址 | 免费看一级特黄a大片 | 久久免费高清视频 | 国产精品麻豆三级一区视频 | 福利片免费看 | 中文字幕乱在线伦视频中文字幕乱码在线 | 日韩a免费| 九色一区二区 | 国产成人av在线影院 | 天堂av免费在线 | 在线观看视频99 | 久久综合五月婷婷 | 精品国自产在线观看 | 日日麻批40分钟视频免费观看 | 国产精品99久久99久久久二8 | 免费久久精品视频 | 久久精品国产一区 | 国产直播av | 久久久国产精品视频 | 91精品麻豆| 九九热.com| 天天操天天摸天天干 | 国产麻豆传媒 | 久草视频免费 | 久久久久免费精品视频 | 欧美视频日韩 | 亚洲成aⅴ人片久久青草影院 | 亚洲成人免费在线 | 亚洲激精日韩激精欧美精品 | 在线观看资源 | 欧美视频在线观看免费网址 | 亚洲精品视频在线观看视频 | 少妇做爰k8经典 | 在线电影av | 99久久久国产精品美女 | 九九九视频精品 | 欧美一级乱黄 | 草久热| 一级黄视频 | 人人爽人人 | 最新婷婷色 | 免费在线观看污网站 | 偷拍区另类综合在线 | 狠狠色伊人亚洲综合网站色 | 国产精品va在线播放 | 天天色综合1 | 国产精久久久 | 国产午夜精品久久久久久久久久 | 在线中文字幕av观看 | 色综合亚洲精品激情狠狠 | 国产精品日韩欧美 | 国产精品麻豆果冻传媒在线播放 | 久草在线播放视频 | 亚洲精品视频在线观看免费 | 毛片无卡免费无播放器 | 综合久久久| 亚洲精品自在在线观看 | 337p日本大胆噜噜噜噜 | 中文字幕你懂的 | 五月天狠狠操 | 午夜性福利| 久在线观看视频 | 国产91在线观看 | 日批视频国产 | 亚州国产精品久久久 | 国产高潮久久 | 久久不射电影院 | 91在线91 | 蜜臀av性久久久久蜜臀aⅴ四虎 | 草在线| 国产精品一区二区免费在线观看 | 五月的婷婷 | 久久8| 天天操夜夜操夜夜操 | 亚洲成人精品在线 | 国产日韩高清在线 | 国产一区二区久久久 | 久久激情电影 | 欧美激情第一区 | 久久精品视频3 | 五月开心六月伊人色婷婷 | 亚洲国内精品 | 四虎国产精品永久在线国在线 | 亚洲精品视频久久 | 久久久久久久精 | 婷婷综合成人 | 99精品在线免费在线观看 | 久草在线精品观看 | 特级毛片aaa | 午夜精品福利影院 | 欧美精品少妇xxxxx喷水 | 免费亚洲一区二区 | 一级黄色片在线免费观看 | 亚洲精品中文字幕在线观看 | 日韩免费视频网站 | 在线看的av网站 | 国产精品手机在线观看 | 久久草草热国产精品直播 | 欧美国产日韩一区二区三区 | 天天插天天爽 | 日韩av图片 | 亚洲区精品 | 97在线观看视频国产 | 岛国av在线不卡 | 亚洲欧美精品在线 | 97激情影院 | 欧美精品久久久 | 欧美日韩一区三区 | 亚洲福利精品 | 国产精品久久久999 国产91九色视频 | 91亚洲在线| 免费在线国产黄色 | 精品国内 | 亚洲综合欧美激情 | 91视频黄色| 国产日韩精品视频 | 91看片淫黄大片在线播放 | 亚洲一二三在线 | 久久久18| 日韩视频免费观看高清 | 国产精品区在线观看 | 国产精品不卡在线观看 | 午夜久久久影院 | 婷婷综合在线 | 午夜久久网站 | av免费在线观看网站 | 91福利免费| 国产精品久久久久av免费 | 欧美黑人性猛交 | 91av在| 中文字幕在线观看完整版电影 | 国产成人久久精品一区二区三区 | 麻豆国产网站 | 免费观看一级视频 | 91香蕉视频 | 国产免费又爽又刺激在线观看 | 国产精品久久久久久a | 日韩精品2区 | 国产群p| 伊人开心激情 | 成年人av在线播放 | 九九热视频在线播放 | 亚洲激情网站免费观看 | 亚洲欧美成aⅴ人在线观看 四虎在线观看 | 在线直播av| 亚洲精品在线观 | 人人爱人人爽 | 久久人人做 | 国产小视频在线观看 | 国产一区在线免费观看视频 | 四虎永久视频 | 亚洲黄色在线观看 | 日日夜夜天天综合 | 日韩中文字幕电影 | 久操操 | 91看片淫黄大片91 | 色 中文字幕 | 黄色一集片 | 外国av网| 91av官网| 婷婷网在线 | 成人在线视频观看 | 白丝av免费观看 | 视频三区在线 | 丁香婷婷久久 | 懂色av懂色av粉嫩av分享吧 | 亚洲欧美激情精品一区二区 | 日韩在线观看第一页 | 日韩精品久久久久久 | 久要激情网 | 国产免费观看久久黄 | 中文字幕成人 | 日韩一区二区三区观看 | 黄色av一区 | 国产免费又黄又爽 | 国产精品24小时在线观看 | 午夜av激情 | 成年人在线免费看 | 久久综合九色 | 我要看黄色一级片 | 81国产精品久久久久久久久久 | 亚洲一区二区三区在线看 | 国产精品久久久久久一区二区三区 | 日韩精品三区四区 | 国产精品美乳一区二区免费 | aaa亚洲精品一二三区 | 六月婷婷久香在线视频 | 久久国产免| 欧美综合色在线图区 | 国产精品美女在线观看 | 97福利在线 | 欧美一区二区在线免费看 | 三级视频国产 | 丁香5月婷婷 | av资源在线看 | 人人精久 | 91视频黄色| 日韩在线一区二区免费 | av黄色影院 | 伊人狠狠 | 成人久久精品视频 | 9在线观看免费 | av中文字幕在线电影 | 免费看特级毛片 | 久久综合久久综合这里只有精品 | 中文字幕视频播放 | 国产精品久久久久久久久久三级 | 1000部国产精品成人观看 | 国产精品系列在线观看 | 西西大胆啪啪 | 日韩大片免费在线观看 | 97在线观看免费视频 | 四虎成人网 | 91精品少妇偷拍99 | 久久视频这里有久久精品视频11 | 麻豆传媒电影在线观看 | 久久久午夜视频 | 最新av观看| 国产福利在线 | 在线午夜| 在线免费精品视频 | 久久国产精品视频 | 成人免费视频免费观看 | 欧美日本不卡 | 黄色三级在线观看 | 中文字幕第| 国产亚洲午夜高清国产拍精品 | 日韩一区二区三区免费视频 | 久久avav | 99视频精品全部免费 在线 | 国内精品小视频 | 91在线视频观看免费 | 国产免费大片 | 超碰在线人人爱 | 国内一区二区视频 | 国产精品亚洲精品 | 免费看精品久久片 | 亚洲不卡在线 | 国产一级黄大片 | 黄色一级在线免费观看 | 国产精品免费久久久久影院仙踪林 | 狠狠ri | 久久久亚洲国产精品麻豆综合天堂 | 日日夜夜精品视频天天综合网 | 国产精品观看在线亚洲人成网 | 久久久久久久久艹 | 少妇精69xxtheporn | 黄色软件视频大全免费下载 | 久久久久国产成人精品亚洲午夜 | 日韩三级精品 | 国产精品乱码久久久久 | 国产亚洲欧美日韩高清 | 黄色电影网站在线观看 | 国产黄在线| 中文在线免费看视频 | 福利片免费看 | av解说在线观看 | 国产精品一区二区免费看 | 91高清视频 | 日韩中文字幕免费 | 在线天堂日本 | 国产精品美女久久久久久久久久久 | 中文字幕在线日亚洲9 | 91成人网在线播放 | 国产精品久久久久久久久软件 | 高清一区二区三区 | 国产美女网站视频 | 婷婷色婷婷 | 亚洲成人国产 | 亚洲日本成人网 | 成人小视频在线观看免费 | 一区二区三区高清在线观看 | 久草在线播放视频 | 久久久综合香蕉尹人综合网 | 国产精品免费一区二区三区 | ,午夜性刺激免费看视频 | 中文字幕乱码一区二区 | 日韩欧美在线播放 | 国产成人在线免费观看 | 国产免费观看久久黄 | 日韩视频免费看 | 国产精品99久久久精品 | 69亚洲乱| 精品国产一区二区三区久久影院 | 国产视频久久久久 | 在线观看精品黄av片免费 | 456成人精品影院 | 最新av网站在线观看 | 国产不卡视频在线播放 | www国产精品com | 人成在线免费视频 | 日日日操操| 亚洲精品自在在线观看 | 国产精品嫩草影视久久久 | 24小时日本在线www免费的 | 国产a国产a国产a | 99热在线免费观看 | 在线综合 亚洲 欧美在线视频 | 中文字幕在线视频一区二区三区 | 1000部国产精品成人观看 | 91热爆视频 | 亚洲国产激情 | 黄色aa久久 | 九九精品久久久 | 国产精品久久久久久久久婷婷 | 激情久久小说 | 9热精品 | 精品一区二区三区久久 | 在线不卡中文字幕播放 | 国产91丝袜在线播放动漫 | 日韩欧美一区二区三区在线 | 一级黄色网址 | 激情五月亚洲 | 亚洲成人黄色网址 | 99精品国产一区二区 | 黄色亚洲 | 亚洲精品久| 97看片吧| 91麻豆精品国产午夜天堂 | 69av视频在线 | 日韩有码在线播放 | 亚洲欧洲国产精品 | 日韩欧美在线免费 | 激情五月六月婷婷 | .精品久久久麻豆国产精品 亚洲va欧美 | 日韩毛片在线一区二区毛片 | 色橹橹欧美在线观看视频高清 | 免费av网站观看 | 久久新 | 美女黄频在线观看 | 美女久久久| 亚洲国产高清在线观看视频 | av午夜电影 | 91av99 | 精品专区 | 日韩av中文在线观看 | www.狠狠插.com | 婷婷六月网 | 国产91九色蝌蚪 | 国产精品 日韩精品 | 日本在线观看一区二区三区 | 在线欧美中文字幕 | 天天草综合网 | 亚洲精品在线看 | 久久免费毛片 | 久草在线视频免费资源观看 | 亚洲成a人片在线观看中文 中文字幕在线视频第一页 狠狠色丁香婷婷综合 | 99 视频 高清 | 欧美精品黑人性xxxx | 免费开视频| 成年人免费在线观看网站 | 国产一二三区av | 黄色在线看网站 | 日韩在线视频一区 | 日日干夜夜干 | 久久人人爽人人爽人人片 | 国产黄色免费观看 | 亚洲成人一二三 | 日韩最新在线视频 | 久久久久久久久精 | 青青网视频 | 亚洲韩国一区二区三区 | 国产精品免费av | 五月婷婷视频在线 | 丁香激情婷婷 | 国产一级二级在线播放 | 极品嫩模被强到高潮呻吟91 | 国产一级片久久 | 亚洲综合精品在线 | www.亚洲在线 | 亚洲最大成人网4388xx | 在线国产一区二区 | 免费观看黄色12片一级视频 | 色激情在线| 欧美性大战久久久久 | 亚洲动漫在线观看 | 亚洲高清资源 | 玖玖精品在线 | 毛片a级片 | 日韩在线观看高清 | 夜夜澡人模人人添人人看 | 欧美日韩视频在线一区 | 欧美精品第一 | 亚洲黄色大片 | 久久久国产网站 | 在线看不卡av | 国产精品va在线观看入 | 亚洲精品中文字幕在线 | 丁香九月激情 | 人人爽人人搞 | 欧美日韩国产色综合一二三四 | 色婷婷啪啪免费在线电影观看 | 色偷偷97| 国产二区视频在线 | 国产毛片aaa | 国产精品av在线免费观看 | 国产黄色片网站 | 日日干夜夜草 | 最新中文字幕 | 一区二区精品 | 国产成人不卡 | 欧美日韩国产在线 | 在线观看的黄色 | 精品国产一区在线观看 | 国产亚洲精品久久网站 | 天天骚夜夜操 | 九九导航 | 亚洲成av人电影 | 精品一区精品二区 | 中文字幕在线观看视频免费 | 麻豆视传媒官网免费观看 | 日韩精品资源 | 中文字幕人成乱码在线观看 | 成人av资源| 久久国产色 | 免费高清无人区完整版 | 亚洲第一成网站 | 色在线国产 | 国产精品久久久久久久久搜平片 | 在线观看精品视频 | 国产在线污 | av电影在线播放 | 天堂视频中文在线 | 国产一区免费看 | 国产日韩视频在线观看 | 色婷婷在线播放 | 国产丝袜一区二区三区 | 久久久久久久电影 | 亚洲精品欧美视频 | 男女视频国产 | 中文字幕二区在线观看 | 色丁香久久| 精品免费视频123区 午夜久久成人 | 国产精品九九九九九九 | 又粗又长又大又爽又黄少妇毛片 | 国内成人av | 精品国产自在精品国产精野外直播 | 97视频网站 | 亚洲综合激情网 | 国产精品久久久久久爽爽爽 | 超级碰视频| 国产精品色婷婷 | 成人黄色免费观看 | 一区二区三区免费 | 国产高清视频免费 | 青青看片| 日韩中文字 | 久草在线免费在线观看 | 天天干天天拍 | 欧洲视频一区 | 91免费试看 | 天天操天天爽天天干 | 国产欧美久久久精品影院 | 国产日产精品一区二区三区四区 | 免费在线成人 | 亚洲综合色视频在线观看 | 四虎在线免费观看 | 久久99免费 | www免费网站在线观看 | 国产中文a | 在线视频成人 | 日本在线视频网址 | 免费av网站在线看 | 97电影手机版 | 热久久国产 | 91欧美国产| 久久伊人爱 | 808电影免费观看三年 | 亚洲精品久久久久www | 国产最新91 | 久久久久亚洲国产 | 91精品小视频| 久久精品9| 狠狠插狠狠操 | 中文字幕免费久久 | 2021av在线| av高清一区 | 91麻豆精品国产91久久久久久久久 | 久草网站在线观看 | 色99之美女主播在线视频 | 在线观看完整版 | 日韩视频免费在线 | 亚洲国产人午在线一二区 | 色婷婷狠狠18| 国产成人精品999 | 美女网站视频久久 | 天天舔天天射天天操 | 精品免费视频 | 日韩欧三级 | 黄色h在线观看 | 婷婷中文字幕 | 久久综合色综合88 | 亚洲国产精品视频在线观看 | 视频一区二区在线观看 | 欧美日韩18 | 久久96国产精品久久99软件 | 久久成人福利 | 99久久久久国产精品免费 | 片网址| 亚洲精品视频www | 九九电影在线 | 97国产超碰 | 一区二区欧美日韩 | 九色91福利| 中文久久精品 | 久草视频精品 | 五月婷婷操 | 久久tv | 免费视频国产 | 亚洲欧洲xxxx | 青草视频在线 | 91精品一区二区三区蜜桃 | 亚洲一区天堂 | 在线精品视频免费观看 | 中文字幕一区在线观看视频 | 四虎国产视频 | 日韩精品在线视频 | 亚洲作爱视频 | 婷婷九月丁香 | 深爱激情综合网 | 国产99色| 久久久精品一区二区 | 在线看片视频 | 亚洲精品乱码久久久久v最新版 | 亚洲最大色| 国产成人精品aaa | 日韩欧美视频免费看 | 国产视频一区二区在线 | 五月婷婷导航 | 国产一在线精品一区在线观看 | 久久草在线免费 | 天天射天天添 | 久草国产精品 | 天天爽夜夜爽精品视频婷婷 | 国产免费一区二区三区最新6 | 在线国产精品一区 | 一区二区在线影院 | 免费影视大全推荐 | 欧美精品免费视频 | 国产精品 中文在线 | 亚洲国产精品va在线看黑人动漫 | 在线直播av| 999在线视频 | 亚洲电影成人 | 狠狠操狠狠干天天操 | 国产精品久久伊人 | 亚洲理论电影 | 一区二区丝袜 | 亚洲精品久久久久999中文字幕 | 992tv在线| 国产原创中文在线 | 97超碰国产精品女人人人爽 | 国产女人40精品一区毛片视频 | 久久久久美女 | 99久久日韩精品视频免费在线观看 | 狠狠操操网| 999久久国精品免费观看网站 | 欧美一二三区在线观看 | 久久久久久免费网 | 在线午夜电影神马影院 | 亚洲精品美女在线观看播放 | 日批网站免费观看 | 夜夜骑首页 | 在线精品视频免费播放 | 久久综合婷婷 | 丁香婷婷色月天 | 91在线视频网址 | 亚洲精品99久久久久久 | 国产精品综合久久久久 | aaa日本高清在线播放免费观看 | 国产在线观看免费观看 | 国产一区二区精品 | 久久tv| 一区二区三区在线播放 | 国产精品久久久久久久电影 | 在线免费看黄网站 | 在线国产日本 | 日韩性色 | 日本中文字幕在线视频 | 午夜精品一区二区三区视频免费看 | 麻豆视频免费在线观看 | 深爱婷婷久久综合 | 久久高清av | 黄色精品一区 | 九九热只有这里有精品 | 国产精成人品免费观看 | 国产成人精品亚洲 | 国产精品 日本 | 日韩国产高清在线 | 日韩电影一区二区在线观看 | 色爽网站 | 日韩欧美视频免费看 | 欧美国产亚洲精品久久久8v | 日韩在线观看一区 | 午夜av一区| 亚洲精品在线观看的 | 亚洲精品乱码久久久久久蜜桃动漫 | 欧美精品一区在线 | 国产精品不卡在线观看 | 免费视频网 | 亚洲成av人片在线观看无 | 欧美精品网站 | 日本天天操 | 国产人在线成免费视频 | 婷婷丁香导航 | 国产精品久久久久久久电影 | 色综合天天综合网国产成人网 | www.五月激情.com | 丁香六月天 | 欧美日本国产在线观看 | 99久久久久久久久 | 精品国产伦一区二区三区观看方式 | 伊人影院在线观看 | 在线一区二区三区 | 人人干在线观看 | 日韩欧美一级二级 | 久久精品成人热国产成 | 国产色女人 | av一区二区在线观看中文字幕 | 99视频免费观看 | 天天插天天狠天天透 | 亚洲成人av在线电影 | 97超碰在线资源 | 欧美日韩高清一区二区 | 国内精品久久久久国产 | 亚洲天堂网站 | 91色亚洲 | 天天干天天拍天天操天天拍 | 亚洲精品美女视频 | 在线观看日韩精品视频 | 天天草天天色 | 精品 激情 | 久久激情五月丁香伊人 | 五月婷婷六月丁香在线观看 | 99精品视频在线观看 | 免费黄色一区 | 日韩中文字幕免费电影 | 免费人做人爱www的视 | 天天插天天干天天操 | 国产日产高清dvd碟片 | 国产人成一区二区三区影院 | 97网| 在线视频日韩一区 | 黄色免费大片 | 一区二区精品视频 | 91免费观看视频在线 | 黄色网中文字幕 | 一区二区三区手机在线观看 | 91.精品高清在线观看 | 国产一区二区久久精品 | 欧美国产视频在线 | www日韩欧美| 中文字幕中文 | 日韩欧美在线观看一区二区三区 | 婷婷资源站 | 丁香婷婷激情网 | 中文字幕成人在线 | 最近免费中文字幕mv在线视频3 | 亚洲天堂精品视频在线观看 | 国产91国语对白在线 | 天天爽天天爽 | 日日射天天射 | 午夜久久影视 | 欧美精品久久久久久久免费 | 日日天天av | 天天干天天射天天操 | 久久精品99久久 | 免费开视频| 在线视频 一区二区 | 欧美激情视频在线免费观看 | 国产亚洲视频中文字幕视频 | 久久国产精品一二三区 | 欧美日韩精品综合 | 国产精品麻豆果冻传媒在线播放 | 亚州欧美精品 | 中文字幕乱码亚洲精品一区 | 高清国产午夜精品久久久久久 | 伊人婷婷色| 国产人成一区二区三区影院 | 久99久视频| 国产成a人亚洲精v品在线观看 | 久久 一区| 久久久久一区二区三区四区 | 日韩欧美在线观看一区 | 亚洲毛片在线观看. | 五月激情在线 | 欧美在线视频一区二区 | 亚洲不卡在线 | 国产小视频福利在线 | 亚洲三级av | 国产成人精品一区二区三区在线观看 | 久久久免费精品国产一区二区 | 国产精品视频地址 | 在线影院 国内精品 | 最近中文国产在线视频 | 成人av资源网| 日韩美女免费线视频 | 欧美一级片免费在线观看 | 国产精品mv在线观看 | 日韩成人邪恶影片 | 婷婷亚洲五月色综合 | 曰本三级在线 | 黄色软件大全网站 | 久久精品99国产国产 | 性色av免费在线观看 | 一性一交视频 | 国产日韩欧美中文 | 狠狠狠色丁香婷婷综合久久五月 | 玖操| 婷婷伊人五月天 | 久久久黄视频 | 日韩精品中文字幕在线 | 国产一级免费片 | a级国产乱理伦片在线观看 亚洲3级 | 国内精品视频一区二区三区八戒 | 视频一区在线播放 | 久久久黄视频 | 国产精品久久久久av免费 | 国产精品一区二区中文字幕 | 日韩xxxx视频 | 中文字幕日本特黄aa毛片 | 五月开心网 | 在线视频观看91 | 一区二区三区在线视频观看58 | 国产亚洲精品成人 | 正在播放国产一区二区 | 成人高清在线 | 国产最新在线观看 | 久久蜜桃av | 欧洲成人av | 国产在线精品区 | 国产精品18videosex性欧美 | 中文字幕在线成人 | 日韩中文字幕视频在线 | 91视频高清完整版 | 久久久国际精品 | 欧美日韩国产精品一区二区亚洲 | 99精品国产亚洲 |