简体   繁体   中英

Weird LINQ to SQL Timeout Issue using FirstOrDefault()

I have the following code which times out:

        using (var ts = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted }))
        {
            ECWSDataContext dc = new ECWSDataContext();

            IQueryable<Ticket> results = dc.Tickets;
            Business.TicketStatistic statistic = results
                .Select(r => new
                {
                    GroupID = 1,
                    IsVoided = r.IsVoided ? 1 : 0,
                    IsWarning = r.TicketFilingTypeID == 5 ? 1 : 0,
                    TotalFelonies = r.TotalFelonies,
                    TotalMisdemeanors = r.TotalMisdemeanors,
                    TotalInfractions = r.TotalInfractions,
                    TotalOrdinances = r.TotalOrdinances,
                    TotalWarnings = r.TotalWarnings
                })
                .GroupBy(t => t.GroupID)
                .Select(g => new Business.TicketStatistic()
                {
                    TotalTickets = g.Count(),
                    TotalVoids = g.Sum(x => x.IsVoided),
                    TotalTicketWarnings = g.Sum(x => x.IsWarning),
                    TotalFelonies = g.Sum(x => x.TotalFelonies),
                    TotalMisdemeanors = g.Sum(x => x.TotalMisdemeanors),
                    TotalInfractions = g.Sum(x => x.TotalInfractions),
                    TotalOrdinances = g.Sum(x => x.TotalOrdinances),
                    TotalOffenseWarnings = g.Sum(x => x.TotalWarnings)
                }).FirstOrDefault();
        }

I profiled the SQL using SQL Server Profiler and grabbed the executed SQL. As expected, it contains a TOP 1. When I run the exact SQL in SQL Management Studio, it comes back in no time at all. Yet, it continues to timeout in the code. Amazingly, changing it to the following works just fine:

        using (var ts = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted }))
        {
            ECWSDataContext dc = new ECWSDataContext();

            IQueryable<Ticket> results = dc.Tickets;

            var stats = results
                .Select(r => new
                {
                    GroupID = 1,
                    IsVoided = r.IsVoided ? 1 : 0,
                    IsWarning = r.TicketFilingTypeID == 5 ? 1 : 0,
                    TotalFelonies = r.TotalFelonies,
                    TotalMisdemeanors = r.TotalMisdemeanors,
                    TotalInfractions = r.TotalInfractions,
                    TotalOrdinances = r.TotalOrdinances,
                    TotalWarnings = r.TotalWarnings
                })
                .GroupBy(t => t.GroupID)
                .Select(g => new Business.TicketStatistic()
                {
                    TotalTickets = g.Count(),
                    TotalVoids = g.Sum(x => x.IsVoided),
                    TotalTicketWarnings = g.Sum(x => x.IsWarning),
                    TotalFelonies = g.Sum(x => x.TotalFelonies),
                    TotalMisdemeanors = g.Sum(x => x.TotalMisdemeanors),
                    TotalInfractions = g.Sum(x => x.TotalInfractions),
                    TotalOrdinances = g.Sum(x => x.TotalOrdinances),
                    TotalOffenseWarnings = g.Sum(x => x.TotalWarnings)
                }).ToArray();

            Business.TicketStatistic statistic = stats.FirstOrDefault();
        }

I understand that now I am enumerating the results before applying the FirstOrDefault() to the now in-memory collection. But it seems strange that executing the same SQL output in the first scenario directly in SQL Server had no problems.

Can somebody maybe explain what is going on here? In this instance, it was a group query that always returned one row regardless. So I am lucky that I can enumerate before applying FirstOrDefault(). But for possible future reference, what if that query returned thousands of rows to which I only wanted the TOP 1.

ADDITION INFO

The SQL using .FirstOrDefault():

SELECT TOP 1 Field1, Field2...
FROM
(
    SELECT SUM(Field) as Field1, ...
    FROM ...
) SUB

The SQL using .ToArray():

SELECT SUM(Field) as Field1, ...
FROM ...

Executing either directly in SQL Mgt Studio resulted in the same results in the same amount of time. However, when LINQ executes the first one, I get a timeout.

This is a common problem when using linq to sql. If you think about sql, when you do a group by and then a firstordefault you're asking sql to aggregate and then unaggregate. It's hard for sql to deal with the individual elements in a group by since it'll be doing multiple queries to reach the individual elements.

When you do ToArray, you're actually pulling the data back into memory and the group by is actually stored in memory with the individual elements so reaching these will be a lot faster.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM