实体框架异步操作需要十倍的时间才能完成
Posted
技术标签:
【中文标题】实体框架异步操作需要十倍的时间才能完成【英文标题】:Entity Framework async operation takes ten times as long to complete 【发布时间】:2015-04-17 01:46:42 【问题描述】:我有一个 MVC 站点,它使用 Entity Framework 6 来处理数据库,我一直在尝试更改它,以便一切都作为异步控制器运行,并且对数据库的调用作为它们的异步对应物运行(例如。 ToListAsync() 而不是 ToList())
我遇到的问题是,简单地将我的查询更改为异步会导致它们非常慢。
以下代码从我的数据上下文中获取“专辑”对象的集合,并转换为相当简单的数据库连接:
// Get the albums
var albums = await this.context.Albums
.Where(x => x.Artist.ID == artist.ID)
.ToListAsync();
这是创建的 SQL:
exec sp_executesql N'SELECT
[Extent1].[ID] AS [ID],
[Extent1].[URL] AS [URL],
[Extent1].[ASIN] AS [ASIN],
[Extent1].[Title] AS [Title],
[Extent1].[ReleaseDate] AS [ReleaseDate],
[Extent1].[AccurateDay] AS [AccurateDay],
[Extent1].[AccurateMonth] AS [AccurateMonth],
[Extent1].[Type] AS [Type],
[Extent1].[Tracks] AS [Tracks],
[Extent1].[MainCredits] AS [MainCredits],
[Extent1].[SupportingCredits] AS [SupportingCredits],
[Extent1].[Description] AS [Description],
[Extent1].[Image] AS [Image],
[Extent1].[HasImage] AS [HasImage],
[Extent1].[Created] AS [Created],
[Extent1].[Artist_ID] AS [Artist_ID]
FROM [dbo].[Albums] AS [Extent1]
WHERE [Extent1].[Artist_ID] = @p__linq__0',N'@p__linq__0 int',@p__linq__0=134
随着事情的发展,这不是一个非常复杂的查询,但 SQL Server 运行它需要将近 6 秒。 SQL Server Profiler 报告它需要 5742 毫秒才能完成。
如果我将代码更改为:
// Get the albums
var albums = this.context.Albums
.Where(x => x.Artist.ID == artist.ID)
.ToList();
然后生成完全相同的 SQL,但根据 SQL Server Profiler,它只运行了 474 毫秒。
数据库在“Albums”表中有大约 3500 行,这并不是很多,并且在“Artist_ID”列上有一个索引,所以应该很快。
我知道异步有开销,但是让事情慢十倍对我来说似乎有点陡峭!我哪里错了?
【问题讨论】:
我觉得不合适。如果使用相同的数据执行相同的查询,SQL Server Profiler 报告的执行时间应该或多或少相同,因为异步是在 c# 中发生的,而不是 Sql。 Sql server 甚至不知道你的 c# 代码是异步的 第一次运行生成的查询时,编译查询可能需要更长的时间(构建执行计划,...),从第二次开始,相同的查询可能会更快( Sql server缓存查询),但应该不会有太大差异。 你需要确定什么是慢的。在无限循环中运行查询。暂停调试器 10 次。它最常停在哪里?发布包含外部代码的堆栈。 看起来问题与 Image 属性有关,我完全忘记了。它是一个 VARBINARY(MAX) 列,因此必然会导致缓慢,但缓慢仅成为异步运行的问题仍然有点奇怪。我已经重组了我的数据库,使图像现在成为链接表的一部分,并且现在一切都快得多了。 问题可能是 EF 向 ADO.NET 发出大量异步读取以检索所有这些字节和行。这样开销就被放大了。由于您没有执行我要求的测量,我们永远不会知道。问题似乎解决了。 【参考方案1】:我发现这个问题非常有趣,特别是因为我在 Ado.Net 和 EF 6 的任何地方都使用async
。我希望有人对这个问题给出解释,但它没有发生。所以我试图在我这边重现这个问题。我希望你们中的一些人会觉得这很有趣。
第一个好消息:我复制了它:) 差异是巨大的。系数为 8 ...
首先,我怀疑与 CommandBehavior
有什么关系,因为 I read an interesting article 与 Ado 关于 async
的关系是这样的:
"由于非顺序访问模式必须存储整行的数据,如果您从服务器读取大列(例如 varbinary(MAX)、varchar(MAX)、nvarchar(MAX ) 或 XML)。”
我怀疑 ToList()
调用是 CommandBehavior.SequentialAccess
和异步调用是 CommandBehavior.Default
(非顺序的,这可能会导致问题)。因此,我下载了 EF6 的源代码,并在各处放置断点(当然,在使用 CommandBehavior
的地方)。
结果:没有。所有的调用都是用CommandBehavior.Default
进行的......所以我试图进入 EF 代码以了解会发生什么......而且......哦哦......我从来没有见过这样的委托代码,一切似乎都懒惰执行......
所以我尝试进行一些分析以了解发生了什么......
而且我觉得我有一些东西......
这是创建我进行基准测试的表的模型,其中包含 3500 行,每个 varbinary(MAX)
中有 256 Kb 随机数据。 (EF 6.1 - CodeFirst - CodePlex):
public class TestContext : DbContext
public TestContext()
: base(@"Server=(localdb)\\v11.0;Integrated Security=true;Initial Catalog=BENCH") // Local instance
public DbSet<TestItem> Items get; set;
public class TestItem
public int ID get; set;
public string Name get; set;
public byte[] BinaryData get; set;
这是我用来创建测试数据和基准 EF 的代码。
using (TestContext db = new TestContext())
if (!db.Items.Any())
foreach (int i in Enumerable.Range(0, 3500)) // Fill 3500 lines
byte[] dummyData = new byte[1 << 18]; // with 256 Kbyte
new Random().NextBytes(dummyData);
db.Items.Add(new TestItem() Name = i.ToString(), BinaryData = dummyData );
await db.SaveChangesAsync();
using (TestContext db = new TestContext()) // EF Warm Up
var warmItUp = db.Items.FirstOrDefault();
warmItUp = await db.Items.FirstOrDefaultAsync();
Stopwatch watch = new Stopwatch();
using (TestContext db = new TestContext())
watch.Start();
var testRegular = db.Items.ToList();
watch.Stop();
Console.WriteLine("non async : " + watch.ElapsedMilliseconds);
using (TestContext db = new TestContext())
watch.Restart();
var testAsync = await db.Items.ToListAsync();
watch.Stop();
Console.WriteLine("async : " + watch.ElapsedMilliseconds);
using (var connection = new SqlConnection(CS))
await connection.OpenAsync();
using (var cmd = new SqlCommand("SELECT ID, Name, BinaryData FROM dbo.TestItems", connection))
watch.Restart();
List<TestItem> itemsWithAdo = new List<TestItem>();
var reader = await cmd.ExecuteReaderAsync(CommandBehavior.SequentialAccess);
while (await reader.ReadAsync())
var item = new TestItem();
item.ID = (int)reader[0];
item.Name = (String)reader[1];
item.BinaryData = (byte[])reader[2];
itemsWithAdo.Add(item);
watch.Stop();
Console.WriteLine("ExecuteReaderAsync SequentialAccess : " + watch.ElapsedMilliseconds);
using (var connection = new SqlConnection(CS))
await connection.OpenAsync();
using (var cmd = new SqlCommand("SELECT ID, Name, BinaryData FROM dbo.TestItems", connection))
watch.Restart();
List<TestItem> itemsWithAdo = new List<TestItem>();
var reader = await cmd.ExecuteReaderAsync(CommandBehavior.Default);
while (await reader.ReadAsync())
var item = new TestItem();
item.ID = (int)reader[0];
item.Name = (String)reader[1];
item.BinaryData = (byte[])reader[2];
itemsWithAdo.Add(item);
watch.Stop();
Console.WriteLine("ExecuteReaderAsync Default : " + watch.ElapsedMilliseconds);
using (var connection = new SqlConnection(CS))
await connection.OpenAsync();
using (var cmd = new SqlCommand("SELECT ID, Name, BinaryData FROM dbo.TestItems", connection))
watch.Restart();
List<TestItem> itemsWithAdo = new List<TestItem>();
var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess);
while (reader.Read())
var item = new TestItem();
item.ID = (int)reader[0];
item.Name = (String)reader[1];
item.BinaryData = (byte[])reader[2];
itemsWithAdo.Add(item);
watch.Stop();
Console.WriteLine("ExecuteReader SequentialAccess : " + watch.ElapsedMilliseconds);
using (var connection = new SqlConnection(CS))
await connection.OpenAsync();
using (var cmd = new SqlCommand("SELECT ID, Name, BinaryData FROM dbo.TestItems", connection))
watch.Restart();
List<TestItem> itemsWithAdo = new List<TestItem>();
var reader = cmd.ExecuteReader(CommandBehavior.Default);
while (reader.Read())
var item = new TestItem();
item.ID = (int)reader[0];
item.Name = (String)reader[1];
item.BinaryData = (byte[])reader[2];
itemsWithAdo.Add(item);
watch.Stop();
Console.WriteLine("ExecuteReader Default : " + watch.ElapsedMilliseconds);
对于常规的 EF 调用 (.ToList()
),分析看起来“正常”并且易于阅读:
在这里,我们找到了秒表的 8.4 秒(分析会减慢性能)。我们还发现沿调用路径的 HitCount = 3500,这与测试中的 3500 行一致。在 TDS 解析器方面,事情开始变得更糟,因为我们在 TryReadByteArray()
方法上读取了 118 353 次调用,这就是缓冲循环发生的原因。 (256kb 的每个 byte[]
平均调用 33.8 次)
对于async
的情况,真的很不一样......首先,.ToListAsync()
调用被安排在 ThreadPool 上,然后等待。这里没有什么了不起的。但是,现在,这是 ThreadPool 上的 async
地狱:
首先,在第一种情况下,我们在整个调用路径中只有 3500 个命中计数,这里我们有 118 371 个。此外,您必须想象我没有放在屏幕截图上的所有同步调用......
其次,在第一种情况下,我们对 TryReadByteArray()
方法的调用“只有 118 353 次”,这里我们有 2 050 210 次调用!它是 17 倍……(在一个 1Mb 大阵列的测试中,它是 160 倍)
还有:
已创建 120 000 个Task
实例
727 519 Interlocked
来电
290 569 Monitor
致电
98 283 ExecutionContext
实例,有 264 481 次捕获
208 733 SpinLock
电话
我的猜测是缓冲是以异步方式进行的(而且不是一种好的方式),并行任务试图从 TDS 读取数据。创建太多任务只是为了解析二进制数据。
作为初步结论,我们可以说 Async 很棒,EF6 很棒,但是 EF6 在其当前实现中使用异步增加了主要开销,在性能方面、线程方面和 CPU 方面(12% CPU ToList()
案例中的使用率和 ToListAsync
案例中的 20% 的使用时间延长了 8 到 10 倍......我在旧的 i7 920 上运行它)。
在做一些测试时,我在想this article again,我注意到我想念的东西:
“对于 .Net 4.5 中的新异步方法,它们的行为与同步方法完全相同,除了一个值得注意的例外:非顺序模式下的 ReadAsync。”
什么?!!!
所以我扩展了我的基准测试,将 Ado.Net 包括在常规/异步调用中,并使用 CommandBehavior.SequentialAccess
/ CommandBehavior.Default
,这是一个很大的惊喜! :
我们与 Ado.Net 的行为完全相同!!!捂脸……
我的最终结论是:EF 6 实现中存在错误。当对包含binary(max)
列的表进行异步调用时,它应该将CommandBehavior
切换为SequentialAccess
。创建太多任务,减慢进程的问题是在 Ado.Net 方面。 EF 的问题是它没有按应有的方式使用 Ado.Net。
现在您知道,与其使用 EF6 异步方法,不如以常规的非异步方式调用 EF,然后使用 TaskCompletionSource<T>
以异步方式返回结果。
注意 1 :由于一个可耻的错误,我编辑了我的帖子....这是更新的结果。
注意 2:我没有将测试扩展到其他用例(例如:nvarchar(max)
有大量数据),但有可能发生相同的行为。
注 3:ToList()
的情况通常是 12% CPU(我的 CPU 的 1/8 = 1 个逻辑核心)。不寻常的是ToListAsync()
案例的最大 20%,好像调度程序无法使用所有 Treads。可能是创建的Task太多,也可能是TDS解析器的瓶颈,我不知道...
【讨论】:
我在 codeplex 上打开了一个问题,希望他们能做点什么。 entityframework.codeplex.com/workitem/2686 我在 github 上托管的新 EF 代码仓库上打开了一个问题:github.com/aspnet/EntityFramework6/issues/88 遗憾的是,GitHub 上的问题已关闭,建议不要将异步与 varbinary 一起使用。理论上 varbinary 应该是异步最有意义的情况,因为在传输文件时线程将被阻塞更长时间。那么如果我们想在数据库中保存二进制数据,我们现在该怎么做呢? 有人知道这是否仍然是 EF Core 中的问题吗?我一直找不到任何信息或基准。 @AndrewLewis 我没有任何科学依据,但我与 EF Core 重复连接池超时,其中导致问题的两个查询是.ToListAsync()
和 .CountAsync()
...这个评论线程,this query 可能会有所帮助。神速。【参考方案2】:
因为几天前我得到了这个问题的链接,所以我决定发布一个小更新。我能够使用当前最新版本的 EF (6.4.0) 和 .NET Framework 4.7.2 重现 original answer 的结果。令人惊讶的是,这个问题从未得到改善。
.NET Framework 4.7.2 | EF 6.4.0 (Values in ms. Average of 10 runs)
non async : 3016
async : 20415
ExecuteReaderAsync SequentialAccess : 2780
ExecuteReaderAsync Default : 21061
ExecuteReader SequentialAccess : 3467
ExecuteReader Default : 3074
这就引出了一个问题:dotnet core 有改进吗?
我将原始答案中的代码复制到一个新的 dotnet core 3.1.3 项目并添加了 EF Core 3.1.3。结果是:
dotnet core 3.1.3 | EF Core 3.1.3 (Values in ms. Average of 10 runs)
non async : 2780
async : 6563
ExecuteReaderAsync SequentialAccess : 2593
ExecuteReaderAsync Default : 6679
ExecuteReader SequentialAccess : 2668
ExecuteReader Default : 2315
令人惊讶的是,有很多改进。似乎仍然存在一些时间延迟,因为线程池被调用,但它比 .NET Framework 实现快了大约 3 倍。
我希望这个答案可以帮助其他人将来以这种方式发送。
【讨论】:
【参考方案3】:有一个解决方案允许在不牺牲性能的情况下使用异步,并使用 EF Core 和 MS SQL 数据库进行测试。
首先,您需要为DBDataReader
制作一个包装器:
-
它的
ReadAsync
方法应该读取整行,将每列的值存储在缓冲区中。
它的GetXyz
方法应该从上述缓冲区中获取值。
(可选)使用GetBytes
+ Encoding.GetString
而不是GetString
。对于我的用例(每行 16KB 文本列),它显着提高了同步和异步的速度。
(可选)调整连接字符串的数据包大小。对于我的用例,值 32767 可显着提高同步和异步速度。
您现在可以创建DbCommandInterceptor
,拦截ReaderExecutingAsync
以创建具有顺序访问的DBDataReader
,由上述包装器包装。
EF Core 将尝试以非顺序方式访问字段 - 这就是包装器必须首先读取和缓冲整行的原因。
这是一个示例实现(拦截异步和同步):
/// <summary>
/// This interceptor optimizes a <see cref="Microsoft.EntityFrameworkCore.DbContext"/> for
/// accessing large columns (text, ntext, varchar(max) and nvarchar(max)). It enables the
/// <see cref="CommandBehavior.SequentialAccess"/> option and uses an optimized method
/// for converting large text columns into <see cref="string"/> objects.
/// </summary>
public class ExampleDbCommandInterceptor : DbCommandInterceptor
public async override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result, CancellationToken cancellationToken = default)
var behavior = CommandBehavior.SequentialAccess;
var reader = await command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(false);
var wrapper = await DbDataReaderOptimizedWrapper.CreateAsync(reader, cancellationToken).ConfigureAwait(false);
return InterceptionResult<DbDataReader>.SuppressWithResult(wrapper);
public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
var behavior = CommandBehavior.SequentialAccess;
var reader = command.ExecuteReader(behavior);
var wrapper = DbDataReaderOptimizedWrapper.Create(reader);
return InterceptionResult<DbDataReader>.SuppressWithResult(wrapper);
/// <summary>
/// This wrapper caches the values of accessed columns of each row, allowing non-sequential access
/// even when <see cref="CommandBehavior.SequentialAccess"/> is specified. It enables using this option it with EF Core.
/// In addition, it provides an optimized method for reading text, ntext, varchar(max) and nvarchar(max) columns.
/// All in all, it speeds up database operations reading from large text columns.
/// </summary>
sealed class DbDataReaderOptimizedWrapper : DbDataReader
readonly DbDataReader reader;
readonly DbColumn[] schema;
readonly object[] cache;
readonly Func<object>[] materializers;
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
private T Get<T>(int ordinal)
if (cache[ordinal] != DBNull.Value) return (T)cache[ordinal];
return (T)(object)null; // this line will throw an exception if T is not a reference type (class), otherwise it will return null
private DbDataReaderOptimizedWrapper(DbDataReader reader, IEnumerable<DbColumn> schema)
this.reader = reader;
this.schema = schema.OrderBy(x => x.ColumnOrdinal).ToArray();
cache = new object[this.schema.Length];
byte[] stringGetterBuffer = null;
string stringGetter(int i)
var dbColumn = this.schema[i];
// Using GetBytes instead of GetString is much faster, but only works for text, ntext, varchar(max) and nvarchar(max)
if (dbColumn.ColumnSize < int.MaxValue) return reader.GetString(i);
if (stringGetterBuffer == null) stringGetterBuffer = new byte[32 * 1024];
var totalRead = 0;
while (true)
var offset = totalRead;
totalRead += (int)reader.GetBytes(i, offset, stringGetterBuffer, offset, stringGetterBuffer.Length - offset);
if (totalRead < stringGetterBuffer.Length) break;
const int maxBufferSize = int.MaxValue / 2;
if (stringGetterBuffer.Length >= maxBufferSize)
throw new OutOfMemoryException($"nameof(DbDataReaderOptimizedWrapper).nameof(GetString) cannot load column 'GetName(i)' because it contains a string longer than maxBufferSize bytes.");
Array.Resize(ref stringGetterBuffer, 2 * stringGetterBuffer.Length);
var c = dbColumn.DataTypeName[0];
var encoding = (c is 'N' or 'n') ? Encoding.Unicode : Encoding.ASCII;
return encoding.GetString(stringGetterBuffer.AsSpan(0, totalRead));
var dict = new Dictionary<Type, Func<DbColumn, int, Func<object>>>
[typeof(bool)] = (column, index) => () => reader.GetBoolean(index),
[typeof(byte)] = (column, index) => () => reader.GetByte(index),
[typeof(char)] = (column, index) => () => reader.GetChar(index),
[typeof(short)] = (column, index) => () => reader.GetInt16(index),
[typeof(int)] = (column, index) => () => reader.GetInt32(index),
[typeof(long)] = (column, index) => () => reader.GetInt64(index),
[typeof(float)] = (column, index) => () => reader.GetFloat(index),
[typeof(double)] = (column, index) => () => reader.GetDouble(index),
[typeof(decimal)] = (column, index) => () => reader.GetDecimal(index),
[typeof(DateTime)] = (column, index) => () => reader.GetDateTime(index),
[typeof(Guid)] = (column, index) => () => reader.GetGuid(index),
[typeof(string)] = (column, index) => () => stringGetter(index),
;
materializers = schema.Select((column, index) => dict[column.DataType](column, index)).ToArray();
public static DbDataReaderOptimizedWrapper Create(DbDataReader reader)
=> new DbDataReaderOptimizedWrapper(reader, reader.GetColumnSchema());
public static async ValueTask<DbDataReaderOptimizedWrapper> CreateAsync(DbDataReader reader, CancellationToken cancellationToken)
=> new DbDataReaderOptimizedWrapper(reader, await reader.GetColumnSchemaAsync(cancellationToken).ConfigureAwait(false));
protected override void Dispose(bool disposing) => reader.Dispose();
public async override ValueTask DisposeAsync() => await reader.DisposeAsync().ConfigureAwait(false);
public override object this[int ordinal] => Get<object>(ordinal);
public override object this[string name] => Get<object>(GetOrdinal(name));
public override int Depth => reader.Depth;
public override int FieldCount => reader.FieldCount;
public override bool HasRows => reader.HasRows;
public override bool IsClosed => reader.IsClosed;
public override int RecordsAffected => reader.RecordsAffected;
public override int VisibleFieldCount => reader.VisibleFieldCount;
public override bool GetBoolean(int ordinal) => Get<bool>(ordinal);
public override byte GetByte(int ordinal) => Get<byte>(ordinal);
public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length) => throw new NotSupportedException();
public override char GetChar(int ordinal) => Get<char>(ordinal);
public override long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length) => throw new NotSupportedException();
public override string GetDataTypeName(int ordinal) => reader.GetDataTypeName(ordinal);
public override DateTime GetDateTime(int ordinal) => Get<DateTime>(ordinal);
public override decimal GetDecimal(int ordinal) => Get<decimal>(ordinal);
public override double GetDouble(int ordinal) => Get<double>(ordinal);
public override IEnumerator GetEnumerator() => reader.GetEnumerator();
public override Type GetFieldType(int ordinal) => reader.GetFieldType(ordinal);
public override float GetFloat(int ordinal) => Get<float>(ordinal);
public override Guid GetGuid(int ordinal) => Get<Guid>(ordinal);
public override short GetInt16(int ordinal) => Get<short>(ordinal);
public override int GetInt32(int ordinal) => Get<int>(ordinal);
public override long GetInt64(int ordinal) => Get<long>(ordinal);
public override string GetName(int ordinal) => reader.GetName(ordinal);
public override int GetOrdinal(string name) => reader.GetOrdinal(name);
public override string GetString(int ordinal) => Get<string>(ordinal);
public override object GetValue(int ordinal) => Get<object>(ordinal);
public override int GetValues(object[] values)
var min = Math.Min(cache.Length, values.Length);
Array.Copy(cache, values, min);
return min;
public override bool IsDBNull(int ordinal) => Convert.IsDBNull(cache[ordinal]);
public override bool NextResult() => reader.NextResult();
public override bool Read()
Array.Clear(cache, 0, cache.Length);
if (reader.Read())
for (int i = 0; i < cache.Length; ++i)
if ((schema[i].AllowDBNull ?? true) && reader.IsDBNull(i))
cache[i] = DBNull.Value;
else cache[i] = materializers[i]();
return true;
return false;
public override void Close() => reader.Close();
public async override Task CloseAsync() => await reader.CloseAsync().ConfigureAwait(false);
public override DataTable GetSchemaTable() => reader.GetSchemaTable();
public async override Task<DataTable> GetSchemaTableAsync(CancellationToken cancellationToken = default) => await reader.GetSchemaTableAsync(cancellationToken).ConfigureAwait(false);
public async override Task<ReadOnlyCollection<DbColumn>> GetColumnSchemaAsync(CancellationToken cancellationToken = default) => await reader.GetColumnSchemaAsync(cancellationToken).ConfigureAwait(false);
public async override Task<bool> NextResultAsync(CancellationToken cancellationToken) => await reader.NextResultAsync(cancellationToken).ConfigureAwait(false);
public async override Task<bool> ReadAsync(CancellationToken cancellationToken)
Array.Clear(cache, 0, cache.Length);
if (await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
for (int i = 0; i < cache.Length; ++i)
if ((schema[i].AllowDBNull ?? true) && await reader.IsDBNullAsync(i, cancellationToken).ConfigureAwait(false))
cache[i] = DBNull.Value;
else cache[i] = materializers[i]();
return true;
return false;
我现在无法提供基准,希望在 cmets 中有人会这样做。
【讨论】:
【参考方案4】:添加到@rducom 给出的答案。 Microsoft.EntityFrameworkCore 6.0.0
中仍然存在此问题
阻塞部分实际上是SqlClient
,@AndriySvyryd 推荐的适用于 EF 核心项目的解决方法是:
不要使用 VARCHAR(MAX) 或不要使用异步查询。
这发生在我使用async
查询读取大型 JSON 对象和图像(二进制)数据时。
链接:
https://github.com/dotnet/efcore/issues/18571#issuecomment-545992812
https://github.com/dotnet/efcore/issues/18571
https://github.com/dotnet/efcore/issues/885
https://github.com/dotnet/SqlClient/issues/245
https://github.com/dotnet/SqlClient/issues/593
【讨论】:
以上是关于实体框架异步操作需要十倍的时间才能完成的主要内容,如果未能解决你的问题,请参考以下文章