ASP. NET Core 3 EF Framework - временная ошибка при выполнении большого количества запросов - PullRequest
0 голосов
/ 01 апреля 2020

Я использую EF Core для веб-сайта ASP. NET. В одном методе у меня есть около 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 ()', но я беспокоюсь, что истинная причина в том, что он выполняет несколько запросов одновременно. Есть ли способ сделать это безопаснее и эффективнее?

Я пробовал искать руководства, но ни в одном из них не указано, что делать, если вы пытаетесь выполнить много запросов одновременно.

1 Ответ

1 голос
/ 01 апреля 2020

Честно говоря, я думаю, что это, вероятно, связано не столько с вашим кодом, сколько с проблемами сети или базы данных. Хотя есть способы улучшить этот код, на самом деле нет причин, по которым он не должен работать согласованно, как есть.

Тем не менее, вы блокируете все эти запросы, что никогда не было хорошей идеей. Все, что делает EF Core - asyn c. Методы syn c просто блокируют асинхронные c методы и существуют только для сценария ios, где невозможно использовать asyn c, например, делегаты событий в настольных приложениях и тому подобное. Короче говоря, вы должны всегда использовать асин c методы, если только вы не попадаете в конкретную c ситуацию, когда вы не можете. В приложении ASP. NET Core такой ситуации нет, поэтому всегда следует использовать asyn c. Длинные и короткие, используйте ToListAsync вместо ToList и await в каждой строке.

Далее, нет смысла в ваших предложениях where. Независимо от того, выбираете ли вы много только для предметов, у которых есть собаки, например, или для всех предметов, независимо от того, есть у них собаки или нет, вы все равно получите те же результаты. В любом случае все это будет выполняться в базе данных, поэтому ни один подход, ни другой не принесут никакой пользы в производительности. Вам также не нужно использовать Include, если вы выбираете из отношений. EF достаточно умен, чтобы выдавать объединения на основе данных, которые он должен вернуть.

var query1 = await context.Playarea
    .SelectMany(x => x.Cats.Select(y => new MyClass(x.Id, y.Name, y.Age))).ToListAsync();

Существует также проблема ваших двух собачьих запросов. Одинаковые результаты будут возвращены для обоих запросов. Единственное отличие состоит в том, что один набор будет иметь 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.

...