ASP.NET Core 3 EF Framework - 运行 大量查询时出现暂时性故障
ASP.NET Core 3 EF Framework - transient failure when running lots of queries
我正在为 ASP.NET 网站使用 EF Core。在一种方法中,我有大约 7 个类似于(简单示例)的复杂查询:
var query1 = context.Playarea
.Include(x => x.Cats)
.Where(x => x.Cats.Any())
.SelectMany(x => x.Cats.Select(y => new MyClass(x.Id, y.Name, y.Age))).ToList();
var query2 = context.Playarea
.Include( x => x.Dogs)
.Where(x => x.Dogs.Any())
.SelectMany(x => x.Dogs, (x, y) => new MyClass(x.Id, y.Name, y.Age)).ToList();
var query3 = context.Playarea
.Include( x => x.Dogs)
.Include( x => x.Leads)
.Where(x => x.Dogs.Any())
.SelectMany(x => x.Dogs, (x, y) => new MyClass(x.Id, y.Leads.Name, y.Age)).ToList();
var query4 = context.Playarea
.Include( x => x.Birds)
.Where(x => x.Birds.Any())
.SelectMany(x => x.Birds, (x, y) => new MyClass(x.Id, y.Name, y.Age)).ToList();
return query1.Concat(query2).Concat(query3).Concat(query4).ToList();
这通常有效,但有时页面会崩溃:
An unhandled exception was thrown by the application. System.InvalidOperationException: An exception has been raised that is likely due to a transient failure. Consider enabling transient error resiliency by adding 'EnableRetryOnFailure()' to the 'UseSqlServer' call.
---> Microsoft.Data.SqlClient.SqlException (0x80131904): A transport-level error has occurred when receiving results from the server. (provider: TCP Provider, error: 0 - The semaphore timeout period has expired.)
---> System.ComponentModel.Win32Exception (121): The semaphore timeout period has expired.
我知道我可以添加 'EnableRetryOnFailure()' 但我担心真正的原因是它同时 运行 多个查询。有没有办法让它更安全、更高效?
我试过查找指南,但其中 none 涵盖了如果您同时尝试大量查询时该怎么做。
老实说,我认为这可能与您的代码关系不大,而与某种网络或数据库问题有关。虽然可以通过多种方式改进此代码,但确实没有理由不按原样始终如一地工作。
也就是说,您阻止了所有这些查询,这绝不是一个好主意。 EF Core 所做的一切都是异步的。同步方法只是阻塞在异步方法上,并且只存在于无法使用异步的场景中,例如桌面应用程序中的事件委托等。简而言之,您应该始终 使用异步方法,除非您运行 遇到无法使用的特定情况。在 ASP.NET Core 应用程序中,不存在这种情况,因此应始终始终使用 async。长短,每行使用ToListAsync
代替ToList
和await
。
接下来,您的 where 子句没有任何意义。例如,无论您 select 只关注有狗的项目,还是所有项目,无论它们是否有狗,您仍然会得到相同的结果。无论哪种方式,所有这些都将在数据库中 运行 ,因此一种方法或另一种方法甚至没有性能优势。如果您 select 来自关系,则也不需要使用 Include
。 EF 足够聪明,可以根据它需要的数据发出联接 return。
var query1 = await context.Playarea
.SelectMany(x => x.Cats.Select(y => new MyClass(x.Id, y.Name, y.Age))).ToListAsync();
还有你的两条狗查询的问题。两个查询的结果相同 return。唯一的区别是一组将具有 y.Name
而另一组将具有 y.Leads.Name
作为 Name
值。包含 Leads
的行为不会以某种方式过滤掉没有 Leads
的结果,当然,无论哪种方式,第一个查询根本不会过滤。我想你会想要像下面这样的东西:
var query3 = await context.Playarea
.SelectMany(x => x.Dogs, (x, y) => new MyClass(x.Id, y.Leads is null ? y.Name : y.Leads.Name, y.Age)).ToListAsync();
换句话说,如果关系存在,将使用Leads.Name
,否则将回退到Name
。
我正在为 ASP.NET 网站使用 EF Core。在一种方法中,我有大约 7 个类似于(简单示例)的复杂查询:
var query1 = context.Playarea
.Include(x => x.Cats)
.Where(x => x.Cats.Any())
.SelectMany(x => x.Cats.Select(y => new MyClass(x.Id, y.Name, y.Age))).ToList();
var query2 = context.Playarea
.Include( x => x.Dogs)
.Where(x => x.Dogs.Any())
.SelectMany(x => x.Dogs, (x, y) => new MyClass(x.Id, y.Name, y.Age)).ToList();
var query3 = context.Playarea
.Include( x => x.Dogs)
.Include( x => x.Leads)
.Where(x => x.Dogs.Any())
.SelectMany(x => x.Dogs, (x, y) => new MyClass(x.Id, y.Leads.Name, y.Age)).ToList();
var query4 = context.Playarea
.Include( x => x.Birds)
.Where(x => x.Birds.Any())
.SelectMany(x => x.Birds, (x, y) => new MyClass(x.Id, y.Name, y.Age)).ToList();
return query1.Concat(query2).Concat(query3).Concat(query4).ToList();
这通常有效,但有时页面会崩溃:
An unhandled exception was thrown by the application. System.InvalidOperationException: An exception has been raised that is likely due to a transient failure. Consider enabling transient error resiliency by adding 'EnableRetryOnFailure()' to the 'UseSqlServer' call.
---> Microsoft.Data.SqlClient.SqlException (0x80131904): A transport-level error has occurred when receiving results from the server. (provider: TCP Provider, error: 0 - The semaphore timeout period has expired.)
---> System.ComponentModel.Win32Exception (121): The semaphore timeout period has expired.
我知道我可以添加 'EnableRetryOnFailure()' 但我担心真正的原因是它同时 运行 多个查询。有没有办法让它更安全、更高效?
我试过查找指南,但其中 none 涵盖了如果您同时尝试大量查询时该怎么做。
老实说,我认为这可能与您的代码关系不大,而与某种网络或数据库问题有关。虽然可以通过多种方式改进此代码,但确实没有理由不按原样始终如一地工作。
也就是说,您阻止了所有这些查询,这绝不是一个好主意。 EF Core 所做的一切都是异步的。同步方法只是阻塞在异步方法上,并且只存在于无法使用异步的场景中,例如桌面应用程序中的事件委托等。简而言之,您应该始终 使用异步方法,除非您运行 遇到无法使用的特定情况。在 ASP.NET Core 应用程序中,不存在这种情况,因此应始终始终使用 async。长短,每行使用ToListAsync
代替ToList
和await
。
接下来,您的 where 子句没有任何意义。例如,无论您 select 只关注有狗的项目,还是所有项目,无论它们是否有狗,您仍然会得到相同的结果。无论哪种方式,所有这些都将在数据库中 运行 ,因此一种方法或另一种方法甚至没有性能优势。如果您 select 来自关系,则也不需要使用 Include
。 EF 足够聪明,可以根据它需要的数据发出联接 return。
var query1 = await context.Playarea
.SelectMany(x => x.Cats.Select(y => new MyClass(x.Id, y.Name, y.Age))).ToListAsync();
还有你的两条狗查询的问题。两个查询的结果相同 return。唯一的区别是一组将具有 y.Name
而另一组将具有 y.Leads.Name
作为 Name
值。包含 Leads
的行为不会以某种方式过滤掉没有 Leads
的结果,当然,无论哪种方式,第一个查询根本不会过滤。我想你会想要像下面这样的东西:
var query3 = await context.Playarea
.SelectMany(x => x.Dogs, (x, y) => new MyClass(x.Id, y.Leads is null ? y.Name : y.Leads.Name, y.Age)).ToListAsync();
换句话说,如果关系存在,将使用Leads.Name
,否则将回退到Name
。